Bash Scripting


In this chapter, we ll take a look at the bash scripting language. The following sections will identify the necessary language constructs for application development, including variables, operations on variables , conditionals, looping, and functions. We ll also demonstrate a number of sample applications to illustrate scripting principles.

Variables

Any worthwhile language permits the creation of variables. In bash, variables are untyped, which means that all variables are in essence strings. This doesn t mean we can t do arithmetic on bash variables, which we ll explore shortly. We can create a variable and then inspect it very easily as:

 $ x=12     $ echo $x     12     $ 

In this example, we create a variable x and bind the value 12 to it. We then echo the variable x and find our value. Note that the lack of space between the variable name , the equals, and the value is relevant. There can be no spaces, otherwise an error will occur. Note also that to reference a variable, we precede it with the dollar sign. This variable is scoped (exists) for the life of the shell from which this sequence was performed. Had this sequence of commands been performed within a script (such as ./test.sh , the variable x would not exist once the script was completed.

As bash doesn t type its variables, we can create a string variable similarly:

 $ b="my string"     $ echo $b     my string     $ 

Note that single quotes also would have worked here. An interesting exception is the use of the backtick . Consider the following:

 $ c='echo $b'     $ echo $c     my string     $ 

The backticks have the effect of evaluating the contents within the backticks, and in this case the result is assigned to the variable c . When we emit variable c , we find the value of the original variable b .

The bash interpreter defines a set of environment variables that effectively define the environment. These variables exist when the bash shell is started (though others can be created using the export command). Consider the script in Listing 20.2, which makes use of special environment variables to identify the environment of the script.

Listing 20.2: Sample Script Illustrating Standard Environmental Variables (on the CD-ROM at ./source/ch20/env.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       echo "Welcome to host $HOSTNAME running $OSTYPE."  4  :       echo "You are user $USER and your home directory is $HOME."  5  :       echo "The version of bash running on this system is $BASH_VERSION."  6  :       sleep 1  7  :       echo "This script has been running for $SECONDS second(s)."  8  :       exit 
end example
 

Upon executing this script, we see the following on a sample GNU/Linux system:

 $ ./env.sh     Welcome to host camus running linux-gnu.     You are user mtj and your home directory is /home/mtj.     The version of bash running on this system is 2.05b.0(1)-release.     This script has been running for 1 second(s).     $ 

Note that we added a useless sleep call to the script (to stall it for one second) so that we could see that the SECONDS variable was working. Also note that the SECONDS variable can be emitted from the command line, but this value represents the number of seconds that the shell has been running.

Bash provides a variety of other special variables. Some of the more important ones are shown in Table 20.1. These can be echoed to understand their formats.

Table 20.1: Useful Environment Variables

Variable

Description

$ PATH

Default path to binaries

$PWD

Current working directory

$OLDPWD

Last working directory

$PPID

Process ID of the interpreter (or script)

$#

Number of arguments

$0, $1, $2, ...

Arguments

$*

All arguments as a single word

One final word about variables in bash and then we ll move on to some real programming. We can declare variables in bash, providing some form of typing. For example, we can declare a constant variable (cannot be changed after definition) or declare an integer or even a variable whose scope will extend beyond the script. Examples of these variables are shown below interactively:

 $ x=1     $ declare -r x     $ x=2     -bash: x: readonly variable     $ echo $x     1     $ y=2     $ declare -i y     $ echo $y     2     $ persist='$PWD'     $ declare -x persist     $ export  grep persist     declare -x persist="/home/mtj" 

The last item may require a little more discussion. In this example, we create a variable persist and assign the current working subdirectory to it. We declare for exporting outside of the environment, which means if it had been done in a script, the variable would remain once the script had completed. This can be useful to allow scripts to alter the environment or to return variables.

Simple Arithmetic

We can perform simple arithmetic on variables, but there are differences from normal assignments that we ve just reviewed. Consider the source in Listing 20.3.

Listing 20.3: Simple Script Illustrating Arithmetic with Variables (on the CD-ROM at ./source/ch20/ arith .sh )
start example
  1  :       #!/bin/bash  2  :  3  :       x=10  4  :       y=5  5  :  6  :       let sum=$x+$y  7  :       diff=$(($x - $y))  8  :       let mul=$x*$y  9  :       let div=$x/$y  10  :       let mod=$x%$y  11  :       let exp=$x**$y  12  :  13  :       echo "$x + $y = $sum"  14  :       echo "$x - $y = $diff"  15  :       echo "$x * $y = $mul"  16  :       echo "$x / $y = $div"  17  :       echo "$x ** $y = $exp"  18:  exit 
end example
 

At lines 3 and 4, we create two local variables called x and y and assign values to them. We then illustrate simple math evaluations using two different forms. The first form uses the let command to assign the evaluated expression to a new variable. No spaces are permitted in this form. The second example uses the $(( < expr > )) form. Note in this case that spaces are permitted, potentially making the expression much easier to read.

Bitwise Operators

Standard bitwise operators are also available in bash. These include bitwise left shift (<<), bitwise right shift (>>), bitwise AND (&), bitwise OR ( ), bitwise negate ( ~ ), bitwise NOT ( ! ), and bitwise XOR ( ^ ). The following interactive session illustrates these operators:

 $ a=4     $ let b="$a<<1"     $ echo $b     8     $ b=8     $ c=4     $ echo $(($c$d))     12     $ echo $((0xc^0x3))     15     $ 

Logical Operators

Traditional logical operators can also be found within bash. These include the logical AND (&&) and logical OR ( ). The following interactive session illustrates these operators:

 $ echo $((2 && 0))     0     $ echo $((4 && 1))     1     $ echo $((3  0))     1     $ echo $((0  0))     0 

In the next section, we ll investigate how these can be used in conditionals for decision points.

Conditional Structures

Bash provides the typical set of conditional constructs. In this section, we ll explore each of these constructs and also investigate some of the other available conditional expressions that can be used.

Conditionals

In this section, we ll look at conditionals. The if/then construct provides a decision point after evaluating a test construct. The test construct returns a value as its result. The result of the test construct is zero for true (test succeeds and subsequent commands are executed) or nonzero for false (test fails else section, if available, is executed). Let s look at a simple example to illustrate (see Listing 20.4).

Listing 20.4: Simple Script Illustrating Basic if/then/else Construct (on the CD-ROM at ./source/ch20/cond.sh )
start example
  1  :       #!/bin/bash  2  :       a=1  3  :       b=2  4  :       if [[ $a -eq $b ]]  5  :       then  6  :         echo "equal"  7  :       else  8  :         echo "unequal"  9  :       fi 
end example
 
Note  

Note that the result of the test construct is the inverse of what you would expect. The is because the exit status of a command is 0 for success/normal and != 0 to indicate an error.

After creating two variables, we test them for equality using the -eq comparison operator. If the test construct is true, we perform the commands contained in the then block. Otherwise, if an else block is present, this is executed (the test construct was false). else-if chains can also be constructed , as shown in Listing 20.5.

Listing 20.5: Simple Script Illustrating the if/then/elif/then/fi Construct (on the CD-ROM at ./source/ch20/cond2.sh )
start example
  1  :       #!/bin/bash  2  :       x=5  3  :       y=8  4  :       if [[ $x -lt $y ]]  5  :       then  6  :         echo "$x < $y"  7  :       elif [[ $x -gt $y ]]  8  :       then  9  :         echo "$x > $y"  10  :       elif [[ $x -eq $y ]]  11  :       then  12  :         echo "$x == $y"  13  :       fi 
end example
 

In this example, we test the integers for using the -lt operator ( less-than ), -gt (greater-than), and finally -eq (equality). Other operators are shown in Table 20.2.

Test constructs can also utilize strings such as is illustrated in Listing 20.6. In this example, we ll also look at two forms of the if/then/fi construct that provide identical functionality.

Table 20.2: Integer Comparison Operators

Operator

Description

-eq

is equal to

-ne

is not equal to

-gt

is greater than

-ge

is greater than or equal to

-lt

is less than

-le

is less than or equal to

Listing 20.6: Simple Script Illustrating the if/then/fi Construct (on the CD-ROM at ./source/ch20/cond3.sh )
start example
  1  :   #!/bin/bash  2  :   str="ernie"  3  :   if [[ $str = "Ernie" ]]  4  :   then  5  :     echo "Its Ernie"  6  :   fi  7  :  8  :  9  :   if [[ "$str" == "Ernie" ]]; then echo "Its Ernie"; fi 
end example
 

After creating a string variable at line 2, we test it against a constant string at line 3. The = operator tests for string equality, as does the == operator. At line 9, we look at a visibly different form of the if/then/fi construct. As it s represented on one line, semicolons are used to separate the individual commands.

In Listing 20.5, we saw the use of the string equality operators. In Table 20.3, we see some of the other string comparison operators.

Table 20.3: String Comparison Operators

Operator

Description

=

is equal to

==

is equal to

!=

is not equal to

<

is alphabetically less than

>

is alphabetically greater than

-z

is null

-n

is not null

As a final look at test constructs, let s look some of the more useful file test operators. Consider the script shown in Listing 20.7. In this script, we emit some information about a file (based upon its attributes). We use the file test operators to determine the attributes of the file.

Listing 20.7: Determine File Attributes Using File Test Operators (on the CD-ROM at ./source/ch20/fileatt.sh )
start example
  1  :       #!/bin/sh  2  :       thefile="test.sh"  3  :  4  :       if [ -e $thefile ]  5  :       then  6  :         echo "File Exists"  7  :  8  :         if [ -f $thefile ]  9  :         then  10  :           echo "regular file"  11  :         elif [ -d $thefile ]  12  :         then  13  :           echo "directory"  14  :         elif [ -h $thefile ]  15  :         then  16  :           echo "symbolic link"  17  :         fi  18  :  19  :       else  20  :         echo "File not present"  21  :       fi  22  :  23  :       exit 
end example
 

The first thing to notice in Listing 20.7 is the embedded if/then/fi construct. Once we identify that the file exists at line 4 using the -e operator (returns true of the file exists), we continue to test the attributes of the file. At line 8, we check to see whether we re dealing with a regular file ( -f ), in other words a real file as compared to a directory, a symbolic link, and so forth. The file tests continue with a directory test at line 11 and finally a symbolic link test at line 14. Lines 19 “21 close out the initial existence test by emitting whether the file was actually present.

A large number of file test operators are provided by bash. Some of the more useful operators are shown in Table 20.4.

Table 20.4: File Test Operators

Operator

Description

-e

Test for file existence

-f

Test for regular file

-s

Test for file with nonzero size

-d

Test for directory

-h

Test for symbolic link

-r

Test for file read permission

-w

Test for file write permission

-x

Test for file execute permission

For the file test operators shown in Table 20.4, a single file argument is provided for each of the tests. Two other useful file test operators compare the dates of two files, illustrated as:

 if [ $file1 -nt $file2 ]     then       echo "$file is newer than $file2"     elif [ $file1 -ot $file2 ]     then       echo "$file1 is older than $file2"     fi 

The file test operator -nt tests whether the first file is newer than the second file, while -ot tests whether the first file is older than the second file.

If we re more interested on the reverse of a test, for example, whether a file is not a directory, then the ! operator can be used. The following code snippet illustrates this use:

 if [ ! -d $file1 ]     then       echo "File is not a directory"     fi 

One special case to note is when you have a single command to perform based upon the success of a given test construct. Consider the following:

 [ -r myfile.txt ] && echo "the file is readable." 

If the test succeeds (the file myfile.txt is readable), then the command that follows is executed. The logical AND operator between the test and command ensures that only if the initial test construct is true will the command that follows be performed. If the test construct is false, the rest of the line is ignored.

This has been a quick introduction to some of the bash test operators. The Resources section at the end of this chapter provides more information to investigate further.

case Construct

Let s look at another conditional structure that provides some advantages over standard if conditionals when testing a large number of items. The case command permits a sequence of test constructs utilizing integers or strings. Consider the example shown in Listing 20.8.

Listing 20.8: Simple Example of the case/esac Construct (on the CD-ROM at ./source/ch20/case.sh )
start example
  1  :       #!/bin/bash  2  :       var=2  3  :  4  :       case "$var" in  5  :         0) echo "The value is 0" ;;  6  :         1) echo The value is 1 ;;  7  :         2) echo The value is 2 ;;  8  :         *) echo The value is not 0, 1, or 2  9  :       esac  10  :  11  :       exit 
end example
 

The case construct shown in Listing 20.8 illustrates testing an integer among 3 values. At line 4, we set up the case construct using $var . At line 5, the test against 0 is performed, and if it succeeds, the commands that follow are executed. Lines 6 and 7 test against values 1 and 2. Finally at line 8, the default * simply says that if all previous tests failed, this line will be executed. At line 9, the case structure is closed. Note that the command list within the tests ends with ;; . This indicates to the interpreter that the commands are finished, and either another case test or the closure of the case construct is coming. Note that the ordering of case tests is important. Consider if line 8 had been the first test instead of the last. In this case, the default would always succeed, which isn t what is desired.

We can also test ranges within the test construct. Consider the script shown in Listing 20.9 that tests against the ranges 0-5 and 6-9 . The special form [0-5] is used to define a range of values between 0 and 5 inclusive.

Listing 20.9: Simple Example of the case/esac Construct (on the CD-ROM at ./source/ch20/case2.sh )
start example
  1  :       #!/bin/bash  2  :       var=2  3  :  4  :       case $var in  5  :         [0-5]) echo The value is between 0 and 5 ;;  6  :         [6-9]) echo The value is between 6 and 9 ;;  7  :         *) echo Its something else...  8  :       esac  9  :  10  :       exit 
end example
 

The case construct can be used to test characters as well. The script shown in Listing 20.10 illustrates character tests. Also shown is the concatenation of ranges, here [a-zA-z ] tests for all alphabetic characters, both lower- and uppercase.

Listing 20.10: Another Example of the case/esac Construct Illustrating Ranges (on the CD-ROM at ./source/ch20/case3.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       char=f  4  :  5  :       case $char in  6  :         [a-zA-z]) echo An upper or lower case character ;;  7  :         [0-9]) echo A number ;;  8  :         *) echo Something else ;;  9  :       esac  10  :  11  :       exit 
end example
 

Finally, strings can also be tested with the case construct. A simple example is shown in Listing 20.11. In this example, a string is checked against four possibilities. Note that at line 7, the test construct is made up of two different tests. If the name is Marc or Tim , then the test is satisfied. We use the logical OR operator in this case, which is legal within the case test construct.

Listing 20.11: Simple String Example of the case/esac Construct (on the CD-ROM at ./source/ch20/case4.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       name=Tim  4  :  5  :       case $name in  6  :         Dan) echo Its Dan. ;;  7  :         Marc  Tim) echo Its me. ;;  8  :         Ronald) echo Its Ronald. ;;  9  :         *) echo I dont know you. ;;  10  :       esac  11  :  12  :       exit 
end example
 

This has been the tip of the iceberg as far as case test constructs go ”many other types of conditionals are possible. The Resources section at the end of this chapter provides other sources that dig deeper into this area.

Looping Structures

Let s now look at how looping constructs are performed within bash. We ll look at the two most commonly used constructs; the while loop and the for loop.

while Loops

The while loop simply performs the commands within the while loop as long as the conditional expression is true. Let s first look at a simple example that counts from 1 to 5 (shown in Listing 20.12).

Listing 20.12: Simple while Loop Example (on the CD-ROM at ./source/ch20/loop.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       var=1  4  :  5  :       while [ $var -le 5 ]  6  :       do  7  :         echo var is $var  8  :         let var=$var+1  9  :       done  10  :  11  :       exit 
end example
 

In this example, we define our looping conditional at line 5 ( var < = 5 ). While this condition is true, we print out the value and increment var . Once the condition is false, we fall through the loop to done (at line 9) and ultimately exit the script.

Loops may also be nested. The sample script in Listing 20.13 illustrates this. In this example, we generate a multiplication table of sorts using two variables. Lines 4 to 18 define the outer loop, while lines 8 to 14 define the inner . The only difference, as in other high-level languages, is that the inner loop is indented to show the structure of the code.

Listing 20.13: Nested while Loop Example (on the CD-ROM at ./source/ch20/loop2.sh )
start example
  1  :       #!/bin/bash  2  :       outer=0  3  :  4  :       while [ $outer -lt 5 ] ; do  5  :  6  :         inner=0  7  :  8  :         while [ $inner -lt 3 ] ; do  9  :  10  :           echo $outer * $inner = $(($outer * $inner))  11  :  12  :           inner=$(expr $inner + 1)  13  :  14  :         done  15  :  16  :         let outer=$outer+1  17  :  18  :       done  19  :  20  :       exit 
end example
 

Another interesting item to note about the script in Listing 20.13 is the arithmetic expressions used. In the outer loop we find the use of let to assign outer to itself plus one. The inner loop uses the expr command, which is an expression evaluator .

for Loops

The for/in/do/done construct in bash allows us to loop through a range of variables. This differs from the classical for loop that is available in high-level languages such as C, but bash s perspective is very useful and offers some capabilities not found in C. Listing 20.14 provides a very simple for loop.

Listing 20.14: Simple for Loop Example (on the CD-ROM at ./source/ch20/forloop.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       echo Counting from 1 to 5  4  :  5  :       for val in 1 2 3 4 5  6  :       do  7  :         echo -n $val  8  :       done  9  :       echo  10  :  11  :       exit 
end example
 

The result of this script is:

 # ./test.sh     Counting from 1 to 5     1 2 3 4 5     # 

Of course, we can emulate the C for loop mechanism very simply as shown in Listing 20.15.

Listing 20.15: Simple for Loop Example Using C-like Construct (on the CD-ROM at ./source/ch20/forloop2.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       for ((var=1 ; var <= 5 ; var++))  4  :       do  5  :         echo -n $var  6  :       done  7  :       echo  8  :  9  :       exit 
end example
 

This code in Listing 20.15 is identical to the original for-in loop shown in Listing 20.14.

We can also use strings within our looping range, as illustrated in Listing 20.16. This script simply iterates through the string s provided range.

Listing 20.16: Another for Loop Illustrating String Ranges (on the CD-ROM at ./source/ch20/forloop3.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       echo -n The first four planets are  4  :       for planet in mercury venus earth mars ; do  5  :         echo -n  $planet  6  :       done  7  :       echo .  8  :  9  :       exit 
end example
 

Where bash shines over traditional high-level languages is in the capability to replace ranges with results of commands. Let s look at a more complicated example of the for-in loop that uses the replacement symbol * , which means the files in the current subdirectory (see Listing 20.17).

Listing 20.17: Listing the Users on the System Using Wildcard Replacement (on the CD-ROM at ./source/ch20/forloop4.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       # Save the current directory  4  :       curwd=$PWD  5  :  6  :       # Change the current directory to /home  7  :       cd /home  8  :  9  :       echo -n Users on the system are:  10  :  11  :       # Loop through each file (via the wildcard)  12  :       for user in *; do  13  :         echo -n  $user  14  :       done  15  :       echo  16  :  17  :       # Return to the previous directory  18  :       cd $curwd  19  :  20  :       exit 
end example
 

Input and Output

We ve seen some examples already of output using the echo command. This command simply emits the provided string to the display. We also saw suppression of the newline character using the -n option. The echo command also provides the means to emit data through a binary interface. For example, to emit horizontal tabs, the \t option can be used, as:

 echo -e \t\t\t\tIndented text. 

Some of the other options that exist are shown in Table 20.5. To enable interpretation of these strings, the -e option must be specified before the string.

Table 20.5: Special Sequences in Echoed Strings

Sequence

Interpretation

\b

Backspace

\f

Form feed

\n

Newline

\r

Carriage return

\t

Horizontal tab

\v

Vertical tab

\\

Backslash

\NNN

ASCII code of octal value

We can accept input from the user using the read command. The read command provides a number of options, a few of which we ll investigate. First, let s look at the basic form of a read command via the interactive bash shell:

 # read var     test string     # echo $var     test string     # read -s var     # echo $var     silent input 

In the first form, we read a string from the user and store it into variable var . The second form of read we specify the -s flag. The -s flag represents silent input, which means that characters that are entered in response to a read are not echoed back to the screen. In this case, we typed silent input , and we see this after the variable is echoed back.

Some of the other options that exist for the read command are shown in Table 20.6

Table 20.6: Other Options for the read Command

Option

Description

-a

Input is assigned into an array, starting with index 0.

-d

Character to use to terminate input (rather than newline).

-n

Maximum number of characters to read.

-p

Prompt string displayed to prompt user for input.

-s

Silent mode (don t echo input characters).

-t

Timeout in seconds for read.

-u

File descriptor to read from rather than terminal.

Functions

Bash allows us to break scripts up into more manageable pieces by creating functions. Functions can be very simple, such as:

 function <name> () {         sequence of command     } 

As in C, the function must be declared before it can be called. Let s now look at a simple example function that sums together two numbers that are passed in from the caller (see Listing 20.18).

Listing 20.18: Creating a Function That Utilizes Parameters (on the CD-ROM at ./source/ch20/func.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       function sum ()  4  :       {  5  :  6  :         echo $((+))  7  :  8  :       }  9  :  10  :       sum 5 10 
end example
 

In this example, we declare a new function called sum (lines 3 “8), which emits the sum of the two parameters passed to it. Recall that $1 represents the first parameter and $2 represents the second. So what does $0 represent? Just as in C, the first argument from the perspective of a main program is the name of the program itself that was called. In this case, the name is the script file itself. What happens if the caller doesn t provide all of the necessary parameters ( passes only one parameter instead of two)? This would be a good time for some error checking, so we can update the script as shown in Listing 20.19.

Listing 20.19: Adding Error Checking to Our Previous Function (on the CD-ROM at ./source/ch20/func2.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       function sum ()  4  :       {  5  :  6  :         if [ $# -ne 2 ] ; then  7  :           echo usage is sum <param1> <param2>  8  :           exit  9  :         fi  10  :  11  :         echo $((+))  12  :  13  :       }  14  :  15  :       sum 5 10 
end example
 

Note in this version (updated from Listing 20.18) that error checking is now performed in lines 6 “9. We test the special variable $# , which represents the number of parameters passed to the constant 2. Since we re expecting two arguments to be passed to us, we echo the use and exit if two parameters are not present.

Note  

The parameter variables are dependent upon context. So, in Listing 20.19, the $1 parameter at line 14 will be different from the $1 present at line 6.

We can also return values from functions. We use the return command to actually return the value from the function, and then we use the special variable $? to access this value from the caller. See the example shown in Listing 20.20.

Listing 20.20: Adding Function return to Our Previous sum Function (on the CD-ROM at ./source/ch20/func3.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       function sum ()  4  :       {  5  :  6  :         if [ $# -ne 2 ] ; then  7  :           echo usage is sum <param1> <param2>  8  :           exit  9  :         fi  10  :  11  :         return $((+))  12  :  13  :       }  14  :  15  :       sum 5 10  16  :       ret=$?  17  :  18  :       echo $ret 
end example
 

In this version, rather than echo the result of the summation, we return it to the caller at line 11. At line 16, we grab the result of the function using the special $? variable. This variable represents the exit status of the last function called.

Sample Scripts

Now that we ve covered some of the basic elements of scripting, from variables to conditional and looping structures, let s look at some sample scripts that actually provide some useful functionality.

Simple Directory Archive Script

The goal of the first script is to provide a subdirectory archive tool. The single parameter for the tool is a subdirectory that will be archived using the tar utility, with the resulting archive file stored in the current working subdirectory. The script source can be found in Listing 20.21.

Listing 20.21: Directory Archive Script (on the CD-ROM at ./source/ch20/archive.sh )
start example
  1  :       #!/bin/bash  2  :  3  :       # First, do some error checking  4  :       if [ $# -ne 1 ] ; then  5  :         echo Usage is ./archive.sh <directory-name>  6  :         exit -1  7  :       fi  8  :  9  :       if [ ! -e  ] ; then  10  :         echo Directory does not exist  11  :         exit -1  12  :       fi  13  :  14  :       if [ ! -d  ] ; then  15  :         echo Target must be a directory.  16  :         exit -1  17  :       fi  18  :  19  :       # Remove the existing archive  20  :       archive=.tgz  21  :  22  :       if [ -f $archive ] ; then  23  :         rm -f $archive  24  :       fi  25  :  26  :       # Archive the directory  27  :       tar czf $archive  28  :  29  :       exit 
end example
 

As is probably apparent right away, the script in Listing 20.21 is mostly error checking. We first ensure that there s a single argument to the script at lines 4 “7. We then check that the target provided actually exists at lines 9 “12, and that it s a directory at lines 14 “17. At line 20, we create the archive file by appending the extension .tgz to the end. If the archive exists, we remove it at lines 22 “24. Finally, we call the tar utility to create the archive at line 27. We specify three arguments to tar: c to create the archive, z to filter using gzip , and f to specify the filename for the archive.

Files Updated/Created Today Script

The goal of this script is to recursively search a directory to print any files that have been updated today. This is a relatively simple task that is also simple to express in bash.

The following sample script illustrates some other concepts not yet covered. See Listing 20.22 for the full script. This script is made up of three parts . The first part (lines 61 “63) invokes the script based upon the user s call. The second part (lines 48 “59) does some basic error checking and then starts the recursive process by interrogating the current subdirectory. Finally, the last part is the recursive function that looks at all files within a given subdirectory. Upon finding a new directory, the recurse() function is called again to dig down further into the tree.

When we call the  fut.sh script, two functions are declared ( recurse() and main() ). The script ultimately ends up at line 61, where the main function is called using the first argument passed to the script as the argument passed to main().

In function main() (line 48), we begin by storing the current data in the format YYYY-MM-DD . This is performed using the date command, specifying the desired format in double quotes. We store this result into a variable called today . Note that today isn t local to main(); it can be used in other functions afterward.

Note  

It is possible to declare a variable as local to a function. This is useful if we wish to store information in a function for recursive uses. To declare a local variable, we simply insert the local keyword before the variable.

The main() function continues by storing the argument (the directory to recurse ) in variable checkdir (line 52). We then test checkdir to see if it s empty (has zero length) at line 54. If it is empty, we store . to checkdir , which represents the current subdirectory. This entire test was done so that if the user passed no arguments, we d simply use the current subdirectory as the argument default. Finally, we call the recurse() function with the checkdir variable (line 58).

The bulk of the script is found in function recurse() . This function recursively digs into the directory to find any files that have changed today. The first thing we do is cd into the subdirectory passed to us (line 15). Note that when . is passed, we cd into the current directory (in other words, no change takes place). We then iterate through the files in the subdirectory (line 18).

The first thing to check for a given file is to see if it s another directory (line 21). If it is, we simply call the recurse() function (recursively) to dig into this subdirectory (line 22). Otherwise, we check to see if the file is a regular file (line 25). If it is, we perform an ls command on the file, gathering a long time format (line 27). The time style format of this ls command ( long-iso ) happens to match the format that we gathered in main() to represent the date for today.

At line 29, we search the ls line ( longfile ) using our date stored in today . This is done through the grep command. We pipe the contents of longfile to grep to search for the today string. If the string is found in longfile , the line will simply result, otherwise a blank line will result. At line 31, we check to see if the check variable (the result of the grep ) is a nonzero length string. If so, we emit the current file and continue the process at line 18 to get the next file in the directory.

Once we ve exhausted the file list from line 18, we exit our loop at line 39. We check to see if the directory passed to us was not .. If not, we cd up one directory (because we cd d down one directory at line 15). If our directory was identified as . (the current directory), we avoid cd ing up one level.

Listing 20.22: Files Updated/Created Today Script (on the CD-ROM at ./source/_ch20/fut.sh )
start example
  1  :       #!/bin/bash  2  :       #  3  :       # fut.sh  4  :       #  5  :       #  Find files created/updated today.  6  :       #  7  :       #  Usage is:  8  :       #  9  :       #    fut.sh <dir>  10  :       #  11  :  12  :       function recurse()  13  :       {  14  :         # 


GNU/Linux Application Programming
GNU/Linux Application Programming (Programming Series)
ISBN: 1584505680
EAN: 2147483647
Year: 2006
Pages: 203
Authors: M. Tim Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net