.. highlight:: console .. role:: bash(code) :language: bash :class: highlight Loops and branches ------------------ In this section, we'll be starting towards some basic programming using the shell. We won't be writing many programs using :bash:`bash`, but we'll introduce some useful control structures, such as :dfn:`loops` and :dfn:`branches` structures. Exit codes and chaining commands ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First, let's start executing some commands in sequence. Last time, you tried looking at the sizes of the 5 largest files by piping the output from one command into another. Pipes aren't the only way that processes can be chained: :bash:`&&` will execute the command on the right only if the command on the left is successful. *Successful* is defined by the :dfn:`exit value` of the command, which you can find by :command:`echo`-ing the :bash:`$?` variable:: $ ls [...] $ echo $? 0 :bash:`0` is the successful exit value of a Unix process: non-zero numbers can be used to encode what went wrong. Try running some commands that might fail, like :command:`grep`-ing for non-existent words or trying to :command:`touch` a file in an area you can't write to, then look at the exit code. The meanings of difference exit codes are usually documented in a program's manpage. Now let's try the :bash:`&&` construct:: $ echo "foo" > foo && echo "Done." $ echo "foo" > /foo && echo "Done." Get the idea? The corresponding syntax for a command chain which continues only if the preceding command *failed* is :bash:`||`, e.g.:: $ echo "foo" > /foo || echo "D'oh, that's write-protected" if ... then ... else ~~~~~~~~~~~~~~~~~~~~ The :bash:`&&` and :bash:`||` constructs are hard to use together to work with both possible outcomes --- there is a special :bash:`if` syntax for this:: $ if test -e myfile; then echo "myfile exists"; else echo "No it doesn't"; fi You can also use any number of optional :bash:`elif` sections before :bash:`else`, with syntax like the :bash:`if` part above. Note here that we have put multiple commands on one line by separating the commands with :kbd:`;`. This is a general feature in :bash:`bash` shell syntax: we could have also left out the semi-colons and had line breaks each time. :command:`test` is a command with a lot more uses than we've shown here. You'll have to read the man page, or guides on the Web to find out more, though. for-loops ~~~~~~~~~ :bash:`for` provides a very powerful cabability: a loop over a set of arguments. Try to understand what the following lines do before running them in your course directory:: $ for i in A B C; do echo $i > baz$i.txt; done $ ls baz* $ for i in $(seq 1 9); do echo $i > baz$i.txt; done $ ls baz* $ for filename in baz*; do echo -n "$(date): $(pwd)/$filename: "; cat $filename; done Note that the for loop variable can have any name and that it takes the value of each of the separate words between :bash:`in` and the semi-colon. Let's also see what the expression:: $ echo "foo $(pwd) bar" does: the :bash:`$( cmd )` construct uses the *output* of running :command:`cmd` in place [#f1]_. .. note:: Sometimes you'll see ```foo``` (with *backtick* characters, not apostrophes) instead of :bash:`$(foo)`. Both are equivalent, but since the bracket form is only one keystroke longer and can be nested, there's usually no reason to use backticks anywhere. Finally, let's do something that's really tedious to do with a pointy-clicky graphical interface:: $ for i in $(seq 1 1000); do date > foo$i.txt; done In a fraction of a second, we have created 1000 files with names like :kbd:`foo2.txt`. Now let's rename them all from :file:`foo...txt` to :file:`oof...date` and append another line to the contents of each file:: $ for i in foo*.txt; do newname=${i/foo/oof}; newname=${newname%.txt}.date; mv $i $newname; date >> $newname; done Have a look at the resulting files. Wouldn't that have been a nightmare to do by hand? Real life tends to produce more realistic examples, but the principle is the same: it's not that uncommon to have to resize 100 plot images or process 1000 data files in a systematic way. .. rubric:: Footnotes .. [#f1] In this use case, though, the :envvar:`$PWD` variable is a better way to get the current directory!