BASH Basics - Part 4: Variables, Tests and Loops

This part covers the most basic introduction to scripting: variables, tests, and loops. The one-liner for making archives out of different folders also gets some company by other nifty one-liners now. But first, we are going to have some quick shortcuts to make working with BASH more enjoyable.

BASH shortcuts

If you want to run just one command as a different user, use the following command:

$ su - otheruser -c "command argument"
For having two commands execute one after each other, connect them with ‘;’ for example:
$ ls /home/odroid/Music; ls /home/odroid/Videos
If you want the second command executed only when the first command is successful, use ‘&&’:
$ apt update && apt full-upgrade
Rarely used is ‘||’, where the second command is executed only when the first command is NOT successful.

In day-to-day work, there are certain steps which we do very often in different contexts. One example is, executing a command as root after we tried as normal user and failed due to insufficient privileges.

After we do cp somefilewithverylongname.conf /etc to copy a background configuration to the /etc directory and fail, we can repeat this as root with just the ‘sudo !!’ command - most of you know this already. The !! stands for a repeat of the last command and can be used with sudo, without or in other combinations. Modified with the print modifier, ‘!!:p’ brings up the last argument to the command line. Usually, you just use the up arrow key, though. But in cases where the arrow keys don't work, for instance sometimes over ssh, it can be a great relief! !-1 uses the next-to-last command, which is also sometimes useful. However, did you know that you can also reuse only the last argument of a command?

$ ls /very/long/path/to/a/directory
$ cd !$
expands to:
$ cd /very/long/path/to/a/directory
and can save a lot of typing.
$ ls /very/long/path/to/a/directory
ton of commands, none of which start with ls
$ !ls
also expands to
$ ls /very/long/path/to/a/directory
in case you want to repeat an older command. Use ‘!rm:p’ to examine the last rm command before executing it, likewise with other dangerous commands. If you want to change only details in the last command, you can do a find and replace of the arguments with the follow:
$ ls /very/long/path/to/a/directory
$ ^very^veryvery
changes and executes the last command to
$ls /veryvery/long/path/to/a/folder
To sum up what we’ve looked at in a list:

  • !! last command
  • !-1 next-to-last command
  • !$ last argument of last command
  • !command1 last line with command1
  • ^searchterm^replaceterm replaces first occurrence of searchterm with replaceterm

For movements on the command line, alt-f moves the cursor forward one word, alt-b backward one word, ctrl-w erases single words backwards, while ctrl-u erases from cursor position to the beginning of the line. You can move to the beginning of the line with ctrl-a and clear the line after the cursor with ctrl-k. A ctrl-t swaps the last two characters before the cursor in case you tend to make this typing error often.

If you want something to lighten you up and mistype sl instead of ls often, you can also install sl with apt install sl. I won't tell you what it does, just try. Most importantly, if you have a unique filename or command after typing the first letters, BASH expands them after you hit tab. Hitting tab twice even gives a helpful list of options if the first letters are not unique.

One last thing, for now, is the use of braces. If you have a file named abcde.conf and want to make a backup with the name abcde.conf.bak, all you have to do is cp abcde.conf{,.bak} which expands to cp abcde.conf abcde.conf.bak with the use of the braces. Everything in the braces expands to the listed options, so if you want to list the Videos directory of the users archie, bert, and claude. Then the filtering does the trick

$ ls /home/{archie,bert,claude}/Videos

Scripting basics

A BASH script is just a text file with the first line #!/bin/bash and made executable with:

$ chmod a+x
If you make a directory named bin in your home directory, scripts in there can be executed from anywhere on Ubuntu. A special entry in ~/.profile takes care of that.

If you want to, you could even code the game "Tetris" in BASH in a little more than 500 lines, as shown in Figure 1.

Figure 1 - Tetris in BASH on the command line
Figure 1 - Tetris in BASH on the command line

A typical example of a script would be - don't forget to execute chmod a+x after you saved it from your test editor.

# This script just puts out "Hello World".
echo "Hello World"
Except for special cases, all text following a # is a comment and not executed.

If you only have "Hello World" on the screen, or even any other fixed text output, this gets boring real quick. Now it’s time to introduce BASH variables to make the script do something different depending on the input. The simplest form is seen as follows in our file.

# Greets currently logged-in user
echo "Hello," "$USER"
“Hello, odroid” is the result. We can also define the variable in the script instead of using an environment variable like USER. Here is our next script file,
# Greets currently logged-in user
echo "Hello," "$user"
“Hello, odroid” is the same output, but a variable user gets defined by the result of the whoami function and then printed with the echo function. You can simulate this also step-by-step on the command line without writing the script in the text editor. If you define a variable $user, don't forget to use unset user afterwards to leave the system clean. We will talk more about variables in the next part. For now, let's get an example of each basic part done first to get a better overview of the typical script usage. The next building block needed are tests inside the script.

For a real-world example, let's look at a short script to test for Internet connectivity,

nc -zw1 $test 443
echo "we have connectivity"
echo "no outside connectivity"
The script defines the variable $test as the server, then uses netcat (nc) in port scan mode for a quick poke, -z is zero-I/O mode, with a quick timeout -w 1 waits at most one second. It checks Google on port 443 (HTTPS). The output is dependent on if you can reach Google's servers or not.

Now, let's look at loops. With variables, tests and loops, you have already 95% of normal script usage covered. A simple loop in a real world script would be to convert each flac file to mp3 in a directory:
for i in *.flac
ffmpeg -i "$i" -acodec libmp3lame "$(basename "${i/.flac}").mp3"
This script loops, converts and renames for each flac file in the current directory. Take a look at how the basename function together with the variable changes the extension from .flac to .mp3 in this example. These are the most basic examples for variables, loops and tests; more to follow later. In the next part, we continue with scripting, and also take a look at BASH history.


Be the first to comment

Leave a Reply