第九章:变量访问
变量使用得当,可以使脚本变得更加强大和有弹性。但这要求我们学习变量的精妙之处及其细微的差别。
$BASH_SUBSHELL
一个指示子shell(subshell)等级的变量。它是Bash版本3新加入的。
参考例子 20-1的用法.
$BASH_VERSINFO[n]
这个数组含有6个元素,指示了安装的Bash版本的信息。它和$BASH_VERSION相似,但它们还是有一些小小的不同。
1 # Bash版本信息: 2 3 for n in 0 1 2 3 4 5 4 do 5 echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}" 6 done 7 8 # BASH_VERSINFO[0] = 3 # 主版本号. 9 # BASH_VERSINFO[1] = 00 # 次版本号. 10 # BASH_VERSINFO[2] = 14 # 补丁级. 11 # BASH_VERSINFO[3] = 1 # 编译版本. 12 # BASH_VERSINFO[4] = release # 发行状态. 13 # BASH_VERSINFO[5] = i386-redhat-linux-gnu # 结构体系 14 # (和变量$MACHTYPE相同).
$BASH_VERSION
安装在系统里的Bash版本。
bash$ echo $BASH_VERSION 3.00.14(1)-release
tcsh% echo $BASH_VERSION BASH_VERSION: Undefined variable.
检查$BASH_VERSION是检测哪个shell在运行的好办法。$SHELL变量不一定能给出正确的答案。
$DIRSTACK
这个内建的变量和dirs命令相符,但dirs是给出整个目录堆栈的内容。
$EDITOR
由脚本调用的默认的编辑器,一般是vi或是emacs.
$EUID
有效用户ID
当前用户无论是什么标识都会被认为是这个有效用户ID,这可能依赖于su.
变量$UID不一定和$EUID相同。
$FUNCNAME
当前函数的名字
1 xyz23 () 2 { 3 echo "$FUNCNAME now executing." # 打印:xyz23 now executing. 4 } 5 6 xyz23 7 8 echo "FUNCNAME = $FUNCNAME" # FUNCNAME = 9 # 在一个函数体外则没有值输出.
$GLOBIGNORE
由通配符(globbing)扩展的一列文件名模式。
$GROUPS
目前用户所属的组
它是当前用户在/etc/passwd文件中记录的所属的组列表(数组)。
root# echo $GROUPS 0
root# echo ${GROUPS[1]}1
root# echo ${GROUPS[5]}6
$HOME
用户的家目录,通常是/home/username(参考例子 9-14)
$HOSTNAME
在系统启动时由一个初始化脚本中用hostname命令给系统指派一个名字。然而,gethostname()函数能设置Bash内部变量E$HOSTNAME。参考例子 9-14.
$HOSTTYPE
机器类型
像$MACHTYPE一样标识系统硬件。
bash$ echo $HOSTTYPE i686
$IFS
内部字段分隔符
此变量决定Bash如何分割字段,或是解释字符串时的字标识分割。
$IFS默认是空白字符(空格,制表符和新行符),它可以被重新设置。例如,在解释一个以逗号分割的数据文件里可设置成逗号分割。注意$*使用了保存在$IFS中的第一个字符。 参考例子 5-1.
bash$ echo $IFS | cat -vte $
bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'w:x:y:z
$IFS处理空白字符和其他的字符不相同。
例子 9-1. $IFS和空白符
1 #!/bin/bash 2 # $IFS处理空白字符和其他字符不相同。 3 4 output_args_one_per_line() 5 { 6 for arg 7 do echo "[$arg]" 8 done 9 } 10 11 echo; echo "IFS=\" \"" 12 echo "-------" 13 14 IFS=" " 15 var=" a b c " 16 output_args_one_per_line $var # output_args_one_per_line函数相当于`echo " a b c "` 17 # 18 # [a] 19 # [b] 20 # [c] 21 22 23 echo; echo "IFS=:" 24 echo "-----" 25 26 IFS=: 27 var=":a::b:c:::" # 像上面一样, 但用":"代替了" ". 28 output_args_one_per_line $var 29 # 30 # [] 31 # [a] 32 # [] 33 # [b] 34 # [c] 35 # [] 36 # [] 37 # [] 38 39 # 在awk中字段分隔符"FS"也有相同的特性. 40 41 # 多谢Stephane Chazelas. 42 43 echo 44 45 exit 0
(多谢S.C.澄清了问题和举的例子)
参考例子 12-37来看一个关于理解$IFS的教学例子。
$IGNOREEOF
忽略EOF:在退出控制台前有多少文件结尾标识(end-of-files,control-D)会被shell忽略。
$LC_COLLATE
它常常在.bashrc或/etc/profile文件里被设置,它控制文件名扩展和模式匹配的展开顺序。如果设置不当,LC_COLLATE会在文件名通配符(filename globbing)里引起不可预料的结果。
到Bash2.05版本止,文件名通配符不再区分在方括号里的字符串范围中的大小写了。例如,ls [A-M]*会匹配File1.txt和file1.txt。为了保持方括号区分大小写的惯例,在/etc/profile文件和/或在~/.bashrc文件里由命令exportLC_COLLATE=C把LC_COLLATE环境变量设置成C可以达到目的。
$LC_CTYPE
这个内部变量控制通配符(globbing)和模式匹配中的字符解释。
$LINENO
这个变量表示在本shell脚本中该变量出现时所在的行数。它只在脚本中它出现时有意义,它一般可用于调试。
1 # *** 开始调试代码块 *** 2 last_cmd_arg=$_ # 保存. 3 4 echo "At line number $LINENO, variable \"v1\" = $v1" 5 echo "Last command argument processed = $last_cmd_arg" 6 # *** 调试代码结束 ***
$MACHTYPE
机器类型
识别系统的硬件类型。
bash$ echo $MACHTYPE i686
$OLDPWD
上一次工作的目录("OLD-print-working-directory",你上一次进入工作的目录)
$OSTYPE
操作系统类型
bash$ echo $OSTYPE linux
$PATH
可执行程序文件的搜索路径。一般有/usr/bin/, /usr/X11R6/bin/, /usr/local/bin,等等。
当给出一个命令时,shell会自动在一个哈希表里搜索由PATH变量里所列的路径寻找该命令程序。$PATH变量被保存在环境变量里,是一串由冒号(:)分割的目录名的列表。通常,系统把此变量的值在/etc/profile文件和/或在~/.bashrc文件中被定义赋值。(参考附录 G).
bash$ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin
PATH=${PATH}:/opt/bin能把/opt/bin目录加到当前现有的目录列表中去。在一个脚本中,它可以用这种方法临时地加一个目录到目录列表中去。当一个脚本退出时,此变量会恢复回原先的$PATH值(一个子进程[比如一个脚本],不能改变父进程的环境变量[比如启动脚本的shell])。
保存在$PATH目录列表中的当前"工作目录"(./)通常因为会引发安全漏洞而被忽略。
$PIPESTATUS
此数组变量保存了最后执行的前台管道的退出状态。相当有趣的是,它不一定和最后执行的命令的退出状态一样。
bash$ echo $PIPESTATUS 0
bash$ ls -al | bogus_commandbash: bogus_command: command not foundbash$ echo $PIPESTATUS141
bash$ ls -al | bogus_commandbash: bogus_command: command not foundbash$ echo $?127
$PIPESTATUS数组的成员保存了每一个在管道里执行的命令各自的退出状态。$PIPESTATUS[0]保存了管道里第一个命令的退出状态,$PIPESTATUS[1]保存了管道里第二个命令的退出状态,以此类推。
在一个登录的shell里$PIPESTATUS变量可能包含了一个无用的0值。(在Bash 3.0以前)
tcsh% bash
bash$ who | grep nobody | sortbash$ echo ${PIPESTATUS[*]}0
如果在一个脚本包含上面的命令,就会产生0 1 0的输出。
多谢Wayne Pollock指出这一点并提供上面的例子。
在某些上下文中,$PIPESTATUS变量会给出一些不可预料的结果。
bash$ echo $BASH_VERSION 3.00.14(1)-release
bash$ $ ls | bogus_command | wcbash: bogus_command: command not found0 0 0
bash$ echo ${PIPESTATUS[@]}141 127 0
Chet Ramey贡献了上面描述ls输出的行为的例子。如果ls写到一个没有进程在读的管道,SIGPIPE信号会杀死它并使它的退出状态为141。否则ls的退出状态为预料之中的0。这个和tr的情况一样。
$PIPESTATUS是一个"挥发性"变量。它需要在管道结束之后并在任何命令干涉之前立即查询。
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
一个进程的$PPID变量保存它的父进程的进程ID(pid)。$PS1
这是主提示符(第一提示符),它能在命令行上看见。
$PS2
副提示符(第二提示符),它在期望有附加的输入时能看见。它显示像">"的提示。
$PS3
第三提示符。它在一个select循环里显示 (参考例子 10-29).
$PS4
第四提示符,它在用-x选项调用一个脚本时的输出的每一行开头显示。它通常显示像"+"的提示。
$PWD
工作目录(即你现在所处的目录)
它类似于内建命令pwd。
1 #!/bin/bash 2 3 E_WRONG_DIRECTORY=73 4 5 clear # 清屏. 6 7 TargetDirectory=/home/bozo/projects/GreatAmericanNovel 8 9 cd $TargetDirectory 10 echo "Deleting stale files in $TargetDirectory." 11 12 if [ "$PWD" != "$TargetDirectory" ] 13 then # 防止意外工作在错误的目录中. 14 echo "Wrong directory!" 15 echo "In $PWD, rather than $TargetDirectory!" 16 echo "Bailing out!" 17 exit $E_WRONG_DIRECTORY 18 fi 19 20 rm -rf * 21 rm .[A-Za-z0-9]* # 删除点文件. 22 # rm -f .[^.]* ..?* 删除以多个点开始为文件名的文件. 23 # (shopt -s dotglob; rm -f *) 也可以. 24 # 多谢S.C.指出来. 25 26 # 文件名除了"/"字符外可以包含ASCII值在0 - 255范围的所有字符 27 # 删除以奇怪的字符开头的文件作为练习由读者实现. 28 29 # 如果需要,这儿有多种其他的操作. 30 31 echo 32 echo "Done." 33 echo "Old files deleted in $TargetDirectory." 34 echo 35 36 37 exit 0
$REPLY
没有变量提供给read命令时的默认变量.这也适用于select命令的目录,但只是提供被选择的变量项目编号而不是变量本身的值.
1 #!/bin/bash 2 # reply.sh 3 4 # REPLY 是一个read命令的默认变量. 5 6 echo 7 echo -n "What is your favorite vegetable? " 8 read 9 10 echo "Your favorite vegetable is $REPLY." 11 # 如果没有变量提供且仅在这种情况,REPLY保存"read"命令上次读到的值 12 # 13 14 echo 15 echo -n "What is your favorite fruit? " 16 read fruit 17 echo "Your favorite fruit is $fruit." 18 echo "but..." 19 echo "Value of \$REPLY is still $REPLY." 20 # $REPLY仍然被设置了它先前的值, 21 #+ 因为变量$fruit保存了新的"read"读到的值. 22 23 echo 24 25 exit 0
$SECONDS
脚本已运行的秒数.
1 #!/bin/bash 2 3 TIME_LIMIT=10 4 INTERVAL=1 5 6 echo 7 echo "Hit Control-C to exit before $TIME_LIMIT seconds." 8 echo 9 10 while [ "$SECONDS" -le "$TIME_LIMIT" ] 11 do 12 if [ "$SECONDS" -eq 1 ] 13 then 14 units=second 15 else 16 units=seconds 17 fi 18 19 echo "This script has been running $SECONDS $units." 20 # 在一个缓慢或负担过重的机器上, 21 #+ 脚本可能偶尔会跳过一个计数. 22 sleep $INTERVAL 23 done 24 25 echo -e "\a" # Beep!(BB声) 26 27 exit 0
$SHELLOPTS
已经激活的shell选项列表,它是一个只读变量.
bash$ echo $SHELLOPTS braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
$SHLVL
SHELL的嵌套级别.指示了Bash被嵌套了多深.在命令行里,$SHLVL是1,因此在一个脚本里,它是2.
$TMOUT
如果$TMOUT环境变量被设为非零值时间值time,那么经过time这么长的时间后,shell提示符会超时.这将使此shell退出登录.
在Bash版本2.05b以上,可以在脚本中把$TMOUT和read命令结合使用.
1 # 在Bash版本2.05b以上运行. 2 3 TMOUT=3 # 提示输入时间为三秒. 4 5 echo "What is your favorite song?" 6 echo "Quickly now, you only have $TMOUT seconds to answer!" 7 read song 8 9 if [ -z "$song" ] 10 then 11 song="(no answer)" 12 # 默认输出. 13 fi 14 15 echo "Your favorite song is $song."
有其他更复杂的在脚本中实现定时输入的方法.另一个方法是设置一个定时循环,超时时给脚本发送一个信号.这个办法要求有一个处理例程来捕捉(trap)(参考例子 29-5)由定时循环产生的信号.(哇哦!)
例子 9-2. 定时输入
1 #!/bin/bash 2 # timed-input.sh 3 4 # TMOUT=3 在新一点的版本中,这个也可以. 5 6 7 TIMELIMIT=3 # 在这个实例中设置成三秒,但可以设置成其它的值。 8 9 PrintAnswer() 10 { 11 if [ "$answer" = TIMEOUT ] 12 then 13 echo $answer 14 else # 不要和上面那个例子弄混了. 15 echo "Your favorite veggie is $answer" 16 kill $! # 不再需要后台运行的TimerOn函数了,杀掉它 17 # $!变量是上一个在后台运行的作业进程的PID 18 fi 19 20 } 21 22 23 24 TimerOn() 25 { 26 sleep $TIMELIMIT && kill -s 14 $ & 27 # 等3秒,然后给脚本发送sigalarm信号. 28 } 29 30 Int14Vector() 31 { 32 answer="TIMEOUT" 33 PrintAnswer 34 exit 14 35 } 36 37 trap Int14Vector 14 # 设置定时中断(14)能暗中给定时间限制 38 39 echo "What is your favorite vegetable " 40 TimerOn 41 read answer 42 PrintAnswer 43 44 45 # 无可否认,这是一个定时输入的复杂的实现, 46 #+ 然而"read"命令的"-t"选项可以简化这个任务。 47 # 参考后面的"t-out.sh"脚本 48 49 # 如果你想要真正优雅的东西... 50 #+ 可以考虑用C或C++写你的应用程序, 51 #+ 使用合适的函数库,例如'alarm'或是'setitimer'. 52 53 exit 0
另外一种选择是使用stty.
例子 9-3. 再来一个定时输入
1 #!/bin/bash 2 # timeout.sh 3 4 # 由Stephane Chazelas所写, 5 #+ 由本书作者作了些修改. 6 7 INTERVAL=5 # 超时间隔 8 9 timedout_read() { 10 timeout=$1 11 varname=$2 12 old_tty_settings=`stty -g` 13 stty -icanon min 0 time ${timeout}0 14 eval read $varname # 或只是读$varname变量 15 stty "$old_tty_settings" 16 # 请参考"stty"的man手册. 17 } 18 19 echo; echo -n "What's your name? Quick! " 20 timedout_read $INTERVAL your_name 21 22 # 这个可能不一定在每种终端都能运行. 23 # 最大的超时值依赖于终端. 24 #+ (通常是25.5秒). 25 26 echo 27 28 if [ ! -z "$your_name" ] # 如果在超时之前名字被键入... 29 then 30 echo "Your name is $your_name." 31 else 32 echo "Timed out." 33 fi 34 35 echo 36 37 # 这个脚本和"timed-input.sh"脚本的行为稍微有点不同. 38 # 每一次击键,计时器都会重新设置(即重新开始). 39 40 exit 0
可能最容易的方法就是使用read命令的-t选项了。
例子 9-4. 定时read
1 #!/bin/bash 2 # t-out.sh 3 # 从"syngin seven"的建议中得到灵感(多谢). 4 5 6 TIMELIMIT=4 # 4秒 7 8 read -t $TIMELIMIT variable <&1 9 # ^^^ 10 # 在这儿, Bash 1.x and 2.x需要"<&1", 11 # 但Bash 3.x则不需要. 12 13 echo 14 15 if [ -z "$variable" ] # 值为null? 16 then 17 echo "Timed out, variable still unset." 18 else 19 echo "variable = $variable" 20 fi 21 22 exit 0
$UID
用户ID号
这是当前用户的用户标识号,它在/etc/passwd文件中记录。
这是当前用户的真实ID,即使只是临时通过su命令转换成另外一个用户也会显示成转换成的ID号。$UID是个只读变量,不能在命令行或是脚本中更改它,并且它和内建命令id是有些相似的。
例子 9-5. 我是root吗?
1 #!/bin/bash 2 # am-i-root.sh: 我是root吗? 3 4 ROOT_UID=0 # Root的$UID为0. 5 6 if [ "$UID" -eq "$ROOT_UID" ] # 真正的"root"才能经得住考验 7 then 8 echo "You are root." 9 else 10 echo "You are just an ordinary user (but mom loves you just the same)." 11 fi 12 13 exit 0 14 15 16 # ============================================================= # 17 # 下面的代码不会执行,因为脚本在上面已经退出了. 18 19 # 另外一种判断是否是root用户的方法: 20 21 ROOTUSER_NAME=root 22 23 username=`id -nu` # 或者... username=`whoami` 24 if [ "$username" = "$ROOTUSER_NAME" ] 25 then 26 echo "Rooty, toot, toot. You are root." 27 else 28 echo "You are just a regular fella." 29 fi
也参考一下例子 2-3.
变量$ENV, $LOGNAME, $MAIL, $TERM, $USER, 和$USERNAME$ENV,都不是Bash内建的。然而常常在Bash的启动文件之一里作为环境变量设置。$SHELL变量是用户的登录shell的名字,它可以在/etc/passwd文件里设置或是在一个“初始化”的脚本里设置,并且它同样不是Bash内建的。
tcsh% echo $LOGNAME bozo tcsh% echo $SHELL /bin/tcsh tcsh% echo $TERM rxvt
bash$ echo $LOGNAMEbozobash$ echo $SHELL/bin/tcshbash$ echo $TERMrxvt
位置参数
- $0, $1, $2,等等
位置参数由命令行传给脚本或传给一个函数,或设置(set)给一个变量(参考例子 4-5和例子 11-15)
$#
$*
所有的位置参数都被当成单个单元。
"$*"必须被引号引起来。
$@
和$*相同,但每个参数都是一个引起的字符串。那是说,参数都是没有被解析或扩展,是完整无缺地被传递的。这是说在参数列表中的每一个参数都被看作是一个单独的单元。
当然,"$@"应该被引号引起来。
例子 9-6. arglist: 用$*和$@列出参数来
1 #!/bin/bash 2 # arglist.sh 3 # 用几个参数来运行这个脚本,比如说"one two three". 4 5 E_BADARGS=65 6 7 if [ ! -n "$1" ] 8 then 9 echo "Usage: `basename $0` argument1 argument2 etc." 10 exit $E_BADARGS 11 fi 12 13 echo 14 15 index=1 # 初始计数. 16 17 echo "Listing args with \"\$*\":" 18 for arg in "$*" # 如果"$*"没有被引号引起来,会完全不能工作. 19 do 20 echo "Arg #$index = $arg" 21 let "index+=1" 22 done # $* sees all arguments as single word. 23 echo "Entire arg list seen as single word." 24 25 echo 26 27 index=1 # Reset count. 28 # What happens if you forget to do this? 29 30 echo "Listing args with \"\$@\":" 31 for arg in "$@" 32 do 33 echo "Arg #$index = $arg" 34 let "index+=1" 35 done # $@ sees arguments as separate words. 36 echo "Arg list seen as separate words." 37 38 echo 39 40 index=1 # Reset count. 41 42 echo "Listing args with \$* (unquoted):" 43 for arg in $* 44 do 45 echo "Arg #$index = $arg" 46 let "index+=1" 47 done # Unquoted $* sees arguments as separate words. 48 echo "Arg list seen as separate words." 49 50 exit 0
在一个shift命令的后面,变量$@会保存除掉先前参数列表的位置参数$1后剩下的命令行参数。
1 #!/bin/bash 2 # 以./scriptname 1 2 3 4 5执行 3 4 echo "$@" # 1 2 3 4 5 5 shift 6 echo "$@" # 2 3 4 5 7 shift 8 echo "$@" # 3 4 5 9 10 # 每次"shift"后会丢掉$1. 11 # 然后"$@"保存剩下的参数。
The $@special parameter finds use as a tool for filtering input into shell scripts.The cat "$@"construction accepts input to a script either from stdinor from files given as parameters to the script. See Example 12-21and Example 12-22.
依赖于$IFS变量的设置,$*和$@变量有时会表现不一致的令人迷惑的行为。
例子 9-7. $*和$@的不一致
1 #!/bin/bash 2 3 # Erratic behavior of the "$*" and "$@" internal Bash variables, 4 #+ depending on whether they are quoted or not. 5 # Inconsistent handling of word splitting and linefeeds. 6 7 8 set -- "First one" "second" "third:one" "" "Fifth: :one" 9 # Setting the script arguments, $1, $2, etc. 10 11 echo 12 13 echo 'IFS unchanged, using "$*"' 14 c=0 15 for i in "$*" # quoted 16 do echo "$((c+=1)): [$i]" # This line remains the same in every instance. 17 # Echo args. 18 done 19 echo --- 20 21 echo 'IFS unchanged, using $*' 22 c=0 23 for i in $* # unquoted 24 do echo "$((c+=1)): [$i]" 25 done 26 echo --- 27 28 echo 'IFS unchanged, using "$@"' 29 c=0 30 for i in "$@" 31 do echo "$((c+=1)): [$i]" 32 done 33 echo --- 34 35 echo 'IFS unchanged, using $@' 36 c=0 37 for i in $@ 38 do echo "$((c+=1)): [$i]" 39 done 40 echo --- 41 42 IFS=: 43 echo 'IFS=":", using "$*"' 44 c=0 45 for i in "$*" 46 do echo "$((c+=1)): [$i]" 47 done 48 echo --- 49 50 echo 'IFS=":", using $*' 51 c=0 52 for i in $* 53 do echo "$((c+=1)): [$i]" 54 done 55 echo --- 56 57 var=$* 58 echo 'IFS=":", using "$var" (var=$*)' 59 c=0 60 for i in "$var" 61 do echo "$((c+=1)): [$i]" 62 done 63 echo --- 64 65 echo 'IFS=":", using $var (var=$*)' 66 c=0 67 for i in $var 68 do echo "$((c+=1)): [$i]" 69 done 70 echo --- 71 72 var="$*" 73 echo 'IFS=":", using $var (var="$*")' 74 c=0 75 for i in $var 76 do echo "$((c+=1)): [$i]" 77 done 78 echo --- 79 80 echo 'IFS=":", using "$var" (var="$*")' 81 c=0 82 for i in "$var" 83 do echo "$((c+=1)): [$i]" 84 done 85 echo --- 86 87 echo 'IFS=":", using "$@"' 88 c=0 89 for i in "$@" 90 do echo "$((c+=1)): [$i]" 91 done 92 echo --- 93 94 echo 'IFS=":", using $@' 95 c=0 96 for i in $@ 97 do echo "$((c+=1)): [$i]" 98 done 99 echo --- 100 101 var=$@ 102 echo 'IFS=":", using $var (var=$@)' 103 c=0 104 for i in $var 105 do echo "$((c+=1)): [$i]" 106 done 107 echo --- 108 109 echo 'IFS=":", using "$var" (var=$@)' 110 c=0 111 for i in "$var" 112 do echo "$((c+=1)): [$i]" 113 done 114 echo --- 115 116 var="$@" 117 echo 'IFS=":", using "$var" (var="$@")' 118 c=0 119 for i in "$var" 120 do echo "$((c+=1)): [$i]" 121 done 122 echo --- 123 124 echo 'IFS=":", using $var (var="$@")' 125 c=0 126 for i in $var 127 do echo "$((c+=1)): [$i]" 128 done 129 130 echo 131 132 # Try this script with ksh or zsh -y. 133 134 exit 0 135 136 # 这个脚本由Stephane Chazelas所写, 137 # 并由本书作者做了少些修改.
$@和$*仅仅在被双引号引住时不同。
例子 9-8. 当$IFS为空时的$*和$@
1 #!/bin/bash 2 3 # 如果$IFS被设置,但值是空的, 4 #+ 则"$*"和"$@"不会像希望的那样显示位置参数。 5 6 mecho () # 显示位置参数. 7 { 8 echo "$1,$2,$3"; 9 } 10 11 12 IFS="" # 设置了,但值是空的. 13 set a b c # 位置参数. 14 15 mecho "$*" # abc,, 16 mecho $* # a,b,c 17 18 mecho $@ # a,b,c 19 mecho "$@" # a,b,c 20 21 # 当$IFS为空时, 22 #+ $*和$@的行为依赖于Bash或是sh正在运行. 23 # 因此在一个脚本里使用这种“特性”是失策的。 24 25 26 # 多谢Stephane Chazelas. 27 28 exit 0
其它的特殊参数
- $-
这原本是一个ksh的结构,但被Bash采纳了,并且不幸的是,它看上去好像不能真正的在Bash脚本中工作。一个可能有用的地方是脚本用来测试自己本身是否是一个交互式的。
$!
在后台运行的最后一个作业的PID(进程ID)。
1 LOG=$0.log 2 3 COMMAND1="sleep 100" 4 5 echo "Logging PIDs background commands for script: $0" >> "$LOG" 6 # So they can be monitored, and killed as necessary. 7 echo >> "$LOG" 8 9 # Logging commands. 10 11 echo -n "PID of \"$COMMAND1\": " >> "$LOG" 12 ${COMMAND1} & 13 echo $! >> "$LOG" 14 # PID of "sleep 100": 1506 15 16 # 多谢Jacques Lederer的建议.
1 possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; } 2 # 强迫一个出错的程序结束退出. 3 # 这很有用,尤其在初始化脚本中. 4 5 # 多谢Sylvain Fourmanoit发现"!"变量创造性的用处.
$_
保存前一个命令最后一个参数的变量值。
例子 9-9. 下划线变量
1 #!/bin/bash 2 3 echo $_ # /bin/bash 4 # 只需调用/bin/bash来运行这个脚本. 5 6 du >/dev/null # 从命令行里没有输出. 7 echo $_ # du 8 9 ls -al >/dev/null # 从命令行里没有输出. 10 echo $_ # -al (它是最后的参数) 11 12 : 13 echo $_ # :
$?
$$
脚本本身的进程PID。$$变量常被用于脚本中生成一个"唯一的"临时文件名(参考例子 A-13, 例子 29-6, 例子 12-28, 和例子 11-25). 这通常比调用mktemp还要简单。
注
当然,当前运行的脚本的PID就是$$。
术语"argument"和"parameter"常常可互相替换。在这个文档的上下文中,它们有相同的意思,即它们是传递给脚本或函数的变量。[译者注:翻译时,译者已经把这两个术语依据上下文意思都做了适当的翻译].