2

I have a script install.sh essentially looks like this:

#!/bin/bash

if [[ ! -f "/usr/bin/jq" ]]; then
  apt-get update && apt-get install -y jq
fi

echo $(date) >> install.log

If I put it on a server, and run it like this:

curl -s0 https://myserver.com/install.sh | bash

after the crazy apt-get output, it won't run the last echo line, instead it prints the line echo $(date) >> test.log as output. While if I save the install.sh on the disk, and run ./install.sh, it runs as expected. the echo line is executed and the install.log is appended.

To further reproduce this issue, I run it locally like this:

cat install.sh | bash

it also has the same problem.

I run apt-get remove jq manually before each test. If jq is installed, then both work as expected. So why it's different to run it from the download vs. run it from local file?

I recorded a short video to demonstrate this problem: https://www.youtube.com/watch?v=lYvGXI7AibA

  • So the question is: "what is the difference between bash foo and bash <foo". (It is not really curl related.) – michas Dec 3 '15 at 10:25
  • 1
    I cannot reproduce your described behavior. If you replace the apt-get line with a simple echo, does it still show that behavior? Try to run with bash -x. – michas Dec 3 '15 at 10:34
  • 1
    bash -x does not solve the problem. But it should show you the problem. What is the difference in the output of bash -x foo and bash -x <foo? – michas Dec 3 '15 at 16:40
  • 1
    or are you writing this to go with some install instructions on your website? if so, please don't do that. there's already way too many people telling ignorant users to do that - and they really don't need to be taught that fetching random scripts from random web sites and running them without examination as root is an acceptable thing to do, Give them the instructions, tell them what to do. If you want to give them a script to automate the process, tell them to download it as a separate step and EXAMINE IT (and perhaps modify it to suit their system) before running it, – cas Dec 4 '15 at 4:24
  • 1
    You might know that you are trustworthy but nobody else knows you from a bar of soap. By telling your users to run your script as root, you're telling them "trust me. give me unrestricted root access to your system". official debian repos are curated, and monitored and with a bug tracking system. your web site with your script is just a random web site, one amongst billions. If you seriously think what you are telling your users to do is in any way acceptable practice then you are simply not qualified to talk about security. you have decided that minor convenience is far more important. – cas Dec 4 '15 at 22:40
1

Generally bash does not care whether it is reading its commands from a file (as in bash foo) or from STDIN (as in bash <foo). However now you start a command (apt-get) which itself wants to ask the user and uses, yes, STDIN to do so.

Therefore weird things happen: bash reads commands from STDIN and executes them one by one. Now one of the command reads from STDIN and, well, reads the next line of the file. Afterwards bash reads the next line from STDIN and therefore skips the one command already read by the other command.

This small script will demonstrate what is going on:

echo "hello"
read -p "how do you do? "
echo "I understand"
echo "you are '$REPLY'."
echo "bye"

Save it in a file and first run it as bash -x foo and afterwards as bash -x <foo.

The same thing will happen in you example.

One solution would be to use process substitution:

bash -x <(cat foo)

or in your case simply:

bash <(curl -s0 https://myserver.com/install.sh)

Some remarks:

Whenever some shell script does weird things, use bash -x to see what is going on.

Writing cat foo| ... is known as Useless use of cat. Just use redirection instead.

  • 1
    Why does apt-get install -y ... still read from stdin? – roaima Dec 3 '15 at 22:46
  • 1
    Interesting. apt-get install -y ... does NOT flush stdin. At least in my test example. Consider apt-get -y purge fonts-georgewilliams; apt-get ( echo 1; echo 2; echo 3 ) | ( apt-get -y install fonts-georgewilliams; read x; echo "x=$x" ) the output is 1. – roaima Dec 4 '15 at 0:19
  • Thanks @michas, it works. But I still don't understand why it's like this. Is apt-get doing any nasty thing? – Elgs Qian Chen Dec 4 '15 at 4:05
  • 1
    Well kind not really nasty things. It just reads from STDIN, which is usually fine and the normal way to get input from the user. But if you tell Bash to to read its commands from STDIN, too, then both read from the same source, which, well, leads to "unexpected" results. (The only strange thing is, why apt-get reads from STDIN at all, because it needs to ask no question.) But generally it is just fine for a command to read from STDIN and exactly this is the reason, why process substitution exists and should be used in that case. – michas Dec 4 '15 at 6:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.