bashheredoc

Using && after a heredoc in bash


I have a bash script whose commands I chain together using &&, as I want the script to stop if individual steps fail.

One of the steps creates a configuration file based on a heredoc:

some_command &&
some_command &&
some_command &&
some_command &&
some_command &&
some_command &&
cat > ./my-conf.yml <<-EOF
host: myhost.example.com
... blah blah ...
EOF
... lots more commands ...

How can I include this command in the && chain? I tried:

CLARIFICATION:

There are lots of (multiline) commands following the command that generates the config file from the heredoc, so ideally I'm looking for a solution that allows me to place the following commands after the heredoc, which is the natural flow of the script. That is I would prefer not to have to inline 20+ commands on a single line.


Solution

  • Chaining commands in a single line

    You can put the control operator && right after the EOF word in your here document and you can chain more than one command:

    cat > file <<-EOF && echo -n "hello " && echo world
    

    It will wait for your here-document and then will print hello world.

    Example

    $ cat > file <<-EOF && echo -n "hello " && echo world
    > a
    > b
    > EOF
    hello world
    
    $ cat file
    a
    b
    

    Chaining commands after the heredoc delimiter

    Now, if you want to place the following commands after the heredoc, you can group it in curly braces and continue chaining commands as follows:

    echo -n "hello " && { cat > file <<-EOF
    a
    b
    EOF
    } && echo world
    

    Example

    $ echo -n "hello " && { cat > file <<-EOF
    > a
    > b
    > EOF
    > } && echo world
    hello world
    
    $ cat file
    a
    b
    

    Using the set built in

    If you're going to use set [-+]e instead of chained commands with &&, you must notice that surrounding a chunk of code with set -e and set +e is not a direct alternative and you must take care of the following:

    Surrounding dependent commands with set [-+]e

    echo first_command
    false # it doesn't stop the execution of the script
    
    # surrounded commands
    set -e
    echo successful_command_a
    false # here stops the execution of the script
    echo successful_command_b
    set +e
    
    # this command is never reached
    echo last_command
    

    As you can see, if you need to go on executing commands after the surrounded commands, this solution doesn't work.

    Grouping Commands to the rescue

    Instead, you can group the surrounded commands in order to create a subshell as follows:

    echo first_command
    false # it doesn't stop the execution of the script
    
    # surrounded commands executed in a subshell
    (
    set -e
    echo successful_command_a
    false # here stops the execution of the group
    echo successful_command_b
    set +e # actually, this is not needed here
    )
    
    # the script is alive here
    false # it doesn't stop the execution of the script
    echo last_command
    

    So, if you need to execute something else after your chained commands and you want to use the set builtin, consider the examples above.

    Also notice the following about subshells:

    Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment, except that traps caught by the shell are reset to the values that the shell inherited from its parent at invocation. Builtin commands that are invoked as part of a pipeline are also executed in a subshell environment. Changes made to the subshell environment cannot affect the shell’s execution environment.