第三十一章 Gotchas
将保留字和字符声明为变量名
case=value0 #引发错误
23skidoo=value1 #引发错误
#以数字开头的变量名是由shell保留使用的
#试试_23skidoo=value1. 用下划线开头的变量名是允许的
#但是... 仅使用下划线来用做变量名也是不行的
_=25
echo $_ #$_是一个特殊的变量,被设置为最后的命令的最后一个参数
xyz((!*=value2 #引起严重错误
用连字符或其他保留字符当做变量(或函数名)
var-1=23 #用"var_1"代替
function-whatever() #用 'function_whatever ()' 代替
function.whatever () # 错误 用 'functionWhatever ()' 代替.
给变量和函数使用相同的名字。这会使脚本不能分辨两者
do_something()
{
echo "This function does something with \"$1\"."
}
do_something=do_something
do_something do_something
#这些都是合法的,但让人混淆
不适当地使用宽白字符。和其它的编程语言相比,Bash非常讲究空白字符的使用
var1 = 23 #'var1=23' 是正确的
let c = $a -$b # 'let c=$a-$b' 或 'let "c = $a - $b"'是正确的.
if [ $a -le 5] # if [ $a -le 5 ] 是正确的.
# if [ "$a" -le 5 ] 会更好
# [[ $a -le 5 ]] 也可以
未初始化的变量被认为是NULL值,而不是有零值。
#!/bin/bash
echo "uninitialized_var = $uninitialized_var"
#uninitialized_var =
混淆测试里的 = 和 -eq 操作符。请记住:= 是比较字符变量,而 -eq 比较整数。
if [ "$a" = 273 ] #$a 是一个整数还是一个字符串?
if [ "$a" -eq 273 ] # 如果$a 是一个整数,用这个表达式.
#有时你能混用 -eq 和 = 而没有不利的结果.
#然而 . . .
a=273.0 #不是一个整数
if [ "$a" = 273 ];then
echo "比较工程."
else
echo "比较不起作用."
fi
# 与 a=" 273" 和 a="0273" 一样.
# 同样, 问题仍然是试图对非整数值使用 "-eq" 测试.
if [ "$a" -eq 273.0 ];then
echo "a = $a"
fi #因错误信息而中断.
# test.sh: [: 273.0: integer expression expected
误用字符串比较操作符
Example 31-1 数字和字符串比较是不相等同的
#!/bin/bash
#
echo
number=1
while [ "$number" < 5 ] # 错误! 应该是: while [ "$number" -lt 5 ]
do
echo -n "$number"
let "number += 1"
done
echo "----------------------"
while [ "$number" \< 5 ]
do
echo -n "$number" # 看起来好像是能工作的, 但它其实是在对 ASCII 码的比较,而非是对数值的比较.
let "number += 1"
done
echo;echo "---------------------"
lesser=5
greater=105
if [ "$greater" \< "$lesser" ];then
echo "$greater is less than $lesser" #事实上, "105" 小于 "5". 是因为使用了字符串比较 (以 ASCII 码的排序顺序比较).
fi
echo
exit 0
有时在测试时的方括号([])里的变量需要引用起来(双引号)。如果用户没有这么做可能会引起不可预料的结果。参考例子7-6,例子16-5和例子9-6
在脚本里的命令可能会因为脚本没有运行权限而导致运行失败。如果用户不能在命令行里调一个命令,即使把这个命令加到一个脚本中也一样会失败。这时可以尝试更改访问命令的属性,甚至可能给他设置suid位(当然是以root来设置)。
试图用 - 来做重定向操作(事实上它不是操作符)会导致令人讨厌的意外。
command1 2> - | command2 #试图把 command1 的错误重定向到一个管道里.
#不会工作
command1 2>& - |command2 #也没效果
用Bash版本2+的功能可以当有错误信息时引发修复动作。
#!/bin/bash
#
minimun_version=2
E_BAD_VERSION=80
if [ "$BASH_VERSION" \< "$minimun_version" ];then
echo "此脚本仅适用于Bash版本$minimum或更高版本"
echo "强烈建议升级"
exit $E_BAD_VERSION
fi
...
在非Linux的机器上使用Bourne shell脚本(#!/bin/sh)的Bash专有功能可能会引起不可预料的行为。Linux系统通常都把sh取别名为bash,但在其他的常见的UNIX系统却不一定是这样。
一个带有DOS分格新行符(\r\n)的脚本会执行失败,因为#!/bin/bash\r\n不是合法的,不同与合法的#!/bin/bash\n。解决办法就是把脚本转换成UNIX分格的新行符。
#!/bin/bash
#
echo "Here"
unix2dos $0 #脚本先把自己改成DOS格式
chmod 755 $0 #更改回执行权限. 'unix2dos'命令会删除执行权限.
./$0
echo "There" #脚本尝试再次运行自己本身.但它是一个 DOS 文件而不会正常工作了.
exit
shell脚本以#!/bin/sh开头将不会在Bash兼容的模式下运行。一些Bash专有的功能可能会被禁用掉。那些需要完全使用Bash专有扩展特性的脚本应该使用#!/bin/bash开头。
脚本里在here document的终结输入的字符串前加入空白字符会引起不可预料的结果。
脚本不能export(导出)变量到它的父进程,或父进程的环境里。就像我们学的生物一样,一个子进程可以从父进程里继承但不能去影响父进程。
WHATEVER=/home/bozo
export WHATEVER
exit 0
bash$ echo $WHATEVER
bash$
可以确定,回到命令提示符,$WHATEVER变量仍然没有设置。
在子shell设置和操作变量,然后尝试在子shell的作用范围外使用相同的变量将会导致非期望的结果。
Example 31-2 子shell缺陷
#!/bin/bash
#
outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo
(
#子shell开始
echo "outer_variable inside subshell = $outer_variable"
inner_variable=inner
echo "inner_variable inside subshell = $inner_variable"
outer_variable=inner
echo "outer_variable inside subshell = $outer_variable"
#export inner_variable
#export outer_variable
)
echo
echo "inner_variable outside subshell = $inner_variable"
echo "outer_variable outside subshell = $outer_variable"
echo
exit 0
把echo的输出用管道输送给read命令可能会产生不可预料的结果。在这个情况下,read表现的好像它不是在一个子shell里一样。可用set命令代替。
Example 31-3 把echo的输出用管道输送给read命令
#!/bin/bash
#
a=aaa
b=bbb
c=ccc
echo "one two three" | read a b c #试图重新给 a, b, 和 c 赋值.
echo
echo "a = $a" #aaa
echo "b = $b" #bbb
echo "c = $c" #ccc
#重新赋值失败.
#用下面的另一种方法.
var=`echo "one two three"`
set -- $var
a=$1;b=$2;c=$3
echo "---------"
echo "a = $a" # a = one
echo "b = $b" # b = two
echo "c = $c" # c = three
#重新赋值成功.
#-----------------
#请注意 echo 值到'read'命令里是在一个子 SHELL 里起作用的.所以,变量的值只在子 SHELL 里被改变了.
a=aaa
b=bbb
c=ccc
echo;echo
echo "one two three" | (
read a b c;
echo "Inside subshell:";
echo "a = $a"; #one
echo "b = $b"; #two
echo "c = $c" #three
)
echo "---------------"
echo "Outside subshell: "
echo "a = $a" #aaa
echo "b = $b" #bbb
echo "c = $c" #ccc
exit 0
#循环管道问题
foundone=false
find $HOME -type f -atime +30 -size 100k |
while true
do
read f
echo "$f is over 100KB and has not been accessed in over 30 days"
echo "Consider moving the file to archives."
foundone=true
echo "Subshell level = $BASH_SUBSHELL" #Subshell level = 1. 现在是在子 shell 里头运行.
done
#foundone 变量在此总是有 false 值,因此它是在子 SHELL 里被设为 true 值的
if [ $foundone = fales ];then
echo "No files need archiving."
fi
# =================现在, 使用正确的方法:===========
foundone=false
for f in $(find $HOME -type f -atime +30 -size 100k) #没有使用管道.
do
echo "$f is over 100KB and has not been accessed in over 30 days"
echo "Consider moving the file to archives."
foundone=true
done
if [ $foundone = fales ];then
echo "No files need archiving."
fi
# =================另一种方法:===========
#脚本中读变量值的相应部分替换在代码块里头读变量,这使变量能在相同的子 SHELL 里共享了.
find $HOME -type f -atime +30 -size 100k | {
foundone=false
while read f
do
echo "$f is over 100KB and has not been accessed in over 30 days"
echo "Consider moving the file to archives."
foundone=true
done
if ! $foundone
then
echo "No files need archiving."
fi
}
相关的问题是:当尝试写 tail -f 的输出给管道并传递给 grep 时会发生问题.
tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log
# "error.log"文件里将不会写入任何东西.
在脚本中使用"suid" 的命令是危险的,因为这会危及系统安全。
用 shell 编写 CGI 程序是值得商榷的。Shell 脚本的变量不是"类型安全的",这样它用于 CGI 连接使用时会引发不希望的结果。其次,它很难防范黑客的攻击。
Bash 不能正确处理双斜线 (//) 字符串。
Linux 或 BSD 上写的 Bash 脚本可能需要修正以使它们也能在商业的 UNIX (或 Apple OSX)上运行。这些脚本常使用比一般的 UNIX 系统上的同类工具更强大功能的 GNU 命令和过滤工具。这方面一个明显的例子是文本处理工具 tr。