Tip 1. 注意shell执行的具体类型
Shell脚本的第一行(Shebang line, e.g. #!/bin/bash)指定了其执行的具体类型,dash/bash/ksh/…等等,之间会有些细微的区别。如果你的脚本没有Shebang line,那么由系统变量$SHELL来决定其执行类型。
PS1:/etc/shells中记录着系统可用的shell类型。所以当你发现同样的脚本在不同系统表现不同的时候,有可能是dash和bash(或是其他shell)之间的区别。
PS2:sh是一种POSIX规范,而众多的shell类型是它的实现。linux系统中的sh一般是个symlink,例如ubnutu中默认的sh就会指向/bin/dash。
Tip 2. 赋值语句两边的空格
a = 'test',这条语句是错误的。因为在shell里赋值语句=的两边是不能有空格的,a=test才是正确写法。这个错误几乎是初学者必犯的错误了。。。
但是在判断语句中[]两侧则必须留空格:if [a == 'test']是错误的;if [ a == 'test' ]是正确的。
Tip 3. 各种预留的$变量
$*表示参数列表$0, $1, $2…$#表示参数列表个数$?表示上一条命令的执行结果$!表示上一条命令的PID号
PS1:对于整个shell来说,参数列表是运行时接的各个参数;对于shell中的函数来说,参数列表是调用时传入的各个参数。
PS2:对于test.sh '1 2' 3 4来说,$*与$@是等价的(均包含1 2 3 4四个参数)。"$*"与"$@"则不同,"$*"是一个参数1 2 3 4,而"$@"为三个参数1 2 3 4)在。Google的shell code规范推荐使用$@。
Tip 4. 检查文件/文件夹是否存在等等
if [ –d ${dir} ]判断文件夹是否存在if [ –f ${file} ]判断文件是否存在- ……
这里想说的是如何查询诸如此类判断条件的说明:man [ 或 man test即可。
Tip 5. [ … ] 与 [[ … ]]
[ … ]指的是标准POSIX规范中的条件判断,而[[ … ]]是对POSIX规范的扩展。
这些区别有:(注意:对POSIX Shell适用的对Extended POSIX Shell均适用,反之则不然)
| POSIX Shell | Extended POSIX Shell | |
|---|---|---|
| 判断字符串相等 | = | == |
| 多条件判断 | -o | || |
| -a | && | |
| 正则匹配 | N/A | =~ |
Tip 6. 替换if判断语句
if [ ! false command ]; then
dosomthing
可以用false command || dosomething替代。同理:
if [ true command ]; then
dosomthing
可以用true command && dosomething替代。
Tip 7. 将执行结果赋予变量
有两种方式:var1=`command` 或者 var1=$(command)。Google的shell code规范推荐后者。
Tip 8. 变量替换
${var1-str}判断当var1为空时,返回str(不改变var1的值)${var1+str}判断当var1不为空时,返回str(不改变var1的值)${var1=str}判断当var1为空时,返回str,且将str赋给var1${var1?str}判断当var1为空时,在错误输出中输出str
PS:也有在var1后加上:的写法,二者大部分情况下是等价的。除了一个变量被定义但没赋值的情况(var1=),这种情况下加:会报错。
${var1:pos}返回第pos个字符后的值${var1:pos:len}返回第pos字符后,长度为len的值${var1/pattern/replacement}将var1匹配到的第一个pattern替换为replacement并返回${var1//pattern/replacement}将var1中的所有pattern替换为replacement并返回${var1/#pattern/replacement}相当于标准正则中的^pattern,即匹配的pattern必须在var1首部-
${var1/%pattern/replacement}相当于标准正则中的pattern$,即匹配的pattern必须在var1尾部 ${var1#pattern}将var1中从前往后遇到的第一个尽可能短的pattern删除并返回${var1##pattern}将var1中从前往后遇到的第一个尽可能长的pattern删除并返回${var1%pattern}将var1中从后往前的第一个尽可能短的pattern删除并返回${var1%%pattern}将var1中从后往前的第一个尽可能长的pattern删除并返回
以上操作均不改变var1的原始值(即不带-i的sed)。实际上以上的所有操作都能通过sed完成,但我的习惯是能用原生的shell替换,就不再调一个sed。例如你要把某文件的后缀名删掉,使用${file%.*}而不是echo "$file" | sed 's/\.[^.]*$//'。
Tip 9. 数值计算与判断
因为shell中没有原生的浮点数,以下所说的数值均是指整数。总体来说,赋值/计算的方法有三种:
letexpr(())(推荐)
#!/bin/bash
foo=1
let bar1=foo+1
let bar2=$foo+2
bar3=$(expr $foo + 3) #不能用foo
bar4=$((foo+4)) #不能用$foo
echo $bar1$bar2$bar3$bar4 # 输出2345
关于数值的比较,初学者很容易犯错,想当然的使用==, <=等等。实际上bash必须使有特定的数值比较符:-eq, -ne, -lt, -gt, etc。
Tip 10. 比较字符串/数值的大小
先来看一段代码:
foo="9.0"
bar="10.0"
if [ $foo > $bar ]; then
echo "Unexpected"
else
echo "Expected"
fi
你觉得它会输出什么?Expected还是什么?这里其实有两个坑:
- 这里的
>不是你所想当然的比较符,它其实是重定向(更确切的说是清空它所接的参数文件的内容,如果没有则创建一个空文件)。所以这个if执行的效果就是在当前目录生成一个叫做10.0的空文件,返回0作为这个if的判断结果,然后输出Unexpected。 - 如果你认为按照Tip 5所说,将
[ $foo > $bar ]改为[[ $foo > $bar ]]就万事大吉,那就掉进了另一个坑。在Tip 9中说到:shell中是没有原生的浮点数的,这里的foo和bar均是按照字符串来对待的。也就是说这个if的比较方式是从高到低,依次比较每个字符:那么很显然9>1,所以仍然会输出Unexpected