First Time Linux

The bash shell

If you've ever used the console in linux, then chances are you've already encountered the bash shell, as this is the shell normally responsible for interpreting your commands. There are other shells available, but default with Mandriva is bash. Every time you type something like:

$ ls

you're giving a command to the bash shell, which interprets it and calls an associated program or script.

Writing a bash script is like storing a set of shell commands in a file, which can then all be run together at any later time without having to type them all in again. The advantages are that you don't need any extra runtime environment to get installed and working, because it's already built in to the system. And if you later use another variety of unix, you can transfer a lot of your bash knowledge over.

Simple commands

As we've already seen, a command to the bash shell can be as simple as listing the files in the current directory with the ls command. Or you can give this command parameters, like this:

$ ls -l example*

which will show a long display (-l) of all the files whose names begin with "example". There are other options for ls to control sorting and filtering of the display, which you can find out about using man ls. For example, ls -lS shows a long listing sorted by size (that's a big S), and ls -lrt shows a long listing sorted by time in reverse order (oldest to youngest).

Joining commands together

You can also pass the results of one command on to a second, and run them together, like this:

$ ls -l example* | grep "txt"

which will make a list of the matching files as before, but instead of displaying it on screen, it will pass it (or "pipe" it) to the grep command. grep will search each line for the characters "txt" and only show those on the screen, so it will show the details of the files "example1.txt" and "example-txt.pl" but not "example12.tar".

This "piping" of outputs from one command into another is very general and powerful, so for example you can show all rpms installed on the system which contain "bash":

$ rpm -qa | grep "bash"

Piping long listings or file contents to the command less is also a useful way of paging through the contents:

$ cat example.txt | less

press space for the next page, cursor up/down to scroll by lines, and 'q' to exit.

Programming with bash

Just from the command line, you can also write simple mini-programs, for example a simple loop, repeating a single command several times:

$ for ((i=1; i<=10; i++)); do echo "i is now " $i; done

This makes a variable called i and sets it to 1, and then goes round a loop until i is bigger than 10. Each time round the loop it prints out a short message to the screen, including the value of i, and increments its value. Try it and see!

But now you've got this useful command, let's say you want to save it and run it again later. Simply use an editor (doesn't matter whether it's a GUI editor like Kate or a command line one like vi), and put the commands in the file. It's nice to save it with a file extension of .sh, so it's obvious that it's a shell script, but it doesn't really matter. Let's save it as testscript.sh

echo "I'm about to run the loop"
for ((i=1; i<=10; i++));
do
    echo "i is now " $i;
done
echo "finished!"

You can now run this script to see what it does:

$ bash testscript.sh

and hey presto, the script is executed. If you want to run the script directly without calling the bash command, you'll first have to make the script executable:

$ chmod u+x testscript.sh

so now if you do a ls -l you'll see that the 'x' flag is set for this file. Now let's run it!

$ ./testscript.sh

(you need the ./ because the shell doesn't normally look in the current directory for executables). The output should be the same as before. Note that if bash isn't your default shell, you'll need to add the line:

#! /bin/bash

to the very top of the script file, to tell the shell that the bash shell should be used to interpret this script.

Other loops with bash

There are many other ways to use for loops with bash, including a powerful way to loop over files:

let "fileNum = 0"
for file in image_*.txt
do
	let "fileNum+=1"
	echo "Found file \"$file\" which is number \"$fileNum\""
done

This example loops over all files in the current directory whose names begin with "image_" and just displays their names to the console. If you wanted, you could pass these filenames to some other shell command or script, perhaps renaming or deleting, or editing meta-information with the id3 command. It also keeps a counter, fileNum, which starts at zero and is incremented for each file found. The expression for matching the filenames is here just a direct match but could also be a regular expression for more complex matching.

You can also iterate over a fixed set of values in a different kind of for loop:

for trackNum in 01 02 03 04 06
do
	echo "Processing track number \"$trackNum\"..."
	# do something with each of the tracks
done

This example gives each of the values in the list 01, 02, 03, 04 and 06 to the variable trackNum and prints them to the console. Again, this could be extended to perform some operation for each of the tracks.

Conditionals using 'if'

Of course there's an 'if' command to only execute a block of code under certain circumstances. It's got a fairly wacky syntax though and is very picky about its spaces - so beware.

One good use for this is to check that the right number of parameters were given to the script, so it can do what it's supposed to do, for example:

if [ -z "$1" ] || [ -z "$2" ]; then
	echo "You must give two parameters";
else
	echo "First parameter is $1, second is $2";
fi
The way this works is it uses a cryptic "-z" parameter which checks if the following variable is missing. The "$1" is the first command line parameter and "$2" the second, so this if gets triggered if either the first or the second parameter is not there. Otherwise it drops through to the else and prints out the parameters.

Another example comparing files

Here's a slightly more complicated command to compare two text files. The idea is to take one large file, and take out all the lines which also appear in a second, smaller file. So the second file contains all the lines which should be removed from the first file. One solution is to just type this command into the command line:

cat first.txt |while read line; do if grep -q "$line" second.txt; then echo ""; else echo $line; fi; done > answer.txt

This uses a loop over all the lines in the first file, and if the result of the grep in the second file is true (a match is found), then just a blank line is output. If the line was not found in the second file, then the line is output. Finally the output is redirected to a new file. It's a good idea to use a comparison tool like kdiff3 on the files to check whether the results are what you expect.

Going further

There are whole books written on this subject, but for a primer see the gnu.org reference page or the tldp.org shell how-tos, including the bash introduction how-to. You can also get lots of information from the man command, for example man bash.

You may also find some more ideas on the hints and tips page, including examples of finding certain files and piping the results to other commands (eg deleting all tif files older than a certain date, or running a sed script on all html files in a tree), resizing a batch of image files, and using the grep command.

Good luck!