混合比较
-a 逻辑与
exp1 -a exp2 如果exp1和exp2都为true的话,这个表达式将返回true
-o 逻辑或
exp1 -o exp2 如果exp1和exp2中有一个为true的话,那么这个表达式就返回true
这与Bash的比较操作符&&和||很相像.在[[]]中使用它.
1 [[ condition1 && condition2 ]]
-o和-a一般都是和test命令或者是[]一起工作.
1 if [ "$exp1" -a "$exp2" ]
请参考Example 8-3,Example 26-16和Example A-28来查看混合比较操作的行为.
注意事项:
[1] S.C.(这家伙是个人名)指出,在使用混合比较的时候即使"$var"也可能会产生问题.
如果$string为空的话,[ -n "$string" -o "$a" = "$b" ]可能在某些版本的Bash中
会有问题.为了附加一个额外的字符到可能的空变量中的一种安全的办法是,
[ "x$string" != x -o "x$a" = "x$b" ](the "x's" cancel out)(没看懂).
cancel out是抵消的意思.
7.4 嵌套的if/then条件test
-------------------------
可以使用if/then来进行嵌套的条件test.最终的结果和上边的使用&&混合比较操作是相同的.
1 if [ condition1 ]
2 then
3 if [ condition2 ]
4 then
5 do-something # 这里只有在condition1和condition2都可用的时候才行.
6 fi
7 fi
具体请查看Example 34-4.
7.5 检查你的test知识
--------------------
系统范围的xinitrc文件可以用来启动X server.这个文件中包含了相当多的if/then test,
就像下边的节选一样:
1 if [ -f $HOME/.Xclients ]; then
2 exec $HOME/.Xclients
3 elif [ -f /etc/X11/xinit/Xclients ]; then
4 exec /etc/X11/xinit/Xclients
5 else
6 # 故障保险设置,虽然我们永远都不会走到这来.
7 # (我们在Xclients中也提供了相同的机制)它不会受伤的.
8 xclock -geometry 100x100-5+5 &
9 xterm -geometry 80x50-50+150 &
10 if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then
11 netscape /usr/share/doc/HTML/index.html &
12 fi
13 fi
对上边的"test"结构进行解释,然后检查整个文件,/etc/X11/xinit/xinitrc,并分析if/then
test结构.你可能需要查看一下后边才能讲解到的grep,sed和正则表达式的知识.
第8章 操作符和相关的主题
========================
8.1 操作符
----------
等号操作符
变量赋值
初始化或者修改变量的值
=
无论在算术运算还是字符串运算中,都是赋值语句.
1 var=27
2 category=minerals # No spaces allowed after the "=".
注意:不要和"="test操作符混淆.
1 # = as a test operator
2
3 if [ "$string1" = "$string2" ]
4 # if [ "X$string1" = "X$string2" ] is safer,
5 # to prevent an error message should one of the variables be empty.
6 # (The prepended "X" characters cancel out.)
7 then
8 command
9 fi
算术操作符
+ 加法
- 减法
* 乘法
/ 除法
** 幂运算
1 # Bash, version 2.02, introduced the "**" exponentiation operator.
2
3 let "z=5**3"
4 echo "z = $z" # z = 125
% 取模
bash$ expr 5 % 3
2
5/3=1余2
模运算经常用在其它的事情中,比如产生特定的范围的数字(Example 9-24,
Example 9-27)和格式化程序的输出(Example 26-15,Example A-6).它甚至可以用来
产生质数,(Example A-16).事实上取模运算在算术运算中使用的频率惊人的高.
Example 8-1 最大公约数
################################Start Script#######################################
1 #!/bin/bash
2 # gcd.sh: 最大公约数
3 # 使用Euclid's 算法
4
5 # 最大公约数,就是2个数能够同时整除的最大的数.
6 #
7
8 # Euclid's算法采用连续除法.
9 # 在每个循环中
10 #+ 被除数 <--- 除数
11 #+ 除数 <--- 余数
12 #+ 直到余数= 0.
13 #+ 在最后的循环中The gcd = 被除数
14 #
15 # 关于这个算法更精彩的讨论
16 # 见Jim Loy's site, http://www.jimloy.com/number/euclids.htm.
17
18
19 # ------------------------------------------------------
20 # 参数检查
21 ARGS=2
22 E_BADARGS=65
23
24 if [ $# -ne "$ARGS" ]
25 then
26 echo "Usage: `basename $0` first-number second-number"
27 exit $E_BADARGS
28 fi
29 # ------------------------------------------------------
30
31
32 gcd ()
33 {
34
35 dividend=$1 # 随便给值
36 divisor=$2 #+ 即使$2大,也没关系.
37 # Why not?
38
39 remainder=1 # 如果再循环中使用为初始化的变量.
40 #+ 那将在第一次循环中产生一个错误消息.
41
42
43 until [ "$remainder" -eq 0 ]
44 do
45 let "remainder = $dividend % $divisor"
46 dividend=$divisor # 现在使用2个最小的数重复.
47 divisor=$remainder
48 done # Euclid's algorithm
49
50 } # Last $dividend is the gcd.
50 } # 最后的$dividend就是gcd.
51
52
53 gcd $1 $2
54
55 echo; echo "GCD of $1 and $2 = $dividend"; echo
56
57
58 # 练习:
59 # --------
60 # 检查命令行参数来确定它们都是整数,
61 #+ and exit the script with an appropriate error message if not.
61 #+ 否则就选择合适的错误消息退出.
62
63 exit 0
################################End Script#########################################
+= 加等于(通过常量增加变量)
let "var += 5" #var将在本身值的基础上增加5
-= 减等于
*= 乘等于
let "var *= 4"
/= 除等于
%= 取模赋值,算术操作经常使用expr或者let表达式.
Example 8-2 使用算术操作符
################################Start Script#######################################
1 #!/bin/bash
2 # Counting to 11 in 10 different ways.
3
4 n=1; echo -n "$n "
5
6 let "n = $n + 1" # let "n = n + 1" 这么写也行
7 echo -n "$n "
8
9
10 : $((n = $n + 1))
11 # ":" 是必须的,这是因为,如果没有":"的话,Bash将
12 #+ 尝试把"$((n = $n + 1))"解释成一个命令
13 echo -n "$n "
14
15 (( n = n + 1 ))
16 # 对于上边的方法的一个更简单的选则.
17 # Thanks, David Lombard, for pointing this out.
18 echo -n "$n "
19
20 n=$(($n + 1))
21 echo -n "$n "
22
23 : $[ n = $n + 1 ]
24 # ":" 是必须的,这是因为,如果没有":"的话,Bash将
25 #+ 尝试把"$[ n = $n + 1 ]" 解释成一个命令
26 # 即使"n"被初始化成为一个字符串,这句也能工作.
27 echo -n "$n "
28
29 n=$[ $n + 1 ]
30 # 即使"n"被初始化成为一个字符串,这句也能工作.
31 #* Avoid this type of construct, since it is obsolete and nonportable.
31 #* 尽量避免这种类型的结果,因为这已经被废弃了,并且不具可移植性.
32 # Thanks, Stephane Chazelas.
33 echo -n "$n "
34
35 # 现在来个C风格的增量操作.
36 # Thanks, Frank Wang, for pointing this out.
37
38 let "n++" # let "++n" also works.
39 echo -n "$n "
40
41 (( n++ )) # (( ++n ) also works.
42 echo -n "$n "
43
44 : $(( n++ )) # : $(( ++n )) also works.
45 echo -n "$n "
46
47 : $[ n++ ] # : $[ ++n ]] also works
48 echo -n "$n "
49
50 echo
51
52 exit 0
################################End Script#########################################
注意:在Bash中的整型变量事实上是32位的,范围是 -2147483648 到2147483647.如果超过这个
范围进行算术操作,将不会得到你期望的结果(就是溢出么).
1 a=2147483646
2 echo "a = $a" # a = 2147483646
3 let "a+=1" # 加1 "a".
4 echo "a = $a" # a = 2147483647
5 let "a+=1" # 再加1 "a" ,将超过上限了.
6 echo "a = $a" # a = -2147483648
7 # 错误 (溢出了)
在Bash 2.05b版本中,Bash支持64位整型了.
注意:Bash并不能理解浮点运算.它把包含的小数点看作字符串.
1 a=1.5
2
3 let "b = $a + 1.3" # 错误.
4 # t2.sh: let: b = 1.5 + 1.3: 表达式的语义错误(错误标志为".5 + 1.3")
5
6 echo "b = $b" # b=1
如果真想做浮点运算的话,使用bc(见12.8节),bc可以进行浮点运算或调用数学库函数.
位操作符.
(晕,有点强大过分了吧,位级操作都支持.)
位操作符在shell脚本中极少使用.它们最主要的用途看起来就是操作和test从sockets中
读出的变量."Bit flipping"与编译语言的联系很紧密,比如c/c++,在这种语言中它可以
运行得足够快.(原文有处on the fly,我查了一下,好像是没事干的意思,没理解)
<< 左移1位(每次左移都将乘2)
<<= 左移几位,=号后边将给出左移几位
let "var <<= 2"就是左移2位(就是乘4)
>> 右移1位(每次右移都将除2)
>>= 右移几位
& 按位与
&= 按位与赋值
| 按位或
|= 按位或赋值
~ 按位非
! 按位否?(没理解和上边的~有什么区别?),感觉是应该放到下边的逻辑操作中
^ 按位异或XOR
^= 异或赋值
逻辑操作:
&& 逻辑与
1 if [ $condition1 ] && [ $condition2 ]
2 # 与: if [ $condition1 -a $condition2 ] 相同
3 # 如果condition1和condition2都为true,那结果就为true.
4
5 if [[ $condition1 && $condition2 ]] # 也可以.
6 # 注意&&不允许出现在[ ... ]中.
注意:&&也可以用在and list中(见25章),但是使用的时候需要依赖上下文.
|| 逻辑或
1 if [ $condition1 ] || [ $condition2 ]
2 # 与: if [ $condition1 -o $condition2 ] 相同
3 # 如果condition1或condition2为true,那结果就为true.
4
5 if [[ $condition1 || $condition2 ]] # 也可以
6 # 注意||不允许出现在[ ... ]中.
注意:Bash将test每个连接到逻辑操作的状态的退出状态(见第6章).
Example 8-3 使用&&和||进行混合状态的test
################################Start Script#######################################
1 #!/bin/bash
2
3 a=24
4 b=47
5
6 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
7 then
8 echo "Test #1 succeeds."
9 else
10 echo "Test #1 fails."
11 fi
12
13 # 错误: if [ "$a" -eq 24 && "$b" -eq 47 ]
14 #+ 尝试执行' [ "$a" -eq 24 '
15 #+ 因为没找到']'所以失败了.
16 #
17 # 注意: 如果 [[ $a -eq 24 && $b -eq 24 ]] 能够工作.
18 # 那这个[[]]的test结构就比[]结构更灵活了.
19 #
20 # (在17行的"&&"与第6行的"&&"意义不同)
21 # Thanks, Stephane Chazelas, for pointing this out.
22
23
24 if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
25 then
26 echo "Test #2 succeeds."
27 else
28 echo "Test #2 fails."
29 fi
30
31
32 # -a和-o选项提供了
33 #+ 一种可选的混合test方法.
34 # Thanks to Patrick Callahan for pointing this out.
35
36
37 if [ "$a" -eq 24 -a "$b" -eq 47 ]
38 then
39 echo "Test #3 succeeds."
40 else
41 echo "Test #3 fails."
42 fi
43
44
45 if [ "$a" -eq 98 -o "$b" -eq 47 ]
46 then
47 echo "Test #4 succeeds."
48 else
49 echo "Test #4 fails."
50 fi
51
52
53 a=rhino
54 b=crocodile
55 if [ "$a" = rhino ] && [ "$b" = crocodile ]
56 then
57 echo "Test #5 succeeds."
58 else
59 echo "Test #5 fails."
60 fi
61
62 exit 0
################################End Script#########################################
&&和||操作也能在算术运算的上下文中找到.
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0
混杂操作:
, 逗号操作符
逗号操作符可以连接2个或多个算术运算.所有的操作都会被执行,但是只有最后一个
操作作为结果.
1 let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
2 echo "t1 = $t1" # t1 = 11
3
4 let "t2 = ((a = 9, 15 / 3))" # Set "a" and calculate "t2".
5 echo "t2 = $t2 a = $a" # t2 = 5 a = 9
","主要用在for循环中,具体见Example 10-12.
8.2 数字常量
------------
shell脚本默认都是将数字作为10进制数处理,除非这个数字某种特殊的标记法或前缀开头.
以0开头就是8进制.以0x开头就是16进制数.使用BASE#NUMBER这种形式可以表示其它进制
表示法
Example 8-4 数字常量的处理
################################Start Script#######################################
1 #!/bin/bash
2 # numbers.sh: 数字常量的几种不同的表示法
3
4 # 10进制: 默认
5 let "dec = 32"
6 echo "decimal number = $dec" # 32
7 # 一切都很正常
8
9
10 # 8进制: 以'0'(零)开头
11 let "oct = 032"
12 echo "octal number = $oct" # 26
13 # 表达式的结果用10进制表示.
14 #
15
16 # 16进制表示:数字以'0x'或者'0X'开头
17 let "hex = 0x32"
18 echo "hexadecimal number = $hex" # 50
19 # 表达式的结果用10进制表示.
20
21 # 其它进制: BASE#NUMBER
22 # BASE between 2 and 64.
22 # 2到64进制都可以.
23 # NUMBER必须在BASE的范围内,具体见下边.
24
25
26 let "bin = 2#111100111001101"
27 echo "binary number = $bin" # 31181
28
29 let "b32 = 32#77"
30 echo "base-32 number = $b32" # 231
31
32 let "b64 = 64#@_"
33 echo "base-64 number = $b64" # 4031
34 # 这种64进制的表示法中的每位数字都必须在64进制表示法的限制字符内.
35 # 10 个数字+ 26 个小写字母+ 26 个大写字母+ @ + _
36
37
38 echo
39
40 echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
41 # 1295 170 44822 3375
42
43
44 # 重要的注意事项:
45 # ---------------
46 # 如果使用的每位数字超出了这个进制表示法规定字符的范围的话,
47 #+ 将给出一个错误消息.
48
49 let "bad_oct = 081"
50 # (部分的) 错误消息输出:
51 # bad_oct = 081: too great for base (error token is "081")
52 # Octal numbers use only digits in the range 0 - 7.
53
54 exit 0 # Thanks, Rich Bartell and Stephane Chazelas, for clarification.
################################End Script#########################################
第三部分 超越基本
++++++++++++++++++++
第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(bryanh@giraffe-data.com)来维护的.
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 添加了额外的选项.
返回顶部