写shell脚本时的常见错误

内容目录
1. for i in `ls *.mp3`
2. cp $file $target
3. [ $foo = "bar" ]
4. cd `dirname "$f"`
5. [ "$foo" = bar && "$bar" = foo ]
6. [[ $foo > 7 ]]
7. grep foo bar | while read line; do ((count++) ); done
8. if [grep foo myfile]
9. if [bar="$foo"]
10. if [ [ a = b ] && [ c = d ] ]
11. cat file | sed s/foo/bar/ > file
12. echo $foo
13. $foo=bar
14. foo = bar
15. echo <<EOF
16. su ­c 'some command'
17. cd /foo; bar
18. [ bar == "$foo" ]
19. for i in {1..10}; do ./something &; done
20. cmd1 && cmd2 || cmd3
21. UTF­8 的 BOM(Byte­Order Marks)问题
22. echo "Hello World!"
23. for arg in $*
24. function foo()

1. for i in `ls *.mp3`
常见的错误写法:
for i in `ls *.mp3`; do     # Wrong!
为什么错误呢?因为 for...in 语句是按照空白来分词的,包含空格的文件名会被拆成多
个词。 如遇到 01 ­ Don't Eat the Yellow Snow.mp3 时,i 的值会依次取 01,­,Don't,
等等。
用双引号也不行,它会将 ls *.mp3 的全部结果当成一个词来处理。
for i in "`ls *.mp3`"; do   # Wrong!
正确的写法是
for i in *.mp3; do


2. cp $file $target
这句话基本上正确,但同样有空格分词的问题。所以应当用双引号:
cp "$file" "$target"
但是如果凑巧文件名以 ­ 开头,这个文件名会被 cp 当作命令行选项来处理,依旧很
头疼。可以试试下面这个。
cp ­­ "$file" "$target"
运气差点的再碰上一个不支持 ­­ 选项的系统,那只能用下面的方法了:使每个变量
都以目录开头。
for i in ./*.mp3; do
cp "$i" /target
...


3. [ $foo = "bar" ]
当$foo 为空时,上面的命令就变成了
[ = "bar" ]
类似地,当$foo 包含空格时:
[ multiple words here = "bar" ]
两者都会出错。所以应当用双引号将变量括起来:
[ "$foo" = bar ]      # 几乎完美了。
但是!当$foo 以 ­ 开头时依然会有问题。 在较新的 bash 中你可以用下面的方法来代
替,[[ 关键字能正确处理空白、空格、带横线等问题。
[[ $foo = bar ]]      # 正确
旧版本 bash 中可以用这个技巧(虽然不好理解):
[ x"$foo" = xbar ]    # 正确
或者干脆把变量放在右边,因为 [ 命令的等号右边即使是空白或是横线开头,依然能
正常工作。 (Java 编程风格中也有类似的做法,虽然目的不一样。)
[ bar = "$foo" ]      # 正确


4. cd `dirname "$f"`
同样也存在空格问题。那么加上引号吧。
cd "`dirname "$f"`"
问题来了,是不是写错了?由于双引号的嵌套,你会认为`dirname 是第一个字符串,
`是第二个字符串。 错了,那是 C 语言。在 bash 中,命令替换(反引号``中的内容)
里面的双引号会被正确地匹配到一起, 不用特意去转义。
$()语法也相同,如下面的写法是正确的。
cd "$(dirname "$f")"


5. [ "$foo" = bar && "$bar" = foo ]
[ 中不能使用 && 符号!因为 [ 的实质是 test 命令,&& 会把这一行分成两个命令的。
应该用以下的写法。
[ bar = "$foo" ­a foo = "$bar" ]       # Right!
[ bar = "$foo" ] && [ foo = "$bar" ]   # Also right!
[[ $foo = bar && $bar = foo ]]         # Also right!


6. [[ $foo > 7 ]]
很可惜 [[ 只适用于字符串,不能做数字比较。数字比较应当这样写:
(( $foo > 7 ))
或者用经典的写法:
[ $foo ­gt 7 ]
但上述使用 ­gt 的写法有个问题,那就是当 $foo 不是数字时就会出错。你必须做好类
型检验。
这样写也行。
[[ $foo ­gt 7 ]]


7. grep foo bar | while read line; do ((count++) ); done
由于格式问题,标题中我多加了一个空格。实际的代码应该是这样的:
grep foo bar | while read line; do ((count++)); done         # 错误!
这行代码数出 bar 文件中包含 foo 的行数,虽然很麻烦(等同于 grep ­c foo bar 或者
grep foo bar | wc ­l)。 乍一看没有问题,但执行之后 count 变量却没有值。因为管道
中的每个命令都放到一个新的子 shell 中执行, 所以子 shell 中定义的 count 变量无法
传递出来。


8. if [grep foo myfile]
初学者常犯的错误,就是将 if 语句后面的 [ 当作 if 语法的一部分。实际上它是一个命
令,相当于 test 命令, 而不是 if 语法。这一点 C 程序员特别应当注意。
if 会将 if 到 then 之间的所有命令的返回值当作判断条件。因此上面的语句应当写成
if grep foo myfile > /dev/null; then

9. if [bar="$foo"]
同样,[ 是个命令,不是 if 语句的一部分,所以要注意空格。
if [ bar = "$foo" ]

10. if [ [ a = b ] && [ c = d ] ]
同样的问题,[ 不是 if 语句的一部分,当然也不是改变逻辑判断的括号。它是一个命
令。可能 C 程序员比较容易犯这个错误?
if [ a = b ] && [ c = d ]        # 正确

11. cat file | sed s/foo/bar/ > file
你不能在同一条管道操作中同时读写一个文件。根据管道的实现方式,file 要么被截
断成 0 字节,要么会无限增长直到填满整个硬盘。 如果想改变原文件的内容,只能
先将输出写到临时文件中再用 mv 命令。
sed 's/foo/bar/g' file > tmpfile && mv tmpfile file

12. echo $foo
这句话还有什么错误码?一般来说是正确的,但下面的例子就有问题了。
MSG="Please enter a file name of the form *.zip"
echo $MSG         # 错误!
如果恰巧当前目录下有 zip 文件,就会显示成
Please enter a file name of the form freenfss.zip lw35nfss.zip
所以即使是 echo 也别忘记给变量加引号。


13. $foo=bar
变量赋值时无需加 $ 符号——这不是 Perl 或 PHP。

14. foo = bar
变量赋值时等号两侧不能加空格——这不是 C 语言。

15. echo <<EOF
here document 是个好东西,它可以输出成段的文字而不用加引号也不用考虑换行符的
处理问题。 不过 here document 输出时应当使用 cat 而不是 echo。
# This is wrong:
echo <<EOF
Hello world
EOF
# This is right:
cat <<EOF
Hello world
EOF


16. su ­c 'some command'
原文的意思是,这条基本上正确,但使用者的目的是要将 ­c 'some command' 传给
shell。 而恰好 su 有个 ­c 参数,所以 su 只会将 'some command' 传给 shell。所以应该
这么写:
su root ­c 'some command'
但是在我的平台上,man su 的结果中关于 ­c 的解释为
­c, ­­commmand=COMMAND
pass a single COMMAND to the shell with ­c
也就是说,­c 'some command' 同样会将 ­c 'some command' 这样一个字符串传递给
shell, 和这条就不符合了。不管怎样,先将这一条写在这里吧。


17. cd /foo; bar
cd 有可能会出错,出错后 bar 命令就会在你预想不到的目录里执行了。所以一定要
记得判断 cd 的返回值。
cd /foo && bar
如果你要根据 cd 的返回值执行多条命令,可以用 ||。
cd /foo || exit 1;
bar
baz
关于目录的一点题外话,假设你要在 shell 程序中频繁变换工作目录,如下面的代码:
find ... ­type d | while read subdir; do
cd "$subdir" && whatever && ... && cd ­
done
不如这样写:
find ... ­type d | while read subdir; do
(cd "$subdir" && whatever && ...)
done
括号会强制启动一个子 shell,这样在这个子 shell 中改变工作目录不会影响父 shell
(执行这个脚本的 shell), 就可以省掉 cd ­ 的麻烦。
你也可以灵活运用 pushd、popd、dirs 等命令来控制工作目录。


18. [ bar == "$foo" ]
[ 命令中不能用 ==,应当写成
[ bar = "$foo" ] && echo yes
[[ bar == $foo ]] && echo yes

19. for i in {1..10}; do ./something &; done
& 后面不应该再放 ; ,因为 & 已经起到了语句分隔符的作用,无需再用;。
for i in {1..10}; do ./something & done

20. cmd1 && cmd2 || cmd3
有人喜欢用这种格式来代替 if...then...else 结构,但其实并不完全一样。如果 cmd2 返
回一个非真值,那么 cmd3 则会被执行。 所以还是老老实实地用 if cmd1; then cmd2;
else cmd3 为好。

21. UTF­8 的 BOM(Byte­Order Marks)问题
UTF­8 编码可以在文件开头用几个字节来表示编码的字节顺序,这几个字节称为
BOM。但 Unix 格式的 UTF­8 编码不需要 BOM。 多余的 BOM 会影响 shell 解析,特别
是开头的 #!/bin/sh 之类的指令将会无法识别。
MS­DOS 格式的换行符(CRLF)也存在同样的问题。如果你将 shell 程序保存成 DOS 格
式,脚本就无法执行了。
$ ./dos
­bash: ./dos: /bin/sh^M: bad interpreter: No such file or directory


22. echo "Hello World!"
交互执行这条命令会产生以下的错误:
­bash: !": event not found
因为 !" 会被当作命令行历史替换的符号来处理。不过在 shell 脚本中没有这样的问题。
不幸的是,你无法使用转义符来转义!:
$ echo "hi\!"
hi\!
解决方案之一,使用单引号,即
$ echo 'Hello, world!'
如果你必须使用双引号,可以试试通过 set +H 来取消命令行历史替换。
set +H
echo "Hello, world!"


23. for arg in $*
$*表示所有命令行参数,所以你可能想这样写来逐个处理参数,但参数中包含空格时
就会失败。如:
#!/bin/bash
# Incorrect version
for x in $*; do
echo "parameter: '$x'"
done
$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg'
parameter: '1'
parameter: 'arg2'
parameter: 'arg3'
正确的方法是使用 "$@"。
#!/bin/bash
# Correct version
for x in "$@"; do
echo "parameter: '$x'"
done
$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg 1'
parameter: 'arg2'
parameter: 'arg3'
在 bash 的手册中对 $* 和 $@ 的说明如下:
*    Expands to the positional parameters, starting from one.  
When the expansion occurs within double quotes, it
expands to a single word with the value of each parameter
separated by the first character of the IFS special variable.  
That is, "$*" is equivalent to "$1c$2c...",
@    Expands to the positional parameters, starting from one.
When the expansion occurs within double quotes, each
parameter expands to a separate word.  That  is,  "$@"  
is equivalent to "$1" "$2" ...  
可见,不加引号时 $* 和 $@ 是相同的,但"$*" 会被扩展成一个字符串,而 "$@" 会
被扩展成每一个参数。


24. function foo()
在 bash 中没有问题,但其他 shell 中有可能出错。不要把 function 和括号一起使用。
最为保险的做法是使用括号,即
foo() {
...
}

 

转载于:https://my.oschina.net/u/3251865/blog/839164

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值