BASH Basics: Introduction to BASH - Part 5

Our adventure into scripting continues with more tests, if statements, input and functions; we also do calculations from within BASH. First, let's look into filename manipulation, since this seems to be one of the most popular things needed in beginner scripts. As a bonus for making it through a lot of theory, we have another example of BASH programming in the form of a game.

Basenames

The first script people usually write are to handle lots of file manipulation. Having to do simple tasks again and again, such as conversions, gets boring real quick. It's only natural that even the most script-averse people try to automate this after a while. Usually, this involves storing the result to the same filename but with a different extension, for example converting file001.png to file001.jpg then, file002, file003 and so on.

So what can BASH do here?

If you open the bash manual, with man bash, somewhere in the nearly 6000 lines of the manual, equal to 35 parts of the BASH series you are reading now, you will find a section about 'Parameter Substitution'. I will save you from scrolling through all this and give you a helpful trick to handle long man pages:

$ man -P "less -p 'Parameter Expansion'" bash
This jumps directly to the section you are looking for, if you remember the right keywords. Let's sum up our findings here, since the man page is even drier than my article and so terse that you have to read it several times for full impact. The variable writing convention $foo is a shorthand, the full version is ${foo} which is also a short version, of ${foo:operators}. What does this mean? Let's look at some of these constructs, and then follow up with examples. There are two operators inside the curly braces, the # and the % operator. They can be used as single characters or in pairs. The resulting combinations are

  • To trim the shortest suffix from the end: ${variable%pattern}
  • To trim the longest suffix from the end: ${variable%%pattern}
  • To trim the shortest prefix from the beginning: ${variable#pattern}
  • To trim the longest prefix from the beginning: ${variable##pattern}

The difference between "shortest" and "longest" only applies if you are using a shell wild card * in your pattern, for something like jpg, they are identical. Now we need a few examples to understand how this works. Since learning by doing is always best, you can experiment with the echo command and several shell variables yourself. If you want to follow the example, make a path /tmp/odroid with mkdir -p /tmp/odroid, followed by touch /tmp/odroid/photos.zip to generate an empty file for your experiment. It's not needed if you follow line-by-line, but for your own experiment, it's better to have something which you can manipulate. Here are the results: If we have the variable

foo="/tmp/odroid/photos.zip"
(Don't forget the quotes to make sure that you can deal with spaces and other difficult special characters) you can use

  • foo=/tmp/odroid/photos.zip"; echo "${foo%/*}" to show the path /tmp/odroid. That is an easy method to get the so-called dirname of a file.
  • foo=/tmp/odroid/photos.zip"; echo "${foo##*/}" yields photos.zip, the basename of the file.
  • foo=photos.zip"; file="${foo%%.*}" gives photos, and foo=photos.zip"; echo "${foo#*/}" gives zip.

One other useful application of curly brackets is curly bracket expansion. For example, if you want to make a backup file of something, but are too lazy to type out the full path and filename for source and destination: cp /path/to/your/file.doc{,.bak} expands to cp /path/to/your/file.doc /path/to/your/file.doc.bak and makes a backup file of your document in the same directory. ${#foo} gives the number of characters of the variable. foo=4-letter-word; echo ${#foo} is 13.

Tests and if statements

To look into tests and if statements in more detail, we want to have an example script with all the basic blocks which can occur, as shown below:

#!/bin/bash  #Start of every BASH script
# Basic if statement # Comment, script purpose
if [ $1 -gt 9 ]  #test condition and if statement
then
    echo $1, that\'s unusual for a lucky number.  # if statement true
    date  # if statement true
fi
Pwd  #always executed
then
    date
else
    pwd
...
The snippet above could be part of such a script. There's also if - elif - else or short for 'else if' statements, case statements, or the option of multiple tests with AND or OR conditions with the operators && for AND and || for OR, but this is beyond the scope of this article. The square brackets, [ ], in the if statement above is a reference to the command test. All the operators that test allows may be used here as well. Look up the man page for test to see all the possible operators; some of the more common ones are listed below.

  • ! EXPRESSION - The EXPRESSION is false.
  • -n STRING - The length of STRING is greater than zero.
  • -z STRING - The lengh of STRING is zero (= it is empty).
  • STRING1 = STRING2 - STRING1 is equal to STRING2
  • STRING1 != STRING2 - STRING1 is not equal to STRING2
  • INTEGER1 -eq INTEGER2 - INTEGER1 is numerically equal to INTEGER2
  • INTEGER1 -gt INTEGER2 - INTEGER1 is numerically greater than INTEGER2
  • INTEGER1 -lt INTEGER2 - INTEGER1 is numerically less than INTEGER2
  • -d FILE - FILE exists and is a directory.
  • -e FILE - FILE exists.
  • -r FILE - FILE exists and the read permission is granted.
  • -s FILE - FILE exists and its size is greater than zero (= it is not empty).
  • -w FILE - FILE exists and the write permission is granted.
  • -x FILE - FILE exists and the execute permission is granted.

Input, more variables

For BASH input, if you want to have an interactive script, you can use ‘read’ to read the variable from the user input. Simple script example, greeting.sh:

echo Who are you?
read name
echo It\'s nice to meet you, $name.
Since the quote has a special meaning for BASH, it gets escaped via the backslash. read -p and read -sp can be used to prompt and be silent, which means not to echo the typed input:
read -p 'Username: ' user
read -sp 'Password: ' pass
stores the username in $user and the password in $pass, without showing it. If the user enters several words when prompted, read varx vary varz would store three words in the three different variables. If more than three words were given, the last variable stores the remaining input. Here are some special variables which you can use in your scripts:

  • $0 - The name of the Bash script.
  • $1 - $9 - The first 9 arguments to the Bash script.
  • $# - Number of arguments passed to the Bash script.
  • $@ - All the arguments supplied to the Bash script.
  • $? - The exit status of the most recently run process.
  • $$ - The process ID of the current script.
  • $USER - The username of the user running the script.
  • $HOSTNAME - The hostname of the machine the script is running on.
  • $SECONDS - The number of seconds since the script was started.
  • $RANDOM - Returns a different random number each time is it referred to.
  • $LINENO - Returns the current line number in the Bash script.

These should be pretty simple, with $0 needing a little more detail: With this, you can have your script act differently depending on which name you used for it, for example, compressing and decompressing files. A full list of pre-defined variables which you can use is available if you type ‘env’ on the command line.

With this, there are now 3 methods of passing data to a BASH script, and which method is best depends on the situation:

  • Command line arguments, like ourscript.sh foo bar baz gives $1=foo, $2=bar and $3=baz.
  • Read input during script execution, see above.
  • Data that has been redirected into the script via stdin. This is more for experienced scripters, basically, it is same as piping results from another script or function into our script with ‘|’.

We used quotes now sometimes without explaining what they are good for. Since BASH uses a space to separate items, device=Odroid XU4; echo $device gives an error message that 'XU4' cannot be found, because the variable content is just 'Odroid' and BASH tries to execute 'XU4'. If you want a variable with spaces, you need to use quotes. Text in quotes indicates to BASH that the contents should be considered as a single item. You may use single quotes ' or double quotes ". Depending on what you want to do, either one is important:

  • Single quotes will treat every character literally. echo 'I am $USER' prints I am $USER.
  • Double quotes will allow you to do substitution (that is, include variables which get evaluated). echo "I am $USER" prints I am odroid.

You can also use command substitution to have a variable filled with the evaluation of command(s) - foo=$(ls /etc | wc -l); echo There are $foo entries in /etc. shows how many configuration entries are in your /etc directory.

If you want to have variables available for other scripts, you need to export them. export foo makes $foo available for following scripts, but if they change $foo, this has no impact on the exporting script. Think of it as making a copy and handing it out.

Calculations

Let's cover calculations in BASH briefly. It's enough to know that $((...)) evaluates the term in the double brackets. So, if you want to know quickly how many seconds a year has, type:

$ echo $((60*60*24*365))
This should return 31536000. Only integer values are allowed, though. This is true for input and output - fractional output gets dropped by BASH. echo $((4/5)) gives 0, and only echo $((5/5)) would give you 1 as a result.

Practical BASH application

Another game as a reward for making it through all the theoretical BASH aspects, in around 350 lines of BASH script, is the game 2048. You can play it with the cursor keys, link to the script in the references. download it with wget.

This was a lot of exciting information, in the next part we take a break from the theory and look at an interesting command line applications and useful BASH scripts. Also a little more (only a little!) about scripting with loops and functions.

References

http://linuxg.net/curly-brackets-expansion-in-bash-with-5-practical-examples/ https://raw.githubusercontent.com/mydzor/bash2048/master/bash2048.sh

Be the first to comment

Leave a Reply