Turandot: Gli enigmi sono tre, la morte una! Caleph: No, no! Gli enigmi sono tre, una la vita! | |
Puccini |
将保留字和字符声明为变量名.
1 case=value0 # 引发错误. 2 23skidoo=value1 # 也会有错误. 3 # 以数字开头的变量名是由shell保留使用的. 4 # 试试 _23skidoo=value1. 用下划线开头的变量名是允许的. 5 6 # 但是 . . . 仅使用下划线来用做变量名也是不行的. 7 _=25 8 echo $_ # $_ 是一个特殊的变量,被设置为最后命令的最后一个参数. 9 10 xyz((!*=value2 # 引起严重的错误. 11 # 在第三版的Bash, 标点不能在变量名中出现. |
用连字符或其他保留字符当做变量名(或函数名).
1 var-1=23 2 # 用 'var_1' 代替. 3 4 function-whatever () # 错误 5 # 用 'function_whatever ()' 代替. 6 7 8 # 在第三版的 Bash, 标点不能在函数名中使用. 9 function.whatever () # 错误 10 # 用 'functionWhatever ()' 代替. |
给变量和函数使用相同的名字. 这会使脚本不能分辨两者.
1 do_something () 2 { 3 echo "This function does something with \"$1\"." 4 } 5 6 do_something=do_something 7 8 do_something do_something 9 10 # 这些都是合法的,但让人混淆. |
不适当地使用宽白符(whitespace). 和其它的编程语言相比,Bash非常讲究空白字符的使用.
1 var1 = 23 # 'var1=23' 正确. 2 # 上面一行,Bash试图执行命令"var1" 3 # 并且它的参数是"="和"23". 4 5 let c = $a - $b # 'let c=$a-$b' 或 'let "c = $a - $b"'是正确的. 6 7 if [ $a -le 5] # if [ $a -le 5 ] 是正确的. 8 # if [ "$a" -le 5 ] 会更好. 9 # [[ $a -le 5 ]] 也可以. |
未初始化的变量(指赋值前的变量)被认为是NULL值的,而不是有零值.
1 #!/bin/bash 2 3 echo "uninitialized_var = $uninitialized_var" 4 # uninitialized_var = |
混淆测试里的= 和 -eq 操作符. 请记住, = 是比较字符变量而 -eq 比较整数.
1 if [ "$a" = 273 ] # $a 是一个整数还是一个字符串? 2 if [ "$a" -eq 273 ] # 如果$a 是一个整数,用这个表达式. 3 4 # 有时你能混用 -eq 和 = 而没有不利的结果. 5 # 然而 . . . 6 7 8 a=273.0 # 不是一个整数. 9 10 if [ "$a" = 273 ] 11 then 12 echo "Comparison works." 13 else 14 echo "Comparison does not work." 15 fi # Comparison does not work. 16 17 # 与 a=" 273" 和 a="0273" 一样. 18 19 20 # 同样, 问题仍然是试图对非整数值使用 "-eq" 测试. 21 22 if [ "$a" -eq 273.0 ] 23 then 24 echo "a = $a" 25 fi # 因错误信息而中断. 26 # test.sh: [: 273.0: integer expression expected |
误用字符串比较操作符.
例子 31-1. 数字和字符串比较是不相等同的
1 #!/bin/bash 2 # bad-op.sh: 在整数比较中使用字符串比较. 3 4 echo 5 number=1 6 7 # 下面的 "while" 循环有两个错误: 8 #+ 一个很明显,另一个比较隐蔽. 9 10 while [ "$number" < 5 ] # 错误! 应该是: while [ "$number" -lt 5 ] 11 do 12 echo -n "$number " 13 let "number += 1" 14 done 15 # 尝试运行时会收到错误信息而退出: 16 #+ bad-op.sh: line 10: 5: No such file or directory 17 # 在单括号里, "<" 需要转义, 18 #+ 而即使是如此, 对此整数比较它仍然是错的. 19 20 21 echo "---------------------" 22 23 24 while [ "$number" \< 5 ] # 1 2 3 4 25 do # 26 echo -n "$number " # 看起来好像是能工作的, 但 . . . 27 let "number += 1" #+ 它其实是在对 ASCII 码的比较, 28 done #+ 而非是对数值的比较. 29 30 echo; echo "---------------------" 31 32 # 下面这样便会引起问题了. 例如: 33 34 lesser=5 35 greater=105 36 37 if [ "$greater" \< "$lesser" ] 38 then 39 echo "$greater is less than $lesser" 40 fi # 105 is less than 5 41 # 事实上, "105" 小于 "5" 42 #+ 是因为使用了字符串比较 (以ASCII码的排序顺序比较). 43 44 echo 45 46 exit 0 |
有时在测试时的方括号([ ])里的变量需要引用起来(双引号). 如果没有这么做可能会引起不可预料的结果. 参考例子 7-6, 例子 16-5, 和 例子 9-6.
在脚本里的命令可能会因为脚本没有运行权限而导致运行失败. 如果用户不能在命令行里调用一个命令,即使把这个命令加到一个脚本中也一样会失败. 这时可以尝试更改访命令的属性,甚至可能给它设置suid位(当然是以root来设置).
试图用 - 来做重定向操作(事实上它不是操作符)会导致令人讨厌的意外.
1 command1 2> - | command2 # 试图把command1的错误重定向到一个管道里... 2 # ...不会工作. 3 4 command1 2>& - | command2 # 也没有效果. 5 6 Thanks, S.C. |
用 Bash 版本 2+ 的功能可以当有错误信息时引发修复动作. 老一些的 Linux机器可能默认的安装是 1.XX 版本的Bash.
1 #!/bin/bash 2 3 minimum_version=2 4 # 因为 Chet Ramey 经常给Bash增加新的特性, 5 # 你把 $minimum_version 设为 2.XX比较合适,或者是其他合适的值. 6 E_BAD_VERSION=80 7 8 if [ "$BASH_VERSION" \< "$minimum_version" ] 9 then 10 echo "This script works only with Bash, version $minimum or greater." 11 echo "Upgrade strongly recommended." 12 exit $E_BAD_VERSION 13 fi 14 15 ... |
在非Linux的机器上使用Bourne shell脚本(#!/bin/sh)的Bash专有功能可能会引起不可预料的行为. Linux系统通常都把sh 取别名为 bash, 但在其他的常见的UNIX系统却不一定是这样.
使用Bash中没有文档化的属性是危险的尝试. 在这本书的前几版中有几个脚本依赖于exit或return的值没有限制不能用负整数(虽然限制了exit或return 的最大值是255). 不幸地是, 在版本 2.05b 以上这种情况就消失了. 参考See 例子 23-9.
一个带有DOS风格新行符 (\r\n) 的脚本会执行失败, 因为#!/bin/bash\r\n 不是合法的,不同于合法的#!/bin/bash\n. 解决办法就是把脚本转换成UNIX风格的新行符.
1 #!/bin/bash 2 3 echo "Here" 4 5 unix2dos $0 # 脚本先把自己改成DOS格式. 6 chmod 755 $0 # 更改回执行权限. 7 # 'unix2dos'命令会删除执行权限. 8 9 ./$0 # 脚本尝试再次运行自己本身. 10 # 但它是一个DOS文件而不会正常工作了. 11 12 echo "There" 13 14 exit 0 |
shell脚本以 #!/bin/sh 行开头将不会在Bash兼容的模式下运行. 一些Bash专有的功能可能会被禁用掉. 那些需要完全使用Bash专有扩展特性的脚本应该用#!/bin/bash开头.
脚本里在 here document 的终结输入的字符串前加入空白字符会引起不可预料的结果.
脚本不能export(导出)变量到它的父进程(parent process),或父进程的环境里. 就像我们学的生物一样,一个子进程可以从父进程里继承但不能去影响父进程.
1 WHATEVER=/home/bozo 2 export WHATEVER 3 exit 0 |
bash$ echo $WHATEVER bash$ |
在子SHELL(subshell)设置和操作变量 , 然后尝试在子SHELL的作用范围外使用相同名的变量将会导致非期望的结果.
例子 31-2. 子SHELL缺陷
1 #!/bin/bash 2 # 在子SHELL中的变量缺陷. 3 4 outer_variable=outer 5 echo 6 echo "outer_variable = $outer_variable" 7 echo 8 9 ( 10 # 子SHELL开始 11 12 echo "outer_variable inside subshell = $outer_variable" 13 inner_variable=inner # Set 14 echo "inner_variable inside subshell = $inner_variable" 15 outer_variable=inner # Will value change globally? 16 echo "outer_variable inside subshell = $outer_variable" 17 18 # 导出变量会有什么不同吗? 19 # export inner_variable 20 # export outer_variable 21 # 试试看. 22 23 # 子SHELL结束 24 ) 25 26 echo 27 echo "inner_variable outside subshell = $inner_variable" # Unset. 28 echo "outer_variable outside subshell = $outer_variable" # Unchanged. 29 echo 30 31 exit 0 32 33 # 如果你没有注释第 19 和 20行会怎么样? 34 # 会有什么不同吗? |
把 echo 的输出用管道(Piping)输送给read命令可能会产生不可预料的结果. 在这个情况下, read 表现地好像它是在一个子SHELL里一样. 可用set 命令代替 (就像在例子 11-16里的一样).
例子 31-3. 把echo的输出用管道输送给read命令
1 #!/bin/bash 2 # badread.sh: 3 # 尝试用 'echo 和 'read' 4 #+ 来达到不用交互地给变量赋值的目的. 5 6 a=aaa 7 b=bbb 8 c=ccc 9 10 echo "one two three" | read a b c 11 # 试图重新给 a, b, 和 c赋值. 12 13 echo 14 echo "a = $a" # a = aaa 15 echo "b = $b" # b = bbb 16 echo "c = $c" # c = ccc 17 # 重新赋值失败. 18 19 # ------------------------------ 20 21 # 用下面的另一种方法. 22 23 var=`echo "one two three"` 24 set -- $var 25 a=$1; b=$2; c=$3 26 27 echo "-------" 28 echo "a = $a" # a = one 29 echo "b = $b" # b = two 30 echo "c = $c" # c = three 31 # 重新赋值成功. 32 33 # ------------------------------ 34 35 # 也请注意echo值到'read'命令里是在一个子SHELL里起作用的. 36 # 所以,变量的值只在子SHELL里被改变了. 37 38 a=aaa # 从头开始. 39 b=bbb 40 c=ccc 41 42 echo; echo 43 echo "one two three" | ( read a b c; 44 echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" ) 45 # a = one 46 # b = two 47 # c = three 48 echo "-----------------" 49 echo "Outside subshell: " 50 echo "a = $a" # a = aaa 51 echo "b = $b" # b = bbb 52 echo "c = $c" # c = ccc 53 echo 54 55 exit 0 |
事实上, 也正如 Anthony Richardson 指出的那样, 管道任何的数据到循环里都会引起相似的问题.
1 # 循环管道问题. 2 # Anthony Richardson编写此例, 3 #+ Wilbert Berendsen补遗此例. 4 5 6 foundone=false 7 find $HOME -type f -atime +30 -size 100k | 8 while true 9 do 10 read f 11 echo "$f is over 100KB and has not been accessed in over 30 days" 12 echo "Consider moving the file to archives." 13 foundone=true 14 # ------------------------------------ 15 echo "Subshell level = $BASH_SUBSHELL" 16 # Subshell level = 1 17 # 没错, 现在是在子shell里头运行. 18 # ------------------------------------ 19 done 20 21 # foundone 变量在此总是有false值 22 #+ 因此它是在子SHELL里被设为true值的 23 if [ $foundone = false ] 24 then 25 echo "No files need archiving." 26 fi 27 28 # =====================现在, 使用正确的方法:================= 29 30 foundone=false 31 for f in $(find $HOME -type f -atime +30 -size 100k) # 没有使用管道. 32 do 33 echo "$f is over 100KB and has not been accessed in over 30 days" 34 echo "Consider moving the file to archives." 35 foundone=true 36 done 37 38 if [ $foundone = false ] 39 then 40 echo "No files need archiving." 41 fi 42 43 # ==================另一种方法================== 44 45 # 脚本中读变量值的相应部分替换在代码块里头读变量, 46 #+ 这使变量能在相同的子SHELL里共享了. 47 # Thank you, W.B. 48 49 find $HOME -type f -atime +30 -size 100k | { 50 foundone=false 51 while read f 52 do 53 echo "$f is over 100KB and has not been accessed in over 30 days" 54 echo "Consider moving the file to archives." 55 foundone=true 56 done 57 58 if ! $foundone 59 then 60 echo "No files need archiving." 61 fi 62 } |
相关的问题是:当尝试写 tail -f 的输出给管道并传递给grep时会发生问题.
1 tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log 2 # "error.log"文件里将不会写入任何东西. |
--
在脚本中使用"suid" 的命令是危险的, 因为这会危及系统安全. [1]
用shell编写CGI程序是值得商榷的. Shell脚本的变量不是"类型安全的", 这样它用于CGI连接使用时会引发不希望的结果. 其次, 它很难防范骇客的攻击.
Bash 不能正确处理双斜线 (//) 字符串.
在Linux 或 BSD上写的Bash脚本可能需要修正以使它们也能在商业的UNIX (或 Apple OSX)上运行. 这些脚本常使用比一般的UNIX系统上的同类工具更强大功能的GNU 命令和过滤工具. 这方面一个明显的例子是文本处理工具tr.
Danger is near thee -- Beware, beware, beware, beware. Many brave hearts are asleep in the deep. So beware -- Beware. | |
A.J. Lamb and H.W. Petrie |
[1] | 给脚本设置suid 权限是没有用的. |