In Beginning with BASH, part 1 , I introduced you to BASH (Bourne Again Shell). I described the basic interface and some commands that make using BASH a little easier. In part 2, I’ll look at customizing BASH to make it even more useful.
The initialization file
In order to customize BASH, you must modify the initialization file. The BASH initialization file is either ~/.bash_profile or ~/.profile, depending on the distribution. Some distributions also call a ~/.bashrc file from the initialization file, where functions and aliases typically are stored. (You can tell if this last possibility is the case by looking in ~/.profile to see if it executes ~/.bashrc.) Basically, these initialization files are scripts that are run as soon as the shell loads (somewhat like AUTOEXEC.BAT for DOS users). For the sake of simplicity, I’ll assume that ~/.profile is used as the BASH initialization file. Of course, when I use ~/.profile, I mean the file .profile in the user's home directory or /home/username/.profile. (The tilde symbol is used as a "shortcut," meaning the current user's home directory.)
In addition, many Linux distributions have a default initialization file that’s used globally, usually the /etc/profile file. This file is executed prior to the user's own ~/.profile script, and it’s used to set system-wide defaults that are available to all users. The distributions that make use of the ~/.bashrc script also use the /etc/bashrc script for system-wide defaults.
Within the ~/.profile file, we can insert such commands as aliases, initialize variables, and so forth. Some of the more commonly used variables are as follows:
|EDITOR||The default editor|
|FCEDIT||The default editor used by the fc command|
|HISTFILE||The file used to store the command history|
|HISTSIZE||The size of the history file|
|HOME||The home directory of the current user|
|OLDPWD||The previous working directory|
|PATH||The search path that BASH uses to look for executables|
|PS1||The first-level prompt displayed on the command line|
|PS2||The second-level prompt displayed on the command line|
|PWD||The current working directory|
|SECONDS||The number of seconds elapsed since the BASH session began|
BASH has many more variables that it can use or reference. For a complete list, look at the manual pages for BASH (man bash).
With BASH, you can customize the shell prompt to your liking and have it give as much or as little information as you want. To create your own prompts, edit ~/.profile and look for the PS1 variable. The default for this prompt is simply % (or # if the user who’s logged in is root). If the PS1 variable exists, change it to suit your needs; if it doesn't exist, create it by inserting the following into ~/.profile:
PS1="[\d \t] [\u@\h]\n\w \$ "
It will give you a shell prompt that looks something like this (assuming that the username is vdanen, the machine name is tux, and the user is currently in his or her home directory):
[Sun Nov 21 15:26:21] [vdanen@tux]
In order to translate the special characters in the PS1 variable, the contents of the variable must be enclosed in quotation marks. If no quotation marks surround the value of PS1, the string is displayed literally, without translation of the special characters. There are a number of special characters or tokens that you can use to customize your prompts further, and they can be combined in any way that the user prefers.
|\!||Displays the history number of this command|
|\#||Displays the command number of the current command|
|\$||Displays a $ in the prompt unless the user is root, in which case it displays #|
|\\||Displays a backslash|
|\d||Displays the current date|
|\h||Displays the host name of the computer on which the shell is running|
|\n||Prints a newline character|
|\s||Displays the name of the shell that’s running|
|\t||Displays the current time|
|\u||Displays the username of the current user|
|\W||Displays the base name of the current working directory|
|\w||Displays the full current working directory|
|\xxx||Displays the character that corresponds to the octal value of the number xxx|
Here are some examples:
The PS2 variable is displayed when BASH expects more input from the user in order to complete a command. By default, this prompt is simply >, but if you want it to display something more meaningful (such as More information needed >), you’d use
PS2="More information needed >"
Shell scripting basics
Shell scripting is very useful to even the basic Linux user. Shell scripting can be used to create a simple way of running often-used commands or of placing a number of commands together that can be run by issuing one command. Shell scripts are similar to DOS batch files in that respect. Shell scripting, though it can be used for tasks as simple as running other commands together, can be used to write complex programs or tasks, too. Shell scripting is a simple form of programming, but it’s efficient and powerful—and easy to use.
Shell scripts must be executable in order to run, just like any other program on your Linux system. Therefore, you must change the mode of the file to executable. To do so, issue the chmod u+x (or chmod 744) command on the script file. A script file must have the appropriate signature bytes in the file. These signature bytes tell the kernel that the script is executable, and they tell the script which interpreter to use on the file. The first two bytes of a script must be the character sequence #! and must be followed by the path and filename of the interpreter. If the kernel sees
as the first line of any executable file, it runs /bin/sh, with the script name as an argument. /bin/sh is the path and name of the BASH program. Some other scripts may have something like #!/bin/perl, which tells the kernel that the script is a Perl script, and so on, with many other interpreters. When writing a script, you should keep in mind that the first line must contain #!/bin/sh and that it must be an executable file.
Like any other programming language, BASH makes use of variables. You can use the environment variables (like PATH or PS1), or you can create your own, depending on your needs.
To create the variable number with a value of 10, you’d use
It’s quite simple. The same goes for character value variables. To define the variable username with a value of Vincent, use
In order to access the contents of a variable, you must place the dollar sign ($) before the variable name. You can echo it to the screen, add it to other variables, or manipulate it in any way you want. To print the value of the username variable, you’d use
There are some built-in shell variables (beyond the basic BASH variables themselves) that are commonly used within shell scripts:
|$#||Stores the number of command-line arguments that are passed to the script|
|$?||Stores the exit value of the last command that was executed|
|$0||Stores the first word of the entered command (in a shell script, it would be the name of the shell script itself)|
|$*||Stores all the arguments that were entered on the command line ($1 $2...)|
|$x||Stores argument x that was used on the command-line ($1 is the first argument, $2 the second, and so on)|
|"$@"||Stores all of the arguments that were entered on the command line, individually quoted ("$1" "$2"...)|
The available built-in variables for BASH scripting are quite useful for a number of tasks, including writing programs that accept command-line parameters—since we can parse arguments passed to the script with some of the variables, as shown previously. These tasks make BASH scripting more useful than merely being a way to clump often-used commands together; it can be used to write actual programs.
Using quotation marks within your code when writing shell scripts (or programs) is very important. Quotation marks are critical to the shell, and they are interpreted differently, depending on how you use them. There are double quotation marks (""), there are single quotation marks (''), there are reversed quotation marks (``), and there is the backslash (\) character. All of these characters have a special function within shell scripting.
Double quotation marks are the most common and least powerful of the different quotation methods. Characters surrounded by double quotes are assigned directly to the variable as one long string, and the shell interprets any special characters. For example, assigning a string containing the current user's name to the variable hello might look like
It would tell the shell to interpret the value of $USERNAME and place it directly in the string. Single quotes, however, treat the same string differently. Single quotes hide special characters from the shell, which means that the value of $USERNAME won’t be used. The value of the variable $hello would be, literally, "Welcome, $USERNAME" if the value of the variable were enclosed by single quotes when it’s assigned initially.
Using the backslash character is another way of hiding special characters from the shell, and it works similarly to the single quote method, except that the backslash method can hide only one character at a time and not a group of characters. Therefore, backslash "quoting" is often used when you want to hide one single character from the shell.
For example, perhaps you wanted to assign the value $10.00 to the variable $price. There are a few ways that this task can be accomplished:
Reversed quotation marks perform an entirely different function. You use them when you want to store the results of a specific command as the value of a variable. For example, to have the value of $files equal to the list of files in the current directory, you’d use
As you’ll see later, reversed quotes are a powerful way of performing actions based on the output of other external commands.
In BASH, a command called test exists to evaluate conditional expressions. What this fact means, quite simply, is that test is a way to see whether a specific condition you want met is True or False. You can use the following syntax for the test command (and whichever condition you use is more a matter of preference and style than anything else):
There are a number of built-in operators for the test command. These operators fall into four categories: integer operators, string operators, file operators, and logical operators.
The following table shows the different integer operators that are used by test (where int[x] is an integer to test):
|int1 -eq int2||Returns True if int1 is equal to int2|
|int1 -ge int2||Returns True if int1 is greater than or equal to int2|
|int1 -gt int2||Returns True if int1 is greater than int2|
|int1 -le int2||Returns True if int1 is less than or equal to int2|
|int1 -lt int2||Returns True if int1 is less than int2|
|int1 -ne int2||Returns True if int1 is not equal to int2|
[ $test -ge 4 ]
would return True because the variable $test (which has a value of 5) is greater than or equal to 4.
The following table shows the different string operators that are used by test (where str[x] is a string to test):
|str1 = str2||Returns True if str1 is the same as str2|
|str1 != str2||Returns True if str1 is not the same as str2|
|str||Returns True if str is not null (empty)|
|-n str||Returns True if the length of str is greater than zero|
|-z str||Returns True if the length of str is equal to zero|
For example, suppose that the script, run.sh, is run with an argument of linux. The contents of the run.sh script are
if [ -z $1 ]
echo "No argument."
if [ -n $1 ]
echo "Argument is $1."
The output of the script will be "Argument is linux." Or if you passed the argument cool!, then the output will be "Argument is cool!." If no argument is passed to the run.sh script, then the output is "No argument." The above listing shows a simple example of handling command-line arguments. I'll look at the if/then statement further in the next part of this series, and I’ll examine a better way to parse command-line arguments.
Here are the file operators that are used by test (where fname is the name of the file to test):
|-d fname||Returns True if fname is a directory|
|-f fname||Returns True if fname is a normal file|
|-r fname||Returns True if fname is readable in the current shell|
|-s fname||Returns True if fname is more than zero bytes in size|
|-w fname||Returns True if fname is writable in the current shell|
|-x fname||Returns True if fname is an executable file|
Here’s an example: We have used the run.sh script in the previous example of the home directory for the current user. If we wanted to test certain conditions on the script, we could do so with the following statements:
[ -x ~/run.sh ]
will return True because the run.sh script is an executable script.
[ -d ~/run.sh ]
will return False because the run.sh script is a file, not a directory.
Logical operators are used to combine two or more of the integer, string, or file operators to test against another single integer, string, or file operator. Here are the logical operators used by test (where expr[x] is an expression to test):
|! expr||Returns True if expr is not True|
|expr1 -a expr2||Returns True if expr1 and expr2 are both True|
|expr1 -o expr2||Returns True if either expr1 or expr2 is True|
Suppose that a script is being written to handle user input and we want to make sure that the user can use either upper or lower case for the input. The simplest way to accomplish this task would be to test for both upper and lower case at the same time by using a logical operator like this:
if [ $answer = "y" -o $answer = "Y" ]
We’ve stored the user's input in the variable $answer and tested to see whether $answer contains the letter y in upper or lower case. If the result is True, we then proceed with the rest of the script.
As you can see, BASH has the ability to be more useful than just working as a simple command interpreter. Writing scripts in BASH allows you to accomplish complex tasks easily, without the need to learn new programming languages or fiddle with compilers. BASH is easy and straightforward, and it can perform a startling number of tricky tasks that will make your life as a Linux user or systems administrator much easier.
In the final part of our series, I’ll take a look at conditional statements, the writing of scripts, proper syntax, and so on. I’ll provide sample scripts, give you a better understanding of your system, and show you how to customize your system to your own unique tastes and tasks even more.
Vincent Danen, a native Canadian in Edmonton, Alberta, has been computing since the age of 10, and hehas been using Linux for nearly two years. Prior to that, he used OS/2 exclusively for approximately four years. Vincent is a firm believer in the philosophy behind the Linux "revolution,” and heattempts to contribute to the Linux causein as many ways as possible—from his Freezer Burn Web site to building and submitting custom RPMs for the Linux Mandrake project. Vincent also has obtained his Linux Administrator certification from Brainbench . He hopes to tackle the RHCE once it can be taken in Canada.
Vincent Danen works on the Red Hat Security Response Team and lives in Canada. He has been writing about and developing on Linux for over 10 years and is a veteran Mac user.