0

Motivation

I'm in a situation where I have to run multiple bash commands with a single bash invocation without the possibility to write a full script file (use case: Passing multiple commands to a container in Kubernetes). A common solution is to combine commands with ; or &&, for instance:

bash -c " \
echo \"Hello World\" ; \
ls -la ; \
run_some_command "

In practice writing bash scripts like that turns out to be error prone, because I often forget the semicolon leading to subtle bugs.

Inspired by this question, I was experiment with writing scripts in a more standard style by using a heredoc:

bash <<EOF
echo "Hello World"
ls -la
run_some_command
EOF

Unfortunately, I noticed that there is a difference in exit code error handling when using a heredoc. For instance:

bash -c " \
run_non_existing_command ; \
echo $? "

outputs (note that $? properly captures the exit code):

bash: run_non_existing_command: command not found
127

whereas

bash <<EOF
run_non_existing_command
echo $?
EOF

outputs (note that $? fails to capture the exit code compared to standard script execution):

bash: line 1: run_non_existing_command: command not found
0

Why is the heredoc version behaving differently? Is it possible to write the script in the heredoc style and maintaining normal exit code handling?

bluenote10
  • 23,414
  • 14
  • 122
  • 178
  • `for instance:` - your instance doesn't combine commands (at least not in a way I define "combine"), it runs `bash -c echo "Hello World"` and then runs `ls -la` and `run_some_command`, all in parent shell. I think you are missing \ slashes – KamilCuk Oct 15 '19 at 12:12
  • @KamilCuk Correct fixed, thanks! Copy paste mistake, when deploying to Kubernetes the `\` are handled via YAML multi line strings... The observations should still be valid. – bluenote10 Oct 15 '19 at 12:25
  • No, it's not the same thing. Probably the same thing would be `bash -c "echo \"Hello World\" ; ls -la ; run_some_command ;"` – KamilCuk Oct 15 '19 at 12:27

1 Answers1

4

Why is the heredoc version behaving differently?

Because $? is expanded before running the command.

The following will output 1, that is the exit status of false command:

false
bash <<EOF
run_non_existing_command
echo $?
EOF

It's the same in principle as the following, which will print 5:

variable=5
bash <<EOF
variable="This is ignored"
echo $variable
EOF

Is it possible to write the script in the heredoc style and maintaining normal exit code handling?

If you want to have the $? expanded inside the subshell, then:

bash <<EOF
run_non_existing_command
echo \$?
EOF

or

bash <<'EOF'
run_non_existing_command
echo $?
EOF

Also note that:

bash -c \
run_non_existing_command ;
echo $? ;

is just equal to:

bash -c run_non_existing_command
echo $?

The echo $? is not executed inside bash -c.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Interesting, is there a simple explanation why quoting the `EOF` gets rid of the necessity to escape the `$?`? – bluenote10 Oct 15 '19 at 12:32
  • 2
    Yes, it was designed this way. See [posix shell 2.7.4 Here-Document](https://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html) `If any character in word is quoted, .... the here-document lines shall not be expanded.` – KamilCuk Oct 15 '19 at 12:45