高级Bash脚本编程指南(二)
时间:2006-12-12 来源:古月石
高级Bash脚本编程指南(二)
第三部分 超越基本
++++++++++++++++++++
第9章 变量重游
================
如果变量使用恰当,将会增加脚本的能量和灵活性.但是前提是这需要仔细学习变量的细节知识.
9.1 内部变量
------------
Builtin variable
这些内建的变量,将影响bash脚本的行为. $BASH
这个变量将指向Bash的二进制执行文件的位置.
bash$ echo $BASH
/bin/bash $BASH_ENV
这个环境变量将指向一个Bash启动文件,这个启动文件将在调用一个脚本时被读取. $BASH_SUBSHELL
这个变量将提醒subshell的层次,这是一个在version3才被添加到Bash中的新特性.
见Example 20-1. $BASH_VERSINFO[n]
记录Bash安装信息的一个6元素的数组.与下边的$BASH_VERSION很像,但这个更加详细.
1 # Bash version info:
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 # Patch 次数.
11 # BASH_VERSINFO[3] = 1 # Build version.
12 # BASH_VERSINFO[4] = release # Release status.
13 # BASH_VERSINFO[5] = i386-redhat-linux-gnu # Architecture $BASH_VERSION
安装在系统上的Bash的版本号.
bash$ echo $BASH_VERSION
3.00.14(1)-release
tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.
使用这个变量对于判断系统上到底运行的是那个shll来说是一种非常好的办法.$SHELL
有时将不能给出正确的答案. $DIRSTACK
在目录栈中最上边的值(将受到pushd和popd的影响).
这个内建的变量与dirs命令是保持一致的,但是dirs命令将显示目录栈的整个内容. $EDITOR
脚本调用的默认编辑器,一般是vi或者是emacs. $EUID
"effective"用户ID号.
当前用户被假定的任何id号.可能在su命令中使用.
注意:$EUID并不一定与$UID相同. $FUNCNAME
当前函数的名字.
1 xyz23 ()
2 {
3 echo "$FUNCNAME now executing." # xyz23 现在正在被执行.
4 }
5
6 xyz23
7
8 echo "FUNCNAME = $FUNCNAME" # FUNCNAME =
9 # 出了函数就变为Null值了. $GLOBIGNORE
一个文件名的模式匹配列表,如果在file globbing中匹配到的文件包含这个列表中的
某个文件,那么这个文件将被从匹配到的文件中去掉. $GROUPS
当前用户属于的组.
这是一个当前用户的组id列表(数组),就像在/etc/passwd中记录的一样.
root# echo $GROUPS
0 root# echo ${GROUPS[1]}
1 root# echo ${GROUPS[5]}
6 $HOME
用户的home目录,一般都是/home/username(见Example 9-14) $HOSTNAME
hostname命令将在一个init脚本中,在启动的时候分配一个系统名字.
gethostname()函数将用来设置这个$HOSTNAME内部变量.(见Example 9-14) $HOSTTYPE
主机类型
就像$MACHTYPE,识别系统的硬件.
bash$ echo $HOSTTYPE
i686 $IFS
内部域分隔符.
这个变量用来决定Bash在解释字符串时如何识别域,或者单词边界.
$IFS默认为空白(空格,tab,和新行),但可以修改,比如在分析逗号分隔的数据文件时.
注意:$*使用$IFS中的第一个字符,具体见Example 5-1.
bash$ echo $IFS | cat -vte
$ bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
w:x:y:o
注意:$IFS并不像它处理其它字符一样处理空白. Example 9-1 $IFS和空白
################################Start Script#######################################
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 # Thank you, Stephane Chazelas.
42
43 echo
44
45 exit 0
################################End Script#########################################
Example 12-37也是使用$IFS的另一个启发性的例子. $IGNOREEOF
忽略EOF: 告诉shell在log out之前要忽略多少文件结束符(control-D). $LC_COLLATE
常在.bashrc或/etc/profile中设置,这个变量用来在文件名扩展和模式匹配校对顺序.
如果$LC_COLLATE被错误的设置,那么将会在filename globbing中引起错误的结果.
注意:在2.05以后的Bash版本中,filename globbing将不在对[]中的字符区分大小写.
比如:ls [A-M]* 将即匹配File1.txt也会匹配file1.txt.为了恢复[]的习惯用法,
设置$LC_COLLATE的值为c,使用export LC_COLLATE=c 在/etc/profile或者是
~/.bashrc中. $LC_CTYPE
这个内部变量用来控制globbing和模式匹配的字符串解释. $LINENO
这个变量记录它所在的shell脚本中它所在行的行号.这个变量一般用于调试目的.
1 # *** BEGIN DEBUG BLOCK ***
2 last_cmd_arg=$_ # Save it.
3
4 echo "At line number $LINENO, variable \"v1\" = $v1"
5 echo "Last command argument processed = $last_cmd_arg"
6 # *** END DEBUG BLOCK *** $MACHTYPE
系统类型
提示系统硬件
bash$ echo $MACHTYPE
i686 $OLDPWD
老的工作目录("OLD-print-working-directory",你所在的之前的目录) $OSTYPE
操作系统类型.
bash$ echo $OSTYPE
linux $PATH
指向Bash外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等.
当给出一个命令时,Bash将自动对$PATH中的目录做一张hash表.$PATH中以":"分隔的
目录列表将被存储在环境变量中.一般的,系统存储的$PATH定义在/ect/processed或
~/.bashrc中(见Appendix G). bash$ echo $PATH
/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin PATH=${PATH}:/opt/bin将把/opt/bin目录附加到$PATH变量中.在脚本中,这是一个
添加目录到$PATH中的便捷方法.这样在这个脚本退出的时候,$PATH将会恢复(因为这个
shell是个子进程,像这样的一个脚本是不会将它的父进程的环境变量修改的) 注意:当前的工作目录"./"一般都在$PATH中被省去. $PIPESTATUS
数组变量将保存最后一个运行的前台管道的退出码.有趣的是,这个退出码和最后一个命令
运行的退出码并不一定相同.
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 $PIPESTATUS数组的每个成员都会保存一个管道命令的退出码,$PIPESTATUS[0]保存第
一个管道命令的退出码,$PIPESTATUS[1]保存第2个,以此类推. 注意:$PIPESTATUS变量在一个login shell中可能会包含一个错误的0值(3.0以下版本)
tcsh% bash bash$ who | grep nobody | sort
bash$ echo ${PIPESTATUS[*]}
0
包含在脚本中的上边这行将会产生一个期望的输出0 1 0. 注意:在某些上下文$PIPESTATUS可能不会给出正确的结果.
bash$ echo $BASH_VERSION
3.00.14(1)-release bash$ $ ls | bogus_command | wc
bash: bogus_command: command not found
0 0 0 bash$ echo ${PIPESTATUS[@]}
141 127 0
Chet Ramey把上边输出不成确原因归咎于ls的行为.因为如果把ls的结果放到管道上,
并且这个输出没被读取,那么SIGPIPE将会kill掉它,并且退出码变为141,而不是我们期
望的0.这种情况也会发生在tr命令中. 注意:$PIPESTATUS是一个"volatile"变量.在任何命令插入之前,并且在pipe询问之后,
这个变量需要立即被捕捉.
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).[1]
使用pidof命令对比一下. $PROMPT_COMMAND
这个变量保存一个在主提示符($PS1)显示之前需要执行的命令. $PS1
主提示符,具体见命令行上的显示. $PS2
第2提示符,当你需要额外的输入的时候将会显示,默认为">". $PS3
第3提示符,在一个select循环中显示(见Example 10-29). $PS4
第4提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边.
默认为"+". $PWD
工作目录(你当前所在的目录).
与pwd内建命令作用相同.
################################Start Script#######################################
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]* # Delete dotfiles.
21 rm .[A-Za-z0-9]* # 删除"."文件(隐含文件).
22 # rm -f .[^.]* ..?* 为了删除以多个"."开头的文件.
23 # (shopt -s dotglob; rm -f *) 也行.
24 # Thanks, S.C. for pointing this out.
25
26 # 文件名能够包含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
################################End Script######################################### $REPLY
read命令如果没有给变量,那么输入将保存在$REPLY中.在select菜单中也可用,但是只
提供选择的变量的项数,而不是变量本身的值.
################################Start Script#######################################
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 # 当且仅当在没有变量提供给"read"命令时,
12 #+ REPLY才保存最后一个"read"命令读入的值.
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还是保存着上一个read命令的值,
21 #+ 因为变量$fruit被传入到了这个新的"read"命令中.
22
23 echo
24
25 exit 0
################################End Script######################################### $SECONDS
这个脚本已经运行的时间(单位为秒).
################################Start Script#######################################
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 #+ 在一个while循环中.
22 sleep $INTERVAL
23 done
24
25 echo -e "\a" # Beep!
26
27 exit 0
################################End Script######################################### $SHELLOPTS
这个变量里保存shell允许的选项,这个变量是只读的.
bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs $SHLVL
Shell层次,就是shell层叠的层次,如果是命令行那$SHLVL就是1,如果命令行执行的脚
本中,$SHLVL就是2,以此类推. $TMOUT
如果$TMOUT环境变量被设置为一个非零的时间值,那么在过了这个指定的时间之后,
shell提示符将会超时,这会引起一个logout. 在2.05b版本的Bash中,已经支持在一个带有read命令的脚本中使用$TMOUT变量.
1 # 需要使用Bash v2.05b或者以后的版本上
2
3 TMOUT=3 # Prompt times out at three seconds.
3 TMOUT=3 # 设置超时的时间为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." 这里有一个更复杂的方法来在一个脚本中实现超时功能.一种办法就是建立一个时间循
环,在超时的时候通知脚本.不过,这也需要一个信号处理机制,在超时的时候来产生中
断.
(参见Example 29-5) Example 9-2 时间输入
################################Start Script#######################################
1 #!/bin/bash
2 # timed-input.sh
3
4 # TMOUT=3 在新版本的Bash上也能工作.
5
6
7 TIMELIMIT=3 # 在这个例子上是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 $! # kill将不再需要TimerOn函数运行在后台.
17 # $! 是运行在后台的最后一个工作的PID.
18 fi
19
20 }
21
22
23
24 TimerOn()
25 {
26 sleep $TIMELIMIT && kill -s 14 $$ &
27 # 等待3秒,然后发送一个信号给脚本.
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 #+ 然而使用"-t"选项来"read"的话,将会简化这个任务.
47 # 见"t-out.sh",在下边.
48
49 # 如果你需要一个真正的幽雅的写法...
50 #+ 建议你使用c/c++来写这个应用,
51 #+ 使用合适的库来完成这个任务,比如'alarm'和'setitimer'.
52
53 exit 0
################################End Script#########################################
使用stty也是一种选择. Example 9-3 再来一个时间输入
################################Start Script#######################################
1 #!/bin/bash
2 # timeout.sh
3
4 # Stephane Chazelas编写,
5 #+ 本书作者进行了一些修改.
6
7 INTERVAL=5 # timeout间隔
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 # 或者就是 read $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 # 最大的timeout依赖于具体的终端.
24 #+ (一般都是25.5秒).
25
26 echo
27
28 if [ ! -z "$your_name" ] # If name input before timeout...
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
################################End Script#########################################
或许,最简单的办法就是使用-t选项来read了. Example 9-4 Timed read
################################Start Script#######################################
1 #!/bin/bash
2 # t-out.sh
3 # "syngin seven"的一个很好的提议 (thanks).
4
5
6 TIMELIMIT=4 # 4 seconds
7
8 read -t $TIMELIMIT variable <&1
9 # ^^^
10 # 在这个例子中,对于Bash 1.x和2.x就需要使用"<&1"
11 # 但对于Bash 3.x就不需要.
12
13 echo
14
15 if [ -z "$variable" ] # Is null?
16 then
17 echo "Timed out, variable still unset."
18 else
19 echo "variable = $variable"
20 fi
21
22 exit 0
################################End Script######################################### $UID
用户ID号.
当前用户的id号,在/etc/passwd中记录.
这个值不会因为用户使用了su命令而改变.$UID是只读变量,不容易在命令行或者是脚
本中被修改,并且和内建的id命令很相像.
Example 9-5 我是root?
################################Start Script#######################################
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` # Or... 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
################################End Script#########################################
见例子Example 2-3
注意:变量$ENV,$LOGNAME,$MAIL,$TERM,$USER,和$USERNAME并不是Bash的内建变量.它
们经常被设置成环境变量,它们一般都放在Bash的安装文件中.$SHELL,用户登录的
shell的名字,可能是从/etc/passwd设置的,也可能是在一个"init"脚本中设置的,同样
的,它也不是Bash的内建变量.
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 位置参数
$0, $1, $2,等等...
位置参数,从命令行传递给脚本,或者是传递给函数.或者赋职给一个变量.
(具体见Example 4-5和Example 11-15) $#
命令行或者是位置参数的个数.(见Example 33-2) $*
所有的位置参数,被作为一个单词.
注意:"$*"必须被""引用.
$@
与$*同义,但是每个参数都是一个独立的""引用字串,这就意味着参数被完整地传递,
并没有被解释和扩展.这也意味着,每个参数列表中的每个参数都被当成一个独立的
单词.
注意:"$@"必须被引用. Example 9-6 arglist:通过$*和$@列出所有的参数
################################Start Script#######################################
1 #!/bin/bash
2 # arglist.sh
3 # 多使用几个参数来调用这个脚本,比如"one tow 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.
22 done # $* 认为所有的参数为一个单词
23 echo "Entire arg list seen as single word."
24
25 echo
26
27 index=1 # 重置数量.
28 # 如果你忘了这句会发生什么?
29
30 echo "Listing args with \"\$@\":"
31 for arg in "$@"
32 do
33 echo "Arg #$index = $arg"
34 let "index+=1"
35 done # $@ 认为每个参数都一个单独的单词.
36 echo "Arg list seen as separate words."
37
38 echo
39
40 index=1 # 重置数量.
41
42 echo "Listing args with \$* (unquoted):"
43 for arg in $*
44 do
45 echo "Arg #$index = $arg"
46 let "index+=1"
47 done # 未""引用的$*把参数作为独立的单词.
48 echo "Arg list seen as separate words."
49
50 exit 0
################################End Script######################################### 在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 # "$@" 将包含剩下的参数.
$@也作为为工具使用,用来过滤传给脚本的输入.
cat "$@"结构接受从stdin传来的输入,也接受从参数中指定的文件传来的输入.
具体见Example 12-21和Example 12-22. 注意:$*和$@的参数有时会不一致,发生令人迷惑的行为,这依赖于$IFS的设置. Example 9-7 不一致的$*和$@行为
################################Start Script#######################################
1 #!/bin/bash
2
3 # "$*"和"$@"的古怪行为,
4 #+ 依赖于它们是否被""引用.
5 # 单词拆分和换行的不一致处理.
6
7
8 set -- "First one" "second" "third:one" "" "Fifth: :one"
9 # 设置这个脚本参数,$1,$2,等等.
10
11 echo
12
13 echo 'IFS unchanged, using "$*"'
14 c=0
15 for i in "$*" # 引用
16 do echo "$((c+=1)): [$i]" # 这行在下边的每个例子中都一样.
17 # Echo参数.
18 done
19 echo ---
20
21 echo 'IFS unchanged, using $*'
22 c=0
23 for i in $* # 未引用
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 # 用ksh或者zsh -y来试试这个脚本.
133
134 exit 0
135
136 # This example script by Stephane Chazelas,
137 # and slightly modified by the document author.
################################End Script#########################################
注意:$@和$*中的参数只有在""中才会不同. Example 9-8 当$IFS为空时的$*和$@
################################Start Script#######################################
1 #!/bin/bash
2
3 # 如果$IFS被设置为空时,
4 #+ 那么"$*" 和"$@" 将不会象期望那样echo出位置参数.
5
6 mecho () # Echo 位置参数.
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 # 所以在脚本中使用这种"feature"不是明智的行为.
24
25
26 # Thanks, Stephane Chazelas.
27
28 exit 0
################################End Script######################################### 其他的特殊参数 $-
传递给脚本的falg(使用set命令).参考Example 11-15. 注意:这起初是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 # 所以它们可以被监控,并且在必要的时候kill掉.
7 echo >> "$LOG"
8
9 # Logging 命令.
10
11 echo -n "PID of \"$COMMAND1\": " >> "$LOG"
12 ${COMMAND1} &
13 echo $! >> "$LOG"
14 # PID of "sleep 100": 1506
15
16 # Thank you, Jacques Lederer, for suggesting this.
1 possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
2 # 强制结束一个品行不良的程序.
3 # 很有用,比如在init脚本中.
4
5 # Thank you,Sylvain Fourmanoit,for this creative use of the "!" variable. $_
保存之前执行的命令的最后一个参数. Example 9-9 下划线变量
################################Start Script#######################################
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 $_ # :
################################End Script######################################### $?
命令,函数或者脚本本身的退出状态(见Example 23-7) $$
脚本自身的进程ID.这个变量经常用来构造一个"unique"的临时文件名.
(参考Example A-13,Example 29-6,Example 12-28和Example 11-25).
这通常比调用mktemp来得简单. 注意事项:
[1] 当前运行的脚本的PID为$$.
[2] "argument"和"parameter"这两个单词经常不加区分的使用.在这整本书中,这两个
单词的意思完全相同.(在翻译的时候就未加区分,统统翻译成参数)
9.2 操作字符串
--------------
Bash支持超多的字符串操作,操作的种类和数量令人惊异.但不幸的是,这些工具缺乏集中性.
一些是参数替换的子集,但是另一些则属于UNIX的expr命令.这就导致了命令语法的不一致和
功能的重叠,当然也会引起混乱. 字符串长度 ${#string}
expr length $string
expr "$string" : '.*' 1 stringZ=abcABC123ABCabc
2
3 echo ${#stringZ} # 15
4 echo `expr length $stringZ` # 15
5 echo `expr "$stringZ" : '.*'` # 15 Example 9-10 在一个文本文件的段间插入空行
################################Start Script#######################################
1 #!/bin/bash
2 # paragraph-space.sh
3
4 # 在一个不空行的文本文件的段间插入空行.
5 # Usage: $0 <FILENAME
6
7 MINLEN=45 # 可能需要修改这个值.
8 # 假定行的长度小于$MINLEN指定的长度
9 #+ $MINLEN中的值用来描述多少个字符结束一个段.
10
11 while read line # 对于需要多行输入的文件基本都是这个样子
12 do
13 echo "$line" # 输出line.
14
15 len=${#line}
16 if [ "$len" -lt "$MINLEN" ]
17 then echo # 在短行后边添加一个空行
18 fi
19 done
20
21 exit 0
################################End Script######################################### 从字符串开始的位置匹配子串的长度 expr match "$string" '$substring'
$substring是一个正则表达式
expr "$string" : '$substring'
$substring是一个正则表达式 1 stringZ=abcABC123ABCabc
2 # |------|
3
4 echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8
5 echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8 索引 expr index $string $substring
匹配到子串的第一个字符的位置. 1 stringZ=abcABC123ABCabc
2 echo `expr index "$stringZ" C12` # 6
3 # C position.
4
5 echo `expr index "$stringZ" 1c` # 3
6 # 'c' (in #3 position) matches before '1'. 在C语言中最近的等价函数为strchr(). 提取子串
${string:position}
在string中从位置$position开始提取子串.
如果$string为"*"或"@",那么将提取从位置$position开始的位置参数,[1] ${string:position:length}
在string中从位置$position开始提取$length长度的子串. ################################Start Script#######################################
1 stringZ=abcABC123ABCabc
2 # 0123456789.....
3 # 0-based indexing.
4
5 echo ${stringZ:0} # abcABC123ABCabc
6 echo ${stringZ:1} # bcABC123ABCabc
7 echo ${stringZ:7} # 23ABCabc
8
9 echo ${stringZ:7:3} # 23A
10 # 3个字符长度的子串.
11
12
13
14 # 有没有可能从字符结尾开始,反向提取子串?
15
16 echo ${stringZ:-4} # abcABC123ABCabc
17 # 以${parameter:-default}方式,默认是提取完整地字符串.
18 # 然而 . . .
19
20 echo ${stringZ:(-4)} # Cabc
21 echo ${stringZ: -4} # Cabc
22 # 现在,它可以工作了.
23 # 使用圆括号或者添加一个空格来转义这个位置参数.
24
25 # Thank you, Dan Jacobson, for pointing this out.
################################End Script#########################################
如果$string参数为"*"或"@",那将最大的提取从$position开始的$length个位置参数.
1 echo ${*:2} # Echo出第2个和后边所有的位置参数.
2 echo ${@:2} # 与前边相同.
3
4 echo ${*:2:3} # 从第2个开始,Echo出后边3个位置参数.
expr substr $string $position $length
在string中从位置$position开始提取$length长度的子串.
1 stringZ=abcABC123ABCabc
2 # 123456789......
3 # 1-based indexing.
4
5 echo `expr substr $stringZ 1 2` # ab
6 echo `expr substr $stringZ 4 3` # ABC expr match "$string" '\($substring\)'
从$string的开始位置提取$substring,$substring是一个正则表达式. expr "$string" : '\($substring\)'
从$string的开始位置提取$substring,$substring是一个正则表达式.
1 stringZ=abcABC123ABCabc
2 # =======
3
4 echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
5 echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
6 echo `expr "$stringZ" : '\(.......\)'` # abcABC1
7 # All of the above forms give an identical result. 子串削除 ${string#substring}
从$string的左边截掉第一个匹配的$substring
${string##substring}
从$string的左边截掉最后一个个匹配的$substring 1 stringZ=abcABC123ABCabc
2 # |----|
3 # |----------|
4
5 echo ${stringZ#a*C} # 123ABCabc
6 # 截掉'a'和'C'之间最近的匹配.
7
8 echo ${stringZ##a*C} # abc
9 # 截掉'a'和'C'之间最远的匹配.
${string%substring}
从$string的右边截掉第一个匹配的$substring
${string%%substring}
从$string的右边截掉最后一个匹配的$substring 1 stringZ=abcABC123ABCabc
2 # ||
3 # |------------|
4
5 echo ${stringZ%b*c} # abcABC123ABCa
6 # 从$stringZ的后边开始截掉'b'和'c'之间的最近的匹配
7
8 echo ${stringZ%%b*c} # a
9 # 从$stringZ的后边开始截掉'b'和'c'之间的最远的匹配 Example 9-11 利用修改文件名,来转换图片格式
################################Start Script#######################################
1 #!/bin/bash
2 # cvt.sh:
3 # 把一个目录下的所有MacPaint格式的图片文件都转换为"pbm"格式的图片文件.
4
5 # 使用来自"netpbm"包的"macptopbm"程序,
6 #+ 这个程序主要是由Brian Henderson([email protected])来维护的.
7 # Netpbm是大多数Linux发行版的标准部分.
8
9 OPERATION=macptopbm
10 SUFFIX=pbm # 新的文件名后缀
11
12 if [ -n "$1" ]
13 then
14 directory=$1 # 如果目录名作为第1个参数给出...
15 else
16 directory=$PWD # 否则使用当前的工作目录.
17 fi
18
19 # 假设在目标目录中的所有文件都是MacPaint格式的图片文件,
20 #+ 以".mac"为文件名的后缀.
21
22 for file in $directory/* # Filename globbing.
23 do
24 filename=${file%.*c} # 去掉文件名的".mac"后缀
25 #+ ('.*c' matches everything
25 #+ ('.*c' 将匹配'.'和'c'之间的任何字符串).
26
27 $OPERATION $file > "$filename.$SUFFIX"
28 # 转换为新的文件名.
29 rm -f $file # 转换完毕后删除原有的文件.
30 echo "$filename.$SUFFIX" # 从stdout输出反馈.
31 done
32
33 exit 0
34
35 # 练习:
36 # --------
37 # 就像它现在这个样子,这个脚本把当前目录的所有文件都转换了.
38 #
39 # 修改这个脚本,让他只转换以".mac"为后缀的文件.
################################End Script#########################################
一个简单的模拟getopt命令的办法就是使用子串提取结构.
Example 9-12 模仿getopt命令
################################Start Script#######################################
1 #!/bin/bash
2 # getopt-simple.sh
3 # Author: Chris Morgan
4 # 授权使用在ABS Guide中.
5
6
7 getopt_simple()
8 {
9 echo "getopt_simple()"
10 echo "Parameters are '$*'"
11 until [ -z "$1" ]
12 do
13 echo "Processing parameter of: '$1'"
14 if [ ${1:0:1} = '/' ]
15 then
16 tmp=${1:1} # 去掉开头的'/' . . .
17 parameter=${tmp%%=*} # 提取名字.
18 value=${tmp##*=} # 提取值.
19 echo "Parameter: '$parameter', value: '$value'"
20 eval $parameter=$value
21 fi
22 shift
23 done
24 }
25
26 # 传递所有的选项到getopt_simple().
27 getopt_simple $*
28
29 echo "test is '$test'"
30 echo "test2 is '$test2'"
31
32 exit 0
33
34 ---
35
36 sh getopt_example.sh /test=value1 /test2=value2
37
38 Parameters are '/test=value1 /test2=value2'
39 Processing parameter of: '/test=value1'
40 Parameter: 'test', value: 'value1'
41 Processing parameter of: '/test2=value2'
42 Parameter: 'test2', value: 'value2'
43 test is 'value1'
44 test2 is 'value2'
################################End Script######################################### 子串替换 ${string/substring/replacement}
使用$replacement来替换第一个匹配的$substring.
${string//substring/replacement}
使用$replacement来替换所有匹配的$substring. 1 stringZ=abcABC123ABCabc
2
3 echo ${stringZ/abc/xyz} # xyzABC123ABCabc
4 # 用'xyz'来替换第一个匹配的'abc'.
5
6 echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
7 # 用'xyz'来替换所有匹配的'abc'. ${string/#substring/replacement}
如果$substring匹配$string的开头部分,那么就用$replacement来替换$substring.
${string/%substring/replacement}
如果$substring匹配$string的结尾部分,那么就用$replacement来替换$substring.
1 stringZ=abcABC123ABCabc
2
3 echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
4 # 用'XYZ'替换开头的'abc'
5
6 echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ
7 # 用'XYZ'替换结尾的'abc' 9.2.1 使用awk来操作字符串
~~~~~~~~~~~~~~~~~~~~~~~~~
Bash脚本也可以使用awk来操作字符串. Example 9-13 提取字符串的一种可选的方法
################################Start Script#######################################
1 #!/bin/bash
2 # substring-extraction.sh
3
4 String=23skidoo1
5 # 012345678 Bash
6 # 123456789 awk
7 # 注意,对于awk和Bash来说,它们使用的是不同的string索引系统:
8 # Bash的第一个字符是从'0'开始记录的.
9 # Awk的第一个字符是从'1'开始记录的.
10
11 echo ${String:2:4} # 位置3 (0-1-2), 4 个字符长
12 # skid
13
14 # awk中等价于${string:pos:length}的命令是substr(string,pos,length).
15 echo | awk '
16 { print substr("'"${String}"'",3,4) # skid
17 }
18 '
19 # 使用一个空的"echo"通过管道给awk一个假的输入,
20 #+ 这样可以不用提供一个文件名.
21
22 exit 0
################################End Script######################################### 9.2.2 更深的讨论
~~~~~~~~~~~~~~~~
关于在脚本中使用字符串更深的讨论,请参考 9.3节,h和expr命令列表的相关章节.
关于脚本的例子,见: 1 Example 12-9
2 Example 9-16
3 Example 9-17
4 Example 9-18
5 Example 9-20 注意事项:
[1] 这适用于命令行参数和函数参数. 9.3 参数替换
------------ 操作和扩展变量 ${parameter}
与$parameter相同,就是parameter的值.在特定的上下文中,只有少部分会产生
${parameter}的混淆.可以组合起来一起赋指给字符串变量. 1 your_id=${USER}-on-${HOSTNAME}
2 echo "$your_id"
3 #
4 echo "Old \$PATH = $PATH"
5 PATH=${PATH}:/opt/bin #Add /opt/bin to $PATH for duration of script.
6 echo "New \$PATH = $PATH"
${parameter-default},${parameter:-default}
如果parameter没被set,那么就使用default. 1 echo ${username-`whoami`}
2 # echo `whoami`的结果,如果没set username变量的话.
注意:${parameter-default}和${parameter:-default}大部分时候是相同的.
额外的":"在parameter被声明的时候(而且被赋空值),会有一些不同. ################################Start Script#######################################
1 #!/bin/bash
2 # param-sub.sh
3
4 # 一个变量是否被声明
5 #+ 将会影响默认选项的触发
6 #+ 甚至于这个变量被设为空.
7
8 username0=
9 echo "username0 has been declared, but is set to null."
10 echo "username0 = ${username0-`whoami`}"
11 # 将不会echo.
12
13 echo
14
15 echo username1 has not been declared.
16 echo "username1 = ${username1-`whoami`}"
17 # 将会echo.
18
19 username2=
20 echo "username2 has been declared, but is set to null."
21 echo "username2 = ${username2:-`whoami`}"
22 # ^
23 # 将会echo因为使用的是:-而不是 -.
24 # 和前边的第一个例子好好比较一下.
25
26
27 #
28
29 # 再来一个:
30
31 variable=
32 # 变量已经被声明了,但是被设置为空.
33
34 echo "${variable-0}" # (no output)
35 echo "${variable:-1}" # 1
36 # ^
37
38 unset variable
39
40 echo "${variable-2}" # 2
41 echo "${variable:-3}" # 3
42
43 exit 0
################################End Script######################################### 如果脚本中并没有传入命令行参数,那么default parameter将被使用.
1 DEFAULT_FILENAME=generic.data
2 filename=${1:-$DEFAULT_FILENAME}
3 # 如果没有参数被传递进来,那么下边的命令快将操作
4 #+ 文件"generic.data"
5 #
6 # 后续命令.
另外参见Example 3-4,Example 28-2,和Example A-6.
与"使用一个与列表来支持一个默认的命令行参数"的方法相比较. ${parameter=default},${parameter:=default}
如果parameter未设置,那么就设置为default.
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
才会有区别,[1]和上边的行为一样.
1 echo ${username=`whoami`}
2 # Variable "username" is now set to `whoami`.
2 # 变量"username"被赋值为`whoami`. ${parameter+alt_value},${parameter:+alt_value}
如果parameter被set了,那就使用alt_value,否则就使用null字符串.
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
会有区别,见下. ################################Start Script#######################################
1 echo "###### \${parameter+alt_value} ########"
2 echo
3
4 a=${param1+xyz}
5 echo "a = $a" # a =
6
7 param2=
8 a=${param2+xyz}
9 echo "a = $a" # a = xyz
10
11 param3=123
12 a=${param3+xyz}
13 echo "a = $a" # a = xyz
14
15 echo
16 echo "###### \${parameter:+alt_value} ########"
17 echo
18
19 a=${param4:+xyz}
20 echo "a = $a" # a =
21
22 param5=
23 a=${param5:+xyz}
24 echo "a = $a" # a =
25 # 与a=${param5+xyz}有不同的结果.
26
27 param6=123
28 a=${param6+xyz}
29 echo "a = $a" # a = xyz
################################End Script######################################### ${parameter?err_msg}, ${parameter:?err_msg}
如果parameter被set,那就是用set的值,否则print err_msg.
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
会有区别,见上. Example 9-14 使用参数替换和error messages
################################Start Script#######################################
1 #!/bin/bash
2
3 # 检查一些系统的环境变量.
4 # 这是个好习惯.
5 # 比如,如果$USER(在console上的用户名)没被set,
6 #+ 那么系统就不会认你.
7
8 : ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
9 echo
10 echo "Name of the machine is $HOSTNAME."
11 echo "You are $USER."
12 echo "Your home directory is $HOME."
13 echo "Your mail INBOX is located in $MAIL."
14 echo
15 echo "If you are reading this message,"
16 echo "critical environmental variables have been set."
17 echo
18 echo
19
20 # ------------------------------------------------------
21
22 # ${variablename?} 结果也可以用来
23 #+ 在一个脚本中检查变量是否被set.
24
25 ThisVariable=Value-of-ThisVariable
26 # 注意,顺便提一下,这个字符串变量可能在它们的名字中会被设置
27 #+ 非法字符
28 : ${ThisVariable?}
29 echo "Value of ThisVariable is $ThisVariable".
30 echo
31 echo
32
33
34 : ${ZZXy23AB?"ZZXy23AB has not been set."}
35 # 如果ZZXy23AB没被set,
36 #+ 那么这个脚本将以一个error message终止.
37
38 # 你可以指定错误消息.
39 # : ${variablename?"ERROR MESSAGE"}
40
41
42 # 同样的结果: dummy_variable=${ZZXy23AB?}
43 # dummy_variable=${ZZXy23AB?"ZXy23AB has not been set."}
44 #
45 # echo ${ZZXy23AB?} >/dev/null
46
47 # 同"set -u"命令来比较这些检查变量是否被set的方法.
48 #
49
50
51
52 echo "You will not see this message, because script already terminated."
53
54 HERE=0
55 exit $HERE # Will NOT exit here.
56
57 # 事实上,这个脚本将返回值1作为退出状态(echo $?).
################################End Script######################################### Example 9-15 参数替换和"usage"messages
################################Start Script#######################################
1 #!/bin/bash
2 # usage-message.sh
3
4 : ${1?"Usage: $0 ARGUMENT"}
5 # 如果没有命令行参数,那么脚本将在此处退出.
6 #+ 并且打出如下的错误消息.
7 # usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
8
9 echo "These two lines echo only if command-line parameter given."
10 echo "command line parameter = \"$1\""
11
12 exit 0 # 如果有命令行参数,那么将在此处退出.
13
14 # 测试这个脚本,第1次测试带参数,第2次测试不带参数.
15 # 如果有参数,那么"$?"就是0.
16 # 如果没有,那么"$?"就是1.
################################End Script######################################### 参数替换和扩展
下边的表达式是使用expr字符串匹配操作的补充(见Example 12-9).
这些特定的使用方法绝大多数情况都是用来分析文件目录名. 变量长度/子串删除 ${#var}
字符串长度($var的字符数量).对于一个数组,${#array}是数组中第一个元素的长度. 一些例外:
${#*}和${#@}将给出位置参数的个数.
对于数组来说${#array[*]}和${$#array[@]}将给出数组元素的个数.
Example 9-16 变量长度
################################Start Script#######################################
1 #!/bin/bash
2 # length.sh
3
4 E_NO_ARGS=65
5
6 if [ $# -eq 0 ] # 这个demo脚本必须有命令行参数.
7 then
8 echo "Please invoke this script with one or more command-line arguments."
9 exit $E_NO_ARGS
10 fi
11
12 var01=abcdEFGH28ij
13 echo "var01 = ${var01}"
14 echo "Length of var01 = ${#var01}"
15 # 现在,让我们试试在里边嵌入一个空格.
16 var02="abcd EFGH28ij"
17 echo "var02 = ${var02}"
18 echo "Length of var02 = ${#var02}"
19
20 echo "Number of command-line arguments passed to script = ${#@}"
21 echo "Number of command-line arguments passed to script = ${#*}"
22
23 exit 0
################################End Script######################################### ${var#Pattern}, ${var##Pattern}
从$var开头删除最近或最远匹配$Pattern的子串. 来自Example A-7例子的一部分.
1 # 来自"days-between.sh"例子的一个函数.
2 # 去掉传递进来的参数开头的0.
3
4 strip_leading_zero () # 去掉开头的0
5 { #+ 从传递进来的参数中.
6 return=${1#0} # "1"指的是"$1" -- 传进来的参数.
7 } # "0"就是我们想从"$1"中删除的子串. 下边是Manfred Schwarb's对上边函数的一个改版.
1 strip_leading_zero2 () # 去掉开头的0,因为如果不去掉的话
2 { # Bash将会把这个值作为8进制解释.
3 shopt -s extglob # 打开扩展globbing.
4 local val=${1##+(0)} # 使用局部变量,匹配最长的连续的0.
5 shopt -u extglob # 打开扩展globbing.
6 _strip_leading_zero2=${val:-0}
7 # 如果输入为0,那么返回0来代替"".
8 } 另一个例子
1 echo `basename $PWD` # 当前工作目录的basename.
2 echo "${PWD##*/}" # 当前工作目录的basename.
3 echo
4 echo `basename $0` # 脚本名字.
5 echo $0 # 脚本名字.
6 echo "${0##*/}" # 脚本名字.
7 echo
8 filename=test.data
9 echo "${filename##*.}" # data ${var%Pattern}, ${var%%Pattern}
从$var结尾删除最近或最远匹配$Pattern的子串. Bash version2 添加了额外的选项. Example 9-17 参数替换中的模式匹配
################################Start Script#######################################
1 #!/bin/bash
2 # patt-matching.sh
3
4 # 使用# ## % %%来进行参数替换操作的模式匹配.
5
6 var1=abcd12345abc6789
7 pattern1=a*c # * (通配符) 匹配a - c之间的任何字符.
8
9 echo
10 echo "var1 = $var1" # abcd12345abc6789
11 echo "var1 = ${var1}" # abcd12345abc6789
12 # (alternate form)
13 echo "Number of characters in ${var1} = ${#var1}"
14 echo
15
16 echo "pattern1 = $pattern1" # a*c (everything between 'a' and 'c')
17 echo "--------------"
18 echo '${var1#$pattern1} =' "${var1#$pattern1}" # d12345abc6789
19 # 最短的可能匹配, 去掉abcd12345abc6789的前3个字符
20 # |-| ^^^
21 echo '${var1##$pattern1} =' "${var1##$pattern1}" # 6789
22 # 最远的匹配,去掉abcd12345abc6789的前12个字符.
23 # |----------| ^^^^
24
25 echo; echo; echo
26
27 pattern2=b*9 # 'b' 到'9'之间的任何字符
28 echo "var1 = $var1" # 还是 abcd12345abc6789
29 echo
30 echo "pattern2 = $pattern2"
31 echo "--------------"
32 echo '${var1%pattern2} =' "${var1%$pattern2}" # abcd12345a
33 # 最近的匹配, 去掉abcd12345abc6789的最后6个字符
34 # |----| ^^^^
35 echo '${var1%%pattern2} =' "${var1%%$pattern2}" # a
36 # 最远匹配, 去掉abcd12345abc6789的最后12个字符
37 # |-------------| ^^^^^^
38
39 # 记住, # 和## 从字符串的左边开始,并且去掉左边的字符串,
40 # % 和 %% 从字符串的右边开始,并且去掉右边的子串.
41
42 echo
43
44 exit 0
################################End Script######################################### Example 9-18 重命名文件扩展名
################################Start Script#######################################
1 #!/bin/bash
2 # rfe.sh: 重命名文件扩展名.
3 #
4 # 用法: rfe old_extension new_extension
5 #
6 # 例子:
7 # 将指定目录的所有 *.gif 文件都重命名为 *.jpg,
8 # 用法: rfe gif jpg
9
10
11 E_BADARGS=65
12
13 case $# in
14 0|1) # "|" 在这里的意思是或操作.
15 echo "Usage: `basename $0` old_file_suffix new_file_suffix"
16 exit $E_BADARGS # 如果只有0个或1个参数,那么就退出.
17 ;;
18 esac
19
20
21 for filename in *.$1
22 # 以第一个参数为扩展名的全部文件的列表
23 do
24 mv $filename ${filename%$1}$2
25 # 从筛选出的文件中先去掉以第一参数结尾的扩展名部门,
26 #+ 然后作为扩展名把第2个参数添加上.
27 done
28
29 exit 0
################################End Script######################################### 变量扩展/子串替换
这些结构都是从ksh中吸收来的. ${var:pos}
变量var从位置pos开始扩展. ${var:pos:len}
从位置pos开始,并扩展len长度个字符.见Example A-14(这个例子里有这种操作的一个
创造性用法) ${var/Pattern/Replacement}
使用Replacement来替换var中的第一个Pattern的匹配. ${var//Pattern/Replacement}
全局替换.在var中所有的匹配,都会用Replacement来替换. 向上边所说,如果Replacement被忽略的话,那么所有匹配到的Pattern都会被删除. Example 9-19 使用模式匹配来分析比较特殊的字符串
################################Start Script#######################################
1 #!/bin/bash
2
3 var1=abcd-1234-defg
4 echo "var1 = $var1"
5
6 t=${var1#*-*}
7 echo "var1 (with everything, up to and including first - stripped out) = $t"
8 # t=${var1#*-} 在这个例子中作用是一样的,
9 #+ 因为 # 匹配这个最近的字符串,
10 #+ 并且 * 匹配前边的任何字符串,包括一个空字符.
11 # (Thanks, Stephane Chazelas, for pointing this out.)
12
13 t=${var1##*-*}
14 echo "If var1 contains a \"-\", returns empty string... var1 = $t"
15
16
17 t=${var1%*-*}
18 echo "var1 (with everything from the last - on stripped out) = $t"
19
20 echo
21
22 # -------------------------------------------
23 path_name=/home/bozo/ideas/thoughts.for.today
24 # -------------------------------------------
25 echo "path_name = $path_name"
26 t=${path_name##/*/}
27 echo "path_name, stripped of prefixes = $t"
28 # 在这个特定的例子中,与 t=`basename $path_name` 的作用一致.
29 # t=${path_name%/}; t=${t##*/} 是一个更一般的解决办法,
30 #+ 但有时还是不行.
31 # 如果 $path_name 以一个新行结束, 那么`basename $path_name` 将不能工作,
32 #+ 但是上边这个表达式可以.
33 # (Thanks, S.C.)
34
35 t=${path_name%/*.*}
36 # 与 t=`dirname $path_name` 效果相同.
37 echo "path_name, stripped of suffixes = $t"
38 # 在某些情况下将失效,比如 "../", "/foo////", # "foo/", "/".
39 # 删除后缀,尤其是在basename没有后缀的时候,
40 #+ 但是dirname还是会使问题复杂化.
41 # (Thanks, S.C.)
42
43 echo
44
45 t=${path_name:11}
46 echo "$path_name, with first 11 chars stripped off = $t"
47 t=${path_name:11:5}
48 echo "$path_name, with first 11 chars stripped off, length 5 = $t"
49
50 echo
51
52 t=${path_name/bozo/clown}
53 echo "$path_name with \"bozo\" replaced by \"clown\" = $t"
54 t=${path_name/today/}
55 echo "$path_name with \"today\" deleted = $t"
56 t=${path_name//o/O}
57 echo "$path_name with all o's capitalized = $t"
58 t=${path_name//o/}
59 echo "$path_name with all o's deleted = $t"
60
61 exit 0
################################End Script######################################### ${var/#Pattern/Replacement}
如果var的前缀匹配到了Pattern,那么就用Replacement来替换Pattern. ${var/%Pattern/Replacement}
如果var的后缀匹配到了Pattern,那么就用Replacement来替换Pattern. Example 9-20 对字符串的前缀或后缀使用匹配模式
################################Start Script#######################################
1 #!/bin/bash
2 # var-match.sh:
3 # 对字符串的前后缀使用匹配替换的一个样本
4
5 v0=abc1234zip1234abc # 原始变量.
6 echo "v0 = $v0" # abc1234zip1234abc
7 echo
8
9 # 匹配字符串的前缀
10 v1=${v0/#abc/ABCDEF} # abc1234zip1234abc
11 # |-|
12 echo "v1 = $v1" # ABCDEF1234zip1234abc
13 # |----|
14
15 # 匹配字符串的后缀
16 v2=${v0/%abc/ABCDEF} # abc1234zip123abc
17 # |-|
18 echo "v2 = $v2" # abc1234zip1234ABCDEF
19 # |----|
20
21 echo
22
23 # ----------------------------------------------------
24 # 必须在开头或结尾匹配,否则,
25 #+ 将不会产生替换结果.
26 # ----------------------------------------------------
27 v3=${v0/#123/000} # 匹配上了,但不是在字符串的开头
28 echo "v3 = $v3" # abc1234zip1234abc
29 # 没替换.
30 v4=${v0/%123/000} # 匹配上了,但不是在字符串结尾.
31 echo "v4 = $v4" # abc1234zip1234abc
32 # 没替换.
33
34 exit 0
################################End Script######################################### ${!varprefix*}, ${!varprefix@}
使用变量的前缀来匹配前边所有声明过的变量.
1 xyz23=whatever
2 xyz24=
3
4 a=${!xyz*} # 以"xyz"作为前缀,匹配所有前边声明过的变量.
5 echo "a = $a" # a = xyz23 xyz24
6 a=${!xyz@} # 同上.
7 echo "a = $a" # a = xyz23 xyz24
8
9 # Bash, version 2.04, 添加了这个特征. 注意事项:
[1] 如果在一个非交互脚本中,$parameter为空的话,那么这个脚本将以127返回.
(127退出码对应的Bash错误码为"command not found"). 9.4 指定类型的变量:declare或者typeset
-------------------------------------
declare或者typeset内建命令(这两个命令是完全一样的)允许指定变量的具体类型.在某些特
定的语言中,这是一种指定类型的很弱的形式.declare命令是在Bash版本2或之后的版本才被
加入的.typeset命令也可以工作在ksh脚本中. declare/typeset 选项 -r 只读
1 declare -r var1
(declare -r var1与readonly var1是完全一样的)
这和C语言中的const关键字一样,都是强制指定只读.如果你尝试修改一个只读变量
的值,那么你将得到一个错误消息. -i 整形
1 declare -i number
2 # 这个脚本将把变量"number"后边的赋值视为一个整形.
3
4 number=3
5 echo "Number = $number" # Number = 3
6
7 number=three
8 echo "Number = $number" # Number = 0
9 # 尝试把"three"解释为整形. 如果把一个变量指定为整形,那么即使没有expr和let命令,也允许使用特定的算术运算
1 n=6/3
2 echo "n = $n" # n = 6/3
3
4 declare -i n
5 n=6/3
6 echo "n = $n" # n = 2 -a 数组
1 declae -a indices
变量indices将被视为数组. -f 函数
1 declare -f
如果使用declare -f而不带参数的话,将会列出这个脚本中之前定义的所有函数.
1 declare -f function_name
如果使用declare -f function_name这种形式的话,将只会列出这个函数的名字. -x export
1 declare -x var3
这种使用方式,将会把var3 export出来. Example 9-21 使用declare来指定变量的类型
################################Start Script#######################################
1 #!/bin/bash
2
3 func1 ()
4 {
5 echo This is a function.
6 }
7
8 declare -f # 列出之前的所有函数.
9
10 echo
11
12 declare -i var1 # var1 是个整形.
13 var1=2367
14 echo "var1 declared as $var1"
15 var1=var1+1 # 变量声明不需使用'let'命令.
16 echo "var1 incremented by 1 is $var1."
17 # 尝试将变量修改为整形.
18 echo "Attempting to change var1 to floating point value, 2367.1."
19 var1=2367.1 # 结果将是一个错误消息,并且变量并没有被修改.
20 echo "var1 is still $var1"
21
22 echo
23
24 declare -r var2=13.36 # 'declare' 允许设置变量的属性,
25 #+ 并且同时分配变量的值.
26 echo "var2 declared as $var2" # 尝试修改只读变量.
27 var2=13.37 # 产生一个错误消息,并且从脚本退出了.
28
29 echo "var2 is still $var2" # 这行将不会被执行.
30
31 exit 0 # 脚本将不会在此处退出.
################################End Script######################################### 注意:使用declare内建命令将会限制变量的作用域.
1 foo ()
2 {
3 FOO="bar"
4 }
5
6 bar ()
7 {
8 foo
9 echo $FOO
10 }
11
12 bar # Prints bar. 然而...
1 foo (){
2 declare FOO="bar"
3 }
4
5 bar ()
6 {
7 foo
8 echo $FOO
9 }
10
11 bar # Prints nothing.
12
13
14 # Thank you, Michael Iatrou, for pointing this out. 9.5 变量的间接引用
------------------
假设一个变量的值是另一个变量的名字.我们有可能从第一个变量中取得第2个变量的值么?
比如,如果a=letter_of_alphabet接着letter_of_alphabet=z,那么我们能从a中得到z么?
答案是:当然可以,并且这被称为间接引用.它使用一个不常用的符号eval var1=\$$var2.
Example 9-22 间接引用
################################Start Script#######################################
1 #!/bin/bash
2 # ind-ref.sh: 间接变量引用
3 # 存取一个变量的值的值(这里翻译得有点拗口,不过凑合吧)
4
5 a=letter_of_alphabet # 变量"a"的值是另一个变量的名字.
6 letter_of_alphabet=z
7
8 echo
9
10 # 直接引用.
11 echo "a = $a" # a = letter_of_alphabet
12
13 # 间接引用.
14 eval a=\$$a
15 echo "Now a = $a" # Now a = z
16
17 echo
18
19
20 # 现在,让我们试试修改第2个引用的值.
21
22 t=table_cell_3
23 table_cell_3=24
24 echo "\"table_cell_3\" = $table_cell_3" # "table_cell_3" = 24
25 echo -n "dereferenced \"t\" = "; eval echo \$$t # 解引用 "t" = 24
26 # 在这个简单的例子中,下边的表达式也能正常工作(为什么?).
27 # eval t=\$$t; echo "\"t\" = $t"
28
29 echo
30
31 t=table_cell_3
32 NEW_VAL=387
33 table_cell_3=$NEW_VAL
34 echo "Changing value of \"table_cell_3\" to $NEW_VAL."
35 echo "\"table_cell_3\" now $table_cell_3"
36 echo -n "dereferenced \"t\" now "; eval echo \$$t
37 # "eval" 将获得两个参数 "echo" 和 "\$$t" (与$table_cell_3等价)
38
39 echo
40
41 # (Thanks, Stephane Chazelas, 澄清了上边的行为.)
42
43
44 # 另一个方法是使用${!t}符号,见"Bash, 版本2"小节.
45 # 也请参阅ex78.sh.
46
47 exit 0
################################End Script#########################################
间接应用到底有什么应用价值?它给Bash添加了一种类似于C语言指针的功能,在Example 34-3
中有例子.并且,还有一些其它的有趣的应用.... Nils Radtke展示了如何建立一个"dynamic"变量名字并且取出其中的值.当sourcing(包含)配置
文件时,这很有用.
################################Start Script#######################################
1 #!/bin/bash
2
3
4 # ---------------------------------------------
5 # 这部分内容可能来自于单独的文件.
6 isdnMyProviderRemoteNet=172.16.0.100
7 isdnYourProviderRemoteNet=10.0.0.10
8 isdnOnlineService="MyProvider"
9 # ---------------------------------------------
10
11
12 remoteNet=$(eval "echo \$$(echo isdn${isdnOnlineService}RemoteNet)")
13 remoteNet=$(eval "echo \$$(echo isdnMyProviderRemoteNet)")
14 remoteNet=$(eval "echo \$isdnMyProviderRemoteNet")
15 remoteNet=$(eval "echo $isdnMyProviderRemoteNet")
16
17 echo "$remoteNet" # 172.16.0.100
18
19 # ================================================================
20
21 # 同时,它甚至能更好.
21 #
22
23 # 考虑下边的脚本,给出了一个变量getSparc,
24 #+ 但是没给出变量getIa64:
25
26 chkMirrorArchs () {
27 arch="$1";
28 if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
29 sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
30 sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
31 then
32 return 0;
33 else
34 return 1;
35 fi;
36 }
37
38 getSparc="true"
39 unset getIa64
40 chkMirrorArchs sparc
41 echo $? # 0
42 # True
43
44 chkMirrorArchs Ia64
45 echo $? # 1
46 # False
47
48 # 注意:
49 # -----<rojy bug>
50 # Even the to-be-substituted variable name part is built explicitly.
51 # The parameters to the chkMirrorArchs calls are all lower case.
52 # The variable name is composed of two parts: "get" and "Sparc" . . .
################################End Script######################################### Example 9-23 传递一个间接引用给awk
################################Start Script#######################################
1 #!/bin/bash
2
3 # "column totaler"脚本的另一个版本
4 #+ 这个版本在目标文件中添加了一个特殊的列(数字的).
5 # 这个脚本使用了间接引用.
6
7 ARGS=2
8 E_WRONGARGS=65
9
10 if [ $# -ne "$ARGS" ] # 检查命令行参数是否是合适的个数.
11 then
12 echo "Usage: `basename $0` filename column-number"
13 exit $E_WRONGARGS
14 fi
15
16 filename=$1
17 column_number=$2
18
19 #===== 上边的这部分,与原来的脚本一样 =====#
20
21
22 # 一个多行的awk脚本被调用,通过 ' ..... '
23
24
25 # awk 脚本开始.
26 # ------------------------------------------------
27 awk "
28
29 { total += \$${column_number} # 间接引用.
30 }
31 END {
32 print total
33 }
34
35 " "$filename"
36 # ------------------------------------------------
37 # awk 脚本结束.
38
39 # 间接的变量引用避免了在一个内嵌的awk脚本中引用
40 #+ 一个shell变量的问题.
41 # Thanks, Stephane Chazelas.
42
43
44 exit 0
################################End Script#########################################
注意: 这个脚本有些狡猾.如果第2个变量修改了它的值,那么第一个变量必须被适当的解引用
(像上边的例子一样).幸运的是,在Bash版本2中引入的${!variable}(参见Example 34-2)
是的间接引用更加直观了. 注意: Bash并不支持指针的算术运算,并且这严格的限制了间接引用的使用.事实上,在脚本语言
中,间接引用本来就是丑陋的部分. 9.6 $RANDOM: 产生随机整数
-------------------------
$RANDOM是Bash的内部函数(并不是常量),这个函数将返回一个范围在0 - 32767之间的一个伪
随机整数.它不应该被用来产生密匙. Example 9-24 产生随机数
################################Start Script#######################################
1 #!/bin/bash
2
3 # $RANDOM 在每次调用的时候,返回一个不同的随机整数.
4 # 指定的范围是: 0 - 32767 (有符号的16-bit 整数).
5
6 MAXCOUNT=10
7 count=1
8
9 echo
10 echo "$MAXCOUNT random numbers:"
11 echo "-----------------"
12 while [ "$count" -le $MAXCOUNT ] # 产生10 ($MAXCOUNT) 个随机整数.
13 do
14 number=$RANDOM
15 echo $number
16 let "count += 1" # 数量加1.
17 done
18 echo "-----------------"
19
20 # 如果你需要在一个特定范围内产生一个随机int,那么使用'modulo'(模)操作.
21 # 这将返回一个除法操作的余数.
22
23 RANGE=500
24
25 echo
26
27 number=$RANDOM
28 let "number %= $RANGE"
29 # ^^
30 echo "Random number less than $RANGE --- $number"
31
32 echo
33
34
35
36 # 如果你需要产生一个比你指定的最小边界大的随机数,
37 #+ 那么建立一个test循环,来丢弃所有产生对比这个数小的随机数.
38
39 FLOOR=200
40
41 number=0 #initialize
42 while [ "$number" -le $FLOOR ]
43 do
44 number=$RANDOM
45 done
46 echo "Random number greater than $FLOOR --- $number"
47 echo
48
49 # 让我们对上边的循环尝试一个小改动,也就是
50 # 让"number = $RANDOM + $FLOOR"
51 # 这将不再需要那个while循环,并且能够运行得更快.
52 # 但是, 这可能会产生一个问题.那么这个问题是什么呢?(译者:这很简单,有可能溢出)
53
54
55
56 # 结合上边两个例子的技术,来达到获得在指定的上下限之间来产生随机数.
57 number=0 #initialize
58 while [ "$number" -le $FLOOR ]
59 do
60 number=$RANDOM
61 let "number %= $RANGE" # 让$number依比例落在$RANGE范围内.
62 done
63 echo "Random number between $FLOOR and $RANGE --- $number"
64 echo
65
66
67
68 # 产生一个二元选择,就是"true"和"false"两个值.
69 BINARY=2
70 T=1
71 number=$RANDOM
72
73 let "number %= $BINARY"
74 # 注意,让"number >>= 14" 将给出一个更好的随机分配
75 #+ (右移14位将把所有为全部清空,除了第15位,因为有符号,所以第16位是符号位).
76 if [ "$number" -eq $T ]
77 then
78 echo "TRUE"
79 else
80 echo "FALSE"
81 fi
82
83 echo
84
85
86 # 抛骰子
87 SPOTS=6 # 模6给出的范围就是0-5.
88 # 加1就会得到期望的范围1 - 6.
89 # Thanks, Paulo Marcel Coelho Aragao, for the simplification.
90 die1=0
91 die2=0
92 # 是否让SPOTS=7比加1更好呢?解释行或者不行的原因?
93
94 # 每次抛骰子,都会给出均等的机会.
95
96 let "die1 = $RANDOM % $SPOTS +1" # 抛第一次.
97 let "die2 = $RANDOM % $SPOTS +1" # 抛第二次.
98 # 上边的那个算术操作,具有更高的优先级呢 --
99 #+ 模操作(%)还是加法操作(+)?
100
101
102 let "throw = $die1 + $die2"
103 echo "Throw of the dice = $throw"
104 echo
105
106
107 exit 0
################################End Script######################################### Example 9-25 从一副扑克牌中取出一张随机的牌
################################Start Script#######################################
1 #!/bin/bash
2 # pick-card.sh
3
4 # 这是一个从数组中取出随机元素的一个例子.
5
6
7 # 取出一张牌,任何一张.
8
9 Suites="Clubs
10 Diamonds
11 Hearts
12 Spades"
13
14 Denominations="2
15 3
16 4
17 5
18 6
19 7
20 8
21 9
22 10
23 Jack
24 Queen
25 King
26 Ace"
27
28 # 注意变量的多行展开.
29
30
31 suite=($Suites) # 读到数组变量中.
32 denomination=($Denominations)
33
34 num_suites=${#suite[*]} # 计算有多少个元素.
35 num_denominations=${#denomination[*]}
36
37 echo -n "${denomination[$((RANDOM%num_denominations))]} of "
38 echo ${suite[$((RANDOM%num_suites))]}
39
40
41 # $bozo sh pick-cards.sh
42 # Jack of Clubs
43
44
45 # Thank you, "jipe," for pointing out this use of $RANDOM.
46 exit 0
################################End Script######################################### Jipe展示了一系列的在一定范围中产生随机数的方法.
1 # 在6到30之间产生随机数.
2 rnumber=$((RANDOM%25+6))
3
4 # 还是产生6-30之间的随机数,
5 #+ 但是这个数字必须被3均分.
6 rnumber=$(((RANDOM%30/3+1)*3))
7
8 # 注意,这可能不会在所有时候都能正常地运行.
9 # It fails if $RANDOM returns 0.
10
11 # Frank Wang 建议用下班的方法来取代:
12 rnumber=$(( RANDOM%27/3*3+6 )) Bill Gradwohl 提出了一个重要的规则来产生正数.
1 rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min)) 这里Bill给出了一个通用函数,这个函数返回一个在两个指定值之间的随机数 Example 9-26 两个指定值之间的随机数
################################Start Script#######################################
1 #!/bin/bash
2 # random-between.sh
3 # 在两个指定值之间的随机数.
4 # Bill Gradwohl编写的本脚本,本文作者作了较小的修改.
5 # 允许使用.
6
7
8 randomBetween() {
9 # 产生一个正的或者负的随机数.
10 #+ 在$max和$max之间
11 #+ 并且可被$divisibleBy整除的.
12 # 给出一个合理的随机分配的返回值.
13 #
14 # Bill Gradwohl - Oct 1, 2003
15
16 syntax() {
17 # 在函数中内嵌函数
18 echo
19 echo "Syntax: randomBetween [min] [max] [multiple]"
20 echo
21 echo "Expects up to 3 passed parameters, but all are completely optional."
22 echo "min is the minimum value"
23 echo "max is the maximum value"
24 echo "multiple specifies that the answer must be a multiple of this value."
25 echo " i.e. answer must be evenly divisible by this number."
26 echo
27 echo "If any value is missing, defaults area supplied as: 0 32767 1"
28 echo "Successful completion returns 0, unsuccessful completion returns"
29 echo "function syntax and 1."
30 echo "The answer is returned in the global variable randomBetweenAnswer"
31 echo "Negative values for any passed parameter are handled correctly."
32 }
33
34 local min=${1:-0}
35 local max=${2:-32767}
36 local divisibleBy=${3:-1}
37 # 默认值分配,用来处理没有参数传递进来的时候.
38
39 local x
40 local spread
41
42 # 确认divisibleBy是正值.
43 [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))
44
45 # 完整性检查.
46 if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then
47 syntax
48 return 1
49 fi
50
51 # 察看是否min和max颠倒了.
52 if [ ${min} -gt ${max} ]; then
53 # 交换它们.
54 x=${min}
55 min=${max}
56 max=${x}
57 fi
58
59 # 如果min自己并不能够被$divisibleBy整除,
60 #+ 那么就调整min的值,使其能够被$divisibleBy整除,前提是不能放大范围.
61 if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
62 if [ ${min} -lt 0 ]; then
63 min=$((min/divisibleBy*divisibleBy))
64 else
65 min=$((((min/divisibleBy)+1)*divisibleBy))
66 fi
67 fi
68
69 # 如果min自己并不能够被$divisibleBy整除,
70 #+ 那么就调整max的值,使其能够被$divisibleBy整除,前提是不能放大范围.
71 if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
72 if [ ${max} -lt 0 ]; then
73 max=$((((max/divisibleBy)-1)*divisibleBy))
74 else
75 max=$((max/divisibleBy*divisibleBy))
76 fi
77 fi
78
79 # ---------------------------------------------------------------------
80 # 现在,来做真正的工作.
81
82 # 注意,为了得到对于端点来说合适的分配,
83 #+ 随机值的范围不得不落在
84 #+ 0 和 abs(max-min)+divisibleBy之间, 而不是 abs(max-min)+1.
85
86 # 对于端点来说,
87 #+ 这个少量的增加将会产生合适的分配.
88
89 # 修改这个公式,使用abs(max-min)+1来代替abs(max-min)+divisibleBy的话,
90 #+ 也能够产生正确的答案, 但是在这种情况下生成的随机值对于正好为端点倍数
91 #+ 的这种情况来说将是不完美的,因为在正好为端点倍数的情况的随机率比较低,
92 #+ 因为你才加1而已,这比正常的公式所产生的机率要小得多(正常为加divisibleBy)
93 # ---------------------------------------------------------------------
94
95 spread=$((max-min))
96 [ ${spread} -lt 0 ] && spread=$((0-spread))
97 let spread+=divisibleBy
98 randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
99
100 return 0
101
102 # 然而,Paulo Marcel Coelho Aragao指出
103 #+ 当$max和$min不能被$divisibleBy整除时,
104 #+ 这个公式将会失败.
105 #
106 # 他建议使用如下的公式:
107 # rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))
108
109 }
110
111 # 让我们测试一下这个函数.
112 min=-14
113 max=20
114 divisibleBy=3
115
116
117 # 产生一个数组answers,answers的下标用来表示在范围内可能出现的值,
118 #+ 而内容记录的是对于这个值出现的次数,如果我们循环足够多次,一定会得到
119 #+ 一次出现机会.
120 declare -a answer
121 minimum=${min}
122 maximum=${max}
123 if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
124 if [ ${minimum} -lt 0 ]; then
125 minimum=$((minimum/divisibleBy*divisibleBy))
126 else
127 minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
128 fi
129 fi
130
131
132 # 如果maximum自己并不能够被$divisibleBy整除,
133 #+ 那么就调整maximum的值,使其能够被$divisibleBy整除,前提是不能放大范围.
134
135 if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
136 if [ ${maximum} -lt 0 ]; then
137 maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
138 else
139 maximum=$((maximum/divisibleBy*divisibleBy))
140 fi
141 fi
142
143
144 # 我们需要产生一个下标全为正的数组,
145 #+ 所以我们需要一个displacement来保正都为正的结果.
146
147
148 displacement=$((0-minimum))
149 for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
150 answer[i+displacement]=0
151 done
152
153
154 # 现在我们循环足够多的次数来得到我们想要的答案.
155 loopIt=1000 # 脚本作者建议 100000,
156 #+ 但是这实在是需要太长的时间了.
157
158 for ((i=0; i<${loopIt}; ++i)); do
159
160 # 注意,我们在这里调用randomBetween函数时,故意将min和max颠倒顺序
161 #+ 我们是为了测试在这种情况下,此函数是否还能得到正确的结果.
162
163 randomBetween ${max} ${min} ${divisibleBy}
164
165 # 如果答案不是我们所预期的,那么就报告一个错误.
166 [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo MIN or MAX error - ${randomBetweenAnswer}!
167 [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo DIVISIBLE BY error - ${randomBetweenAnswer}!
168
169 # 将统计值存到answer之中.
170 answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
171 done
172
173
174
175 # 让我们察看一下结果
176
177 for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
178 [ ${answer[i+displacement]} -eq 0 ] && echo "We never got an answer of $i." || echo "${i} occurred ${answer[i+displacement]} times."
179 done
180
181
182 exit 0
################################End Script######################################### $RANDOM到底有多随机?最好的办法就是写个脚本来测试一下.跟踪随机数的分配情况.
让我们用随机数摇一个骰子.
Example 9-27 使用随机数来摇一个骰子
################################Start Script#######################################
1 #!/bin/bash
2 # RANDOM到底有多random?
3
4 RANDOM=$$ # 使用脚本的进程ID来作为随机数的产生种子.
5
6 PIPS=6 # 一个骰子有6面.
7 MAXTHROWS=600 # 如果你没别的事干,那么可以增加这个数值.
8 throw=0 # 抛骰子的次数.
9
10 ones=0 # 必须把所有count都初始化为0,
11 twos=0 #+ 因为未初始化的变量为null,不是0.
12 threes=0
13 fours=0
14 fives=0
15 sixes=0
16
17 print_result ()
18 {
19 echo
20 echo "ones = $ones"
21 echo "twos = $twos"
22 echo "threes = $threes"
23 echo "fours = $fours"
24 echo "fives = $fives"
25 echo "sixes = $sixes"
26 echo
27 }
28
29 update_count()
30 {
31 case "$1" in
32 0) let "ones += 1";; # 因为骰子没有0,所以给1.
33 1) let "twos += 1";; # 对tows做同样的事.
34 2) let "threes += 1";;
35 3) let "fours += 1";;
36 4) let "fives += 1";;
37 5) let "sixes += 1";;
38 esac
39 }
40
41 echo
42
43
44 while [ "$throw" -lt "$MAXTHROWS" ]
45 do
46 let "die1 = RANDOM % $PIPS"
47 update_count $die1
48 let "throw += 1"
49 done
50
51 print_result
52
53 exit 0
54
55 # 如果RANDOM是真正的随机,那么摇出来结果应该平均的.
56 # $MAXTHROWS设为600,那么每面都应该为100,上下的出入不应该超过20.
57 #
58 # 记住RANDOM毕竟只是一个伪随机数,
59 #+ 并且不是十分完美的.
60
61 # 随机数的产生是一个深奥并复杂的问题.
62 # 足够长的随机序列,不但会展现杂乱无章的一面,
63 #+ 而且会展现机会均等的一面.
64
65 # 一个很简单的练习:
66 # -----------------
67 # 重写这个例子,做成抛1000次硬币的形式.
68 # 分为正反两面.
################################End Script#########################################
像我们在上边的例子中看到的,最好在每次随机数产生时都使用新的种子.应为如果使用同样的
种子的话,那么随机数将产生相同的序列.[2](C中random()函数也会有这样的行为) Example 9-28 重新分配随机数种子
################################Start Script#######################################
1 #!/bin/bash
2 # seeding-random.sh: 设置RANDOM变量作为种子.
3
4 MAXCOUNT=25 # 决定产生多少个随机数.
5
6 random_numbers ()
7 {
8 count=0
9 while [ "$count" -lt "$MAXCOUNT" ]
10 do
11 number=$RANDOM
12 echo -n "$number "
13 let "count += 1"
14 done
15 }
16
17 echo; echo
18
19 RANDOM=1 # 为随机数的产生设置RANDOM种子.
20 random_numbers
21
22 echo; echo
23
24 RANDOM=1 # 设置同样的种子...
25 random_numbers # ...将会和上边产生的随机数列相同.
26 #
27 # 复制一个相同的随机数序列在什么时候有用呢?
28
29 echo; echo
30
31 RANDOM=2 # 再试一下,但这次使用不同的种子...
32 random_numbers # 将给出一个不同的随机数序列.
33
34 echo; echo
35
36 # RANDOM=$$ 使用脚本的进程id 作为随机数的种子.
37 # 从'time'或'date'命令中取得RANDOM作为种子也是很常用的办法.
38
39 # 一个有想象力的方法...
40 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
41 # 首先从/dev/urandom(系统伪随机设备文件)中取出1行,
42 #+ 然后着这个可打印行转换为(8进制)数,通过使用"od"命令,
43 #+ 最后使用"awk"来获得一个数,
44 #+ 这个数将作为随机数产生的种子.
45 RANDOM=$SEED
46 random_numbers
47
48 echo; echo
49
50 exit 0
################################End Script#########################################
注意:/dev/urandom设备文件提供了一种比单独使用$RANDOM更好的,能产生更"随机"的随机数
的方法.
dd if=/dev/urandom of=targetfile bs=1 count=XX能够产生一个很分散的为随机数.
然而,将这个数赋值到一个脚本文件的变量中,还需要可操作性,比如使用"od"命令
(就像上边的例子,见Example 12-13),或者使用dd命令(见Example 12-55),或者管道到
"md5sum"命令中(见Example 33-14).
当然还有其它的产生伪随机数的方法.Awk就可以提供一个方便的方法.
Example 9-29 使用awk产生伪随机数
################################Start Script#######################################
1 #!/bin/bash
2 # random2.sh: 产生一个范围0 - 1的为随机数.
3 # 使用awk的rand()函数.
4
5 AWKSCRIPT=' { srand(); print rand() } '
6 # Command(s) / 传到awk中的参数
7 # 注意,srand()函数用来产生awk的随机数种子.
8
9
10 echo -n "Random number between 0 and 1 = "
11
12 echo | awk "$AWKSCRIPT"
13 # 如果你省去'echo'那么将发生什么?
14
15 exit 0
16
17
18 # Exercises:
18 # 练习:
19 # -----
20
21 # 1) 使用循环结构,打印出10个不同的随机数.
22 # (提示: 在循环的每次执行过程中,你必须使用"srand()"函数来生成不同的
23 #+ 种子.如果你没做这件事那么将发生什么?
24
25 # 2) 使用一个整数乘法作为一个放缩因子,在10到100的范围之间,
26 #+ 来产生随机数.
27
28 # 3) 同上边的练习 #2,但这次产生随机整数.
################################End Script#########################################
"data"命令也可以用来产生伪随机整数序列. 注意事项:
[1] 真正的随机事件(在它存在的范围内),只发生在特定的几个未知的自然界现象中,比如
放射性衰变.计算机只能产生模拟的随机事件,并且计算机产生的"随机"数因此只能称
为伪随机数.
[2] 计算机产生的伪随机数序列用的种子可以被看成是一种标识标签.比如,使用种子23所
产生的伪随机数序列就被称作序列#23.
一个伪随机序列的特点就是在这个序列开始重复之前的所有元素的个数的和,也就是
这个序列的长度.一个好的伪随机产生算法将可以产生一个非常长的不重复的序列.
9.7 双圆括号结构
----------------
((...))与let命令很像,允许算术扩展和赋值.举个简单的例子a=$(( 5 + 3 )),将把a设为
"5+3"或者8.然而,双圆括号也是一种在Bash中允许使用C风格的变量处理的机制. Example 9-30 C风格的变量处理
################################Start Script#######################################
1 #!/bin/bash
2 # 处理一个变量,C风格,使用((...))结构.
3
4
5 echo
6
7 (( a = 23 )) # 给一个变量赋值,从"="两边的空格就能看出这是c风格的处理.
8 echo "a (initial value) = $a"
9
10 (( a++ )) # 变量'a'后加1,C风格.
11 echo "a (after a++) = $a"
12
13 (( a-- )) # 变量'a'后减1,C风格.
14 echo "a (after a--) = $a"
15
16
17 (( ++a )) # 变量'a'预加1,C风格.
18 echo "a (after ++a) = $a"
19
20 (( --a )) # 变量'a'预减1,C风格.
21 echo "a (after --a) = $a"
22
23 echo
24
25 ########################################################
26 # 注意:在C语言中,预减和后减操作
27 #+ 会有些不同的副作用.
28
29 n=1; let --n && echo "True" || echo "False" # False
30 n=1; let n-- && echo "True" || echo "False" # True
31
32 # Thanks, Jeroen Domburg.
33 ########################################################
34
35 echo
36
37 (( t = a<45?7:11 )) # C风格的3元操作.
38 echo "If a < 45, then t = 7, else t = 11."
39 echo "t = $t " # Yes!
40
41 echo
42
43
44 # ----------------
45 # 复活节彩蛋注意!
46 # ----------------
47 # Chet Ramey 显然的偷偷摸摸的做了一些未公开的C风格的结构
48 #+ 放在Bash中(准确地说是根据ksh来改写的,这更接近些)
49 # 在Bash文档中,Ramey调用((...))shell算法,
50 #+ 但是它可以走得更远.
51 # 对不起, Chet, 现在秘密被公开了.
52
53 # See also "for" and "while" loops using the ((...)) construct.
53 # 也参考一些"for"和"while"循环中使用((...))结构的例子.
54
55 # 这些只能工作在2.04或者更高版本的Bash中.
56
57 exit 0
################################End Script#########################################
见Example 10-12.
++++++++++++++++++++
第9章 变量重游
================
如果变量使用恰当,将会增加脚本的能量和灵活性.但是前提是这需要仔细学习变量的细节知识.
9.1 内部变量
------------
Builtin variable
这些内建的变量,将影响bash脚本的行为. $BASH
这个变量将指向Bash的二进制执行文件的位置.
bash$ echo $BASH
/bin/bash $BASH_ENV
这个环境变量将指向一个Bash启动文件,这个启动文件将在调用一个脚本时被读取. $BASH_SUBSHELL
这个变量将提醒subshell的层次,这是一个在version3才被添加到Bash中的新特性.
见Example 20-1. $BASH_VERSINFO[n]
记录Bash安装信息的一个6元素的数组.与下边的$BASH_VERSION很像,但这个更加详细.
1 # Bash version info:
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 # Patch 次数.
11 # BASH_VERSINFO[3] = 1 # Build version.
12 # BASH_VERSINFO[4] = release # Release status.
13 # BASH_VERSINFO[5] = i386-redhat-linux-gnu # Architecture $BASH_VERSION
安装在系统上的Bash的版本号.
bash$ echo $BASH_VERSION
3.00.14(1)-release
tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.
使用这个变量对于判断系统上到底运行的是那个shll来说是一种非常好的办法.$SHELL
有时将不能给出正确的答案. $DIRSTACK
在目录栈中最上边的值(将受到pushd和popd的影响).
这个内建的变量与dirs命令是保持一致的,但是dirs命令将显示目录栈的整个内容. $EDITOR
脚本调用的默认编辑器,一般是vi或者是emacs. $EUID
"effective"用户ID号.
当前用户被假定的任何id号.可能在su命令中使用.
注意:$EUID并不一定与$UID相同. $FUNCNAME
当前函数的名字.
1 xyz23 ()
2 {
3 echo "$FUNCNAME now executing." # xyz23 现在正在被执行.
4 }
5
6 xyz23
7
8 echo "FUNCNAME = $FUNCNAME" # FUNCNAME =
9 # 出了函数就变为Null值了. $GLOBIGNORE
一个文件名的模式匹配列表,如果在file globbing中匹配到的文件包含这个列表中的
某个文件,那么这个文件将被从匹配到的文件中去掉. $GROUPS
当前用户属于的组.
这是一个当前用户的组id列表(数组),就像在/etc/passwd中记录的一样.
root# echo $GROUPS
0 root# echo ${GROUPS[1]}
1 root# echo ${GROUPS[5]}
6 $HOME
用户的home目录,一般都是/home/username(见Example 9-14) $HOSTNAME
hostname命令将在一个init脚本中,在启动的时候分配一个系统名字.
gethostname()函数将用来设置这个$HOSTNAME内部变量.(见Example 9-14) $HOSTTYPE
主机类型
就像$MACHTYPE,识别系统的硬件.
bash$ echo $HOSTTYPE
i686 $IFS
内部域分隔符.
这个变量用来决定Bash在解释字符串时如何识别域,或者单词边界.
$IFS默认为空白(空格,tab,和新行),但可以修改,比如在分析逗号分隔的数据文件时.
注意:$*使用$IFS中的第一个字符,具体见Example 5-1.
bash$ echo $IFS | cat -vte
$ bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
w:x:y:o
注意:$IFS并不像它处理其它字符一样处理空白. Example 9-1 $IFS和空白
################################Start Script#######################################
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 # Thank you, Stephane Chazelas.
42
43 echo
44
45 exit 0
################################End Script#########################################
Example 12-37也是使用$IFS的另一个启发性的例子. $IGNOREEOF
忽略EOF: 告诉shell在log out之前要忽略多少文件结束符(control-D). $LC_COLLATE
常在.bashrc或/etc/profile中设置,这个变量用来在文件名扩展和模式匹配校对顺序.
如果$LC_COLLATE被错误的设置,那么将会在filename globbing中引起错误的结果.
注意:在2.05以后的Bash版本中,filename globbing将不在对[]中的字符区分大小写.
比如:ls [A-M]* 将即匹配File1.txt也会匹配file1.txt.为了恢复[]的习惯用法,
设置$LC_COLLATE的值为c,使用export LC_COLLATE=c 在/etc/profile或者是
~/.bashrc中. $LC_CTYPE
这个内部变量用来控制globbing和模式匹配的字符串解释. $LINENO
这个变量记录它所在的shell脚本中它所在行的行号.这个变量一般用于调试目的.
1 # *** BEGIN DEBUG BLOCK ***
2 last_cmd_arg=$_ # Save it.
3
4 echo "At line number $LINENO, variable \"v1\" = $v1"
5 echo "Last command argument processed = $last_cmd_arg"
6 # *** END DEBUG BLOCK *** $MACHTYPE
系统类型
提示系统硬件
bash$ echo $MACHTYPE
i686 $OLDPWD
老的工作目录("OLD-print-working-directory",你所在的之前的目录) $OSTYPE
操作系统类型.
bash$ echo $OSTYPE
linux $PATH
指向Bash外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等.
当给出一个命令时,Bash将自动对$PATH中的目录做一张hash表.$PATH中以":"分隔的
目录列表将被存储在环境变量中.一般的,系统存储的$PATH定义在/ect/processed或
~/.bashrc中(见Appendix G). bash$ echo $PATH
/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin PATH=${PATH}:/opt/bin将把/opt/bin目录附加到$PATH变量中.在脚本中,这是一个
添加目录到$PATH中的便捷方法.这样在这个脚本退出的时候,$PATH将会恢复(因为这个
shell是个子进程,像这样的一个脚本是不会将它的父进程的环境变量修改的) 注意:当前的工作目录"./"一般都在$PATH中被省去. $PIPESTATUS
数组变量将保存最后一个运行的前台管道的退出码.有趣的是,这个退出码和最后一个命令
运行的退出码并不一定相同.
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 $PIPESTATUS数组的每个成员都会保存一个管道命令的退出码,$PIPESTATUS[0]保存第
一个管道命令的退出码,$PIPESTATUS[1]保存第2个,以此类推. 注意:$PIPESTATUS变量在一个login shell中可能会包含一个错误的0值(3.0以下版本)
tcsh% bash bash$ who | grep nobody | sort
bash$ echo ${PIPESTATUS[*]}
0
包含在脚本中的上边这行将会产生一个期望的输出0 1 0. 注意:在某些上下文$PIPESTATUS可能不会给出正确的结果.
bash$ echo $BASH_VERSION
3.00.14(1)-release bash$ $ ls | bogus_command | wc
bash: bogus_command: command not found
0 0 0 bash$ echo ${PIPESTATUS[@]}
141 127 0
Chet Ramey把上边输出不成确原因归咎于ls的行为.因为如果把ls的结果放到管道上,
并且这个输出没被读取,那么SIGPIPE将会kill掉它,并且退出码变为141,而不是我们期
望的0.这种情况也会发生在tr命令中. 注意:$PIPESTATUS是一个"volatile"变量.在任何命令插入之前,并且在pipe询问之后,
这个变量需要立即被捕捉.
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).[1]
使用pidof命令对比一下. $PROMPT_COMMAND
这个变量保存一个在主提示符($PS1)显示之前需要执行的命令. $PS1
主提示符,具体见命令行上的显示. $PS2
第2提示符,当你需要额外的输入的时候将会显示,默认为">". $PS3
第3提示符,在一个select循环中显示(见Example 10-29). $PS4
第4提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边.
默认为"+". $PWD
工作目录(你当前所在的目录).
与pwd内建命令作用相同.
################################Start Script#######################################
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]* # Delete dotfiles.
21 rm .[A-Za-z0-9]* # 删除"."文件(隐含文件).
22 # rm -f .[^.]* ..?* 为了删除以多个"."开头的文件.
23 # (shopt -s dotglob; rm -f *) 也行.
24 # Thanks, S.C. for pointing this out.
25
26 # 文件名能够包含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
################################End Script######################################### $REPLY
read命令如果没有给变量,那么输入将保存在$REPLY中.在select菜单中也可用,但是只
提供选择的变量的项数,而不是变量本身的值.
################################Start Script#######################################
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 # 当且仅当在没有变量提供给"read"命令时,
12 #+ REPLY才保存最后一个"read"命令读入的值.
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还是保存着上一个read命令的值,
21 #+ 因为变量$fruit被传入到了这个新的"read"命令中.
22
23 echo
24
25 exit 0
################################End Script######################################### $SECONDS
这个脚本已经运行的时间(单位为秒).
################################Start Script#######################################
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 #+ 在一个while循环中.
22 sleep $INTERVAL
23 done
24
25 echo -e "\a" # Beep!
26
27 exit 0
################################End Script######################################### $SHELLOPTS
这个变量里保存shell允许的选项,这个变量是只读的.
bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs $SHLVL
Shell层次,就是shell层叠的层次,如果是命令行那$SHLVL就是1,如果命令行执行的脚
本中,$SHLVL就是2,以此类推. $TMOUT
如果$TMOUT环境变量被设置为一个非零的时间值,那么在过了这个指定的时间之后,
shell提示符将会超时,这会引起一个logout. 在2.05b版本的Bash中,已经支持在一个带有read命令的脚本中使用$TMOUT变量.
1 # 需要使用Bash v2.05b或者以后的版本上
2
3 TMOUT=3 # Prompt times out at three seconds.
3 TMOUT=3 # 设置超时的时间为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." 这里有一个更复杂的方法来在一个脚本中实现超时功能.一种办法就是建立一个时间循
环,在超时的时候通知脚本.不过,这也需要一个信号处理机制,在超时的时候来产生中
断.
(参见Example 29-5) Example 9-2 时间输入
################################Start Script#######################################
1 #!/bin/bash
2 # timed-input.sh
3
4 # TMOUT=3 在新版本的Bash上也能工作.
5
6
7 TIMELIMIT=3 # 在这个例子上是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 $! # kill将不再需要TimerOn函数运行在后台.
17 # $! 是运行在后台的最后一个工作的PID.
18 fi
19
20 }
21
22
23
24 TimerOn()
25 {
26 sleep $TIMELIMIT && kill -s 14 $$ &
27 # 等待3秒,然后发送一个信号给脚本.
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 #+ 然而使用"-t"选项来"read"的话,将会简化这个任务.
47 # 见"t-out.sh",在下边.
48
49 # 如果你需要一个真正的幽雅的写法...
50 #+ 建议你使用c/c++来写这个应用,
51 #+ 使用合适的库来完成这个任务,比如'alarm'和'setitimer'.
52
53 exit 0
################################End Script#########################################
使用stty也是一种选择. Example 9-3 再来一个时间输入
################################Start Script#######################################
1 #!/bin/bash
2 # timeout.sh
3
4 # Stephane Chazelas编写,
5 #+ 本书作者进行了一些修改.
6
7 INTERVAL=5 # timeout间隔
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 # 或者就是 read $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 # 最大的timeout依赖于具体的终端.
24 #+ (一般都是25.5秒).
25
26 echo
27
28 if [ ! -z "$your_name" ] # If name input before timeout...
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
################################End Script#########################################
或许,最简单的办法就是使用-t选项来read了. Example 9-4 Timed read
################################Start Script#######################################
1 #!/bin/bash
2 # t-out.sh
3 # "syngin seven"的一个很好的提议 (thanks).
4
5
6 TIMELIMIT=4 # 4 seconds
7
8 read -t $TIMELIMIT variable <&1
9 # ^^^
10 # 在这个例子中,对于Bash 1.x和2.x就需要使用"<&1"
11 # 但对于Bash 3.x就不需要.
12
13 echo
14
15 if [ -z "$variable" ] # Is null?
16 then
17 echo "Timed out, variable still unset."
18 else
19 echo "variable = $variable"
20 fi
21
22 exit 0
################################End Script######################################### $UID
用户ID号.
当前用户的id号,在/etc/passwd中记录.
这个值不会因为用户使用了su命令而改变.$UID是只读变量,不容易在命令行或者是脚
本中被修改,并且和内建的id命令很相像.
Example 9-5 我是root?
################################Start Script#######################################
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` # Or... 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
################################End Script#########################################
见例子Example 2-3
注意:变量$ENV,$LOGNAME,$MAIL,$TERM,$USER,和$USERNAME并不是Bash的内建变量.它
们经常被设置成环境变量,它们一般都放在Bash的安装文件中.$SHELL,用户登录的
shell的名字,可能是从/etc/passwd设置的,也可能是在一个"init"脚本中设置的,同样
的,它也不是Bash的内建变量.
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 位置参数
$0, $1, $2,等等...
位置参数,从命令行传递给脚本,或者是传递给函数.或者赋职给一个变量.
(具体见Example 4-5和Example 11-15) $#
命令行或者是位置参数的个数.(见Example 33-2) $*
所有的位置参数,被作为一个单词.
注意:"$*"必须被""引用.
$@
与$*同义,但是每个参数都是一个独立的""引用字串,这就意味着参数被完整地传递,
并没有被解释和扩展.这也意味着,每个参数列表中的每个参数都被当成一个独立的
单词.
注意:"$@"必须被引用. Example 9-6 arglist:通过$*和$@列出所有的参数
################################Start Script#######################################
1 #!/bin/bash
2 # arglist.sh
3 # 多使用几个参数来调用这个脚本,比如"one tow 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.
22 done # $* 认为所有的参数为一个单词
23 echo "Entire arg list seen as single word."
24
25 echo
26
27 index=1 # 重置数量.
28 # 如果你忘了这句会发生什么?
29
30 echo "Listing args with \"\$@\":"
31 for arg in "$@"
32 do
33 echo "Arg #$index = $arg"
34 let "index+=1"
35 done # $@ 认为每个参数都一个单独的单词.
36 echo "Arg list seen as separate words."
37
38 echo
39
40 index=1 # 重置数量.
41
42 echo "Listing args with \$* (unquoted):"
43 for arg in $*
44 do
45 echo "Arg #$index = $arg"
46 let "index+=1"
47 done # 未""引用的$*把参数作为独立的单词.
48 echo "Arg list seen as separate words."
49
50 exit 0
################################End Script######################################### 在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 # "$@" 将包含剩下的参数.
$@也作为为工具使用,用来过滤传给脚本的输入.
cat "$@"结构接受从stdin传来的输入,也接受从参数中指定的文件传来的输入.
具体见Example 12-21和Example 12-22. 注意:$*和$@的参数有时会不一致,发生令人迷惑的行为,这依赖于$IFS的设置. Example 9-7 不一致的$*和$@行为
################################Start Script#######################################
1 #!/bin/bash
2
3 # "$*"和"$@"的古怪行为,
4 #+ 依赖于它们是否被""引用.
5 # 单词拆分和换行的不一致处理.
6
7
8 set -- "First one" "second" "third:one" "" "Fifth: :one"
9 # 设置这个脚本参数,$1,$2,等等.
10
11 echo
12
13 echo 'IFS unchanged, using "$*"'
14 c=0
15 for i in "$*" # 引用
16 do echo "$((c+=1)): [$i]" # 这行在下边的每个例子中都一样.
17 # Echo参数.
18 done
19 echo ---
20
21 echo 'IFS unchanged, using $*'
22 c=0
23 for i in $* # 未引用
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 # 用ksh或者zsh -y来试试这个脚本.
133
134 exit 0
135
136 # This example script by Stephane Chazelas,
137 # and slightly modified by the document author.
################################End Script#########################################
注意:$@和$*中的参数只有在""中才会不同. Example 9-8 当$IFS为空时的$*和$@
################################Start Script#######################################
1 #!/bin/bash
2
3 # 如果$IFS被设置为空时,
4 #+ 那么"$*" 和"$@" 将不会象期望那样echo出位置参数.
5
6 mecho () # Echo 位置参数.
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 # 所以在脚本中使用这种"feature"不是明智的行为.
24
25
26 # Thanks, Stephane Chazelas.
27
28 exit 0
################################End Script######################################### 其他的特殊参数 $-
传递给脚本的falg(使用set命令).参考Example 11-15. 注意:这起初是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 # 所以它们可以被监控,并且在必要的时候kill掉.
7 echo >> "$LOG"
8
9 # Logging 命令.
10
11 echo -n "PID of \"$COMMAND1\": " >> "$LOG"
12 ${COMMAND1} &
13 echo $! >> "$LOG"
14 # PID of "sleep 100": 1506
15
16 # Thank you, Jacques Lederer, for suggesting this.
1 possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
2 # 强制结束一个品行不良的程序.
3 # 很有用,比如在init脚本中.
4
5 # Thank you,Sylvain Fourmanoit,for this creative use of the "!" variable. $_
保存之前执行的命令的最后一个参数. Example 9-9 下划线变量
################################Start Script#######################################
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 $_ # :
################################End Script######################################### $?
命令,函数或者脚本本身的退出状态(见Example 23-7) $$
脚本自身的进程ID.这个变量经常用来构造一个"unique"的临时文件名.
(参考Example A-13,Example 29-6,Example 12-28和Example 11-25).
这通常比调用mktemp来得简单. 注意事项:
[1] 当前运行的脚本的PID为$$.
[2] "argument"和"parameter"这两个单词经常不加区分的使用.在这整本书中,这两个
单词的意思完全相同.(在翻译的时候就未加区分,统统翻译成参数)
9.2 操作字符串
--------------
Bash支持超多的字符串操作,操作的种类和数量令人惊异.但不幸的是,这些工具缺乏集中性.
一些是参数替换的子集,但是另一些则属于UNIX的expr命令.这就导致了命令语法的不一致和
功能的重叠,当然也会引起混乱. 字符串长度 ${#string}
expr length $string
expr "$string" : '.*' 1 stringZ=abcABC123ABCabc
2
3 echo ${#stringZ} # 15
4 echo `expr length $stringZ` # 15
5 echo `expr "$stringZ" : '.*'` # 15 Example 9-10 在一个文本文件的段间插入空行
################################Start Script#######################################
1 #!/bin/bash
2 # paragraph-space.sh
3
4 # 在一个不空行的文本文件的段间插入空行.
5 # Usage: $0 <FILENAME
6
7 MINLEN=45 # 可能需要修改这个值.
8 # 假定行的长度小于$MINLEN指定的长度
9 #+ $MINLEN中的值用来描述多少个字符结束一个段.
10
11 while read line # 对于需要多行输入的文件基本都是这个样子
12 do
13 echo "$line" # 输出line.
14
15 len=${#line}
16 if [ "$len" -lt "$MINLEN" ]
17 then echo # 在短行后边添加一个空行
18 fi
19 done
20
21 exit 0
################################End Script######################################### 从字符串开始的位置匹配子串的长度 expr match "$string" '$substring'
$substring是一个正则表达式
expr "$string" : '$substring'
$substring是一个正则表达式 1 stringZ=abcABC123ABCabc
2 # |------|
3
4 echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8
5 echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8 索引 expr index $string $substring
匹配到子串的第一个字符的位置. 1 stringZ=abcABC123ABCabc
2 echo `expr index "$stringZ" C12` # 6
3 # C position.
4
5 echo `expr index "$stringZ" 1c` # 3
6 # 'c' (in #3 position) matches before '1'. 在C语言中最近的等价函数为strchr(). 提取子串
${string:position}
在string中从位置$position开始提取子串.
如果$string为"*"或"@",那么将提取从位置$position开始的位置参数,[1] ${string:position:length}
在string中从位置$position开始提取$length长度的子串. ################################Start Script#######################################
1 stringZ=abcABC123ABCabc
2 # 0123456789.....
3 # 0-based indexing.
4
5 echo ${stringZ:0} # abcABC123ABCabc
6 echo ${stringZ:1} # bcABC123ABCabc
7 echo ${stringZ:7} # 23ABCabc
8
9 echo ${stringZ:7:3} # 23A
10 # 3个字符长度的子串.
11
12
13
14 # 有没有可能从字符结尾开始,反向提取子串?
15
16 echo ${stringZ:-4} # abcABC123ABCabc
17 # 以${parameter:-default}方式,默认是提取完整地字符串.
18 # 然而 . . .
19
20 echo ${stringZ:(-4)} # Cabc
21 echo ${stringZ: -4} # Cabc
22 # 现在,它可以工作了.
23 # 使用圆括号或者添加一个空格来转义这个位置参数.
24
25 # Thank you, Dan Jacobson, for pointing this out.
################################End Script#########################################
如果$string参数为"*"或"@",那将最大的提取从$position开始的$length个位置参数.
1 echo ${*:2} # Echo出第2个和后边所有的位置参数.
2 echo ${@:2} # 与前边相同.
3
4 echo ${*:2:3} # 从第2个开始,Echo出后边3个位置参数.
expr substr $string $position $length
在string中从位置$position开始提取$length长度的子串.
1 stringZ=abcABC123ABCabc
2 # 123456789......
3 # 1-based indexing.
4
5 echo `expr substr $stringZ 1 2` # ab
6 echo `expr substr $stringZ 4 3` # ABC expr match "$string" '\($substring\)'
从$string的开始位置提取$substring,$substring是一个正则表达式. expr "$string" : '\($substring\)'
从$string的开始位置提取$substring,$substring是一个正则表达式.
1 stringZ=abcABC123ABCabc
2 # =======
3
4 echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
5 echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
6 echo `expr "$stringZ" : '\(.......\)'` # abcABC1
7 # All of the above forms give an identical result. 子串削除 ${string#substring}
从$string的左边截掉第一个匹配的$substring
${string##substring}
从$string的左边截掉最后一个个匹配的$substring 1 stringZ=abcABC123ABCabc
2 # |----|
3 # |----------|
4
5 echo ${stringZ#a*C} # 123ABCabc
6 # 截掉'a'和'C'之间最近的匹配.
7
8 echo ${stringZ##a*C} # abc
9 # 截掉'a'和'C'之间最远的匹配.
${string%substring}
从$string的右边截掉第一个匹配的$substring
${string%%substring}
从$string的右边截掉最后一个匹配的$substring 1 stringZ=abcABC123ABCabc
2 # ||
3 # |------------|
4
5 echo ${stringZ%b*c} # abcABC123ABCa
6 # 从$stringZ的后边开始截掉'b'和'c'之间的最近的匹配
7
8 echo ${stringZ%%b*c} # a
9 # 从$stringZ的后边开始截掉'b'和'c'之间的最远的匹配 Example 9-11 利用修改文件名,来转换图片格式
################################Start Script#######################################
1 #!/bin/bash
2 # cvt.sh:
3 # 把一个目录下的所有MacPaint格式的图片文件都转换为"pbm"格式的图片文件.
4
5 # 使用来自"netpbm"包的"macptopbm"程序,
6 #+ 这个程序主要是由Brian Henderson([email protected])来维护的.
7 # Netpbm是大多数Linux发行版的标准部分.
8
9 OPERATION=macptopbm
10 SUFFIX=pbm # 新的文件名后缀
11
12 if [ -n "$1" ]
13 then
14 directory=$1 # 如果目录名作为第1个参数给出...
15 else
16 directory=$PWD # 否则使用当前的工作目录.
17 fi
18
19 # 假设在目标目录中的所有文件都是MacPaint格式的图片文件,
20 #+ 以".mac"为文件名的后缀.
21
22 for file in $directory/* # Filename globbing.
23 do
24 filename=${file%.*c} # 去掉文件名的".mac"后缀
25 #+ ('.*c' matches everything
25 #+ ('.*c' 将匹配'.'和'c'之间的任何字符串).
26
27 $OPERATION $file > "$filename.$SUFFIX"
28 # 转换为新的文件名.
29 rm -f $file # 转换完毕后删除原有的文件.
30 echo "$filename.$SUFFIX" # 从stdout输出反馈.
31 done
32
33 exit 0
34
35 # 练习:
36 # --------
37 # 就像它现在这个样子,这个脚本把当前目录的所有文件都转换了.
38 #
39 # 修改这个脚本,让他只转换以".mac"为后缀的文件.
################################End Script#########################################
一个简单的模拟getopt命令的办法就是使用子串提取结构.
Example 9-12 模仿getopt命令
################################Start Script#######################################
1 #!/bin/bash
2 # getopt-simple.sh
3 # Author: Chris Morgan
4 # 授权使用在ABS Guide中.
5
6
7 getopt_simple()
8 {
9 echo "getopt_simple()"
10 echo "Parameters are '$*'"
11 until [ -z "$1" ]
12 do
13 echo "Processing parameter of: '$1'"
14 if [ ${1:0:1} = '/' ]
15 then
16 tmp=${1:1} # 去掉开头的'/' . . .
17 parameter=${tmp%%=*} # 提取名字.
18 value=${tmp##*=} # 提取值.
19 echo "Parameter: '$parameter', value: '$value'"
20 eval $parameter=$value
21 fi
22 shift
23 done
24 }
25
26 # 传递所有的选项到getopt_simple().
27 getopt_simple $*
28
29 echo "test is '$test'"
30 echo "test2 is '$test2'"
31
32 exit 0
33
34 ---
35
36 sh getopt_example.sh /test=value1 /test2=value2
37
38 Parameters are '/test=value1 /test2=value2'
39 Processing parameter of: '/test=value1'
40 Parameter: 'test', value: 'value1'
41 Processing parameter of: '/test2=value2'
42 Parameter: 'test2', value: 'value2'
43 test is 'value1'
44 test2 is 'value2'
################################End Script######################################### 子串替换 ${string/substring/replacement}
使用$replacement来替换第一个匹配的$substring.
${string//substring/replacement}
使用$replacement来替换所有匹配的$substring. 1 stringZ=abcABC123ABCabc
2
3 echo ${stringZ/abc/xyz} # xyzABC123ABCabc
4 # 用'xyz'来替换第一个匹配的'abc'.
5
6 echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
7 # 用'xyz'来替换所有匹配的'abc'. ${string/#substring/replacement}
如果$substring匹配$string的开头部分,那么就用$replacement来替换$substring.
${string/%substring/replacement}
如果$substring匹配$string的结尾部分,那么就用$replacement来替换$substring.
1 stringZ=abcABC123ABCabc
2
3 echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
4 # 用'XYZ'替换开头的'abc'
5
6 echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ
7 # 用'XYZ'替换结尾的'abc' 9.2.1 使用awk来操作字符串
~~~~~~~~~~~~~~~~~~~~~~~~~
Bash脚本也可以使用awk来操作字符串. Example 9-13 提取字符串的一种可选的方法
################################Start Script#######################################
1 #!/bin/bash
2 # substring-extraction.sh
3
4 String=23skidoo1
5 # 012345678 Bash
6 # 123456789 awk
7 # 注意,对于awk和Bash来说,它们使用的是不同的string索引系统:
8 # Bash的第一个字符是从'0'开始记录的.
9 # Awk的第一个字符是从'1'开始记录的.
10
11 echo ${String:2:4} # 位置3 (0-1-2), 4 个字符长
12 # skid
13
14 # awk中等价于${string:pos:length}的命令是substr(string,pos,length).
15 echo | awk '
16 { print substr("'"${String}"'",3,4) # skid
17 }
18 '
19 # 使用一个空的"echo"通过管道给awk一个假的输入,
20 #+ 这样可以不用提供一个文件名.
21
22 exit 0
################################End Script######################################### 9.2.2 更深的讨论
~~~~~~~~~~~~~~~~
关于在脚本中使用字符串更深的讨论,请参考 9.3节,h和expr命令列表的相关章节.
关于脚本的例子,见: 1 Example 12-9
2 Example 9-16
3 Example 9-17
4 Example 9-18
5 Example 9-20 注意事项:
[1] 这适用于命令行参数和函数参数. 9.3 参数替换
------------ 操作和扩展变量 ${parameter}
与$parameter相同,就是parameter的值.在特定的上下文中,只有少部分会产生
${parameter}的混淆.可以组合起来一起赋指给字符串变量. 1 your_id=${USER}-on-${HOSTNAME}
2 echo "$your_id"
3 #
4 echo "Old \$PATH = $PATH"
5 PATH=${PATH}:/opt/bin #Add /opt/bin to $PATH for duration of script.
6 echo "New \$PATH = $PATH"
${parameter-default},${parameter:-default}
如果parameter没被set,那么就使用default. 1 echo ${username-`whoami`}
2 # echo `whoami`的结果,如果没set username变量的话.
注意:${parameter-default}和${parameter:-default}大部分时候是相同的.
额外的":"在parameter被声明的时候(而且被赋空值),会有一些不同. ################################Start Script#######################################
1 #!/bin/bash
2 # param-sub.sh
3
4 # 一个变量是否被声明
5 #+ 将会影响默认选项的触发
6 #+ 甚至于这个变量被设为空.
7
8 username0=
9 echo "username0 has been declared, but is set to null."
10 echo "username0 = ${username0-`whoami`}"
11 # 将不会echo.
12
13 echo
14
15 echo username1 has not been declared.
16 echo "username1 = ${username1-`whoami`}"
17 # 将会echo.
18
19 username2=
20 echo "username2 has been declared, but is set to null."
21 echo "username2 = ${username2:-`whoami`}"
22 # ^
23 # 将会echo因为使用的是:-而不是 -.
24 # 和前边的第一个例子好好比较一下.
25
26
27 #
28
29 # 再来一个:
30
31 variable=
32 # 变量已经被声明了,但是被设置为空.
33
34 echo "${variable-0}" # (no output)
35 echo "${variable:-1}" # 1
36 # ^
37
38 unset variable
39
40 echo "${variable-2}" # 2
41 echo "${variable:-3}" # 3
42
43 exit 0
################################End Script######################################### 如果脚本中并没有传入命令行参数,那么default parameter将被使用.
1 DEFAULT_FILENAME=generic.data
2 filename=${1:-$DEFAULT_FILENAME}
3 # 如果没有参数被传递进来,那么下边的命令快将操作
4 #+ 文件"generic.data"
5 #
6 # 后续命令.
另外参见Example 3-4,Example 28-2,和Example A-6.
与"使用一个与列表来支持一个默认的命令行参数"的方法相比较. ${parameter=default},${parameter:=default}
如果parameter未设置,那么就设置为default.
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
才会有区别,[1]和上边的行为一样.
1 echo ${username=`whoami`}
2 # Variable "username" is now set to `whoami`.
2 # 变量"username"被赋值为`whoami`. ${parameter+alt_value},${parameter:+alt_value}
如果parameter被set了,那就使用alt_value,否则就使用null字符串.
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
会有区别,见下. ################################Start Script#######################################
1 echo "###### \${parameter+alt_value} ########"
2 echo
3
4 a=${param1+xyz}
5 echo "a = $a" # a =
6
7 param2=
8 a=${param2+xyz}
9 echo "a = $a" # a = xyz
10
11 param3=123
12 a=${param3+xyz}
13 echo "a = $a" # a = xyz
14
15 echo
16 echo "###### \${parameter:+alt_value} ########"
17 echo
18
19 a=${param4:+xyz}
20 echo "a = $a" # a =
21
22 param5=
23 a=${param5:+xyz}
24 echo "a = $a" # a =
25 # 与a=${param5+xyz}有不同的结果.
26
27 param6=123
28 a=${param6+xyz}
29 echo "a = $a" # a = xyz
################################End Script######################################### ${parameter?err_msg}, ${parameter:?err_msg}
如果parameter被set,那就是用set的值,否则print err_msg.
这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
会有区别,见上. Example 9-14 使用参数替换和error messages
################################Start Script#######################################
1 #!/bin/bash
2
3 # 检查一些系统的环境变量.
4 # 这是个好习惯.
5 # 比如,如果$USER(在console上的用户名)没被set,
6 #+ 那么系统就不会认你.
7
8 : ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
9 echo
10 echo "Name of the machine is $HOSTNAME."
11 echo "You are $USER."
12 echo "Your home directory is $HOME."
13 echo "Your mail INBOX is located in $MAIL."
14 echo
15 echo "If you are reading this message,"
16 echo "critical environmental variables have been set."
17 echo
18 echo
19
20 # ------------------------------------------------------
21
22 # ${variablename?} 结果也可以用来
23 #+ 在一个脚本中检查变量是否被set.
24
25 ThisVariable=Value-of-ThisVariable
26 # 注意,顺便提一下,这个字符串变量可能在它们的名字中会被设置
27 #+ 非法字符
28 : ${ThisVariable?}
29 echo "Value of ThisVariable is $ThisVariable".
30 echo
31 echo
32
33
34 : ${ZZXy23AB?"ZZXy23AB has not been set."}
35 # 如果ZZXy23AB没被set,
36 #+ 那么这个脚本将以一个error message终止.
37
38 # 你可以指定错误消息.
39 # : ${variablename?"ERROR MESSAGE"}
40
41
42 # 同样的结果: dummy_variable=${ZZXy23AB?}
43 # dummy_variable=${ZZXy23AB?"ZXy23AB has not been set."}
44 #
45 # echo ${ZZXy23AB?} >/dev/null
46
47 # 同"set -u"命令来比较这些检查变量是否被set的方法.
48 #
49
50
51
52 echo "You will not see this message, because script already terminated."
53
54 HERE=0
55 exit $HERE # Will NOT exit here.
56
57 # 事实上,这个脚本将返回值1作为退出状态(echo $?).
################################End Script######################################### Example 9-15 参数替换和"usage"messages
################################Start Script#######################################
1 #!/bin/bash
2 # usage-message.sh
3
4 : ${1?"Usage: $0 ARGUMENT"}
5 # 如果没有命令行参数,那么脚本将在此处退出.
6 #+ 并且打出如下的错误消息.
7 # usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
8
9 echo "These two lines echo only if command-line parameter given."
10 echo "command line parameter = \"$1\""
11
12 exit 0 # 如果有命令行参数,那么将在此处退出.
13
14 # 测试这个脚本,第1次测试带参数,第2次测试不带参数.
15 # 如果有参数,那么"$?"就是0.
16 # 如果没有,那么"$?"就是1.
################################End Script######################################### 参数替换和扩展
下边的表达式是使用expr字符串匹配操作的补充(见Example 12-9).
这些特定的使用方法绝大多数情况都是用来分析文件目录名. 变量长度/子串删除 ${#var}
字符串长度($var的字符数量).对于一个数组,${#array}是数组中第一个元素的长度. 一些例外:
${#*}和${#@}将给出位置参数的个数.
对于数组来说${#array[*]}和${$#array[@]}将给出数组元素的个数.
Example 9-16 变量长度
################################Start Script#######################################
1 #!/bin/bash
2 # length.sh
3
4 E_NO_ARGS=65
5
6 if [ $# -eq 0 ] # 这个demo脚本必须有命令行参数.
7 then
8 echo "Please invoke this script with one or more command-line arguments."
9 exit $E_NO_ARGS
10 fi
11
12 var01=abcdEFGH28ij
13 echo "var01 = ${var01}"
14 echo "Length of var01 = ${#var01}"
15 # 现在,让我们试试在里边嵌入一个空格.
16 var02="abcd EFGH28ij"
17 echo "var02 = ${var02}"
18 echo "Length of var02 = ${#var02}"
19
20 echo "Number of command-line arguments passed to script = ${#@}"
21 echo "Number of command-line arguments passed to script = ${#*}"
22
23 exit 0
################################End Script######################################### ${var#Pattern}, ${var##Pattern}
从$var开头删除最近或最远匹配$Pattern的子串. 来自Example A-7例子的一部分.
1 # 来自"days-between.sh"例子的一个函数.
2 # 去掉传递进来的参数开头的0.
3
4 strip_leading_zero () # 去掉开头的0
5 { #+ 从传递进来的参数中.
6 return=${1#0} # "1"指的是"$1" -- 传进来的参数.
7 } # "0"就是我们想从"$1"中删除的子串. 下边是Manfred Schwarb's对上边函数的一个改版.
1 strip_leading_zero2 () # 去掉开头的0,因为如果不去掉的话
2 { # Bash将会把这个值作为8进制解释.
3 shopt -s extglob # 打开扩展globbing.
4 local val=${1##+(0)} # 使用局部变量,匹配最长的连续的0.
5 shopt -u extglob # 打开扩展globbing.
6 _strip_leading_zero2=${val:-0}
7 # 如果输入为0,那么返回0来代替"".
8 } 另一个例子
1 echo `basename $PWD` # 当前工作目录的basename.
2 echo "${PWD##*/}" # 当前工作目录的basename.
3 echo
4 echo `basename $0` # 脚本名字.
5 echo $0 # 脚本名字.
6 echo "${0##*/}" # 脚本名字.
7 echo
8 filename=test.data
9 echo "${filename##*.}" # data ${var%Pattern}, ${var%%Pattern}
从$var结尾删除最近或最远匹配$Pattern的子串. Bash version2 添加了额外的选项. Example 9-17 参数替换中的模式匹配
################################Start Script#######################################
1 #!/bin/bash
2 # patt-matching.sh
3
4 # 使用# ## % %%来进行参数替换操作的模式匹配.
5
6 var1=abcd12345abc6789
7 pattern1=a*c # * (通配符) 匹配a - c之间的任何字符.
8
9 echo
10 echo "var1 = $var1" # abcd12345abc6789
11 echo "var1 = ${var1}" # abcd12345abc6789
12 # (alternate form)
13 echo "Number of characters in ${var1} = ${#var1}"
14 echo
15
16 echo "pattern1 = $pattern1" # a*c (everything between 'a' and 'c')
17 echo "--------------"
18 echo '${var1#$pattern1} =' "${var1#$pattern1}" # d12345abc6789
19 # 最短的可能匹配, 去掉abcd12345abc6789的前3个字符
20 # |-| ^^^
21 echo '${var1##$pattern1} =' "${var1##$pattern1}" # 6789
22 # 最远的匹配,去掉abcd12345abc6789的前12个字符.
23 # |----------| ^^^^
24
25 echo; echo; echo
26
27 pattern2=b*9 # 'b' 到'9'之间的任何字符
28 echo "var1 = $var1" # 还是 abcd12345abc6789
29 echo
30 echo "pattern2 = $pattern2"
31 echo "--------------"
32 echo '${var1%pattern2} =' "${var1%$pattern2}" # abcd12345a
33 # 最近的匹配, 去掉abcd12345abc6789的最后6个字符
34 # |----| ^^^^
35 echo '${var1%%pattern2} =' "${var1%%$pattern2}" # a
36 # 最远匹配, 去掉abcd12345abc6789的最后12个字符
37 # |-------------| ^^^^^^
38
39 # 记住, # 和## 从字符串的左边开始,并且去掉左边的字符串,
40 # % 和 %% 从字符串的右边开始,并且去掉右边的子串.
41
42 echo
43
44 exit 0
################################End Script######################################### Example 9-18 重命名文件扩展名
################################Start Script#######################################
1 #!/bin/bash
2 # rfe.sh: 重命名文件扩展名.
3 #
4 # 用法: rfe old_extension new_extension
5 #
6 # 例子:
7 # 将指定目录的所有 *.gif 文件都重命名为 *.jpg,
8 # 用法: rfe gif jpg
9
10
11 E_BADARGS=65
12
13 case $# in
14 0|1) # "|" 在这里的意思是或操作.
15 echo "Usage: `basename $0` old_file_suffix new_file_suffix"
16 exit $E_BADARGS # 如果只有0个或1个参数,那么就退出.
17 ;;
18 esac
19
20
21 for filename in *.$1
22 # 以第一个参数为扩展名的全部文件的列表
23 do
24 mv $filename ${filename%$1}$2
25 # 从筛选出的文件中先去掉以第一参数结尾的扩展名部门,
26 #+ 然后作为扩展名把第2个参数添加上.
27 done
28
29 exit 0
################################End Script######################################### 变量扩展/子串替换
这些结构都是从ksh中吸收来的. ${var:pos}
变量var从位置pos开始扩展. ${var:pos:len}
从位置pos开始,并扩展len长度个字符.见Example A-14(这个例子里有这种操作的一个
创造性用法) ${var/Pattern/Replacement}
使用Replacement来替换var中的第一个Pattern的匹配. ${var//Pattern/Replacement}
全局替换.在var中所有的匹配,都会用Replacement来替换. 向上边所说,如果Replacement被忽略的话,那么所有匹配到的Pattern都会被删除. Example 9-19 使用模式匹配来分析比较特殊的字符串
################################Start Script#######################################
1 #!/bin/bash
2
3 var1=abcd-1234-defg
4 echo "var1 = $var1"
5
6 t=${var1#*-*}
7 echo "var1 (with everything, up to and including first - stripped out) = $t"
8 # t=${var1#*-} 在这个例子中作用是一样的,
9 #+ 因为 # 匹配这个最近的字符串,
10 #+ 并且 * 匹配前边的任何字符串,包括一个空字符.
11 # (Thanks, Stephane Chazelas, for pointing this out.)
12
13 t=${var1##*-*}
14 echo "If var1 contains a \"-\", returns empty string... var1 = $t"
15
16
17 t=${var1%*-*}
18 echo "var1 (with everything from the last - on stripped out) = $t"
19
20 echo
21
22 # -------------------------------------------
23 path_name=/home/bozo/ideas/thoughts.for.today
24 # -------------------------------------------
25 echo "path_name = $path_name"
26 t=${path_name##/*/}
27 echo "path_name, stripped of prefixes = $t"
28 # 在这个特定的例子中,与 t=`basename $path_name` 的作用一致.
29 # t=${path_name%/}; t=${t##*/} 是一个更一般的解决办法,
30 #+ 但有时还是不行.
31 # 如果 $path_name 以一个新行结束, 那么`basename $path_name` 将不能工作,
32 #+ 但是上边这个表达式可以.
33 # (Thanks, S.C.)
34
35 t=${path_name%/*.*}
36 # 与 t=`dirname $path_name` 效果相同.
37 echo "path_name, stripped of suffixes = $t"
38 # 在某些情况下将失效,比如 "../", "/foo////", # "foo/", "/".
39 # 删除后缀,尤其是在basename没有后缀的时候,
40 #+ 但是dirname还是会使问题复杂化.
41 # (Thanks, S.C.)
42
43 echo
44
45 t=${path_name:11}
46 echo "$path_name, with first 11 chars stripped off = $t"
47 t=${path_name:11:5}
48 echo "$path_name, with first 11 chars stripped off, length 5 = $t"
49
50 echo
51
52 t=${path_name/bozo/clown}
53 echo "$path_name with \"bozo\" replaced by \"clown\" = $t"
54 t=${path_name/today/}
55 echo "$path_name with \"today\" deleted = $t"
56 t=${path_name//o/O}
57 echo "$path_name with all o's capitalized = $t"
58 t=${path_name//o/}
59 echo "$path_name with all o's deleted = $t"
60
61 exit 0
################################End Script######################################### ${var/#Pattern/Replacement}
如果var的前缀匹配到了Pattern,那么就用Replacement来替换Pattern. ${var/%Pattern/Replacement}
如果var的后缀匹配到了Pattern,那么就用Replacement来替换Pattern. Example 9-20 对字符串的前缀或后缀使用匹配模式
################################Start Script#######################################
1 #!/bin/bash
2 # var-match.sh:
3 # 对字符串的前后缀使用匹配替换的一个样本
4
5 v0=abc1234zip1234abc # 原始变量.
6 echo "v0 = $v0" # abc1234zip1234abc
7 echo
8
9 # 匹配字符串的前缀
10 v1=${v0/#abc/ABCDEF} # abc1234zip1234abc
11 # |-|
12 echo "v1 = $v1" # ABCDEF1234zip1234abc
13 # |----|
14
15 # 匹配字符串的后缀
16 v2=${v0/%abc/ABCDEF} # abc1234zip123abc
17 # |-|
18 echo "v2 = $v2" # abc1234zip1234ABCDEF
19 # |----|
20
21 echo
22
23 # ----------------------------------------------------
24 # 必须在开头或结尾匹配,否则,
25 #+ 将不会产生替换结果.
26 # ----------------------------------------------------
27 v3=${v0/#123/000} # 匹配上了,但不是在字符串的开头
28 echo "v3 = $v3" # abc1234zip1234abc
29 # 没替换.
30 v4=${v0/%123/000} # 匹配上了,但不是在字符串结尾.
31 echo "v4 = $v4" # abc1234zip1234abc
32 # 没替换.
33
34 exit 0
################################End Script######################################### ${!varprefix*}, ${!varprefix@}
使用变量的前缀来匹配前边所有声明过的变量.
1 xyz23=whatever
2 xyz24=
3
4 a=${!xyz*} # 以"xyz"作为前缀,匹配所有前边声明过的变量.
5 echo "a = $a" # a = xyz23 xyz24
6 a=${!xyz@} # 同上.
7 echo "a = $a" # a = xyz23 xyz24
8
9 # Bash, version 2.04, 添加了这个特征. 注意事项:
[1] 如果在一个非交互脚本中,$parameter为空的话,那么这个脚本将以127返回.
(127退出码对应的Bash错误码为"command not found"). 9.4 指定类型的变量:declare或者typeset
-------------------------------------
declare或者typeset内建命令(这两个命令是完全一样的)允许指定变量的具体类型.在某些特
定的语言中,这是一种指定类型的很弱的形式.declare命令是在Bash版本2或之后的版本才被
加入的.typeset命令也可以工作在ksh脚本中. declare/typeset 选项 -r 只读
1 declare -r var1
(declare -r var1与readonly var1是完全一样的)
这和C语言中的const关键字一样,都是强制指定只读.如果你尝试修改一个只读变量
的值,那么你将得到一个错误消息. -i 整形
1 declare -i number
2 # 这个脚本将把变量"number"后边的赋值视为一个整形.
3
4 number=3
5 echo "Number = $number" # Number = 3
6
7 number=three
8 echo "Number = $number" # Number = 0
9 # 尝试把"three"解释为整形. 如果把一个变量指定为整形,那么即使没有expr和let命令,也允许使用特定的算术运算
1 n=6/3
2 echo "n = $n" # n = 6/3
3
4 declare -i n
5 n=6/3
6 echo "n = $n" # n = 2 -a 数组
1 declae -a indices
变量indices将被视为数组. -f 函数
1 declare -f
如果使用declare -f而不带参数的话,将会列出这个脚本中之前定义的所有函数.
1 declare -f function_name
如果使用declare -f function_name这种形式的话,将只会列出这个函数的名字. -x export
1 declare -x var3
这种使用方式,将会把var3 export出来. Example 9-21 使用declare来指定变量的类型
################################Start Script#######################################
1 #!/bin/bash
2
3 func1 ()
4 {
5 echo This is a function.
6 }
7
8 declare -f # 列出之前的所有函数.
9
10 echo
11
12 declare -i var1 # var1 是个整形.
13 var1=2367
14 echo "var1 declared as $var1"
15 var1=var1+1 # 变量声明不需使用'let'命令.
16 echo "var1 incremented by 1 is $var1."
17 # 尝试将变量修改为整形.
18 echo "Attempting to change var1 to floating point value, 2367.1."
19 var1=2367.1 # 结果将是一个错误消息,并且变量并没有被修改.
20 echo "var1 is still $var1"
21
22 echo
23
24 declare -r var2=13.36 # 'declare' 允许设置变量的属性,
25 #+ 并且同时分配变量的值.
26 echo "var2 declared as $var2" # 尝试修改只读变量.
27 var2=13.37 # 产生一个错误消息,并且从脚本退出了.
28
29 echo "var2 is still $var2" # 这行将不会被执行.
30
31 exit 0 # 脚本将不会在此处退出.
################################End Script######################################### 注意:使用declare内建命令将会限制变量的作用域.
1 foo ()
2 {
3 FOO="bar"
4 }
5
6 bar ()
7 {
8 foo
9 echo $FOO
10 }
11
12 bar # Prints bar. 然而...
1 foo (){
2 declare FOO="bar"
3 }
4
5 bar ()
6 {
7 foo
8 echo $FOO
9 }
10
11 bar # Prints nothing.
12
13
14 # Thank you, Michael Iatrou, for pointing this out. 9.5 变量的间接引用
------------------
假设一个变量的值是另一个变量的名字.我们有可能从第一个变量中取得第2个变量的值么?
比如,如果a=letter_of_alphabet接着letter_of_alphabet=z,那么我们能从a中得到z么?
答案是:当然可以,并且这被称为间接引用.它使用一个不常用的符号eval var1=\$$var2.
Example 9-22 间接引用
################################Start Script#######################################
1 #!/bin/bash
2 # ind-ref.sh: 间接变量引用
3 # 存取一个变量的值的值(这里翻译得有点拗口,不过凑合吧)
4
5 a=letter_of_alphabet # 变量"a"的值是另一个变量的名字.
6 letter_of_alphabet=z
7
8 echo
9
10 # 直接引用.
11 echo "a = $a" # a = letter_of_alphabet
12
13 # 间接引用.
14 eval a=\$$a
15 echo "Now a = $a" # Now a = z
16
17 echo
18
19
20 # 现在,让我们试试修改第2个引用的值.
21
22 t=table_cell_3
23 table_cell_3=24
24 echo "\"table_cell_3\" = $table_cell_3" # "table_cell_3" = 24
25 echo -n "dereferenced \"t\" = "; eval echo \$$t # 解引用 "t" = 24
26 # 在这个简单的例子中,下边的表达式也能正常工作(为什么?).
27 # eval t=\$$t; echo "\"t\" = $t"
28
29 echo
30
31 t=table_cell_3
32 NEW_VAL=387
33 table_cell_3=$NEW_VAL
34 echo "Changing value of \"table_cell_3\" to $NEW_VAL."
35 echo "\"table_cell_3\" now $table_cell_3"
36 echo -n "dereferenced \"t\" now "; eval echo \$$t
37 # "eval" 将获得两个参数 "echo" 和 "\$$t" (与$table_cell_3等价)
38
39 echo
40
41 # (Thanks, Stephane Chazelas, 澄清了上边的行为.)
42
43
44 # 另一个方法是使用${!t}符号,见"Bash, 版本2"小节.
45 # 也请参阅ex78.sh.
46
47 exit 0
################################End Script#########################################
间接应用到底有什么应用价值?它给Bash添加了一种类似于C语言指针的功能,在Example 34-3
中有例子.并且,还有一些其它的有趣的应用.... Nils Radtke展示了如何建立一个"dynamic"变量名字并且取出其中的值.当sourcing(包含)配置
文件时,这很有用.
################################Start Script#######################################
1 #!/bin/bash
2
3
4 # ---------------------------------------------
5 # 这部分内容可能来自于单独的文件.
6 isdnMyProviderRemoteNet=172.16.0.100
7 isdnYourProviderRemoteNet=10.0.0.10
8 isdnOnlineService="MyProvider"
9 # ---------------------------------------------
10
11
12 remoteNet=$(eval "echo \$$(echo isdn${isdnOnlineService}RemoteNet)")
13 remoteNet=$(eval "echo \$$(echo isdnMyProviderRemoteNet)")
14 remoteNet=$(eval "echo \$isdnMyProviderRemoteNet")
15 remoteNet=$(eval "echo $isdnMyProviderRemoteNet")
16
17 echo "$remoteNet" # 172.16.0.100
18
19 # ================================================================
20
21 # 同时,它甚至能更好.
21 #
22
23 # 考虑下边的脚本,给出了一个变量getSparc,
24 #+ 但是没给出变量getIa64:
25
26 chkMirrorArchs () {
27 arch="$1";
28 if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
29 sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
30 sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
31 then
32 return 0;
33 else
34 return 1;
35 fi;
36 }
37
38 getSparc="true"
39 unset getIa64
40 chkMirrorArchs sparc
41 echo $? # 0
42 # True
43
44 chkMirrorArchs Ia64
45 echo $? # 1
46 # False
47
48 # 注意:
49 # -----<rojy bug>
50 # Even the to-be-substituted variable name part is built explicitly.
51 # The parameters to the chkMirrorArchs calls are all lower case.
52 # The variable name is composed of two parts: "get" and "Sparc" . . .
################################End Script######################################### Example 9-23 传递一个间接引用给awk
################################Start Script#######################################
1 #!/bin/bash
2
3 # "column totaler"脚本的另一个版本
4 #+ 这个版本在目标文件中添加了一个特殊的列(数字的).
5 # 这个脚本使用了间接引用.
6
7 ARGS=2
8 E_WRONGARGS=65
9
10 if [ $# -ne "$ARGS" ] # 检查命令行参数是否是合适的个数.
11 then
12 echo "Usage: `basename $0` filename column-number"
13 exit $E_WRONGARGS
14 fi
15
16 filename=$1
17 column_number=$2
18
19 #===== 上边的这部分,与原来的脚本一样 =====#
20
21
22 # 一个多行的awk脚本被调用,通过 ' ..... '
23
24
25 # awk 脚本开始.
26 # ------------------------------------------------
27 awk "
28
29 { total += \$${column_number} # 间接引用.
30 }
31 END {
32 print total
33 }
34
35 " "$filename"
36 # ------------------------------------------------
37 # awk 脚本结束.
38
39 # 间接的变量引用避免了在一个内嵌的awk脚本中引用
40 #+ 一个shell变量的问题.
41 # Thanks, Stephane Chazelas.
42
43
44 exit 0
################################End Script#########################################
注意: 这个脚本有些狡猾.如果第2个变量修改了它的值,那么第一个变量必须被适当的解引用
(像上边的例子一样).幸运的是,在Bash版本2中引入的${!variable}(参见Example 34-2)
是的间接引用更加直观了. 注意: Bash并不支持指针的算术运算,并且这严格的限制了间接引用的使用.事实上,在脚本语言
中,间接引用本来就是丑陋的部分. 9.6 $RANDOM: 产生随机整数
-------------------------
$RANDOM是Bash的内部函数(并不是常量),这个函数将返回一个范围在0 - 32767之间的一个伪
随机整数.它不应该被用来产生密匙. Example 9-24 产生随机数
################################Start Script#######################################
1 #!/bin/bash
2
3 # $RANDOM 在每次调用的时候,返回一个不同的随机整数.
4 # 指定的范围是: 0 - 32767 (有符号的16-bit 整数).
5
6 MAXCOUNT=10
7 count=1
8
9 echo
10 echo "$MAXCOUNT random numbers:"
11 echo "-----------------"
12 while [ "$count" -le $MAXCOUNT ] # 产生10 ($MAXCOUNT) 个随机整数.
13 do
14 number=$RANDOM
15 echo $number
16 let "count += 1" # 数量加1.
17 done
18 echo "-----------------"
19
20 # 如果你需要在一个特定范围内产生一个随机int,那么使用'modulo'(模)操作.
21 # 这将返回一个除法操作的余数.
22
23 RANGE=500
24
25 echo
26
27 number=$RANDOM
28 let "number %= $RANGE"
29 # ^^
30 echo "Random number less than $RANGE --- $number"
31
32 echo
33
34
35
36 # 如果你需要产生一个比你指定的最小边界大的随机数,
37 #+ 那么建立一个test循环,来丢弃所有产生对比这个数小的随机数.
38
39 FLOOR=200
40
41 number=0 #initialize
42 while [ "$number" -le $FLOOR ]
43 do
44 number=$RANDOM
45 done
46 echo "Random number greater than $FLOOR --- $number"
47 echo
48
49 # 让我们对上边的循环尝试一个小改动,也就是
50 # 让"number = $RANDOM + $FLOOR"
51 # 这将不再需要那个while循环,并且能够运行得更快.
52 # 但是, 这可能会产生一个问题.那么这个问题是什么呢?(译者:这很简单,有可能溢出)
53
54
55
56 # 结合上边两个例子的技术,来达到获得在指定的上下限之间来产生随机数.
57 number=0 #initialize
58 while [ "$number" -le $FLOOR ]
59 do
60 number=$RANDOM
61 let "number %= $RANGE" # 让$number依比例落在$RANGE范围内.
62 done
63 echo "Random number between $FLOOR and $RANGE --- $number"
64 echo
65
66
67
68 # 产生一个二元选择,就是"true"和"false"两个值.
69 BINARY=2
70 T=1
71 number=$RANDOM
72
73 let "number %= $BINARY"
74 # 注意,让"number >>= 14" 将给出一个更好的随机分配
75 #+ (右移14位将把所有为全部清空,除了第15位,因为有符号,所以第16位是符号位).
76 if [ "$number" -eq $T ]
77 then
78 echo "TRUE"
79 else
80 echo "FALSE"
81 fi
82
83 echo
84
85
86 # 抛骰子
87 SPOTS=6 # 模6给出的范围就是0-5.
88 # 加1就会得到期望的范围1 - 6.
89 # Thanks, Paulo Marcel Coelho Aragao, for the simplification.
90 die1=0
91 die2=0
92 # 是否让SPOTS=7比加1更好呢?解释行或者不行的原因?
93
94 # 每次抛骰子,都会给出均等的机会.
95
96 let "die1 = $RANDOM % $SPOTS +1" # 抛第一次.
97 let "die2 = $RANDOM % $SPOTS +1" # 抛第二次.
98 # 上边的那个算术操作,具有更高的优先级呢 --
99 #+ 模操作(%)还是加法操作(+)?
100
101
102 let "throw = $die1 + $die2"
103 echo "Throw of the dice = $throw"
104 echo
105
106
107 exit 0
################################End Script######################################### Example 9-25 从一副扑克牌中取出一张随机的牌
################################Start Script#######################################
1 #!/bin/bash
2 # pick-card.sh
3
4 # 这是一个从数组中取出随机元素的一个例子.
5
6
7 # 取出一张牌,任何一张.
8
9 Suites="Clubs
10 Diamonds
11 Hearts
12 Spades"
13
14 Denominations="2
15 3
16 4
17 5
18 6
19 7
20 8
21 9
22 10
23 Jack
24 Queen
25 King
26 Ace"
27
28 # 注意变量的多行展开.
29
30
31 suite=($Suites) # 读到数组变量中.
32 denomination=($Denominations)
33
34 num_suites=${#suite[*]} # 计算有多少个元素.
35 num_denominations=${#denomination[*]}
36
37 echo -n "${denomination[$((RANDOM%num_denominations))]} of "
38 echo ${suite[$((RANDOM%num_suites))]}
39
40
41 # $bozo sh pick-cards.sh
42 # Jack of Clubs
43
44
45 # Thank you, "jipe," for pointing out this use of $RANDOM.
46 exit 0
################################End Script######################################### Jipe展示了一系列的在一定范围中产生随机数的方法.
1 # 在6到30之间产生随机数.
2 rnumber=$((RANDOM%25+6))
3
4 # 还是产生6-30之间的随机数,
5 #+ 但是这个数字必须被3均分.
6 rnumber=$(((RANDOM%30/3+1)*3))
7
8 # 注意,这可能不会在所有时候都能正常地运行.
9 # It fails if $RANDOM returns 0.
10
11 # Frank Wang 建议用下班的方法来取代:
12 rnumber=$(( RANDOM%27/3*3+6 )) Bill Gradwohl 提出了一个重要的规则来产生正数.
1 rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min)) 这里Bill给出了一个通用函数,这个函数返回一个在两个指定值之间的随机数 Example 9-26 两个指定值之间的随机数
################################Start Script#######################################
1 #!/bin/bash
2 # random-between.sh
3 # 在两个指定值之间的随机数.
4 # Bill Gradwohl编写的本脚本,本文作者作了较小的修改.
5 # 允许使用.
6
7
8 randomBetween() {
9 # 产生一个正的或者负的随机数.
10 #+ 在$max和$max之间
11 #+ 并且可被$divisibleBy整除的.
12 # 给出一个合理的随机分配的返回值.
13 #
14 # Bill Gradwohl - Oct 1, 2003
15
16 syntax() {
17 # 在函数中内嵌函数
18 echo
19 echo "Syntax: randomBetween [min] [max] [multiple]"
20 echo
21 echo "Expects up to 3 passed parameters, but all are completely optional."
22 echo "min is the minimum value"
23 echo "max is the maximum value"
24 echo "multiple specifies that the answer must be a multiple of this value."
25 echo " i.e. answer must be evenly divisible by this number."
26 echo
27 echo "If any value is missing, defaults area supplied as: 0 32767 1"
28 echo "Successful completion returns 0, unsuccessful completion returns"
29 echo "function syntax and 1."
30 echo "The answer is returned in the global variable randomBetweenAnswer"
31 echo "Negative values for any passed parameter are handled correctly."
32 }
33
34 local min=${1:-0}
35 local max=${2:-32767}
36 local divisibleBy=${3:-1}
37 # 默认值分配,用来处理没有参数传递进来的时候.
38
39 local x
40 local spread
41
42 # 确认divisibleBy是正值.
43 [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))
44
45 # 完整性检查.
46 if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then
47 syntax
48 return 1
49 fi
50
51 # 察看是否min和max颠倒了.
52 if [ ${min} -gt ${max} ]; then
53 # 交换它们.
54 x=${min}
55 min=${max}
56 max=${x}
57 fi
58
59 # 如果min自己并不能够被$divisibleBy整除,
60 #+ 那么就调整min的值,使其能够被$divisibleBy整除,前提是不能放大范围.
61 if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
62 if [ ${min} -lt 0 ]; then
63 min=$((min/divisibleBy*divisibleBy))
64 else
65 min=$((((min/divisibleBy)+1)*divisibleBy))
66 fi
67 fi
68
69 # 如果min自己并不能够被$divisibleBy整除,
70 #+ 那么就调整max的值,使其能够被$divisibleBy整除,前提是不能放大范围.
71 if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
72 if [ ${max} -lt 0 ]; then
73 max=$((((max/divisibleBy)-1)*divisibleBy))
74 else
75 max=$((max/divisibleBy*divisibleBy))
76 fi
77 fi
78
79 # ---------------------------------------------------------------------
80 # 现在,来做真正的工作.
81
82 # 注意,为了得到对于端点来说合适的分配,
83 #+ 随机值的范围不得不落在
84 #+ 0 和 abs(max-min)+divisibleBy之间, 而不是 abs(max-min)+1.
85
86 # 对于端点来说,
87 #+ 这个少量的增加将会产生合适的分配.
88
89 # 修改这个公式,使用abs(max-min)+1来代替abs(max-min)+divisibleBy的话,
90 #+ 也能够产生正确的答案, 但是在这种情况下生成的随机值对于正好为端点倍数
91 #+ 的这种情况来说将是不完美的,因为在正好为端点倍数的情况的随机率比较低,
92 #+ 因为你才加1而已,这比正常的公式所产生的机率要小得多(正常为加divisibleBy)
93 # ---------------------------------------------------------------------
94
95 spread=$((max-min))
96 [ ${spread} -lt 0 ] && spread=$((0-spread))
97 let spread+=divisibleBy
98 randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
99
100 return 0
101
102 # 然而,Paulo Marcel Coelho Aragao指出
103 #+ 当$max和$min不能被$divisibleBy整除时,
104 #+ 这个公式将会失败.
105 #
106 # 他建议使用如下的公式:
107 # rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))
108
109 }
110
111 # 让我们测试一下这个函数.
112 min=-14
113 max=20
114 divisibleBy=3
115
116
117 # 产生一个数组answers,answers的下标用来表示在范围内可能出现的值,
118 #+ 而内容记录的是对于这个值出现的次数,如果我们循环足够多次,一定会得到
119 #+ 一次出现机会.
120 declare -a answer
121 minimum=${min}
122 maximum=${max}
123 if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
124 if [ ${minimum} -lt 0 ]; then
125 minimum=$((minimum/divisibleBy*divisibleBy))
126 else
127 minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
128 fi
129 fi
130
131
132 # 如果maximum自己并不能够被$divisibleBy整除,
133 #+ 那么就调整maximum的值,使其能够被$divisibleBy整除,前提是不能放大范围.
134
135 if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
136 if [ ${maximum} -lt 0 ]; then
137 maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
138 else
139 maximum=$((maximum/divisibleBy*divisibleBy))
140 fi
141 fi
142
143
144 # 我们需要产生一个下标全为正的数组,
145 #+ 所以我们需要一个displacement来保正都为正的结果.
146
147
148 displacement=$((0-minimum))
149 for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
150 answer[i+displacement]=0
151 done
152
153
154 # 现在我们循环足够多的次数来得到我们想要的答案.
155 loopIt=1000 # 脚本作者建议 100000,
156 #+ 但是这实在是需要太长的时间了.
157
158 for ((i=0; i<${loopIt}; ++i)); do
159
160 # 注意,我们在这里调用randomBetween函数时,故意将min和max颠倒顺序
161 #+ 我们是为了测试在这种情况下,此函数是否还能得到正确的结果.
162
163 randomBetween ${max} ${min} ${divisibleBy}
164
165 # 如果答案不是我们所预期的,那么就报告一个错误.
166 [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo MIN or MAX error - ${randomBetweenAnswer}!
167 [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo DIVISIBLE BY error - ${randomBetweenAnswer}!
168
169 # 将统计值存到answer之中.
170 answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
171 done
172
173
174
175 # 让我们察看一下结果
176
177 for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
178 [ ${answer[i+displacement]} -eq 0 ] && echo "We never got an answer of $i." || echo "${i} occurred ${answer[i+displacement]} times."
179 done
180
181
182 exit 0
################################End Script######################################### $RANDOM到底有多随机?最好的办法就是写个脚本来测试一下.跟踪随机数的分配情况.
让我们用随机数摇一个骰子.
Example 9-27 使用随机数来摇一个骰子
################################Start Script#######################################
1 #!/bin/bash
2 # RANDOM到底有多random?
3
4 RANDOM=$$ # 使用脚本的进程ID来作为随机数的产生种子.
5
6 PIPS=6 # 一个骰子有6面.
7 MAXTHROWS=600 # 如果你没别的事干,那么可以增加这个数值.
8 throw=0 # 抛骰子的次数.
9
10 ones=0 # 必须把所有count都初始化为0,
11 twos=0 #+ 因为未初始化的变量为null,不是0.
12 threes=0
13 fours=0
14 fives=0
15 sixes=0
16
17 print_result ()
18 {
19 echo
20 echo "ones = $ones"
21 echo "twos = $twos"
22 echo "threes = $threes"
23 echo "fours = $fours"
24 echo "fives = $fives"
25 echo "sixes = $sixes"
26 echo
27 }
28
29 update_count()
30 {
31 case "$1" in
32 0) let "ones += 1";; # 因为骰子没有0,所以给1.
33 1) let "twos += 1";; # 对tows做同样的事.
34 2) let "threes += 1";;
35 3) let "fours += 1";;
36 4) let "fives += 1";;
37 5) let "sixes += 1";;
38 esac
39 }
40
41 echo
42
43
44 while [ "$throw" -lt "$MAXTHROWS" ]
45 do
46 let "die1 = RANDOM % $PIPS"
47 update_count $die1
48 let "throw += 1"
49 done
50
51 print_result
52
53 exit 0
54
55 # 如果RANDOM是真正的随机,那么摇出来结果应该平均的.
56 # $MAXTHROWS设为600,那么每面都应该为100,上下的出入不应该超过20.
57 #
58 # 记住RANDOM毕竟只是一个伪随机数,
59 #+ 并且不是十分完美的.
60
61 # 随机数的产生是一个深奥并复杂的问题.
62 # 足够长的随机序列,不但会展现杂乱无章的一面,
63 #+ 而且会展现机会均等的一面.
64
65 # 一个很简单的练习:
66 # -----------------
67 # 重写这个例子,做成抛1000次硬币的形式.
68 # 分为正反两面.
################################End Script#########################################
像我们在上边的例子中看到的,最好在每次随机数产生时都使用新的种子.应为如果使用同样的
种子的话,那么随机数将产生相同的序列.[2](C中random()函数也会有这样的行为) Example 9-28 重新分配随机数种子
################################Start Script#######################################
1 #!/bin/bash
2 # seeding-random.sh: 设置RANDOM变量作为种子.
3
4 MAXCOUNT=25 # 决定产生多少个随机数.
5
6 random_numbers ()
7 {
8 count=0
9 while [ "$count" -lt "$MAXCOUNT" ]
10 do
11 number=$RANDOM
12 echo -n "$number "
13 let "count += 1"
14 done
15 }
16
17 echo; echo
18
19 RANDOM=1 # 为随机数的产生设置RANDOM种子.
20 random_numbers
21
22 echo; echo
23
24 RANDOM=1 # 设置同样的种子...
25 random_numbers # ...将会和上边产生的随机数列相同.
26 #
27 # 复制一个相同的随机数序列在什么时候有用呢?
28
29 echo; echo
30
31 RANDOM=2 # 再试一下,但这次使用不同的种子...
32 random_numbers # 将给出一个不同的随机数序列.
33
34 echo; echo
35
36 # RANDOM=$$ 使用脚本的进程id 作为随机数的种子.
37 # 从'time'或'date'命令中取得RANDOM作为种子也是很常用的办法.
38
39 # 一个有想象力的方法...
40 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
41 # 首先从/dev/urandom(系统伪随机设备文件)中取出1行,
42 #+ 然后着这个可打印行转换为(8进制)数,通过使用"od"命令,
43 #+ 最后使用"awk"来获得一个数,
44 #+ 这个数将作为随机数产生的种子.
45 RANDOM=$SEED
46 random_numbers
47
48 echo; echo
49
50 exit 0
################################End Script#########################################
注意:/dev/urandom设备文件提供了一种比单独使用$RANDOM更好的,能产生更"随机"的随机数
的方法.
dd if=/dev/urandom of=targetfile bs=1 count=XX能够产生一个很分散的为随机数.
然而,将这个数赋值到一个脚本文件的变量中,还需要可操作性,比如使用"od"命令
(就像上边的例子,见Example 12-13),或者使用dd命令(见Example 12-55),或者管道到
"md5sum"命令中(见Example 33-14).
当然还有其它的产生伪随机数的方法.Awk就可以提供一个方便的方法.
Example 9-29 使用awk产生伪随机数
################################Start Script#######################################
1 #!/bin/bash
2 # random2.sh: 产生一个范围0 - 1的为随机数.
3 # 使用awk的rand()函数.
4
5 AWKSCRIPT=' { srand(); print rand() } '
6 # Command(s) / 传到awk中的参数
7 # 注意,srand()函数用来产生awk的随机数种子.
8
9
10 echo -n "Random number between 0 and 1 = "
11
12 echo | awk "$AWKSCRIPT"
13 # 如果你省去'echo'那么将发生什么?
14
15 exit 0
16
17
18 # Exercises:
18 # 练习:
19 # -----
20
21 # 1) 使用循环结构,打印出10个不同的随机数.
22 # (提示: 在循环的每次执行过程中,你必须使用"srand()"函数来生成不同的
23 #+ 种子.如果你没做这件事那么将发生什么?
24
25 # 2) 使用一个整数乘法作为一个放缩因子,在10到100的范围之间,
26 #+ 来产生随机数.
27
28 # 3) 同上边的练习 #2,但这次产生随机整数.
################################End Script#########################################
"data"命令也可以用来产生伪随机整数序列. 注意事项:
[1] 真正的随机事件(在它存在的范围内),只发生在特定的几个未知的自然界现象中,比如
放射性衰变.计算机只能产生模拟的随机事件,并且计算机产生的"随机"数因此只能称
为伪随机数.
[2] 计算机产生的伪随机数序列用的种子可以被看成是一种标识标签.比如,使用种子23所
产生的伪随机数序列就被称作序列#23.
一个伪随机序列的特点就是在这个序列开始重复之前的所有元素的个数的和,也就是
这个序列的长度.一个好的伪随机产生算法将可以产生一个非常长的不重复的序列.
9.7 双圆括号结构
----------------
((...))与let命令很像,允许算术扩展和赋值.举个简单的例子a=$(( 5 + 3 )),将把a设为
"5+3"或者8.然而,双圆括号也是一种在Bash中允许使用C风格的变量处理的机制. Example 9-30 C风格的变量处理
################################Start Script#######################################
1 #!/bin/bash
2 # 处理一个变量,C风格,使用((...))结构.
3
4
5 echo
6
7 (( a = 23 )) # 给一个变量赋值,从"="两边的空格就能看出这是c风格的处理.
8 echo "a (initial value) = $a"
9
10 (( a++ )) # 变量'a'后加1,C风格.
11 echo "a (after a++) = $a"
12
13 (( a-- )) # 变量'a'后减1,C风格.
14 echo "a (after a--) = $a"
15
16
17 (( ++a )) # 变量'a'预加1,C风格.
18 echo "a (after ++a) = $a"
19
20 (( --a )) # 变量'a'预减1,C风格.
21 echo "a (after --a) = $a"
22
23 echo
24
25 ########################################################
26 # 注意:在C语言中,预减和后减操作
27 #+ 会有些不同的副作用.
28
29 n=1; let --n && echo "True" || echo "False" # False
30 n=1; let n-- && echo "True" || echo "False" # True
31
32 # Thanks, Jeroen Domburg.
33 ########################################################
34
35 echo
36
37 (( t = a<45?7:11 )) # C风格的3元操作.
38 echo "If a < 45, then t = 7, else t = 11."
39 echo "t = $t " # Yes!
40
41 echo
42
43
44 # ----------------
45 # 复活节彩蛋注意!
46 # ----------------
47 # Chet Ramey 显然的偷偷摸摸的做了一些未公开的C风格的结构
48 #+ 放在Bash中(准确地说是根据ksh来改写的,这更接近些)
49 # 在Bash文档中,Ramey调用((...))shell算法,
50 #+ 但是它可以走得更远.
51 # 对不起, Chet, 现在秘密被公开了.
52
53 # See also "for" and "while" loops using the ((...)) construct.
53 # 也参考一些"for"和"while"循环中使用((...))结构的例子.
54
55 # 这些只能工作在2.04或者更高版本的Bash中.
56
57 exit 0
################################End Script#########################################
见Example 10-12.
相关阅读 更多 +