Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Cooper

Variable Assignment

=

the assignment operator (no space before and after)

Caution

Do not confuse this with = and -eq, which test, rather than assign!

Note that = can be either an assignment or a test operator, depending on context.

Example 4-2. Plain Variable Assignment

#!/bin/bash
 # Naked variables
 
 echo
 
 # When is a variable "naked", i.e., lacking the '$' in front?
 # When it is being assigned, rather than referenced.
 
 # Assignment
 a=879
 echo "The value of \"a\" is $a."
 
 # Assignment using 'let'
 let a=16+5
 echo "The value of \"a\" is now $a."
 
 echo
 
 # In a 'for' loop (really, a type of disguised assignment):
 echo -n "Values of \"a\" in the loop are: "
 for a in 7 8 9 11
 do
   echo -n "$a "
 done
 
 echo
 echo
 
 # In a 'read' statement (also a type of assignment):
 echo -n "Enter \"a\" "
 read a
 echo "The value of \"a\" is now $a."
 
 echo
 
 exit 0

Example 4-3. Variable Assignment, plain and fancy

#!/bin/bash
 
 a=23              # Simple case
 echo $a
 b=$a
 echo $b
 
 # Now, getting a little bit fancier (command substitution).
 
 a=`echo Hello!`   # Assigns result of 'echo' command to 'a'
 echo $a
 #  Note that including an exclamation mark (!) within a
 #+ command substitution construct #+ will not work from the command line,
 #+ since this triggers the Bash "history mechanism."
 #  Inside a script, however, the history functions are disabled.
 
 a=`ls -l`         # Assigns result of 'ls -l' command to 'a'
 echo $a           # Unquoted, however, removes tabs and newlines.
 echo
 echo "$a"         # The quoted variable preserves whitespace.
                   # (See the chapter on "Quoting.")
 
 exit 0

Bash Variables Are Untyped

Unlike many other programming languages, Bash does not segregate its variables by "type". Essentially, Bash variables are character strings, but, depending on context, Bash permits integer operations and comparisons on variables. The determining factor is whether the value of a variable contains only digits.

Example 4-4. Integer or string?

#!/bin/bash
 # int-or-string.sh: Integer or string?
 
 a=2334                   # Integer.
 let "a += 1"
 echo "a = $a "           # a = 2335
 echo                     # Integer, still.
 
 
 b=${a/23/BB}             # Substitute "BB" for "23".
                          # This transforms $b into a string.
 echo "b = $b"            # b = BB35
 declare -i b             # Declaring it an integer doesn't help.
 echo "b = $b"            # b = BB35
 
 let "b += 1"             # BB35 + 1 =
 echo "b = $b"            # b = 1
 echo
 
 c=BB34
 echo "c = $c"            # c = BB34
 d=${c/BB/23}             # Substitute "23" for "BB".
                          # This makes $d an integer.
 echo "d = $d"            # d = 2334
 let "d += 1"             # 2334 + 1 =
 echo "d = $d"            # d = 2335
 echo
 
 # What about null variables?
 e=""
 echo "e = $e"            # e =
 let "e += 1"             # Arithmetic operations allowed on a null variable?
 echo "e = $e"            # e = 1
 echo                     # Null variable transformed into an integer.
 
 # What about undeclared variables?
 echo "f = $f"            # f =
 let "f += 1"             # Arithmetic operations allowed?
 echo "f = $f"            # f = 1
 echo                     # Undeclared variable transformed into an integer.
 
 
 
 # Variables in Bash are essentially untyped.
 
 exit 0

Untyped variables are both a blessing and a curse. They permit more flexibility in scripting (enough rope to hang yourself!) and make it easier to grind out lines of code. However, they permit errors to creep in and encourage sloppy programming habits.

The burden is on the programmer to keep track of what type the script variables are. Bash will not do it for you.

Communications Commands

Certain of the following commands find use in chasing spammers, as well as in network data transfer and analysis.

Information and Statistics

host

Searches for information about an Internet host by name or IP address, using DNS.

bash$ host surfacemail.com
 surfacemail.com. has address 202.92.42.236
 	      

ipcalc

Displays IP information for a host. With the -h option, ipcalc does a reverse DNS lookup, finding the name of the host (server) from the IP address.

bash$ ipcalc -h 202.92.42.236
 HOSTNAME=surfacemail.com
 	      

nslookup

Do an Internet "name server lookup" on a host by IP address. This is essentially equivalent to ipcalc -h or dig -x . The command may be run either interactively or noninteractively, i.e., from within a script.

The nslookup command has allegedly been "deprecated," but it still has its uses.

bash$ nslookup -sil 66.97.104.180
 nslookup kuhleersparnis.ch
  Server:         135.116.137.2
  Address:        135.116.137.2#53
 
  Non-authoritative answer:
  Name:   kuhleersparnis.ch
 	      

dig

Domain Information Groper. Similar to nslookup, dig does an Internet "name server lookup" on a host. May be run either interactively or noninteractively, i.e., from within a script.

Some interesting options to dig are +time=N for setting a query timeout to N seconds, +nofail for continuing to query servers until a reply is received, and -x for doing a reverse address lookup.

Compare the output of dig -x with ipcalc -h and nslookup.

bash$ dig -x 81.9.6.2
 ;; Got answer:
  ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 11649
  ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
 
  ;; QUESTION SECTION:
  ;2.6.9.81.in-addr.arpa.         IN      PTR
 
  ;; AUTHORITY SECTION:
  6.9.81.in-addr.arpa.    3600    IN      SOA     ns.eltel.net. noc.eltel.net.
  2002031705 900 600 86400 3600
 
  ;; Query time: 537 msec
  ;; SERVER: 135.116.137.2#53(135.116.137.2)
  ;; WHEN: Wed Jun 26 08:35:24 2002
  ;; MSG SIZE  rcvd: 91
 	      

Example 12-36. Finding out where to report a spammer

#!/bin/bash
 # spam-lookup.sh: Look up abuse contact to report a spammer.
 # Thanks, Michael Zick.
 
 # Check for command-line arg.
 ARGCOUNT=1
 E_WRONGARGS=65
 if [ $# -ne "$ARGCOUNT" ]
 then
   echo "Usage: `basename $0` domain-name"
   exit $E_WRONGARGS
 fi
 
 
 dig +short $1.contacts.abuse.net -c in -t txt
 # Also try:
 #     dig +nssearch $1
 #     Tries to find "authoritative name servers" and display SOA records.
 
 # The following also works:
 #     whois -h whois.abuse.net $1
 #           ^^ ^^^^^^^^^^^^^^^  Specify host.  
 #     Can even lookup multiple spammers with this, i.e."
 #     whois -h whois.abuse.net $spamdomain1 $spamdomain2 . . .
 
 
 #  Exercise:
 #  --------
 #  Expand the functionality of this script
 #+ so that it automatically e-mails a notification
 #+ to the responsible ISP's contact address(es).
 #  Hint: use the "mail" command.
 
 exit $?
 
 # spam-lookup.sh chinatietong.com
 #                A known spam domain.
 
 # "crnet_mgr@chinatietong.com"
 # "crnet_tec@chinatietong.com"
 # "postmaster@chinatietong.com"
 
 
 #  For a more elaborate version of this script,
 #+ see the SpamViz home page, http://www.spamviz.net/index.html.

Example 12-37. Analyzing a spam domain

#! /bin/bash
 # is-spammer.sh: Identifying spam domains
 
 # $Id: is-spammer, v 1.4 2004/09/01 19:37:52 mszick Exp $
 # Above line is RCS ID info.
 #
 #  This is a simplified version of the "is_spammer.bash
 #+ script in the Contributed Scripts appendix.
 
 # is-spammer <domain.name>
 
 # Uses an external program: 'dig'
 # Tested with version: 9.2.4rc5
 
 # Uses functions.
 # Uses IFS to parse strings by assignment into arrays.
 # And even does something useful: checks e-mail blacklists.
 
 # Use the domain.name(s) from the text body:
 # http://www.good_stuff.spammer.biz/just_ignore_everything_else
 #                       ^^^^^^^^^^^
 # Or the domain.name(s) from any e-mail address:
 # Really_Good_Offer@spammer.biz
 #
 # as the only argument to this script.
 #(PS: have your Inet connection running)
 #
 # So, to invoke this script in the above two instances:
 #       is-spammer.sh spammer.biz
 
 
 # Whitespace == :Space:Tab:Line Feed:Carriage Return:
 WSP_IFS=$'\x20'$'\x09'$'\x0A'$'\x0D'
 
 # No Whitespace == Line Feed:Carriage Return
 No_WSP=$'\x0A'$'\x0D'
 
 # Field separator for dotted decimal ip addresses
 ADR_IFS=${No_WSP}'.'
 
 # Get the dns text resource record.
 # get_txt <error_code> <list_query>
 get_txt() {
 
     # Parse $1 by assignment at the dots.
     local -a dns
     IFS=$ADR_IFS
     dns=( $1 )
     IFS=$WSP_IFS
     if [ "${dns[0]}" == '127' ]
     then
         # See if there is a reason.
         echo $(dig +short $2 -t txt)
     fi
 }
 
 # Get the dns address resource record.
 # chk_adr <rev_dns> <list_server>
 chk_adr() {
     local reply
     local server
     local reason
 
     server=${1}${2}
     reply=$( dig +short ${server} )
 
     # If reply might be an error code . . .
     if [ ${#reply} -gt 6 ]
     then
         reason=$(get_txt ${reply} ${server} )
         reason=${reason:-${reply}}
     fi
     echo ${reason:-' not blacklisted.'}
 }
 
 # Need to get the IP address from the name.
 echo 'Get address of: '$1
 ip_adr=$(dig +short $1)
 dns_reply=${ip_adr:-' no answer '}
 echo ' Found address: '${dns_reply}
 
 # A valid reply is at least 4 digits plus 3 dots.
 if [ ${#ip_adr} -gt 6 ]
 then
     echo
     declare query
 
     # Parse by assignment at the dots.
     declare -a dns
     IFS=$ADR_IFS
     dns=( ${ip_adr} )
     IFS=$WSP_IFS
 
     # Reorder octets into dns query order.
     rev_dns="${dns[3]}"'.'"${dns[2]}"'.'"${dns[1]}"'.'"${dns[0]}"'.'
 
 # See: http://www.spamhaus.org (Conservative, well maintained)
     echo -n 'spamhaus.org says: '
     echo $(chk_adr ${rev_dns} 'sbl-xbl.spamhaus.org')
 
 # See: http://ordb.org (Open mail relays)
     echo -n '   ordb.org  says: '
     echo $(chk_adr ${rev_dns} 'relays.ordb.org')
 
 # See: http://www.spamcop.net/ (You can report spammers here)
     echo -n ' spamcop.net says: '
     echo $(chk_adr ${rev_dns} 'bl.spamcop.net')
 
 # # # other blacklist operations # # #
 
 # See: http://cbl.abuseat.org.
     echo -n ' abuseat.org says: '
     echo $(chk_adr ${rev_dns} 'cbl.abuseat.org')
 
 # See: http://dsbl.org/usage (Various mail relays)
     echo
     echo 'Distributed Server Listings'
     echo -n '       list.dsbl.org says: '
     echo $(chk_adr ${rev_dns} 'list.dsbl.org')
 
     echo -n '   multihop.dsbl.org says: '
     echo $(chk_adr ${rev_dns} 'multihop.dsbl.org')
 
     echo -n 'unconfirmed.dsbl.org says: '
     echo $(chk_adr ${rev_dns} 'unconfirmed.dsbl.org')
 
 else
     echo
     echo 'Could not use that address.'
 fi
 
 exit 0
 
 # Exercises:
 # --------
 
 # 1) Check arguments to script,
 #    and exit with appropriate error message if necessary.
 
 # 2) Check if on-line at invocation of script,
 #    and exit with appropriate error message if necessary.
 
 # 3) Substitute generic variables for "hard-coded" BHL domains.
 
 # 4) Set a time-out for the script using the "+time=" option
      to the 'dig' command.

For a much more elaborate version of the above script, see Example A-27.

traceroute

Trace the route taken by packets sent to a remote host. This command works within a LAN, WAN, or over the Internet. The remote host may be specified by an IP address. The output of this command may be filtered by grep or sed in a pipe.

bash$ traceroute 81.9.6.2
 traceroute to 81.9.6.2 (81.9.6.2), 30 hops max, 38 byte packets
  1  tc43.xjbnnbrb.com (136.30.178.8)  191.303 ms  179.400 ms  179.767 ms
  2  or0.xjbnnbrb.com (136.30.178.1)  179.536 ms  179.534 ms  169.685 ms
  3  192.168.11.101 (192.168.11.101)  189.471 ms  189.556 ms *
  ...
 	      

ping

Broadcast an "ICMP ECHO_REQUEST" packet to another machine, either on a local or remote network. This is a diagnostic tool for testing network connections, and it should be used with caution.

A successful ping returns an exit status of 0. This can be tested for in a script.

bash$ ping localhost
 PING localhost.localdomain (127.0.0.1) from 127.0.0.1 : 56(84) bytes of data.
  64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=0 ttl=255 time=709 usec
  64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=255 time=286 usec
 
  --- localhost.localdomain ping statistics ---
  2 packets transmitted, 2 packets received, 0% packet loss
  round-trip min/avg/max/mdev = 0.286/0.497/0.709/0.212 ms
 	      

whois

Perform a DNS (Domain Name System) lookup. The -h option permits specifying which particular whois server to query. See Example 4-6 and Example 12-36.

finger

Retrieve information about users on a network. Optionally, this command can display a user's ~/.plan, ~/.project, and ~/.forward files, if present.

bash$ finger
 Login  Name           Tty      Idle  Login Time   Office     Office Phone
  bozo   Bozo Bozeman   tty1        8  Jun 25 16:59
  bozo   Bozo Bozeman   ttyp0          Jun 25 16:59
  bozo   Bozo Bozeman   ttyp1          Jun 25 17:07
 
 
 
 bash$ finger bozo
 Login: bozo                             Name: Bozo Bozeman
  Directory: /home/bozo                   Shell: /bin/bash
  Office: 2355 Clown St., 543-1234
  On since Fri Aug 31 20:13 (MST) on tty1    1 hour 38 minutes idle
  On since Fri Aug 31 20:13 (MST) on pts/0   12 seconds idle
  On since Fri Aug 31 20:13 (MST) on pts/1
  On since Fri Aug 31 20:31 (MST) on pts/2   1 hour 16 minutes idle
  No mail.
  No Plan.
 	      

Out of security considerations, many networks disable finger and its associated daemon. [1]

chfn

Change information disclosed by the finger command.

vrfy

Verify an Internet e-mail address.

Remote Host Access

sx, rx

The sx and rx command set serves to transfer files to and from a remote host using the xmodem protocol. These are generally part of a communications package, such as minicom.

sz, rz

The sz and rz command set serves to transfer files to and from a remote host using the zmodem protocol. Zmodem has certain advantages over xmodem, such as faster transmission rate and resumption of interrupted file transfers. Like sx and rx, these are generally part of a communications package.

ftp

Utility and protocol for uploading / downloading files to or from a remote host. An ftp session can be automated in a script (see Example 17-6, Example A-4, and Example A-13).

uucp

UNIX to UNIX copy. This is a communications package for transferring files between UNIX servers. A shell script is an effective way to handle a uucp command sequence.

Since the advent of the Internet and e-mail, uucp seems to have faded into obscurity, but it still exists and remains perfectly workable in situations where an Internet connection is not available or appropriate.

cu

Call Up a remote system and connect as a simple terminal. This command is part of the uucp package. It is a sort of dumbed-down version of telnet.

telnet

Utility and protocol for connecting to a remote host.

Caution

The telnet protocol contains security holes and should therefore probably be avoided.

wget

The wget utility non-interactively retrieves or downloads files from a Web or ftp site. It works well in a script.
wget -p http://www.xyz23.com/file01.html
 wget -r ftp://ftp.xyz24.net/~bozo/project_files/ -O $SAVEFILE

Example 12-38. Getting a stock quote

#!/bin/bash
 # quote-fetch.sh: Download a stock quote.
 
 
 E_NOPARAMS=66
 
 if [ -z "$1" ]  # Must specify a stock (symbol) to fetch.
   then echo "Usage: `basename $0` stock-symbol"
   exit $E_NOPARAMS
 fi
 
 stock_symbol=$1
 
 file_suffix=.html
 # Fetches an HTML file, so name it appropriately.
 URL='http://finance.yahoo.com/q?s='
 # Yahoo finance board, with stock query suffix.
 
 # -----------------------------------------------------------
 wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
 # -----------------------------------------------------------
 
 
 # To look up stuff on http://search.yahoo.com:
 # -----------------------------------------------------------
 # URL="http://search.yahoo.com/search?fr=ush-news&p=${query}"
 # wget -O "$savefilename" "${URL}"
 # -----------------------------------------------------------
 # Saves a list of relevant URLs.
 
 exit $?
 
 # Exercises:
 # ---------
 #
 # 1) Add a test to ensure the user running the script is on-line.
 #    (Hint: parse the output of 'ps -ax' for "ppp" or "connect."
 #
 # 2) Modify this script to fetch the local weather report,
 #+   taking the user's zip code as an argument.

See also Example A-29 and Example A-30.

lynx

The lynx Web and file browser can be used inside a script (with the -dump option) to retrieve a file from a Web or ftp site non-interactively.
lynx -dump http://www.xyz23.com/file01.html >$SAVEFILE

With the -traversal option, lynx starts at the HTTP URL specified as an argument, then "crawls" through all links located on that particular server. Used together with the -crawl option, outputs page text to a log file.

rlogin

Remote login, initates a session on a remote host. This command has security issues, so use ssh instead.

rsh

Remote shell, executes command(s) on a remote host. This has security issues, so use ssh instead.

rcp

Remote copy, copies files between two different networked machines. Using rcp and similar utilities with security implications in a shell script may not be advisable. Consider, instead, using ssh or an expect script.

ssh

Secure shell, logs onto a remote host and executes commands there. This secure replacement for telnet, rlogin, rcp, and rsh uses identity authentication and encryption. See its manpage for details.

Example 12-39. Using ssh

#!/bin/bash
 # remote.bash: Using ssh.
 
 # This example by Michael Zick.
 # Used with permission.
 
 
 #   Presumptions:
 #   ------------
 #   fd-2 isn't being captured ( '2>/dev/null' ).
 #   ssh/sshd presumes stderr ('2') will display to user.
 #
 #   sshd is running on your machine.
 #   For any 'standard' distribution, it probably is,
 #+  and without any funky ssh-keygen having been done.
 
 # Try ssh to your machine from the command line:
 #
 # $ ssh $HOSTNAME
 # Without extra set-up you'll be asked for your password.
 #   enter password
 #   when done,  $ exit
 #
 # Did that work? If so, you're ready for more fun.
 
 # Try ssh to your machine as 'root':
 #
 #   $  ssh -l root $HOSTNAME
 #   When asked for password, enter root's, not yours.
 #          Last login: Tue Aug 10 20:25:49 2004 from localhost.localdomain
 #   Enter 'exit' when done.
 
 #  The above gives you an interactive shell.
 #  It is possible for sshd to be set up in a 'single command' mode,
 #+ but that is beyond the scope of this example.
 #  The only thing to note is that the following will work in
 #+ 'single command' mode.
 
 
 # A basic, write stdout (local) command.
 
 ls -l
 
 # Now the same basic command on a remote machine.
 # Pass a different 'USERNAME' 'HOSTNAME' if desired:
 USER=${USERNAME:-$(whoami)}
 HOST=${HOSTNAME:-$(hostname)}
 
 #  Now excute the above command line on the remote host,
 #+ with all transmissions encrypted.
 
 ssh -l ${USER} ${HOST} " ls -l "
 
 #  The expected result is a listing of your username's home
 #+ directory on the remote machine.
 #  To see any difference, run this script from somewhere
 #+ other than your home directory.
 
 #  In other words, the Bash command is passed as a quoted line
 #+ to the remote shell, which executes it on the remote machine.
 #  In this case, sshd does  ' bash -c "ls -l" '   on your behalf.
 
 #  For information on topics such as not having to enter a
 #+ password/passphrase for every command line, see
 #+    man ssh
 #+    man ssh-keygen
 #+    man sshd_config.
 
 exit 0

Caution

Within a loop, ssh may cause unexpected behavior. According to a Usenet post in the comp.unix shell archives, ssh inherits the loop's stdin. To remedy this, pass ssh either the -n or -f option.

Thanks, Jason Bechtel, for pointing this out.

Local Network

write

This is a utility for terminal-to-terminal communication. It allows sending lines from your terminal (console or xterm) to that of another user. The mesg command may, of course, be used to disable write access to a terminal

Since write is interactive, it would not normally find use in a script.

netconfig

A command-line utility for configuring a network adapter (using DHCP). This command is native to Red Hat centric Linux distros.

Mail

mail

Send or read e-mail messages.

This stripped-down command-line mail client works fine as a command embedded in a script.

Example 12-40. A script that mails itself

#!/bin/sh
 # self-mailer.sh: Self-mailing script
 
 adr=${1:-`whoami`}     # Default to current user, if not specified.
 #  Typing 'self-mailer.sh wiseguy@superdupergenius.com'
 #+ sends this script to that addressee.
 #  Just 'self-mailer.sh' (no argument) sends the script
 #+ to the person invoking it, for example, bozo@localhost.localdomain.
 #
 #  For more on the ${parameter:-default} construct,
 #+ see the "Parameter Substitution" section
 #+ of the "Variables Revisited" chapter.
 
 # ============================================================================
   cat $0 | mail -s "Script \"`basename $0`\" has mailed itself to you." "$adr"
 # ============================================================================
 
 # --------------------------------------------
 #  Greetings from the self-mailing script.
 #  A mischievous person has run this script,
 #+ which has caused it to mail itself to you.
 #  Apparently, some people have nothing better
 #+ to do with their time.
 # --------------------------------------------
 
 echo "At `date`, script \"`basename $0`\" mailed to "$adr"."
 
 exit 0
mailto

Similar to the mail command, mailto sends e-mail messages from the command line or in a script. However, mailto also permits sending MIME (multimedia) messages.

vacation

This utility automatically replies to e-mails that the intended recipient is on vacation and temporarily unavailable. This runs on a network, in conjunction with sendmail, and is not applicable to a dial-up POPmail account.

Notes

[1]

A daemon is a background process not attached to a terminal session. Daemons perform designated services either at specified times or explicitly triggered by certain events.

The word "daemon" means ghost in Greek, and there is certainly something mysterious, almost supernatural, about the way UNIX daemons silently wander about behind the scenes, carrying out their appointed tasks.

Exit and Exit Status

...there are dark corners in the Bourne shell, and people use all of them.

Chet Ramey

The exit command may be used to terminate a script, just as in a C program. It can also return a value, which is available to the script's parent process.

Every command returns an exit status (sometimes referred to as a return status ). A successful command returns a 0, while an unsuccessful one returns a non-zero value that usually may be interpreted as an error code. Well-behaved UNIX commands, programs, and utilities return a 0 exit code upon successful completion, though there are some exceptions.

Likewise, functions within a script and the script itself return an exit status. The last command executed in the function or script determines the exit status. Within a script, an exit nnn command may be used to deliver an nnn exit status to the shell (nnn must be a decimal number in the 0 - 255 range).

Note

When a script ends with an exit that has no parameter, the exit status of the script is the exit status of the last command executed in the script (previous to the exit).

#!/bin/bash
 
 COMMAND_1
 
 . . .
 
 # Will exit with status of last command.
 COMMAND_LAST
 
 exit

The equivalent of a bare exit is exit $? or even just omitting the exit.

#!/bin/bash
 
 COMMAND_1
 
 . . .
 
 # Will exit with status of last command.
 COMMAND_LAST
 
 exit $?

#!/bin/bash
 
 COMMAND1
 
 . . . 
 
 # Will exit with status of last command.
 COMMAND_LAST

$? reads the exit status of the last command executed. After a function returns, $? gives the exit status of the last command executed in the function. This is Bash's way of giving functions a "return value." After a script terminates, a $? from the command line gives the exit status of the script, that is, the last command executed in the script, which is, by convention, 0 on success or an integer in the range 1 - 255 on error.

Example 6-1. exit / exit status

#!/bin/bash
 
 echo hello
 echo $?    # Exit status 0 returned because command executed successfully.
 
 lskdf      # Unrecognized command.
 echo $?    # Non-zero exit status returned because command failed to execute.
 
 echo
 
 exit 113   # Will return 113 to shell.
            # To verify this, type "echo $?" after script terminates.
 
 #  By convention, an 'exit 0' indicates success,
 #+ while a non-zero exit value means an error or anomalous condition.

$? is especially useful for testing the result of a command in a script (see Example 12-32 and Example 12-17).

Note

The !, the logical "not" qualifier, reverses the outcome of a test or command, and this affects its exit status.

Example 6-2. Negating a condition using !

true  # the "true" builtin.
 echo "exit status of \"true\" = $?"     # 0
 
 ! true
 echo "exit status of \"! true\" = $?"   # 1
 # Note that the "!" needs a space.
 #    !true   leads to a "command not found" error
 #
 # The '!' operator prefixing a command invokes the Bash history mechanism.
 
 true
 !true
 # No error this time, but no negation either.
 # It just repeats the previous command (true).
 
 # Thanks, Stephan�Chazelas and Kristopher Newsome.

Caution

Certain exit status codes have reserved meanings and should not be user-specified in a script.

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 bracket special character). It is a synonym for test, and a builtin for efficiency reasons. This command considers its arguments as comparison expressions or file tests and returns an exit status corresponding to 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 also return an exit status of 0 if the arithmetic expressions they evaluate expand to a non-zero value. These arithmetic expansion constructs may therefore be used to perform arithmetic comparisons.
    let "1<2" returns 0 (as "1<2" expands to "1")
     (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")

  • 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."
     fi
     
     word=Linux
     letter_sequence=inu
     if 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"
     fi
     
     
     if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
     then echo "Command succeeded."
     else echo "Command failed."
     fi

  • An if/then construct can contain nested comparisons and tests.
    if echo "Next *if* is part of the comparison for the first *if*."
     
       if [[ $comparison = "integer" ]]
         then (( a < b ))
       else
         [[ $a < $b ]]
       fi
     
     then
       echo '$a is less than $b'
     fi

    This detailed "if-test" explanation courtesy of Stephan�Chazelas.

Example 7-1. What is truth?

#!/bin/bash
 
 #  Tip:
 #  If you're unsure of how a certain condition would evaluate,
 #+ test it in an if-test.
 
 echo
 
 echo "Testing \"0\""
 if [ 0 ]      # zero
 then
   echo "0 is true."
 else
   echo "0 is false."
 fi            # 0 is true.
 
 echo
 
 echo "Testing \"1\""
 if [ 1 ]      # one
 then
   echo "1 is true."
 else
   echo "1 is false."
 fi            # 1 is true.
 
 echo
 
 echo "Testing \"-1\""
 if [ -1 ]     # minus one
 then
   echo "-1 is true."
 else
   echo "-1 is false."
 fi            # -1 is true.
 
 echo
 
 echo "Testing \"NULL\""
 if [ ]        # NULL (empty condition)
 then
   echo "NULL is true."
 else
   echo "NULL is false."
 fi            # NULL is false.
 
 echo
 
 echo "Testing \"xyz\""
 if [ xyz ]    # string
 then
   echo "Random string is true."
 else
   echo "Random string is false."
 fi            # Random string is true.
 
 echo
 
 echo "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.
 
 echo
 
 echo "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.
 
 echo
 
 
 xyz=          # 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.
 
 echo
 
 echo "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"?
 
 echo
 
 exit 0

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

if [ condition-true ]
 then
    command 1
    command 2
    ...
 else
    # Optional (may be left out if not needed).
    # Adds default code block executing if original condition tests false.
    command 3
    command 4
    ...
 fi

Note

When if and then are on same line in a condition test, a semicolon must terminate the if statement. Both if and then are keywords. Keywords (or commands) begin statements, and before a new statement on the same 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
    command3
 elif [ condition2 ]
 # Same as else if
 then
    command4
    command5
 else
    default-command
 fi

The if test condition-true construct is the exact equivalent of if [ condition-true ]. As it happens, the left bracket, [ , is a token which invokes the test command. The closing right bracket, ] , in an if/test should not therefore be strictly necessary, however newer versions of Bash require it.

Note

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 test
 test is a shell builtin
 bash$ type '['
 [ is a shell builtin
 bash$ type '[['
 [[ is a shell keyword
 bash$ type ']]'
 ]] is a shell keyword
 bash$ type ']'
 bash: type: ]: not found
 	      

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

#!/bin/bash
 
 echo
 
 if test -z "$1"
 then
   echo "No command-line arguments."
 else
   echo "First command-line argument is $1."
 fi
 
 echo
 
 if /usr/bin/test -z "$1"      # Same result as "test" builtin".
 then
   echo "No command-line arguments."
 else
   echo "First command-line argument is $1."
 fi
 
 echo
 
 if [ -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."
 fi
 
 echo
 
 if /usr/bin/[ -z "$1"         # Again, functionally identical to above.
 # if /usr/bin/[ -z "$1" ]     # Works, but gives an error message.
 then
   echo "No command-line arguments."
 else
   echo "First command-line argument is $1."
 fi
 
 echo
 
 exit 0

The [[ ]] construct is the more versatile Bash version of [ ]. This is the extended test command, adopted from ksh88.

Note

No filename expansion or word splitting takes place between [[ and ]], but there is parameter expansion and command substitution.

file=/etc/passwd
 
 if [[ -e $file ]]
 then
   echo "Password file exists."
 fi

Tip

Using the [[ ... ]] test construct, rather than [ ... ] can prevent many logic errors in scripts. For example, the &&, ||, <, and > operators work within a [[ ]] test, despite giving an error within a [ ] construct.

Note

Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] ) are strictly necessary.
dir=/home/bozo
 
 if 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=20
 var2=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 arithmetic expression. If the expression evaluates as zero, it returns an exit status of 1, or "false". A non-zero expression returns an exit status of 0, or "true". This is in marked contrast to using the test and [ ] constructs previously discussed.

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

#!/bin/bash
 # 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 ))                                      # true
 echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0
 
 (( 5 > 9 ))                                      # false
 echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1
 
 (( 5 - 5 ))                                      # 0
 echo "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.
 
 exit 0

File test operators

Returns true if...

-e

file exists

-a

file exists

This is identical in effect to -e. It has been "deprecated," 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 (floppy, cdrom, etc.)

-c

file is a character device (keyboard, modem, sound card, etc.)

-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. [1] 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. [2] 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, he can only delete files in it that he owns. This keeps users from inadvertently overwriting or deleting each other's files in a publicly accessible directory, such as /tmp.

-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 <ligelowbee@yahoo.com>
 # Used 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. 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 directorys to search 
 #to current directory.  Otherwise set the directorys to search 
 #to the agrs 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 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 0

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

Notes

[1]

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

[2]

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

Other Comparison Operators

A binary comparison operator compares two variables or quantities. Note the separation between integer and string comparison.

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, Stephan�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 26-11 for an application of this comparison operator.

-z

string is "null", that is, has zero length

-n

string is not "null".

Caution

The -n test absolutely 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. [1]

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
 
 
 # Lets 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 ("$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 naked.
 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 naked.
 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'
 
 NOARGS=65
 NOTFOUND=66
 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 $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 $NOTFOUND
 fi  
 
 if [ ${filename##*.} != "gz" ]
 # Using bracket in variable substitution.
 then
   echo "File $1 is not a gzipped file!"
   exit $NOTGZIP
 fi  
 
 zcat $1 | more
 
 # Uses the filter 'more.'
 # 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 are 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 [ "$exp1" -a "$exp2" ]

Refer to Example 8-3, Example 26-16, and Example A-28 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).

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" ]
 # 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.) 
 then
    command
 fi

arithmetic operators

+

plus

-

minus

*

multiplication

/

division

**

exponentiation
# Bash, version 2.02, introduced the "**" exponentiation operator.
 
 let "z=5**3"
 echo "z = $z"   # z = 125

%

modulo, or mod (returns the remainder of an integer division operation)

bash$ echo `expr 5 % 3`
 2
 	      

This operator finds use in, among other things, generating numbers within a specific range (see Example 9-24 and Example 9-27) and formatting program output (see Example 26-15 and Example A-6). It can even be used to generate prime numbers, (see Example A-16). Modulo turns up surprisingly often in various 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=65
 
 if [ $# -ne "$ARGS" ]
 then
   echo "Usage: `basename $0` first-number second-number"
   exit $E_BADARGS
 fi
 # ------------------------------------------------------
 
 
 gcd ()
 {
 
                                  #  Arbitrary assignment.
   dividend=$1                    #  It does not matter
   divisor=$2                     #+ which of the two is larger.
                                  #  Why?
 
   remainder=1                    #  If uninitialized variable used in loop,
                                  #+ it results in an error message
 				 #+ on first pass through loop.
 
   until [ "$remainder" -eq 0 ]
   do
     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
 
 
 # Exercise :
 # --------
 #  Check command-line arguments to make sure they are integers,
 #+ and exit the script with an appropriate error message if not.
 
 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 Bash are actually signed long (32-bit) integers, in the range of -2147483648 to 2147483647. An operation that takes a variable outside these limits will give an erroneous result.
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)

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 run fast enough to permit its use on the fly.

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 negate

!

bitwise NOT

^

bitwise XOR

^=

"bitwise XOR-equal"

logical operators

&&

and (logical)

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 within [ ... ] construct.

Note

&& may also, depending on context, be used in an and list to concatenate commands.

||

or (logical)

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 within [ ... ] 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), but only the last operation is returned.

let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
 echo "t1 = $t1"               # t1 = 11
 
 let "t2 = ((a = 9, 15 / 3))"  # Set "a" and calculate "t2".
 echo "t2 = $t2    a = $a"     # t2 = 5    a = 9

Numerical Constants

A shell script interprets a number as decimal (base 10), unless that number has a special prefix or notation. A number preceded by a 0 is octal (base 8). A number preceded by 0x is hexadecimal (base 16). A number with an embedded # evaluates as BASE#NUMBER (with range and notational restrictions).

Example 8-4. Representation of numerical constants

#!/bin/bash
 # numbers.sh: Representation of numbers in different bases.
 
 # Decimal: the default
 let "dec = 32"
 echo "decimal number = $dec"             # 32
 # Nothing out of the ordinary here.
 
 
 # Octal: numbers preceded by '0' (zero)
 let "oct = 032"
 echo "octal number = $oct"               # 26
 # Expresses result in decimal.
 # --------- ------ -- -------
 
 # Hexadecimal: numbers preceded by '0x' or '0X'
 let "hex = 0x32"
 echo "hexadecimal number = $hex"         # 50
 # Expresses result in decimal.
 
 # Other bases: BASE#NUMBER
 # BASE between 2 and 64.
 # NUMBER must use symbols within the BASE range, see below.
 
 
 let "bin = 2#111100111001101"
 echo "binary number = $bin"              # 31181
 
 let "b32 = 32#77"
 echo "base-32 number = $b32"             # 231
 
 let "b64 = 64#@_"
 echo "base-64 number = $b64"             # 4031
 # This notation only works for a limited range (2 - 64) of ASCII characters.
 # 10 digits + 26 lowercase characters + 26 uppercase characters + @ + _
 
 
 echo
 
 echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
                                          # 1295 170 44822 3375
 
 
 #  Important note:
 #  --------------
 #  Using a digit out of range of the specified base notation
 #+ gives an error message.
 
 let "bad_oct = 081"
 # (Partial) error message output:
 #  bad_oct = 081: value too great for base (error token is "081")
 #              Octal numbers use only digits in the range 0 - 7.
 
 exit 0       # Thanks, Rich Bartell and Stephane Chazelas, for clarification.

Internal Variables

Builtin variables

variables affecting bash script behavior

$BASH

the path to the Bash binary itself
bash$ echo $BASH
 /bin/bash

$BASH_ENV

an environmental variable pointing to a Bash startup file to be read when a script is invoked

$BASH_SUBSHELL

a variable indicating the subshell level. This is a new addition to Bash, version 3.

See Example 20-1 for usage.

$BASH_VERSINFO[n]

a 6-element array containing version information about the installed release of Bash. This is similar to $BASH_VERSION, below, but a bit more detailed.

# Bash version info:
 
 for n in 0 1 2 3 4 5
 do
   echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
 done  
 
 # BASH_VERSINFO[0] = 3                      # Major version no.
 # BASH_VERSINFO[1] = 00                     # Minor version no.
 # BASH_VERSINFO[2] = 14                     # Patch level.
 # BASH_VERSINFO[3] = 1                      # Build version.
 # BASH_VERSINFO[4] = release                # Release status.
 # BASH_VERSINFO[5] = i386-redhat-linux-gnu  # Architecture
                                             # (same as $MACHTYPE).

$BASH_VERSION

the version of Bash installed on the system

bash$ echo $BASH_VERSION
 3.00.14(1)-release
 	      

tcsh% echo $BASH_VERSION
 BASH_VERSION: Undefined variable.
 	      

Checking $BASH_VERSION is a good method of determining which shell is running. $SHELL does not necessarily give the correct answer.

$DIRSTACK

the top value in the directory stack (affected by pushd and popd)

This builtin variable corresponds to the dirs command, however dirs shows the entire contents of the directory stack.

$EDITOR

the default editor invoked by a script, usually vi or emacs.

$EUID

"effective" user ID number

Identification number of whatever identity the current user has assumed, perhaps by means of su.

Caution

The $EUID is not necessarily the same as the $UID.

$FUNCNAME

name of the current function

xyz23 ()
 {
   echo "$FUNCNAME now executing."  # xyz23 now executing.
 }
 
 xyz23
 
 echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
                                    # Null value outside a function.

$GLOBIGNORE

A list of filename patterns to be excluded from matching in globbing.

$GROUPS

groups current user belongs to

This is a listing (array) of the group id numbers for current user, as recorded in /etc/passwd.

root# echo $GROUPS
 0
 
 
 root# echo ${GROUPS[1]}
 1
 
 
 root# echo ${GROUPS[5]}
 6
 	      

$HOME

home directory of the user, usually /home/username (see Example 9-14)

$HOSTNAME

The hostname command assigns the system name at bootup in an init script. However, the gethostname() function sets the Bash internal variable $HOSTNAME. See also Example 9-14.

$HOSTTYPE

host type

Like $MACHTYPE, identifies the system hardware.

bash$ echo $HOSTTYPE
 i686
$IFS

internal field separator

This variable determines how Bash recognizes fields, or word boundaries when it interprets character strings.

$IFS defaults to whitespace (space, tab, and newline), but may be changed, for example, to parse a comma-separated data file. Note that $* uses the first character held in $IFS. See Example 5-1.

bash$ echo $IFS | cat -vte
 $
 
 
 bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
 w:x:y:z
 	      

Caution

$IFS does not handle whitespace the same as it does other characters.

Example 9-1. $IFS and whitespace

#!/bin/bash
 # $IFS treats whitespace differently than other characters.
 
 output_args_one_per_line()
 {
   for arg
   do echo "[$arg]"
   done
 }
 
 echo; echo "IFS=\" \""
 echo "-------"
 
 IFS=" "
 var=" a  b c   "
 output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
 #
 # [a]
 # [b]
 # [c]
 
 
 echo; echo "IFS=:"
 echo "-----"
 
 IFS=:
 var=":a::b:c:::"               # Same as above, but substitute ":" for " ".
 output_args_one_per_line $var
 #
 # []
 # [a]
 # []
 # [b]
 # [c]
 # []
 # []
 # []
 
 # The same thing happens with the "FS" field separator in awk.
 
 # Thank you, Stephane Chazelas.
 
 echo
 
 exit 0

(Thanks, S. C., for clarification and examples.)

See also Example 12-37 for an instructive example of using $IFS.

$IGNOREEOF

ignore EOF: how many end-of-files (control-D) the shell will ignore before logging out.

$LC_COLLATE

Often set in the .bashrc or /etc/profile files, this variable controls collation order in filename expansion and pattern matching. If mishandled, LC_COLLATE can cause unexpected results in filename globbing.

Note

As of version 2.05 of Bash, filename globbing no longer distinguishes between lowercase and uppercase letters in a character range between brackets. For example, ls [A-M]* would match both File1.txt and file1.txt. To revert to the customary behavior of bracket matching, set LC_COLLATE to C by an export LC_COLLATE=C in /etc/profile and/or ~/.bashrc.

$LC_CTYPE

This internal variable controls character interpretation in globbing and pattern matching.

$LINENO

This variable is the line number of the shell script in which this variable appears. It has significance only within the script in which it appears, and is chiefly useful for debugging purposes.

# *** BEGIN DEBUG BLOCK ***
 last_cmd_arg=$_  # Save it.
 
 echo "At line number $LINENO, variable \"v1\" = $v1"
 echo "Last command argument processed = $last_cmd_arg"
 # *** END DEBUG BLOCK ***

$MACHTYPE

machine type

Identifies the system hardware.

bash$ echo $MACHTYPE
 i686
$OLDPWD

old working directory ("OLD-print-working-directory", previous directory you were in)

$OSTYPE

operating system type

bash$ echo $OSTYPE
 linux
$PATH

path to binaries, usually /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, etc.

When given a command, the shell automatically does a hash table search on the directories listed in the path for the executable. The path is stored in the environmental variable, $PATH, a list of directories, separated by colons. Normally, the system stores the $PATH definition in /etc/profile and/or ~/.bashrc (see Appendix G).

bash$ echo $PATH
 /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

PATH=${PATH}:/opt/bin appends the /opt/bin directory to the current path. In a script, it may be expedient to temporarily add a directory to the path in this way. When the script exits, this restores the original $PATH (a child process, such as a script, may not change the environment of the parent process, the shell).

Note

The current "working directory", ./, is usually omitted from the $PATH as a security measure.

$PIPESTATUS

Array variable holding exit status(es) of last executed foreground pipe. Interestingly enough, this does not necessarily give the same result as the exit status of the last executed command.

bash$ echo $PIPESTATUS
 0
 
 bash$ ls -al | bogus_command
 bash: bogus_command: command not found
 bash$ echo $PIPESTATUS
 141
 
 bash$ ls -al | bogus_command
 bash: bogus_command: command not found
 bash$ echo $?
 127
 	      

The members of the $PIPESTATUS array hold the exit status of each respective command executed in a pipe. $PIPESTATUS[0] holds the exit status of the first command in the pipe, $PIPESTATUS[1] the exit status of the second command, and so on.

Caution

The $PIPESTATUS variable may contain an erroneous 0 value in a login shell (in releases prior to 3.0 of Bash).

tcsh% bash
 
 bash$ who | grep nobody | sort
 bash$ echo ${PIPESTATUS[*]}
 0
 	      

The above lines contained in a script would produce the expected 0 1 0 output.

Thank you, Wayne Pollock for pointing this out and supplying the above example.

Caution

In the version 2.05b.0(1)-release of Bash, and possibly earlier versions as well, the $PIPESTATUS variable appears to be broken. This is a bug that has been (mostly) fixed in Bash, version 3.0 and later.

bash$ echo $BASH_VERSION
 2.05b.0(1)-release
 
 bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
  0       0       0
 
 bash$ echo ${PIPESTATUS[@]}
 141 127 0
 
 
 
 
 
 bash$ echo $BASH_VERSION
 3.00.0(1)-release
 
 bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
  0       0       0
 
 bash$ echo ${PIPESTATUS[@]}
 0 127 0
 	      

Note

$PIPESTATUS is a "volatile" variable. It needs to be captured immediately after the pipe in question, before any other command intervenes.

bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
  0       0       0
 
 bash$ echo ${PIPESTATUS[@]}
 0 127 0
 
 bash$ echo ${PIPESTATUS[@]}
 0
 	      

$PPID

The $PPID of a process is the process ID (pid) of its parent process. [1]

Compare this with the pidof command.

$PROMPT_COMMAND

A variable holding a command to be executed just before the primary prompt, $PS1 is to be displayed.

$PS1

This is the main prompt, seen at the command line.

$PS2

The secondary prompt, seen when additional input is expected. It displays as ">".

$PS3

The tertiary prompt, displayed in a select loop (see Example 10-29).

$PS4

The quartenary prompt, shown at the beginning of each line of output when invoking a script with the -x option. It displays as "+".

$PWD

working directory (directory you are in at the time)

This is the analog to the pwd builtin command.

#!/bin/bash
 
 E_WRONG_DIRECTORY=73
 
 clear # Clear screen.
 
 TargetDirectory=/home/bozo/projects/GreatAmericanNovel
 
 cd $TargetDirectory
 echo "Deleting stale files in $TargetDirectory."
 
 if [ "$PWD" != "$TargetDirectory" ]
 then    # Keep from wiping out wrong directory by accident.
   echo "Wrong directory!"
   echo "In $PWD, rather than $TargetDirectory!"
   echo "Bailing out!"
   exit $E_WRONG_DIRECTORY
 fi  
 
 rm -rf *
 rm .[A-Za-z0-9]*    # Delete dotfiles.
 # rm -f .[^.]* ..?*   to remove filenames beginning with multiple dots.
 # (shopt -s dotglob; rm -f *)   will also work.
 # Thanks, S.C. for pointing this out.
 
 # Filenames may contain all characters in the 0 - 255 range, except "/".
 # Deleting files beginning with weird characters is left as an exercise.
 
 # Various other operations here, as necessary.
 
 echo
 echo "Done."
 echo "Old files deleted in $TargetDirectory."
 echo
 
 
 exit 0

$REPLY

The default value when a variable is not supplied to read. Also applicable to select menus, but only supplies the item number of the variable chosen, not the value of the variable itself.

#!/bin/bash
 # reply.sh
 
 # REPLY is the default value for a 'read' command.
 
 echo
 echo -n "What is your favorite vegetable? "
 read
 
 echo "Your favorite vegetable is $REPLY."
 #  REPLY holds the value of last "read" if and only if
 #+ no variable supplied.
 
 echo
 echo -n "What is your favorite fruit? "
 read fruit
 echo "Your favorite fruit is $fruit."
 echo "but..."
 echo "Value of \$REPLY is still $REPLY."
 #  $REPLY is still set to its previous value because
 #+ the variable $fruit absorbed the new "read" value.
 
 echo
 
 exit 0

$SECONDS

The number of seconds the script has been running.

#!/bin/bash
 
 TIME_LIMIT=10
 INTERVAL=1
 
 echo
 echo "Hit Control-C to exit before $TIME_LIMIT seconds."
 echo
 
 while [ "$SECONDS" -le "$TIME_LIMIT" ]
 do
   if [ "$SECONDS" -eq 1 ]
   then
     units=second
   else  
     units=seconds
   fi
 
   echo "This script has been running $SECONDS $units."
   #  On a slow or overburdened machine, the script may skip a count
   #+ every once in a while.
   sleep $INTERVAL
 done
 
 echo -e "\a"  # Beep!
 
 exit 0

$SHELLOPTS

the list of enabled shell options, a readonly variable
bash$ echo $SHELLOPTS
 braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
 	      

$SHLVL

Shell level, how deeply Bash is nested. If, at the command line, $SHLVL is 1, then in a script it will increment to 2.

$TMOUT

If the $TMOUT environmental variable is set to a non-zero value time, then the shell prompt will time out after time seconds. This will cause a logout.

As of version 2.05b of Bash, it is now possible to use $TMOUT in a script in combination with read.

# Works in scripts for Bash, versions 2.05b and later.
 
 TMOUT=3    # Prompt times out at three seconds.
 
 echo "What is your favorite song?"
 echo "Quickly now, you only have $TMOUT seconds to answer!"
 read song
 
 if [ -z "$song" ]
 then
   song="(no answer)"
   # Default response.
 fi
 
 echo "Your favorite song is $song."

There are other, more complex, ways of implementing timed input in a script. One alternative is to set up a timing loop to signal the script when it times out. This also requires a signal handling routine to trap (see Example 29-5) the interrupt generated by the timing loop (whew!).

Example 9-2. Timed Input

#!/bin/bash
 # timed-input.sh
 
 # TMOUT=3    Also works, as of newer versions of Bash.
 
 
 TIMELIMIT=3  # Three seconds in this instance. May be set to different value.
 
 PrintAnswer()
 {
   if [ "$answer" = TIMEOUT ]
   then
     echo $answer
   else       # Don't want to mix up the two instances. 
     echo "Your favorite veggie is $answer"
     kill $!  # Kills no longer needed TimerOn function running in background.
              # $! is PID of last job running in background.
   fi
 
 }  
 
 
 
 TimerOn()
 {
   sleep $TIMELIMIT && kill -s 14 $$ &
   # Waits 3 seconds, then sends sigalarm to script.
 }  
 
 Int14Vector()
 {
   answer="TIMEOUT"
   PrintAnswer
   exit 14
 }  
 
 trap Int14Vector 14   # Timer interrupt (14) subverted for our purposes.
 
 echo "What is your favorite vegetable "
 TimerOn
 read answer
 PrintAnswer
 
 
 #  Admittedly, this is a kludgy implementation of timed input,
 #+ however the "-t" option to "read" simplifies this task.
 #  See "t-out.sh", below.
 
 #  If you need something really elegant...
 #+ consider writing the application in C or C++,
 #+ using appropriate library functions, such as 'alarm' and 'setitimer'.
 
 exit 0

An alternative is using stty.

Example 9-3. Once more, timed input

#!/bin/bash
 # timeout.sh
 
 #  Written by Stephane Chazelas,
 #+ and modified by the document author.
 
 INTERVAL=5                # timeout interval
 
 timedout_read() {
   timeout=$1
   varname=$2
   old_tty_settings=`stty -g`
   stty -icanon min 0 time ${timeout}0
   eval read $varname      # or just  read $varname
   stty "$old_tty_settings"
   # See man page for "stty".
 }
 
 echo; echo -n "What's your name? Quick! "
 timedout_read $INTERVAL your_name
 
 #  This may not work on every terminal type.
 #  The maximum timeout depends on the terminal.
 #+ (it is often 25.5 seconds).
 
 echo
 
 if [ ! -z "$your_name" ]  # If name input before timeout...
 then
   echo "Your name is $your_name."
 else
   echo "Timed out."
 fi
 
 echo
 
 # The behavior of this script differs somewhat from "timed-input.sh".
 # At each keystroke, the counter resets.
 
 exit 0

Perhaps the simplest method is using the -t option to read.

Example 9-4. Timed read

#!/bin/bash
 # t-out.sh
 # Inspired by a suggestion from "syngin seven" (thanks).
 
 
 TIMELIMIT=4         # 4 seconds
 
 read -t $TIMELIMIT variable <&1
 
 echo
 
 if [ -z "$variable" ]
 then
   echo "Timed out, variable still unset."
 else  
   echo "variable = $variable"
 fi  
 
 exit 0
 
 # Exercise for the reader:
 # -----------------------
 # Why is the redirection (<&1) necessary in line 8?
 # What happens if it is omitted?
$UID

user ID number

current user's user identification number, as recorded in /etc/passwd

This is the current user's real id, even if she has temporarily assumed another identity through su. $UID is a readonly variable, not subject to change from the command line or within a script, and is the counterpart to the id builtin.

Example 9-5. Am I root?

#!/bin/bash
 # am-i-root.sh:   Am I root or not?
 
 ROOT_UID=0   # Root has $UID 0.
 
 if [ "$UID" -eq "$ROOT_UID" ]  # Will the real "root" please stand up?
 then
   echo "You are root."
 else
   echo "You are just an ordinary user (but mom loves you just the same)."
 fi
 
 exit 0
 
 
 # ============================================================= #
 # Code below will not execute, because the script already exited.
 
 # An alternate method of getting to the root of matters:
 
 ROOTUSER_NAME=root
 
 username=`id -nu`              # Or...   username=`whoami`
 if [ "$username" = "$ROOTUSER_NAME" ]
 then
   echo "Rooty, toot, toot. You are root."
 else
   echo "You are just a regular fella."
 fi

See also Example 2-3.

Note

The variables $ENV, $LOGNAME, $MAIL, $TERM, $USER, and $USERNAME are not Bash builtins. These are, however, often set as environmental variables in one of the Bash startup files. $SHELL, the name of the user's login shell, may be set from /etc/passwd or in an "init" script, and it is likewise not a Bash builtin.

tcsh% echo $LOGNAME
 bozo
 tcsh% echo $SHELL
 /bin/tcsh
 tcsh% echo $TERM
 rxvt
 
 bash$ echo $LOGNAME
 bozo
 bash$ echo $SHELL
 /bin/tcsh
 bash$ echo $TERM
 rxvt
 	      

Positional Parameters

$0, $1, $2, etc.

positional parameters, passed from command line to script, passed to a function, or set to a variable (see Example 4-5 and Example 11-15)

$#

number of command line arguments [2] or positional parameters (see Example 33-2)

$*

All of the positional parameters, seen as a single word

Note

"$*" must be quoted.

$@

Same as $*, but each parameter is a quoted string, that is, the parameters are passed on intact, without interpretation or expansion. This means, among other things, that each parameter in the argument list is seen as a separate word.

Note

Of course, "$@" should be quoted.

Example 9-6. arglist: Listing arguments with $* and $@

#!/bin/bash
 # arglist.sh
 # Invoke this script with several arguments, such as "one two three".
 
 E_BADARGS=65
 
 if [ ! -n "$1" ]
 then
   echo "Usage: `basename $0` argument1 argument2 etc."
   exit $E_BADARGS
 fi  
 
 echo
 
 index=1          # Initialize count.
 
 echo "Listing args with \"\$*\":"
 for arg in "$*"  # Doesn't work properly if "$*" isn't quoted.
 do
   echo "Arg #$index = $arg"
   let "index+=1"
 done             # $* sees all arguments as single word. 
 echo "Entire arg list seen as single word."
 
 echo
 
 index=1          # Reset count.
                  # What happens if you forget to do this?
 
 echo "Listing args with \"\$@\":"
 for arg in "$@"
 do
   echo "Arg #$index = $arg"
   let "index+=1"
 done             # $@ sees arguments as separate words. 
 echo "Arg list seen as separate words."
 
 echo
 
 index=1          # Reset count.
 
 echo "Listing args with \$* (unquoted):"
 for arg in $*
 do
   echo "Arg #$index = $arg"
   let "index+=1"
 done             # Unquoted $* sees arguments as separate words. 
 echo "Arg list seen as separate words."
 
 exit 0

Following a shift, the $@ holds the remaining command-line parameters, lacking the previous $1, which was lost.
#!/bin/bash
 # Invoke with ./scriptname 1 2 3 4 5
 
 echo "$@"    # 1 2 3 4 5
 shift
 echo "$@"    # 2 3 4 5
 shift
 echo "$@"    # 3 4 5
 
 # Each "shift" loses parameter $1.
 # "$@" then contains the remaining parameters.

The $@ special parameter finds use as a tool for filtering input into shell scripts. The cat "$@" construction accepts input to a script either from stdin or from files given as parameters to the script. See Example 12-21 and Example 12-22.

Caution

The $* and $@ parameters sometimes display inconsistent and puzzling behavior, depending on the setting of $IFS.

Example 9-7. Inconsistent $* and $@ behavior

#!/bin/bash
 
 #  Erratic behavior of the "$*" and "$@" internal Bash variables,
 #+ depending on whether they are quoted or not.
 #  Inconsistent handling of word splitting and linefeeds.
 
 
 set -- "First one" "second" "third:one" "" "Fifth: :one"
 # Setting the script arguments, $1, $2, etc.
 
 echo
 
 echo 'IFS unchanged, using "$*"'
 c=0
 for i in "$*"               # quoted
 do echo "$((c+=1)): [$i]"   # This line remains the same in every instance.
                             # Echo args.
 done
 echo ---
 
 echo 'IFS unchanged, using $*'
 c=0
 for i in $*                 # unquoted
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS unchanged, using "$@"'
 c=0
 for i in "$@"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS unchanged, using $@'
 c=0
 for i in $@
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 IFS=:
 echo 'IFS=":", using "$*"'
 c=0
 for i in "$*"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using $*'
 c=0
 for i in $*
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 var=$*
 echo 'IFS=":", using "$var" (var=$*)'
 c=0
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using $var (var=$*)'
 c=0
 for i in $var
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 var="$*"
 echo 'IFS=":", using $var (var="$*")'
 c=0
 for i in $var
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using "$var" (var="$*")'
 c=0
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using "$@"'
 c=0
 for i in "$@"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using $@'
 c=0
 for i in $@
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 var=$@
 echo 'IFS=":", using $var (var=$@)'
 c=0
 for i in $var
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using "$var" (var=$@)'
 c=0
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 var="$@"
 echo 'IFS=":", using "$var" (var="$@")'
 c=0
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 done
 echo ---
 
 echo 'IFS=":", using $var (var="$@")'
 c=0
 for i in $var
 do echo "$((c+=1)): [$i]"
 done
 
 echo
 
 # Try this script with ksh or zsh -y.
 
 exit 0
 
 # This example script by Stephane Chazelas,
 # and slightly modified by the document author.

Note

The $@ and $* parameters differ only when between double quotes.

Example 9-8. $* and $@ when $IFS is empty

#!/bin/bash
 
 #  If $IFS set, but empty,
 #+ then "$*" and "$@" do not echo positional params as expected.
 
 mecho ()       # Echo positional parameters.
 {
 echo "$1,$2,$3";
 }
 
 
 IFS=""         # Set, but empty.
 set a b c      # Positional parameters.
 
 mecho "$*"     # abc,,
 mecho $*       # a,b,c
 
 mecho $@       # a,b,c
 mecho "$@"     # a,b,c
 
 #  The behavior of $* and $@ when $IFS is empty depends
 #+ on whatever Bash or sh version being run.
 #  It is therefore inadvisable to depend on this "feature" in a script.
 
 
 # Thanks, Stephane Chazelas.
 
 exit 0

Other Special Parameters

$-

Flags passed to script (using set). See Example 11-15.

Caution

This was originally a ksh construct adopted into Bash, and unfortunately it does not seem to work reliably in Bash scripts. One possible use for it is to have a script self-test whether it is interactive.

$!

PID (process ID) of last job run in background

LOG=$0.log
 
 COMMAND1="sleep 100"
 
 echo "Logging PIDs background commands for script: $0" >> "$LOG"
 # So they can be monitored, and killed as necessary.
 echo >> "$LOG"
 
 # Logging commands.
 
 echo -n "PID of \"$COMMAND1\":  " >> "$LOG"
 ${COMMAND1} &
 echo $! >> "$LOG"
 # PID of "sleep 100":  1506
 
 # Thank you, Jacques Lederer, for suggesting this.

possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
 # Forces completion of an ill-behaved program.
 # Useful, for example, in init scripts.
 
 # Thank you, Sylvain Fourmanoit, for this creative use of the "!" variable.

$_

Special variable set to last argument of previous command executed.

Example 9-9. Underscore variable

#!/bin/bash
 
 echo $_              # /bin/bash
                      # Just called /bin/bash to run the script.
 
 du >/dev/null        # So no output from command.
 echo $_              # du
 
 ls -al >/dev/null    # So no output from command.
 echo $_              # -al  (last argument)
 
 :
 echo $_              # :
$?

Exit status of a command, function, or the script itself (see Example 23-7)

$$

Process ID of the script itself. The $$ variable often finds use in scripts to construct "unique" temp file names (see Example A-13, Example 29-6, Example 12-28, and Example 11-25). This is usually simpler than invoking mktemp.

Notes

[1]

The PID of the currently running script is $$, of course.

[2]

The words "argument" and "parameter" are often used interchangeably. In the context of this document, they have the same precise meaning, that of a variable passed to a script or function.

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье