Search     or:     and:
  Краткое описание
 W. R. Стивенс TCP 
 W. R. Стивенс IPC 
 K. Bauer 
 Gary V. Vaughan 
 Д Вилер 
 В. Сталлинг 
 Pramode C.E. 
 Steve Pate 
 William Gropp 
 С Бекман 
 Р Стивенс 
 Mendel Cooper 
 М Перри 
 C.S. Rodriguez 
 Robert Love 
 Daniel Bovet 
 Д Джеф 
 G. Kroah-Hartman 
 B. Hansen 
Последние статьи :
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
  SQL 30.07   
  JFS 10.06   
  B-trees 01.06   
TOP 20
 Go Web ...577 
 Steve Pate 3...464 
 Rodriguez 6...433 
 TCP 3...390 
 Rodriguez 2...388 
 Rubni-Corbet -> Глав...371 
 Steve Pate 1...363 
 Daniel Bovet 3...356 
 Стивенс 9...353 
 Максвелл 1...351 
 Максвелл 1...351 
 B.Blunden 1...351 
 Robert Love 3...350 
 Mod_perl 2...350 
 Daniel Bovet 4...345 
 UML 3...345 
 Robert Love 2...344 
 Стивенс 6...342 
 Robbins 1...340 
  01.04.2017 : 2166403 посещений


Variable Assignment


the assignment operator (no space before and after)


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

 # Naked variables
 # When is a variable "naked", i.e., lacking the '$' in front?
 # When it is being assigned, rather than referenced.
 # Assignment
 echo "The value of \"a\" is $a."
 # Assignment using 'let'
 let a=16+5
 echo "The value of \"a\" is now $a."
 # 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
   echo -n "$a "
 # In a 'read' statement (also a type of assignment):
 echo -n "Enter \"a\" "
 read a
 echo "The value of \"a\" is now $a."
 exit 0

Example 4-3. Variable Assignment, plain and fancy

 a=23              # Simple case
 echo $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 "$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?

 # 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 = $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
 # What about null variables?
 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


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

bash$ host has address


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


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
  Non-authoritative answer:


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
 ;; Got answer:
  ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 11649
  ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
  ;         IN      PTR
  ;; AUTHORITY SECTION:    3600    IN      SOA
  2002031705 900 600 86400 3600
  ;; Query time: 537 msec
  ;; SERVER:
  ;; WHEN: Wed Jun 26 08:35:24 2002
  ;; MSG SIZE  rcvd: 91

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

 # Look up abuse contact to report a spammer.
 # Thanks, Michael Zick.
 # Check for command-line arg.
 if [ $# -ne "$ARGCOUNT" ]
   echo "Usage: `basename $0` domain-name"
   exit $E_WRONGARGS
 dig +short $ -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 $1
 #           ^^ ^^^^^^^^^^^^^^^  Specify host.  
 #     Can even lookup multiple spammers with this, i.e."
 #     whois -h $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 $?
 #                A known spam domain.
 # ""
 # ""
 # ""
 #  For a more elaborate version of this script,
 #+ see the SpamViz home page,

Example 12-37. Analyzing a spam domain

#! /bin/bash
 # 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 <>
 # 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 from the text body:
 #                       ^^^^^^^^^^^
 # Or the from any e-mail address:
 # as the only argument to this script.
 #(PS: have your Inet connection running)
 # So, to invoke this script in the above two instances:
 # Whitespace == :Space:Tab:Line Feed:Carriage Return:
 # No Whitespace == Line Feed:Carriage Return
 # Field separator for dotted decimal ip addresses
 # Get the dns text resource record.
 # get_txt <error_code> <list_query>
 get_txt() {
     # Parse $1 by assignment at the dots.
     local -a dns
     dns=( $1 )
     if [ "${dns[0]}" == '127' ]
         # See if there is a reason.
         echo $(dig +short $2 -t txt)
 # Get the dns address resource record.
 # chk_adr <rev_dns> <list_server>
 chk_adr() {
     local reply
     local server
     local reason
     reply=$( dig +short ${server} )
     # If reply might be an error code . . .
     if [ ${#reply} -gt 6 ]
         reason=$(get_txt ${reply} ${server} )
     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 ]
     declare query
     # Parse by assignment at the dots.
     declare -a dns
     dns=( ${ip_adr} )
     # Reorder octets into dns query order.
 # See: (Conservative, well maintained)
     echo -n ' says: '
     echo $(chk_adr ${rev_dns} '')
 # See: (Open mail relays)
     echo -n '  says: '
     echo $(chk_adr ${rev_dns} '')
 # See: (You can report spammers here)
     echo -n ' says: '
     echo $(chk_adr ${rev_dns} '')
 # # # other blacklist operations # # #
 # See:
     echo -n ' says: '
     echo $(chk_adr ${rev_dns} '')
 # See: (Various mail relays)
     echo 'Distributed Server Listings'
     echo -n ' says: '
     echo $(chk_adr ${rev_dns} '')
     echo -n ' says: '
     echo $(chk_adr ${rev_dns} '')
     echo -n ' says: '
     echo $(chk_adr ${rev_dns} '')
     echo 'Could not use that address.'
 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.


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
 traceroute to (, 30 hops max, 38 byte packets
  1 (  191.303 ms  179.400 ms  179.767 ms
  2 (  179.536 ms  179.534 ms  169.685 ms
  3 (  189.471 ms  189.556 ms *


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 ( from : 56(84) bytes of data.
  64 bytes from localhost.localdomain ( icmp_seq=0 ttl=255 time=709 usec
  64 bytes from localhost.localdomain ( 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


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.


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]


Change information disclosed by the finger command.


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.


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).


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.


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.


Utility and protocol for connecting to a remote host.


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


The wget utility non-interactively retrieves or downloads files from a Web or ftp site. It works well in a script.
wget -p
 wget -r -O $SAVEFILE

Example 12-38. Getting a stock quote

 # Download a stock quote.
 if [ -z "$1" ]  # Must specify a stock (symbol) to fetch.
   then echo "Usage: `basename $0` stock-symbol"
   exit $E_NOPARAMS
 # Fetches an HTML file, so name it appropriately.
 # Yahoo finance board, with stock query suffix.
 # -----------------------------------------------------------
 wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
 # -----------------------------------------------------------
 # To look up stuff on
 # -----------------------------------------------------------
 # URL="${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.


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 >$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.


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


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


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.


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

 # 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:
 #  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


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


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.


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



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

 # Self-mailing script
 adr=${1:-`whoami`}     # Default to current user, if not specified.
 #  Typing ''
 #+ sends this script to that addressee.
 #  Just '' (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

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.


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.



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).


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).

 . . .
 # Will exit with status of last command.

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

 . . .
 # Will exit with status of last command.
 exit $?

 . . . 
 # Will exit with status of last command.

$? 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

 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.
 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).


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.
 # No error this time, but no negation either.
 # It just repeats the previous command (true).
 # Thanks, Stephan�Chazelas and Kristopher Newsome.


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."
     # The very useful "if-grep" construct:
     # ----------------------------------- 
     if grep -q Bash file
     then echo "File contains at least one occurrence of Bash."
     if echo "$word" | grep -q "$letter_sequence"
     # The "-q" option to grep suppresses output.
       echo "$letter_sequence found in $word"
       echo "$letter_sequence not found in $word"
     then echo "Command succeeded."
     else echo "Command failed."

  • 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 ))
         [[ $a < $b ]]
       echo '$a is less than $b'

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

Example 7-1. What is truth?

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

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

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


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 is a contraction for else if. The effect is to nest an inner if/then construct within an outer one.

if [ condition1 ]
 elif [ condition2 ]
 # Same as else if

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.


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/[

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

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


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

 if [[ -e $file ]]
   echo "Password file exists."


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.


Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] ) are strictly necessary.
 if cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.
   echo "Now in $dir."
   echo "Can't change to $dir."
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" -ne "$var2" ] && echo "$var1 is not equal to $var2"
 [ -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 (( ))

 # 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...


file exists


file exists

This is identical in effect to -e. It has been "deprecated," and its use is discouraged.


file is a regular file (not a directory or device file)


file is not zero size


file is a directory


file is a block device (floppy, cdrom, etc.)


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


file is a pipe


file is a symbolic link


file is a symbolic link


file is a socket


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.


file has read permission (for the user running the test)


file has write permission (for the user running the test)


file has execute permission (for the user running the test)


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.


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.


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.


you are owner of file


group-id of file same as yours


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

 # Written by Lee bigelow <>
 # 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. /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.
 #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
 	    echo "$directory is not a directory"
 	    echo "Usage: $0 dir1 dir2 ..."
 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.



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


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


is equal to

if [ "$a" -eq "$b" ]


is not equal to

if [ "$a" -ne "$b" ]


is greater than

if [ "$a" -gt "$b" ]


is greater than or equal to

if [ "$a" -ge "$b" ]


is less than

if [ "$a" -lt "$b" ]


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 =.


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.


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


string is not "null".


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

 #  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.
 if [ "$a" -ne "$b" ]
   echo "$a is not equal to $b"
   echo "(arithmetic comparison)"
 if [ "$a" != "$b" ]
   echo "$a is not equal to $b."
   echo "(string comparison)"
   #     "4"  != "5"
   # ASCII 52 != ASCII 53
 # In this particular instance, both "-ne" and "!=" work.
 exit 0

Example 7-6. Testing whether a string is null

 # 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.
   echo "String \"string1\" is not null."
   echo "String \"string1\" is null."
 # Wrong result.
 # Shows $string1 as not null, although it was not initialized.
 # Lets try it again.
 if [ -n "$string1" ]  # This time, $string1 is quoted.
   echo "String \"string1\" is not null."
   echo "String \"string1\" is null."
 fi                    # Quote strings within test brackets!
 if [ $string1 ]       # This time, $string1 stands naked.
   echo "String \"string1\" is not null."
   echo "String \"string1\" is null."
 # 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 "]" 
 if [ $string1 ]       # Again, $string1 stands naked.
   echo "String \"string1\" is not null."
   echo "String \"string1\" is null."
 # Again, gives correct result.
 # Still, it is better to quote it ("$string1"), because . . .
 string1="a = b"
 if [ $string1 ]       # Again, $string1 stands naked.
   echo "String \"string1\" is not null."
   echo "String \"string1\" is null."
 # Not quoting "$string1" now gives wrong result!
 exit 0
 # Thank you, also, Florian Wisser, for the "heads-up".

Example 7-7. zmore

 # zmore
 #View gzipped files with 'more'
 if [ $# -eq 0 ] # same effect as:  if [ -z "$1" ]
 # $1 can exist, but be empty:  zmore "" arg2 arg3
   echo "Usage: `basename $0` filename" >&2
   # Error message to stderr.
   exit $NOARGS
   # Returns 65 as exit status of script (error code).
 if [ ! -f "$filename" ]   # Quoting $filename allows for possible spaces.
   echo "File $filename not found!" >&2
   # Error message to stderr.
   exit $NOTFOUND
 if [ ${filename##*.} != "gz" ]
 # Using bracket in variable substitution.
   echo "File $1 is not a gzipped file!"
   exit $NOTGZIP
 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


logical and

exp1 -a exp2 returns true if both exp1 and exp2 are true.


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.



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).



variable assignment

Initializing or changing the value of a variable


All-purpose assignment operator, which works for both arithmetic and string assignments.

 category=minerals  # No spaces allowed after the "=".


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.) 

arithmetic operators










# 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`

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

 # 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,
 # ------------------------------------------------------
 # Argument check
 if [ $# -ne "$ARGS" ]
   echo "Usage: `basename $0` first-number second-number"
   exit $E_BADARGS
 # ------------------------------------------------------
 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 ]
     let "remainder = $dividend % $divisor"
     dividend=$divisor            # Now repeat with 2 smallest numbers.
   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

 # 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 "
 exit 0


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.
 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.


Bash does not understand floating point arithmetic. It treats numbers containing a decimal point as strings.
 let "b = $a + 1.3"  # Error.
 # 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)



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.


&& 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.


Bash tests the exit status of each statement linked with a logical operator.

Example 8-3. Compound Condition Tests Using && and ||

 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
   echo "Test #1 succeeds."
   echo "Test #1 fails."
 # 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 ]
   echo "Test #2 succeeds."
   echo "Test #2 fails."
 #  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 ]
   echo "Test #3 succeeds."
   echo "Test #3 fails."
 if [ "$a" -eq 98 -o "$b" -eq 47 ]
   echo "Test #4 succeeds."
   echo "Test #4 fails."
 if [ "$a" = rhino ] && [ "$b" = crocodile ]
   echo "Test #5 succeeds."
   echo "Test #5 fails."
 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

 # 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 $((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


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


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


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

See Example 20-1 for usage.


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
   echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
 # 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).


the version of Bash installed on the system

bash$ echo $BASH_VERSION

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.


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.


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


"effective" user ID number

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


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


name of the current function

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


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


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
 root# echo ${GROUPS[1]}
 root# echo ${GROUPS[5]}


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


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.


host type

Like $MACHTYPE, identifies the system hardware.

bash$ echo $HOSTTYPE

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 "$*"'


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

Example 9-1. $IFS and whitespace

 # $IFS treats whitespace differently than other characters.
   for arg
   do echo "[$arg]"
 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 "-----"
 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.
 exit 0

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

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


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


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.


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.


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


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.

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


machine type

Identifies the system hardware.

bash$ echo $MACHTYPE

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


operating system type

bash$ echo $OSTYPE

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

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).


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


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
 bash$ ls -al | bogus_command
 bash: bogus_command: command not found
 bash$ echo $PIPESTATUS
 bash$ ls -al | bogus_command
 bash: bogus_command: command not found
 bash$ echo $?

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.


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[*]}

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.


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
 bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
  0       0       0
 bash$ echo ${PIPESTATUS[@]}
 141 127 0
 bash$ echo $BASH_VERSION
 bash$ $ ls | bogus_command | wc
 bash: bogus_command: command not found
  0       0       0
 bash$ echo ${PIPESTATUS[@]}
 0 127 0


$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[@]}


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

Compare this with the pidof command.


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


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


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


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


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


working directory (directory you are in at the time)

This is the analog to the pwd builtin command.

 clear # Clear screen.
 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!"
 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 "Done."
 echo "Old files deleted in $TargetDirectory."
 exit 0


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.

 # REPLY is the default value for a 'read' command.
 echo -n "What is your favorite vegetable? "
 echo "Your favorite vegetable is $REPLY."
 #  REPLY holds the value of last "read" if and only if
 #+ no variable supplied.
 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.
 exit 0


The number of seconds the script has been running.

 echo "Hit Control-C to exit before $TIME_LIMIT seconds."
 while [ "$SECONDS" -le "$TIME_LIMIT" ]
   if [ "$SECONDS" -eq 1 ]
   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
 echo -e "\a"  # Beep!
 exit 0


the list of enabled shell options, a readonly variable
bash$ echo $SHELLOPTS


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


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" ]
   song="(no answer)"
   # Default response.
 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

 # TMOUT=3    Also works, as of newer versions of Bash.
 TIMELIMIT=3  # Three seconds in this instance. May be set to different value.
   if [ "$answer" = TIMEOUT ]
     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.
   sleep $TIMELIMIT && kill -s 14 $$ &
   # Waits 3 seconds, then sends sigalarm to script.
   exit 14
 trap Int14Vector 14   # Timer interrupt (14) subverted for our purposes.
 echo "What is your favorite vegetable "
 read answer
 #  Admittedly, this is a kludgy implementation of timed input,
 #+ however the "-t" option to "read" simplifies this task.
 #  See "", 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

 #  Written by Stephane Chazelas,
 #+ and modified by the document author.
 INTERVAL=5                # timeout interval
 timedout_read() {
   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).
 if [ ! -z "$your_name" ]  # If name input before timeout...
   echo "Your name is $your_name."
   echo "Timed out."
 # The behavior of this script differs somewhat from "".
 # At each keystroke, the counter resets.
 exit 0

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

Example 9-4. Timed read

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

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?

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

See also Example 2-3.


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
 tcsh% echo $SHELL
 tcsh% echo $TERM
 bash$ echo $LOGNAME
 bash$ echo $SHELL
 bash$ echo $TERM

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


"$*" 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.


Of course, "$@" should be quoted.

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

 # Invoke this script with several arguments, such as "one two three".
 if [ ! -n "$1" ]
   echo "Usage: `basename $0` argument1 argument2 etc."
   exit $E_BADARGS
 index=1          # Initialize count.
 echo "Listing args with \"\$*\":"
 for arg in "$*"  # Doesn't work properly if "$*" isn't quoted.
   echo "Arg #$index = $arg"
   let "index+=1"
 done             # $* sees all arguments as single word. 
 echo "Entire arg list seen as single word."
 index=1          # Reset count.
                  # What happens if you forget to do this?
 echo "Listing args with \"\$@\":"
 for arg in "$@"
   echo "Arg #$index = $arg"
   let "index+=1"
 done             # $@ sees arguments as separate words. 
 echo "Arg list seen as separate words."
 index=1          # Reset count.
 echo "Listing args with \$* (unquoted):"
 for arg in $*
   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.
 # Invoke with ./scriptname 1 2 3 4 5
 echo "$@"    # 1 2 3 4 5
 echo "$@"    # 2 3 4 5
 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.


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

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

 #  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 'IFS unchanged, using "$*"'
 for i in "$*"               # quoted
 do echo "$((c+=1)): [$i]"   # This line remains the same in every instance.
                             # Echo args.
 echo ---
 echo 'IFS unchanged, using $*'
 for i in $*                 # unquoted
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS unchanged, using "$@"'
 for i in "$@"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS unchanged, using $@'
 for i in $@
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using "$*"'
 for i in "$*"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using $*'
 for i in $*
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using "$var" (var=$*)'
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using $var (var=$*)'
 for i in $var
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using $var (var="$*")'
 for i in $var
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using "$var" (var="$*")'
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using "$@"'
 for i in "$@"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using $@'
 for i in $@
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using $var (var=$@)'
 for i in $var
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using "$var" (var=$@)'
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using "$var" (var="$@")'
 for i in "$var"
 do echo "$((c+=1)): [$i]"
 echo ---
 echo 'IFS=":", using $var (var="$@")'
 for i in $var
 do echo "$((c+=1)): [$i]"
 # Try this script with ksh or zsh -y.
 exit 0
 # This example script by Stephane Chazelas,
 # and slightly modified by the document author.


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

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

 #  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.


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

 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

 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.



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


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.

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

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

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