I copied this from my edited version of the LDP's - Advanced Bash Scripting Guide ________________________________________________________________ 7.2. File test operators Returns true if... -e file exists -a file exists This is identical in effect to -e. It has been "deprecated," [30] and its use is discouraged. -f file is a regular file (not a directory or device file) -s file is not zero size -d file is a directory -b file is a block device -c file is a character device device0="/dev/sda2" # / (root directory) if [ -b "$device0" ] then echo "$device0 is a block device." fi # /dev/sda2 is a block device. device1="/dev/ttyS1" # PCMCIA modem card. if [ -c "$device1" ] then echo "$device1 is a character device." fi # /dev/ttyS1 is a character device. -p file is a pipe -h file is a symbolic link -L file is a symbolic link -S file is a socket -t file (descriptor) is associated with a terminal device This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal. -r file has read permission (for the user running the test) -w file has write permission (for the user running the test) -x file has execute permission (for the user running the test) -g set-group-id (sgid) flag set on file or directory If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory, not necessarily to the group of the user who created the file. This may be useful for a directory shared by a workgroup. -u set-user-id (suid) flag set on file A binary owned by root with set-user-id flag set runs with root privileges, even when an ordinary user invokes it. [31] This is useful for executables (such as pppd and cdrecord) that need to access system hardware. Lacking the suid flag, these binaries could not be invoked by a non-root user. -rwsr-xr-t 1 root 178236 Oct 2 2000 /usr/sbin/pppd A file with the suid flag set shows an s in its permissions. -k sticky bit set Commonly known as the sticky bit, the save-text-mode flag is a special type of file permission. If a file has this flag set, that file will be kept in cache memory, for quicker access. [32] If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing. drwxrwxrwt 7 root 1024 May 19 21:26 tmp/ If a user does not own a directory that has the sticky bit set, but has write permission in that directory, she can only delete those files that she owns in it. This keeps users from inadvertently overwriting or deleting each other's files in a publicly accessible directory, such as /tmp. (The owner of the directory or root can, of course, delete or rename files there.) -O you are owner of file -G group-id of file same as yours -N file modified since it was last read f1 -nt f2 file f1 is newer than f2 f1 -ot f2 file f1 is older than f2 f1 -ef f2 files f1 and f2 are hard links to the same file ! "not" -- reverses the sense of the tests above (returns true if condition absent). #### Example 7-4. Testing for broken links #### !/bin/bash # broken-link.sh # Written by Lee bigelow # Used in ABS Guide with permission. # A pure shell script to find dead symlinks and output them quoted # + so they can be fed to xargs and dealt with :) # + eg. sh broken-link.sh /somedir /someotherdir|xargs rm # # This, however, is a better method: # # find "somedir" -type l -print0|\ # xargs -r0 file|\ # grep "broken symbolic"| # sed -e 's/^\|: *broken symbolic.*$/"/g' # # + but that wouldn't be pure Bash, now would it. # Caution: beware the /proc file system and any circular links! # # If no args are passed to the script set directories-to-search # + to current directory. Otherwise set the directories-to-search # + to the args passed. # [ $# -eq 0 ] && directorys=`pwd` || directorys=$@ # Setup the function linkchk to check the directory it is passed #+ for files that are links and don't exist, then print them quoted. # If one of the elements in the directory is a subdirectory then #+ send that subdirectory to the linkcheck function. # linkchk () { for element in $1/*; do [ -h "$element" -a ! -e "$element" ] && echo \"$element\" [ -d "$element" ] && linkchk $element # Of course, '-h' tests for symbolic link, '-d' for directory. done } # Send each arg that was passed to the script to the linkchk() function #+ if it is a valid directoy. If not, then print the error message #+ and usage info. # for directory in $directorys; do if [ -d $directory ] then linkchk $directory else echo "$directory is not a directory" echo "Usage: $0 dir1 dir2 ..." fi done exit $? #### Example 30-1, Example 11-7, Example 11-3, Example 30-3, and Example A-1 also illustrate uses of the file test operators. ________________________________________________________________ 7.3. Other Comparison Operators A binary comparison operator compares two variables or quantities. Note that integer and string comparison use a different set of operators. --- integer comparison --- -eq is equal to if [ "$a" -eq "$b" ] -ne is not equal to if [ "$a" -ne "$b" ] -gt is greater than if [ "$a" -gt "$b" ] -ge is greater than or equal to if [ "$a" -ge "$b" ] -lt is less than if [ "$a" -lt "$b" ] -le is less than or equal to if [ "$a" -le "$b" ] < is less than (within double parentheses) (("$a" < "$b")) <= is less than or equal to (within double parentheses) (("$a" <= "$b")) > is greater than (within double parentheses) (("$a" > "$b")) >= is greater than or equal to (within double parentheses) (("$a" >= "$b")) --- string comparison --- = is equal to if [ "$a" = "$b" ] == is equal to if [ "$a" == "$b" ] This is a synonym for =. Note: The == comparison operator behaves differently within a double-brackets test than within single brackets. [[ $a == z* ]] # True if $a starts with an "z" (pattern matching). [[ $a == "z*" ]] # True if $a is equal to z* (literal matching). [ $a == z* ] # File globbing and word splitting take place. [ "$a" == "z*" ] # True if $a is equal to z* (literal matching). # Thanks, Stéphane Chazelas != is not equal to if [ "$a" != "$b" ] This operator uses pattern matching within a [[ ... ]] construct. < is less than, in ASCII alphabetical order if [[ "$a" < "$b" ]] if [ "$a" \< "$b" ] Note that the "<" needs to be escaped within a [ ] construct. > is greater than, in ASCII alphabetical order if [[ "$a" > "$b" ]] if [ "$a" \> "$b" ] Note that the ">" needs to be escaped within a [ ] construct. See Example 27-11 for an application of this comparison operator. -z string is null, that is, has zero length String='' # Zero-length ("null") string variable. if [ -z "$String" ]; then echo "\$String is null." else echo "\$String is NOT null." fi # $String is null. -n string is not null. Caution The -n test requires that the string be quoted within the test brackets. Using an unquoted string with ! -z, or even just the unquoted string alone within test brackets (see Example 7-6) normally works, however, this is an unsafe practice. Always quote a tested string. [33] ##### Example 7-5. Arithmetic and string comparisons ############## #!/bin/bash a=4 b=5 # Here "a" and "b" can be treated either as integers or strings. # There is some blurring between the arithmetic and string comparisons, #+ since Bash variables are not strongly typed. # Bash permits integer operations and comparisons on variables #+ whose value consists of all-integer characters. # Caution advised, however. echo if [ "$a" -ne "$b" ] then echo "$a is not equal to $b" echo "(arithmetic comparison)" fi echo if [ "$a" != "$b" ] then echo "$a is not equal to $b." echo "(string comparison)" # "4" != "5" # ASCII 52 != ASCII 53 fi # In this particular instance, both "-ne" and "!=" work. echo exit 0 Example 7-6. Testing whether a string is null #!/bin/bash # str-test.sh: Testing null strings and unquoted strings, #+ but not strings and sealing wax, not to mention cabbages and kings . . . # Using if [ ... ] # If a string has not been initialized, it has no defined value. # This state is called "null" (not the same as zero!). if [ -n $string1 ] # string1 has not been declared or initialized. then echo "String \"string1\" is not null." else echo "String \"string1\" is null." fi # Wrong result. # Shows $string1 as not null, although it was not initialized. echo # Let's try it again. if [ -n "$string1" ] # This time, $string1 is quoted. then echo "String \"string1\" is not null." else echo "String \"string1\" is null." fi # Quote strings within test brackets! echo if [ $string1 ] # This time, $string1 stands naked. then echo "String \"string1\" is not null." else echo "String \"string1\" is null." fi # This works fine. # The [ ... ] test operator alone detects whether the string is null. # However it is good practice to quote it (if [ "$string1" ]). # # As Stephane Chazelas points out, # if [ $string1 ] has one argument, "]" # if [ "$string1" ] has two arguments, the empty "$string1" and "]" echo string1=initialized if [ $string1 ] # Again, $string1 stands unquoted. then echo "String \"string1\" is not null." else echo "String \"string1\" is null." fi # Again, gives correct result. # Still, it is better to quote it ("$string1"), because . . . string1="a = b" if [ $string1 ] # Again, $string1 stands unquoted. then echo "String \"string1\" is not null." else echo "String \"string1\" is null." fi # Not quoting "$string1" now gives wrong result! exit 0 # Thank you, also, Florian Wisser, for the "heads-up". Example 7-7. zmore #!/bin/bash # zmore # View gzipped files with 'more' filter. E_NOARGS=65 E_NOTFOUND=66 E_NOTGZIP=67 if [ $# -eq 0 ] # same effect as: if [ -z "$1" ] # $1 can exist, but be empty: zmore "" arg2 arg3 then echo "Usage: `basename $0` filename" >&2 # Error message to stderr. exit $E_NOARGS # Returns 65 as exit status of script (error code). fi filename=$1 if [ ! -f "$filename" ] # Quoting $filename allows for possible spaces. then echo "File $filename not found!" >&2 # Error message to stderr. exit $E_NOTFOUND fi if [ ${filename##*.} != "gz" ] # Using bracket in variable substitution. then echo "File $1 is not a gzipped file!" exit $E_NOTGZIP fi zcat $1 | more # Uses the 'more' filter. # May substitute 'less' if desired. exit $? # Script returns exit status of pipe. # Actually "exit $?" is unnecessary, as the script will, in any case, #+ return the exit status of the last command executed. compound comparison -a logical and exp1 -a exp2 returns true if both exp1 and exp2 are true. -o logical or exp1 -o exp2 returns true if either exp1 or exp2 is true. These are similar to the Bash comparison operators && and ||, used within double brackets. [[ condition1 && condition2 ]] The -o and -a operators work with the test command or occur within single test brackets. if [ "$expr1" -a "$expr2" ]; then echo "Both expr1 and expr2 are true." else echo "Either expr1 or expr2 is false." fi Caution - But, as rihad points out: [ 1 -eq 1 ] && [ -n "`echo true 1>&2`" ] # true [ 1 -eq 2 ] && [ -n "`echo true 1>&2`" ] # (no output) # ^^^^^^^ False condition. So far, everything as expected. # However ... [ 1 -eq 2 -a -n "`echo true 1>&2`" ] # true # ^^^^^^^ False condition. So, why "true" output? # Is it because both condition clauses within brackets evaluate? [[ 1 -eq 2 && -n "`echo true 1>&2`" ]] # (no output) # No, that's not it. # Apparently && and || "short-circuit" while -a and -o do not. Refer to Example 8-3, Example 27-17, and Example A-29 to see compound comparison operators in action. ________________________________________________________________ 7.4. Nested if/then Condition Tests Condition tests using the if/then construct may be nested. The net result is equivalent to using the && compound comparison operator. a=3 if [ "$a" -gt 0 ] then if [ "$a" -lt 5 ] then echo "The value of \"a\" lies somewhere between 0 and 5." fi fi # Same result as: if [ "$a" -gt 0 ] && [ "$a" -lt 5 ] then echo "The value of \"a\" lies somewhere between 0 and 5." fi Example 36-4 demonstrates a nested if/then condition test. ________________________________________________________________ 7.5. Testing Your Knowledge of Tests The systemwide xinitrc file can be used to launch the X server. This file contains quite a number of if/then tests. The following is excerpted from an "ancient" version of xinitrc (Red Hat 7.1, or thereabouts). if [ -f $HOME/.Xclients ]; then exec $HOME/.Xclients elif [ -f /etc/X11/xinit/Xclients ]; then exec /etc/X11/xinit/Xclients else # failsafe settings. Although we should never get here # (we provide fallbacks in Xclients as well) it can't hurt. xclock -geometry 100x100-5+5 & xterm -geometry 80x50-50+150 & if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then netscape /usr/share/doc/HTML/index.html & fi fi Explain the test constructs in the above snippet, then examine an updated version of the file, /etc/X11/xinit/xinitrc, and analyze the if/then test constructs there. You may need to refer ahead to the discussions of grep, sed, and regular expressions. ________________________________________________________________ Chapter 8. Operations and Related Topics 8.1. Operators assignment variable assignment Initializing or changing the value of a variable = All-purpose assignment operator, which works for both arithmetic and string assignments. var=27 category=minerals # No spaces allowed after the "=". Caution Do not confuse the "=" assignment operator with the = test operator. # = as a test operator if [ "$string1" = "$string2" ] then command fi # if [ "X$string1" = "X$string2" ] is safer, #+ to prevent an error message should one of the variables be empty. # (The prepended "X" characters cancel out.) arithmetic operators + plus - minus * multiplication / division ** exponentiation # Bash, version 2.02, introduced the "**" exponentiation operator. let "z=5**3" # 5 * 5 * 5 echo "z = $z" # z = 125 % modulo, or mod (returns the remainder of an integer division operation) bash$ expr 5 % 3 2 5/3 = 1, with remainder 2 This operator finds use in, among other things, generating numbers within a specific range (see Example 9-11 and Example 9-15) and formatting program output (see Example 27-16 and Example A-6). It can even be used to generate prime numbers, (see Example A-15). Modulo turns up surprisingly often in numerical recipes. Example 8-1. Greatest common divisor #!/bin/bash # gcd.sh: greatest common divisor # Uses Euclid's algorithm # The "greatest common divisor" (gcd) of two integers #+ is the largest integer that will divide both, leaving no remainder. # Euclid's algorithm uses successive division. # In each pass, #+ dividend <--- divisor #+ divisor <--- remainder #+ until remainder = 0. # The gcd = dividend, on the final pass. # # For an excellent discussion of Euclid's algorithm, see #+ Jim Loy's site, http://www.jimloy.com/number/euclids.htm. # ------------------------------------------------------ # Argument check ARGS=2 E_BADARGS=85 if [ $# -ne "$ARGS" ] then echo "Usage: `basename $0` first-number second-number" exit $E_BADARGS fi # ------------------------------------------------------ gcd () { dividend=$1 # Arbitrary assignment. divisor=$2 #! It doesn't matter which of the two is larger. # Why not? remainder=1 # If an uninitialized variable is used inside #+ test brackets, an error message results. until [ "$remainder" -eq 0 ] do # ^^^^^^^^^^ Must be previously initialized! let "remainder = $dividend % $divisor" dividend=$divisor # Now repeat with 2 smallest numbers. divisor=$remainder done # Euclid's algorithm } # Last $dividend is the gcd. gcd $1 $2 echo; echo "GCD of $1 and $2 = $dividend"; echo # Exercises : # --------- # 1) Check command-line arguments to make sure they are integers, #+ and exit the script with an appropriate error message if not. # 2) Rewrite the gcd () function to use local variables. exit 0 += plus-equal (increment variable by a constant) let "var += 5" results in var being incremented by 5. -= minus-equal (decrement variable by a constant) *= times-equal (multiply variable by a constant) let "var *= 4" results in var being multiplied by 4. /= slash-equal (divide variable by a constant) %= mod-equal (remainder of dividing variable by a constant) Arithmetic operators often occur in an expr or let expression. Example 8-2. Using Arithmetic Operations #!/bin/bash # Counting to 11 in 10 different ways. n=1; echo -n "$n " let "n = $n + 1" # let "n = n + 1" also works. echo -n "$n " : $((n = $n + 1)) # ":" necessary because otherwise Bash attempts #+ to interpret "$((n = $n + 1))" as a command. echo -n "$n " (( n = n + 1 )) # A simpler alternative to the method above. # Thanks, David Lombard, for pointing this out. echo -n "$n " n=$(($n + 1)) echo -n "$n " : $[ n = $n + 1 ] # ":" necessary because otherwise Bash attempts #+ to interpret "$[ n = $n + 1 ]" as a command. # Works even if "n" was initialized as a string. echo -n "$n " n=$[ $n + 1 ] # Works even if "n" was initialized as a string. #* Avoid this type of construct, since it is obsolete and nonportable. # Thanks, Stephane Chazelas. echo -n "$n " # Now for C-style increment operators. # Thanks, Frank Wang, for pointing this out. let "n++" # let "++n" also works. echo -n "$n " (( n++ )) # (( ++n )) also works. echo -n "$n " : $(( n++ )) # : $(( ++n )) also works. echo -n "$n " : $[ n++ ] # : $[ ++n ] also works echo -n "$n " echo exit 0 Note Integer variables in older versions of Bash were signed long (32-bit) integers, in the range of -2147483648 to 2147483647. An operation that took a variable outside these limits gave an erroneous result. echo $BASH_VERSION # 1.14 a=2147483646 echo "a = $a" # a = 2147483646 let "a+=1" # Increment "a". echo "a = $a" # a = 2147483647 let "a+=1" # increment "a" again, past the limit. echo "a = $a" # a = -2147483648 # ERROR: out of range, # + and the leftmost bit, the sign bit, # + has been set, making the result negative. As of version >= 2.05b, Bash supports 64-bit integers. Caution Bash does not understand floating point arithmetic. It treats numbers containing a decimal point as strings. a=1.5 let "b = $a + 1.3" # Error. # t2.sh: let: b = 1.5 + 1.3: syntax error in expression # (error token is ".5 + 1.3") echo "b = $b" # b=1 Use bc in scripts that that need floating point calculations or math library functions. bitwise operators. The bitwise operators seldom make an appearance in shell scripts. Their chief use seems to be manipulating and testing values read from ports or sockets. "Bit flipping" is more relevant to compiled languages, such as C and C++, which provide direct access to system hardware. bitwise operators << bitwise left shift (multiplies by 2 for each shift position) <<= left-shift-equal let "var <<= 2" results in var left-shifted 2 bits (multiplied by 4) >> bitwise right shift (divides by 2 for each shift position) >>= right-shift-equal (inverse of <<=) & bitwise AND &= bitwise AND-equal | bitwise OR |= bitwise OR-equal ~ bitwise NOT ^ bitwise XOR ^= bitwise XOR-equal logical (boolean) operators ! NOT if [ ! -f $FILENAME ] then ... && AND if [ $condition1 ] && [ $condition2 ] # Same as: if [ $condition1 -a $condition2 ] # Returns true if both condition1 and condition2 hold true... if [[ $condition1 && $condition2 ]] # Also works. # Note that && operator not permitted inside brackets #+ of [ ... ] construct. Note && may also be used, depending on context, in an and list to concatenate commands. || OR if [ $condition1 ] || [ $condition2 ] # Same as: if [ $condition1 -o $condition2 ] # Returns true if either condition1 or condition2 holds true... if [[ $condition1 || $condition2 ]] # Also works. # Note that || operator not permitted inside brackets #+ of a [ ... ] construct. Note Bash tests the exit status of each statement linked with a logical operator. Example 8-3. Compound Condition Tests Using && and || #!/bin/bash a=24 b=47 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ] then echo "Test #1 succeeds." else echo "Test #1 fails." fi # ERROR: if [ "$a" -eq 24 && "$b" -eq 47 ] #+ attempts to execute ' [ "$a" -eq 24 ' #+ and fails to finding matching ']'. # # Note: if [[ $a -eq 24 && $b -eq 24 ]] works. # The double-bracket if-test is more flexible #+ than the single-bracket version. # (The "&&" has a different meaning in line 17 than in line 6.) # Thanks, Stephane Chazelas, for pointing this out. if [ "$a" -eq 98 ] || [ "$b" -eq 47 ] then echo "Test #2 succeeds." else echo "Test #2 fails." fi # The -a and -o options provide #+ an alternative compound condition test. # Thanks to Patrick Callahan for pointing this out. if [ "$a" -eq 24 -a "$b" -eq 47 ] then echo "Test #3 succeeds." else echo "Test #3 fails." fi if [ "$a" -eq 98 -o "$b" -eq 47 ] then echo "Test #4 succeeds." else echo "Test #4 fails." fi a=rhino b=crocodile if [ "$a" = rhino ] && [ "$b" = crocodile ] then echo "Test #5 succeeds." else echo "Test #5 fails." fi exit 0 The && and || operators also find use in an arithmetic context. bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0)) 1 0 1 0 miscellaneous operators , Comma operator The comma operator chains together two or more arithmetic operations. All the operations are evaluated (with possible side effects. [34] let "t1 = ((5 + 3, 7 - 1, 15 - 4))" echo "t1 = $t1" ^^^^^^ # t1 = 11 # Here t1 is set to the result of the last operation. Why? let "t2 = ((a = 9, 15 / 3))" # Set "a" and calculate "t2". echo "t2 = $t2 a = $a" # t2 = 5 a = 9 The comma operator finds use mainly in for loops. See Example 11-12. ________________________________________________________________ ##################################################################### Substring Replacement ${string/substring/replacement} Replace first match of $substring with $replacement. [45] ${string//substring/replacement} Replace all matches of $substring with $replacement. stringZ=abcABC123ABCabc echo ${stringZ/abc/xyz} # xyzABC123ABCabc # Replaces first match of 'abc' with 'xyz'. echo ${stringZ//abc/xyz} # xyzABC123ABCxyz # Replaces all matches of 'abc' with # 'xyz'. echo --------------- echo "$stringZ" # abcABC123ABCabc echo --------------- # The string itself is not altered! # Can the match and replacement strings be parameterized? match=abc repl=000 echo ${stringZ/$match/$repl} # 000ABC123ABCabc # ^ ^ ^^^ echo ${stringZ//$match/$repl} # 000ABC123ABC000 # Yes! ^ ^ ^^^ ^^^ echo # What happens if no $replacement string is supplied? echo ${stringZ/abc} # ABC123ABCabc echo ${stringZ//abc} # ABC123ABC # A simple deletion takes place. ${string/#substring/replacement} If $substring matches front end of $string, substitute $replacement for $substring. ${string/%substring/replacement} If $substring matches back end of $string, substitute $replacement for $substring. stringZ=abcABC123ABCabc echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc # Replaces front-end match of 'abc' with 'XY Z'. echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ # Replaces back-end match of 'abc' with 'XYZ '.