Bash scripting for beginning system administrators

A hands-on approach


A UNIX shell is essentially the API between the user, the kernel, and the system hardware. The shell is very important on any UNIX or Linux system and is one of the most vital aspects to learn proper systems administration and security. Typically driven by a CLI, the shell can literally make or break your system. The open source bash shell that this article examines is one of the most powerful, practical, and extensible shells available. In this article, you will learn the basic techniques of bash scripting, its everyday uses, and methods for employing it to create near-bulletproof shell scripts.

History of the bash shell

The Bourne Again Shell (bash) got its start in 1987, when it was written as a GNU project that many Linux distributions quickly adopted. Currently, many different versions of bash are freely available.

One of the more positive aspects of bash is its built-in security features. Bash keeps a record of what the user has typed exactly and writes it to a hidden file called .bash_history within the user’s home directory. So, if you implement bash, you can easily track your systems users much more closely. In fact, for many Linux systems, bash is commonly preinstalled as the default shell environment.

Bash command syntax and keywords have taken and improved upon the architecture and technical details of the Korn shell (ksh) and the C shell (csh). In addition, bash's syntax has many extensions that other shells lack. Integer calculations are more efficiently completed in bash than in other shells, and bash can redirect standard output (stdout) and standard error (stderr) more easily than older shells.

Bash is also ideal for secure environments, having a restricted start mode that can confine a user's ability within the shell to a brief, determinable list of commands. Open to modification, you can edit your own set of bash shell login control files—namely hidden files, such as .bashrc, .bash_profile, .bash_logout, and .profile—to customize your login shell.

Uses and functions of the bash shell

To write effective bash shell scripts, you must master the basic bash command set for navigation and daily routine tasks performed within the shell.

The bash login process

Upon login, users typically have a global profile as well as two personal files that are executed (.bash_profile and .bashrc). Figure 1 shows the usual process flow.

Figure 1. The bash shell login process
Illustration of the bash shell login process

Now, take a look at a typical Linux user’s .bash_profile (Listing 1) and .bashrc (Listing 2) scripts. These scripts are loaded from the user's home directory.

Listing 1. A typical .bash_profile file
[fred.smythe@server01 ~]$ cat .bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc

# User specific environment and startup programs
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH


export PATH

In the .bashrc file in Listing 2, some user aliases are configured, and the global bashrc file is loaded if it exists.

Listing 2. A typical .bashrc file
[fred.smythe@server01 ~]$ cat .bashrc
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias tc6='cd /opt/tomcat/6.0.13'
alias conf6='cd /opt/tomcat/6.0.13/conf'
alias bin6='cd /opt/tomcat/6.0.13/bin'
alias scr='cd /opt/tomcat/scripts'
alias reports='cd /opt/tomcat/reports'
alias temp6='cd /opt/tomcat/6.0.13/template'

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc

The system level process init spawns the getty or agetty process. This process in turn calls the /bin/login program, which presents the familiar UNIX or Linux login prompt. The user proceeds to login to access the system, at which point the login program spawns a new bash shell. When a bash shell user logs in, the following events occur in this order: the global profile is read; the user's individual bash profile is read; and then the user's personal '.bashrc' is read, which will typically set aliases, define additional functions, and define the user's individual environmental variables. Lastly, the bash user is presented with a bash shell prompt, and the motd file is read and displayed. Figure 2 below is an illustrated example.

Figure 2. Bash shell login process details
Illustration of bash shell login details
Illustration of bash shell login details

The user then has a standard command set of programs available to him or her from within your bash shell environment $PATH variable. If a command isn't in the user's $PATH, but he or she has permissions to execute the command, you must use the full path, as shown in Listing 3.

Listing 3. Example of $PATH issues within the bash shell
[fred.smythe@server01 ~]$ ifconfig -a
-bash: ifconfig: command not found
[fred.smythe@server01 ~]$ which ifconfig
/usr/bin/which: no ifconfig in (/usr/local/bin:/bin:/usr/bin:/home/fred.smythe/bin)

This issue comes up when the binary program ifconfig is not within the user’s defined PATH variable. However, if you know the full path of the command, you can run it as shown in Listing 4.

Listing 4. Using the full path of a command to overcome a $PATH issue in the bash shell
[fred.smythe@server01 ~]$ /sbin/ifconfig -a
eth0      Link encap:Ethernet  HWaddr 00:50:56:96:2E:B3
          inet addr:  Bcast:  Mask:

Listing 5 shows a method using an alias to overcome the $PATH issue. Within your bash scripts, you may want to run commands with the full path, depending on who will run the intended script.

Listing 5. Setting an alias to overcome a $PATH issue in the bash shell
[fred.smythe@server01 ~]$ alias ifconfig='/sbin/ifconfig'
[fred.smythe@server01 ~]$ ifconfig
eth0      Link encap:Ethernet  HWaddr 00:50:56:96:2E:B3
          inet addr:  Bcast:  Mask:


When a command or the bash shell itself initiates (or spawns) a new shell sub-process to perform a task, it is known as forking. When this new process—the child process—is executing, the parent process will still be running. If a parent process should die before the child process, the child process can become a defunct process—also known as a zombie process which usually results in a hung process or hung application. Thus, this hung process must be killed or terminated abnormally. Although a parent process can access the process ID of its child process and can thus pass arguments to it, the reverse is false. When a shell script process exits or returns to the parent process, the exit code should be 0. If it is anything else, there was likely an error or problem with the process.

The exit code of the last command executed—echo $?—is shown in Listing 6.

Listing 6. Exit code examples
# ls -ld /tmp
drwxrwxrwt 5 root root 4096 Aug 19 19:45 /tmp
[root@server01 ~]# echo $?
0		// Good command return of 0.
[root@server01 ~]# ls -l /junk
ls: /junk: No such file or directory
[root@server01 ~]# echo $?
2		// Something went wrong, there was an error, so return 2.

Listing 7 illustrates a parent and child process shown within a bash environment.

Listing 7. Example of a parent and child process in a bash environment
[root@server02 htdocs]# ps -ef | grep httpd
UID       PID   PPID  C STIME TTY      TIME      CMD
root      8495     1  0 Jul26 ?        00:00:03 /usr/sbin/httpd -k start 
apache    9254  8495  0 Aug15 ?        00:00:00 /usr/sbin/httpd -k start

Knowing your environment

If you type the command env, you see what your current bash shell default environmental variables are set to, including your user name and tty (terminal) information, your $PATH values, and your present working directory ($PWD). Take a look at Listing 8.

Listing 8. Example of a bash environment
[fred.smythe@server01 ~]$ env
SSH_CLIENT= 56513 22
LESSOPEN=|/usr/bin/ %s

You can navigate your Linux file systems using the bash commands shown in Listing 9.

Listing 9: Navigating in a bash environment
[fred.smythe@server01 ~]$ ls -l
total 0
[fred.smythe@server01 ~]$ cd /tmp
[fred.smythe@server01 tmp]$ df -ha .
Filesystem            Size  Used Avail Use% Mounted on
                      2.0G   68M  1.8G   4% /tmp

In the listing, commands were issued one at a time. But, you could have run them together using the semi-colon (;) separator, as shown in Listing 10.

Listing 10: Sequential command execution in bash
[fred.smythe@server01 tmp]$ ls -l ;cd /tmp;df -ha .
total 40
-rw-r----- 1 root root  1748 May 22  2009 index.html
-rw-r----- 1 root root   786 Aug 17 04:59 index.jsp
drwx------ 2 root root 16384 Jul 15  2009 lost+found
drwx------ 2 root root  4096 Aug  9 21:04 vmware-root
Filesystem            Size  Used Avail Use% Mounted on
                      2.0G   68M  1.8G   4% /tmp
[fred.smythe@server01 tmp]$

On the bash command-line, command completion shortens the typing needed for everyday tasks. Simply type the beginning of a command, then press the Tab key. Note that if you cannot access the command or file because of permissions restrictions, command completion won't function for you.

Help yourself out in bash

Bash provides several forms of help:

  • man (shown below in Listing 11):
    Listing 11. Example of man pages in bash
    [fred.smythe@server01 tmp]$ man perl
    PERL(1)                Perl Programmers Reference Guide                PERL(1)
       perl - Practical Extraction and Report Language
     perl [ -sTtuUWX ] [ -hv ] [ -V[:configvar] ]     -cw ] [ -d[t][:debugger] ] [ -D
     [num- ber/list] ] [ -pna ] [ -Fpattern ] [ -l[octal] ] [ -0[octal/hexadecimal] ]
       [ -Idir ] [ -m[-]module ] [ -M[-] module... ] [ -f ] [ -C [number/list] ] 
       [ -P ] [ -S ] [ -x[dir] ]      [ -i[extension] ]      [ -e command ] [ -- ] 
       [ program- file ] [ argument ]...
  • whatis (shown below in Listing 12):
    Listing 12. Example of the whatis command in bash
    [fred.smythe@server01 tmp]$ whatis perl
    perl                 (1)  - Practical Extraction and Report Language
    perl                (rpm) - The Perl programming language
  • apropos (shown below in Listing 13):
    Listing 13. Apropos example in bash
    [root@server01 ~]# apropos perl | more
    B                    (3pm)  - The Perl Compiler
    B::Asmdata           (3pm)  - Autogenerated data about Perl ops, 
         used to generate bytecode
    B::Assembler         (3pm)  - Assemble Perl bytecode
  • which (shown below in Listing 14):
    Listing 14. The which command in bash
    [root@server01 ~]# which perl

The bash shell contains two types of commands: builtins, which are internal, and external programs (or external filters and commands), which are typically self-contained binary program files. Listing 15 shows how to find the built-in commands in bash using the alias command.

Listing 15. Finding the built-in commands within bash
[root@server01 ~]# man -k builtins| more
. [builtins]         (1)  - bash built-in commands, see bash(1)
: [builtins]         (1)  - bash built-in commands, see bash(1)
[ [builtins]         (1)  - bash built-in commands, see bash(1)
alias [builtins]     (1)  - bash built-in commands, see bash(1)
bash [builtins]      (1)  - bash built-in commands, see bash(1)
bg [builtins]        (1)  - bash built-in commands, see bash(1)
bind [builtins]      (1)  - bash built-in commands, see bash(1)
break [builtins]     (1)  - bash built-in commands, see bash(1)
builtin [builtins]   (1)  - bash built-in commands, see bash(1)

To identify a specific command in bash, use the type command, as shown in Listing 16.

Listing 16. Finding the built-ins commands using the type command
[root@apache-02 htdocs]# type lsof
lsof is /usr/sbin/lsof
[root@apache-02 htdocs]# type alias
alias is a shell builtin

Listing 17 provides an example of lsof, an external command. This command is actually a binary file residing in the Linux file system; it's installed via a package of the same name.

Listing 17. Finding the external command details within bash
[root@server01 ~]# which lsof
[root@server01 ~]# rpm -qa lsof-4.78-3.i386
[root@server01 ~]# rpm -qa lsof

Bash scripting on the fly

One of the bash shell's most powerful features is its ability to allow command-line scripting on the fly. Consider the example in Listing 18, which sets a shell variable, tests for the value of the variable, and then programmatically executes another command if the value is greater than zero.

Listing 18. Scripting on the fly with bash
[fred.smythe@server01 ~]$ DEBUG=1
[fred.smythe@server01 ~]$ test $DEBUG -gt 0 && echo "Debug turned on"
Debug turned on

Here's an example of a for loop written on the fly (shown in Listing 19). Note that here, you're typing interactively at the shell prompt; at each >, you type the next line of your interactive shell script.

Listing 19. A for loop written on the fly within bash
$ for SVR in 1 2 3
> do
> echo server0$
> done

Note that alternatively, you could have run this code as sequential commands separated by semi-colons.

Using keywords

The command for is not a program but a kind of special built-in known as a keyword. The list of keywords in the average distribution of bash on Linux are shown in Listing 20.

Listing 20. Keywords within bash
true, false, test, case, esac, break, continue, eval, exec, exit, export, readonly, 
return, set, shift, source, time, trap, unset, time, date, do, done, if, fi, else, elif, 
function, for, in, then, until, while, select

You should avoid these keywords—or what are known as bash shell reserved words—when choosing names for shell variables.

Piping commands in bash

The bash shell lets you redirect standard input, standard output, and standard error on a Linux or UNIX system. Consider the examples in Listing 21.

Listing 21. Piping commands together inside bash
$ USER="fred.smythe"
$ echo -n "User $USER home is: " && cat /etc/passwd | grep $USER | awk -F: '{print $6}'
User fred.smythe home is: /home/fred.smythe
# Re-direction of standard output (>) of the date command to a file : 
[root@server01 ~]# date > /tmp/today.txt
[root@server01 ~]# cat /tmp/today.txt
Thu Aug 19 19:38:33 UTC 2010 

# Re-direction of standard input (<) to standard output (>) … 
[root@server01 ~]# cat < /tmp/today.txt > /tmp/today.txt.backup
[root@server01 ~]# cat /tmp/today.txt.backup
Thu Aug 19 19:38:33 UTC 2010

Compound command lines

Compound command lines can use and combine multiple instances of standard input, standard output, and standard error redirection and/or pipes to perform exacting operations with high degrees of precision and accuracy. Listing 22 provides an example.

Listing 22. Example of re-direction inside bash
# command1 < input_file1.txt > output_file1.txt
# command1 | command2 | command3 > output_file.log

You can use complex combined command lines, for example, to find Apache permission-denied errors by searching within all found compressed error logs and counting the number of errors.

$ find ./ -name 'error_log.*.gz' | xargs zcat | grep 'Permission denied'| wc -l

Writing quality bash scripts

To achieve production-quality or enterprise-level scripting, here are a few key points to keep in mind:

  • Always comment your scripts with a brief heading.
  • Comment your code heavily so that at a later date, you can easily remember the reason your source code was written as it is. Remember that the first line of your script needs to be the #!/bin/bash line.
  • Write your script actions to a log file, with a date and timestamp for later examination. Be verbose with your output, log success messages, and clearly state error messages or conditions. It's also probably wise to log the start and stop time of your script. Using the tee command, you can append to a log and standard output at the same time:
    DATEFMT=`date "+%m/%d/%Y %H:%M:%S"`
    echo "$DATEFMT: My message" | tee -a /tmp/myscript.log
  • If your scripts write to a log file, always create a new log file and include the date, hour, minute, and even the seconds in the new log's file name. Then, with a simple find command, you can easily rotate and compress your script's own logs each time you run it:
    DATELOG=`date "+%m%d%y"`
    # gzip the old mail_stats logs, older than 7 days
    find /logs -type f -name "mail_stats.log.????????????" -mtime +7 | xargs gzip -9
            	# remove the old gzipped mail_stats logs after 30 days 
            	find /logs -type f -name "mail_stats.log.*.gz" -mtime +30 -exec rm {} \;
            	# mail_log utility log resets
    echo "" > /var/log/mail_stats.log.$DATELOG
  • Build error-checking logic into your scripts and never assume anything. Doing so will save a tremendous amount of pain and grief later.
  • Incorporate the use of functions and shell script libraries (by sourcing in another script) where possible within your script. Doing so reuses tested and proven code and will save you from repeating script code and making potential errors.
  • Filter input parameters from users:
    if [ $NUMPARAMETERS != 3 ];then
      echo "Insufficient number of parameter passed!”
      echo “Please run script in format ./ $1 $2 $3” 
      exit 2
  • Consider adding a debug mode or function within your script—say, with the set –x command.
  • Add functions to alert or send a trap for particular events within your script. You can do this via SNMP commands or an audible bell (echo x), and then send an email message with the mail command.
  • If you're writing a script that will be used like an interactive menu, consider the user's environmental shell and the commands they have access to. If in doubt, use full paths for all commands within your scripts.
  • Add individual and unique return codes to your bash shell script. When you write a large script, you'll be able to easily identify exactly where an error or problem is occurring by pin-pointing the return code:
    if [ “$ERRCHECK” != “” ];then
     	  echo “Error Detected : $ERRCHECK ! Cannot continue, exit $EXITVAL value” 
      exit $EXITVAL
  • Test your scripts thoroughly in a lab environment for all possible conditions. Have other users perform testing on the scripts, as well, and intentionally try to "break" them.
  • If your script is operating on input data from users or a data input file, always thoroughly filter, check, and validate the input data. Scripts that work from a data list can be run on multiple, different sets of data lists, as desired.
  • For scripts that will run for a long time, consider placing a timeout function within the script to terminate or stop after n number of minutes:
    stty –icannon min 0 time 1800
  • Properly indent your code to make it more readable.

For scripts you write for a specific purpose, you may want to add an interactive warning prompt message to give the user information about the script's purpose. For example, the script in Listing 23 retrieves remote logs and creates a report email.

Listing 23. Simple bash script to retrieve and report on logs

cd /data01/maillog

MAILSVRS=$(/bin/cat /data01/maillog/mail_servers)
DATELOG=`date "+%m%d%y"`
MAILADDRESSES=$(/bin/cat /data01/maillog/addresses)

echo “Mail Report to $ MAILADMINS” 

# 1 - Get some logs …
for svr in $MAILSVRS
  if [ -e "/data01/maillog/$svr.maillog.$DATELOG.gz" ]; then
     /bin/rm -f /data01/maillog/$svr.maillog.$DATELOG.gz

# 2 - Combine all maillogs from all servers to onefile ($ALLMAILLOGS) ...
/bin/zcat server16.maillog.$DATELOG.gz server17.maillog.$DATELOG.gz 
server18.maillog.$DATELOG.gz server19.maillog.$DATELOG.gz >> 

# 3 - Run another script 
/bin/cat $ALLMAILLOGS | /data01/maillog/ | tee -a $MAILREPORT

# 4 -  Get all of the mailstats logs to one log file to send by Email
  /bin/cat /data01/maillog/mailstats.log.server*.$DATELOG > $MAILSTATSLOGALL
# Send the $MAILADMINS the mail reports
  /bin/cat $MAILSTATSLOGALL | mail -s "ACME Servers Outbound Mail Servers 

For an improved version of the same script with more robust features, download the source code for this article.

Variables, syntax format, and structure inside bash scripting

Within bash, several methods are valid for defining and setting variables. The script in Listing 24 provides an example of these shell variable declaration methods.

Listing 24. Defining bash variables
$ cat

A="String Value 1"
B='String Value 2'
export E="String Value 3"
# if the variable $F is not ALREADY set to a value, assign "String Value 4" ...
F=${F:="String Value 4"}

echo "A=$A"
echo "B=$B"
echo "C=$C"
echo "D=$D"
echo "E=$E"
echo "F=$F"

exit 0

$ ./
A=String Value 1
B=String Value 2
E=String Value 3
F=String Value 4

Gathering user input

To gather user input, you use the read statement and assign a variable to the user input, as shown in Listing 25.

Listing 25. Obtaining user input from a bash script
$ cat
echo "Please enter your first and last name:"
read ans
echo "Hellow $ans , welcome to bash scripting ...!"
exit 0

$ ./
Please enter your first and last name:
Joe Jackson
Hello Joe Jackson , welcome to bash scripting ...!

To use a function within your bash shell script, you use the methods shown in Listing 26.

Listing 26. Implementing functions within bash
$ cat

funcOne() {
  echo "Running function 1, with parameter $1"
  echo "Current listing /tmp directory, last 3 lines"
  ls -lrt /tmp | tail -3

echo "Hello World"
funcOne "Server01"
exit 0

$ ./
Hello World
Running function 1, with parameter Server01
Sun Aug 22 22:43:04 UTC 2010
Current listing /tmp directory, last 3 lines
-rw-r-  1 dsmith   progdevel 12749743 Aug 16 20:32 files.tar.gz
drwxr-xr-x  2 jjones   progdevel     4096 Aug 16 20:42 ff_hosting_files
-rw-r-  1 rhill    heng          1440 Aug 22 19:07 myscript.log

Or, you can write the function shown in Listing 27.

Listing 27. Alternate function definition within bash

function funcTwo () {
  echo "Running function 2, with parameter $1"
  echo "Current listing /var directory, last 3 lines"
  ls -lrt /var | tail -3

funcTwo "Server02"

exit 0

$ ./
Running function 2, with parameter Server02
Sun Aug 22 22:53:16 UTC 2010
Current listing /var directory, last 3 lines
drwxrwxrwt   3 root    root     4096 Aug  6 18:22 tmp
drwxr-xr-x   6 root    root     4096 Aug 22 04:02 log
drwxrwxr-x   4 root    lock     4096 Aug 22 04:22 lock

The function keyword is optional. The only rule is that you need to declare the function before you can use it later in the program. You invoke the function simply by calling its name and passing on to it any further required or optional input parameters.

Loops and decision-making

Let's say you need to do some repetitive work on several servers. You can easily use a for loop within bash to do the job quickly. Listing 28 shows the code.

Listing 28. Bash scripting example of a simple for loop
$SERVERS=”server01 server02 server03 server04 server05” 
for i in $SERVERS
  echo "Removing file from server: $i"
  ssh $i rm -f /tmp/junk1.txt 

A while loop within bash allows you to execute a statement a given number of times, or until a certain condition is met. Listing 29 provides an example.

Listing 29. A simple while loop
while [ $LOOPCTR -lt 6 ]
  echo “Running patch script for server0$LOOPCTR” 
  /data/ server0$LOOPCTR
  LOOPCTR=`expr $LOOPCTR + 1`

A case statement within bash lets you test for more than one condition or value and act accordingly. Using such a statement can sometimes be a better route than nested for loops or if statements to cut down on repetitive code or just for better structure. Listing 30 shows a brief case statement that make calls to functions, depending on the condition of the variable $key.

Listing 30. Example of a case statement in bash
  case $key in
  q) logit "Quit";
  1) echo “\tChecking Mail Servers”;
  2) echo "\tChecking Web Servers";
  3) echo “\tChecking App Servers;
  4) echo “\tChecking Database Servers”;
  b) echo  "Go Back to Main menu";
  *) echo "$key invalid choice";

Pros and cons of bash scripting

Need something done quickly? If you've got bash, you can cut down the time needed to write the newest corporate gizmo web widget like a chainsaw going through warm butter. Bash scripting is not a programming language or an application. No compiler is required, and you don't need special libraries or a software development environment to work from. But bash shell scripts can act like applications and even perform some of the tasks and jobs that applications can do.

On the pro side, bash offers:

  • Fast development time and code that is easily modifiable. The basics of bash scripting change little over time.
  • Fairly straightforward syntax in comparison with some programming languages, where depending on the programming language, coding or syntactical changes may be frequent.
  • Advanced bash features (for example, epoch, functions, signal control, multiple extensions, command history, the means of using one-dimensional arrays) that give users much more power than before.
  • Nothing much beyond a *nix bash shell required to perform bash shell scripting.

On the con side, bash code:

  • Is executed by the shell, then passed to the kernel, which is typically slower than a compiled binary program that is pure machine code. As a result, bash shell scripting may be undesirable for some application designs or functions.
  • Is clear text or easily viewable by anyone with Read permissions, whereas compiled binaries are not readable. This poses a huge security risk when trying to encode sensitive data.
  • Has no particular set of standard functions, whereas many modern programming languages have intrinsic or built-in functions for various programmatic needs.


Now that you have the basics of bash shell scripting well in hand, you must promise to only use your powers for the greater good. So, go out and save the *nix world with your newly found crontabs, one-line wonders, and interactive menus. Don't be afraid to be a "bash" administrator!

Downloadable resources

Related topics


Sign in or register to add and subscribe to comments.

Zone=AIX and UNIX
ArticleTitle=Bash scripting for beginning system administrators