Cari di Shell Script 
    Shell Script Linux Modules Tutorial
Daftar Isi
(Sebelumnya) 6. Exit and Exit Status8. Operations and Related Topics (Berikutnya)

Chapter 7. Tests

Every reasonably complete programming language can test for a condition, then act according to the result of the test. Bash has the test command, various bracket and parenthesis operators, and the if/then construct.


7.1. Test Constructs

  • An if/then construct tests whether the exit status of a list of commands is 0 (since 0 means "success" by UNIX convention), and if so, executes one or more commands.

  • There exists a dedicated command called [ (left bracketspecial character). It is a synonym for test,and a builtin for efficiencyreasons. This command considers its arguments as comparisonexpressions or file tests and returns an exit status correspondingto the result of the comparison (0 for true, 1 for false).

  • With version 2.02, Bash introduced the [[ ... ]] extended test command, which performs comparisons in a manner more familiar to programmers from other languages. Note that [[ is a keyword, not a command.

    Bash sees [[ $a -lt $b ]] as a single element, which returns an exit status.

  • The (( ... )) and let ... constructs return an exit status, according to whether the arithmetic expressions they evaluate expand to a non-zero value. These arithmetic-expansion constructs may therefore be used to perform arithmetic comparisons.

    (( 0 && 1 )) # Logical ANDecho $? # 1 ***# And so ...let "num = (( 0 && 1 ))" echo $num   # 0# But ...let "num = (( 0 && 1 ))" echo $? # 1 ***(( 200 || 11 ))  # Logical ORecho $? # 0 ***# ...let "num = (( 200 || 11 ))" echo $num   # 1let "num = (( 200 || 11 ))" echo $? # 0 ***(( 200 | 11 ))   # Bitwise ORecho $?  # 0 ***# ...let "num = (( 200 | 11 ))" echo $num # 203let "num = (( 200 | 11 ))" echo $?  # 0 ***# The "let" construct returns the same exit status#+ as the double-parentheses arithmetic expansion.

    Again, note that the exit status of an arithmetic expression is not an error value.

    var=-2 && (( var+=2 ))echo $?   # 1var=-2 && (( var+=2 )) && echo $var  # Will not echo $var!

  • An if can test any command, not just conditions enclosed within brackets.

    if cmp a b &> /dev/null  # Suppress output.then echo "Files a and b are identical." else echo "Files a and b differ." fi# The very useful "if-grep" construct:# ----------------------------------- if grep -q Bash file  then echo "File contains at least one occurrence of Bash." fiword=Linuxletter_sequence=inuif echo "$word" | grep -q "$letter_sequence" # The "-q" option to grep suppresses output.then  echo "$letter_sequence found in $word" else  echo "$letter_sequence not found in $word" fiif COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED  then echo "Command succeeded."   else echo "Command failed." fi

  • These last two examples courtesy of St�phane Chazelas.

Example 7-1. What is truth?

#!/bin/bash#  Tip:#  If you're unsure how a certain condition might evaluate,#+ test it in an if-test.echoecho "Testing "0"" if [ 0 ]  # zerothen  echo "0 is true." else  # Or else ...  echo "0 is false." fi # 0 is true.echoecho "Testing "1"" if [ 1 ]  # onethen  echo "1 is true." else  echo "1 is false." fi # 1 is true.echoecho "Testing "-1"" if [ -1 ] # minus onethen  echo "-1 is true." else  echo "-1 is false." fi # -1 is true.echoecho "Testing "NULL"" if [ ] # NULL (empty condition)then  echo "NULL is true." else  echo "NULL is false." fi # NULL is false.echoecho "Testing "xyz"" if [ xyz ] # stringthen  echo "Random string is true." else  echo "Random string is false." fi # Random string is true.echoecho "Testing "$xyz"" if [ $xyz ]   # Tests if $xyz is null, but...  # it's only an uninitialized variable.then  echo "Uninitialized variable is true." else  echo "Uninitialized variable is false." fi # Uninitialized variable is false.echoecho "Testing "-n $xyz"" if [ -n "$xyz" ] # More pedantically correct.then  echo "Uninitialized variable is true." else  echo "Uninitialized variable is false." fi # Uninitialized variable is false.echoxyz=  # Initialized, but set to null value.echo "Testing "-n $xyz"" if [ -n "$xyz" ]then  echo "Null variable is true." else  echo "Null variable is false." fi # Null variable is false.echo# When is "false" true?echo "Testing "false"" if [ "false" ]  #  It seems that "false" is just a string ...then  echo ""false" is true." #+ and it tests true.else  echo ""false" is false." fi # "false" is true.echoecho "Testing "$false""  # Again, uninitialized variable.if [ "$false" ]then  echo ""$false" is true." else  echo ""$false" is false." fi # "$false" is false.  # Now, we get the expected result.#  What would happen if we tested the uninitialized variable "$true"?echoexit 0

Exercise. Explain the behavior of Example 7-1, above.

if [ condition-true ]then   command 1   command 2   ...else  # Or else ...  # Adds default code block executing if original condition tests false.   command 3   command 4   ...fi

When if and thenare on same line in a condition test, a semicolon mustterminate the if statement. Bothif and thenare keywords. Keywords (orcommands) begin statements, and before a new statement on thesame line begins, the old one must terminate.

if [ -x "$filename" ]; then

Else if and elif

elif

elif is a contraction for else if. The effect is to nest an inner if/then construct within an outer one.

if [ condition1 ]then   command1   command2   command3elif [ condition2 ]# Same as else ifthen   command4   command5else   default-commandfi

The if test condition-true construct is theexact equivalent of if [ condition-true ].As it happens, the left bracket, [ , is atoken[1]which invokes the test command. The closingright bracket, ] , in an if/test should nottherefore be strictly necessary, however newer versions of Bashrequire it.

The test command is a Bash builtin which tests file types and compares strings. Therefore, in a Bash script, test does not call the external /usr/bin/test binary, which is part of the sh-utils package. Likewise, [ does not call /usr/bin/[, which is linked to /usr/bin/test.

bash$ type testtest is a shell builtinbash$ type '['[ is a shell builtinbash$ type '[['[[ is a shell keywordbash$ type ']]']] is a shell keywordbash$ type ']'bash: type: ]: not found  

If, for some reason, you wish to use /usr/bin/test in a Bash script, then specify it by full pathname.

Example 7-2. Equivalence of test, /usr/bin/test, [ ], and /usr/bin/[

#!/bin/bashechoif test -z "$1" then  echo "No command-line arguments." else  echo "First command-line argument is $1." fiechoif /usr/bin/test -z "$1"  # Equivalent to "test" builtin.#  ^^^^^^^^^^^^^  # Specifying full pathname.then  echo "No command-line arguments." else  echo "First command-line argument is $1." fiechoif [ -z "$1" ] # Functionally identical to above code blocks.#   if [ -z "$1" should work, but...#+  Bash responds to a missing close-bracket with an error message.then  echo "No command-line arguments." else  echo "First command-line argument is $1." fiechoif /usr/bin/[ -z "$1" ]   # Again, functionally identical to above.# if /usr/bin/[ -z "$1"   # Works, but gives an error message.# # Note:#   This has been fixed in Bash, version 3.x.then  echo "No command-line arguments." else  echo "First command-line argument is $1." fiechoexit 0

Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] )are strictly necessary.

dir=/home/bozoif cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.  echo "Now in $dir." else  echo "Can't change to $dir." fi
The "if COMMAND" construct returns the exit status of COMMAND.

Similarly, a condition within test brackets may stand alone without an if, when used in combination with a list construct.

var1=20var2=22[ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2" home=/home/bozo[ -d "$home" ] || echo "$home directory does not exist."

The (( ))construct expands and evaluates an arithmeticexpression. If the expression evaluates as zero, it returnsan exit status of1, or "false". A non-zeroexpression returns an exit status of 0,or "true". This is in marked contrast to usingthe test and [ ] constructspreviously discussed.

Example 7-3. Arithmetic Tests using (( ))

#!/bin/bash# arith-tests.sh# Arithmetic tests.# The (( ... )) construct evaluates and tests numerical expressions.# Exit status opposite from [ ... ] construct!(( 0 ))echo "Exit status of "(( 0 ))" is $?." # 1(( 1 ))echo "Exit status of "(( 1 ))" is $?." # 0(( 5 > 4 ))  # trueecho "Exit status of "(( 5 > 4 ))" is $?." # 0(( 5 > 9 ))  # falseecho "Exit status of "(( 5 > 9 ))" is $?." # 1(( 5 == 5 )) # trueecho "Exit status of "(( 5 == 5 ))" is $?." # 0# (( 5 = 5 ))  gives an error message.(( 5 - 5 ))  # 0echo "Exit status of "(( 5 - 5 ))" is $?." # 1(( 5 / 4 ))  # Division o.k.echo "Exit status of "(( 5 / 4 ))" is $?." # 0(( 1 / 2 ))  # Division result < 1.echo "Exit status of "(( 1 / 2 ))" is $?." # Rounded off to 0. # 1(( 1 / 0 )) 2>/dev/null  # Illegal division by 0.#   ^^^^^^^^^^^echo "Exit status of "(( 1 / 0 ))" is $?." # 1# What effect does the "2>/dev/null" have?# What would happen if it were removed?# Try removing it, then rerunning the script.# ======================================= ## (( ... )) also useful in an if-then test.var1=5var2=4if (( var1 > var2 ))then #^  ^  Note: Not $var1, $var2. Why?  echo "$var1 is greater than $var2" fi # 5 is greater than 4exit 0

Notes

[1]

Atoken is a symbol or shortstring with a special meaning attached to it (a meta-meaning). In Bash,certain tokens, such as [ and . (dot-command), may expand tokeywords and commands.


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," [1] 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

function show_input_type(){   [ -p /dev/fd/0 ] && echo PIPE || echo STDIN}show_input_type "Input"   # STDINecho "Input" | show_input_type # PIPE# This example courtesy of Carl Anderson.

-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. [2] 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 suidflag set shows an s in itspermissions.

-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. [3] 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 stickybit set, but has write permission in that directory, shecan only delete those files that she owns in it. Thiskeeps users from inadvertently overwriting or deletingeach other's files in a publicly accessible directory,such as /tmp.(The owner of the directory orroot can, of course, delete orrename 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 thanf2

f1 -ot f2

file f1 is older thanf2

f1 -ef f2

files f1 andf2 are hard links to the samefile

!

"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 <[email protected]># 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 $directoryelse echo "$directory is not a directory" echo "Usage: $0 dir1 dir2 ..." fidoneexit $?

Example 31-1, Example 11-7, Example 11-3, Example 31-3, and Example A-1 also illustrate uses of the file test operators.

Notes

[1]

Per the 1913 edition of Webster'sDictionary:

Deprecate...To pray against, as an evil;to seek to avert by prayer;to desire the removal of;to seek deliverance from;to express deep regret for;to disapprove of strongly.

[2]

Be aware that suid binaries may open security holes. The suid flag has no effect on shell scripts.

[3]

On Linux systems, the sticky bit is no longer used for files, only on directories.


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" ]

Note the whitespace framing the =.

if [ "$a"="$b" ] is not equivalent to the above.

==

is equal to

if [ "$a" == "$b" ]

This is a synonym for =.

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.

The -n testrequires that the string be quoted within thetest brackets. Using an unquoted string with! -z, or even just theunquoted string alone within test brackets (see Example 7-6) normally works, however, this isan unsafe practice. Always quotea tested string. [1]

Example 7-5. Arithmetic and string comparisons

#!/bin/basha=4b=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.echoif [ "$a" -ne "$b" ]then  echo "$a is not equal to $b"   echo "(arithmetic comparison)" fiechoif [ "$a" != "$b" ]then  echo "$a is not equal to $b."   echo "(string comparison)"   # "4"  != "5"   # ASCII 52 != ASCII 53fi# In this particular instance, both "-ne" and "!=" work.echoexit 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!echoif [ $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 "]" echostring1=initializedif [ $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=85E_NOTFOUND=86E_NOTGZIP=87if [ $# -eq 0 ] # same effect as:  if [ -z "$1" ]# $1 can exist, but be empty:  zmore "" arg2 arg3then  echo "Usage: `basename $0` filename" >&2  # Error message to stderr.  exit $E_NOARGS  # Returns 85 as exit status of script (error code).fi  filename=$1if [ ! -f "$filename" ]   # Quoting $filename allows for possible spaces.then  echo "File $filename not found!" >&2   # Error message to stderr.  exit $E_NOTFOUNDfi  if [ ${filename##*.} != "gz" ]# Using bracket in variable substitution.then  echo "File $1 is not a gzipped file!"   exit $E_NOTGZIPfi  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 ifboth exp1 and exp2 are true.

-o

logical or

exp1 -o exp2 returnstrue if either exp1 or exp2 istrue.

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

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.

Notes

[1]

As S.C. points out, in a compound test, even quoting the string variable might not suffice. [ -n "$string" -o "$a" = "$b" ] may cause an error with some versions of Bash if $string is empty. The safe way is to append an extra character to possibly empty variables, [ "x$string" != x -o "x$a" = "x$b" ] (the "x's" cancel out).


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=3if [ "$a" -gt 0 ]then  if [ "$a" -lt 5 ]  then echo "The value of "a" lies somewhere between 0 and 5."   fifi# Same result as:if [ "$a" -gt 0 ] && [ "$a" -lt 5 ]then  echo "The value of "a" lies somewhere between 0 and 5." fi

Example 37-4 and Example 17-11 demonstrate nested if/then condition tests.


7.5. Testing Your Knowledge of Tests

The systemwide xinitrc file can be usedto launch the X server. This file contains quite a numberof if/then tests. The followingis excerpted from an "ancient" version ofxinitrc (Red Hat 7.1,or thereabouts).

if [ -f $HOME/.Xclients ]; then  exec $HOME/.Xclientselif [ -f /etc/X11/xinit/Xclients ]; then  exec /etc/X11/xinit/Xclientselse # 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 & fifi

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.


Copyright © 2000, by Mendel Cooper <[email protected]>
(Sebelumnya) 6. Exit and Exit Status8. Operations and Related Topics (Berikutnya)