Cooper
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 #!/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 CommandsCertain 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.
- 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. | 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 |
| 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.
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). #!/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). | 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. |
|
| 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 |
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 |
| 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. | 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. | 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 |
| 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.
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 operatorsReturns 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.
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.
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.
Other Comparison OperatorsA 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 =. | 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". | 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.
|
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.
Operatorsassignment - 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 "=". |
| 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 |
| 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. |
| 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 - <<
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. |
| && 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 || #!/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 ConstantsA 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. | 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
|
| $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. | 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 - $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). | 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. | 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.
|
| 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
|
|
| $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.
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. | 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
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 $@ #!/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. | 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. |
| 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. | 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.
|
|