Chaining commands (Part 1: Lists and Compound Commands)
Basic and conditional chaining. Even without redirections (nor background execution), Bash features multiple ways of chaining commands. Let us quickly explore the specificities of them, in order to create those delightfully convenient one-liner scripts.
Basic chainings
<newline> is not the only command delimiter that exists in Bash, so that you can execute a set of commands when hitting the Enter key in your terminal.
If you execute command1 ; command2, Bash will start by executing command1, wait until this command is finished, and then execute immediately after command2 no matter if command1 failed.
This feature may seem useless, but it is in fact quite useful for executing a group of commands that belong together.
Example: sleep 30;qsub my_job.j will defer your submission by 30 seconds (allowing you to change your mind, and cancel it before your job is submitted!)
Example: for my_file in "my jobs"/*.j ; do echo "submitting: $my_file" ; qsub "$my_file" ; done will submit all your jobs in the my jobs directory (note the quoting required for escaping the space: never use spaces in filenames!).
My real script (it fails otherwise...): for file in *.j;do sleep 10;while [ $(qstat|grep -c ' qw ') -gt 0 ];do sleep 1;done;
echo "submitting $file";qsub $file;done
Example: cd ~/cp2k/makefiles/ ; make ; cd - is the kind of commands list I used to compile CP2K without messing with my working directory.
Using lists of commands (instead of single commands) can help you use your bash history much more efficiently (instead of using the Ctrl-o trick)!
Note that you can use the syntax: command1;command2 or command1 ; command2 equivalently.
Conditional chainings
In Bash it is possible to quickly chain commands depending on their execution success. Two mechanisms exist:
command1 && command2 where command1 is executed, and immediately upon completion, command2 is executed iff (if and only if) command1 was executed with success.
command1 || command2 where command1 is executed, and immediately upon completion, command2 is executed iff (if and only if) command1 failed.
Example: mkdir my_dir && cd my_dir,
or cp2k.sopt -i input.in || mail -s "job error" my.mail@example.com
Interlude: Computational details
What is the magic behind it? What does it means for a command to fail? Spoiler alert: exit status
Each command return upon completion an integer between 0 and 255 called an exit status. Usually, a non-zero exit status indicates that something went wrong.
Note: You can check the exit status of the last command with the special parameter $?
Note: What about negative status code? -n and 256-n are equivalent in this encoding, so -1 is the same as 255.
Note: Was my command killed? You can check it with the exit code: if terminated by a signal (code number: s), the exit code is 128+s.
Back to the chaining: precedence and grouping
One can need to group commands due to precedence considerations. It is called compound commands.
Example: cd ~/cp2k/makefiles/ && { make;cd -; } is a much more robust version of the example above.
Control operators' precedence is best explained with an exemple: command1 && command2 || command3 && command4 ; command5 || command6 is equivalent to { { { command1 && command2; } || command3; } && command4; } ; { command5 || command6; }
Note: The semi-colon, and the spaces within the braces are mandatory. The syntaxes { command } and {command;} are invalid in Bash.
Advanced: an other compound command is available in Bash: (commands list) which executes commands list in a sub-shell. Example: a=42;(a=3;echo "a=$a");echo "a=$a"