写好Shell脚本那些不得不知道的细节
想写好Shell脚本,有很多细节不得不知道,细节的不注意会给脚本调试带来很多麻烦,甚至导致运行的结果天差地别,下面总结了我实际工作中遇到的18大细节,分享给大家。
1. Shell四则运算
在我们日常的shell编程中,经常需要进行数值的运算,而Shell的四则运算有很多细节需要注意,稍不留神就容易出错。 expr运算式后面每个参数间要有空格,如下所示
[root@node02 ~]# expr 2+2
2+2
[root@node02 ~]# expr 2 + 2
4
其他三种不需要
[root@node02 ~]# echo $((2+2))
4
[root@node02 ~]# let var=2+2
[root@node02 ~]# echo $var
4
[root@node02 ~]# echo 2+2 | bc
4
另外Shell本身不支持浮点运算,如果需要进行浮点计算,可以使用下面两种方法:
- 用bc计算器实现
echo "scale=2; 8*3/7" | bc
这里一定要使用双引号,scale表示保留小数点后位数
- 使用awk内部的计算方法
awk 'BEGIN{print 7.01*5-4.01 }'
2. 条件表达式
cmd1成功执行才执行cmd2,cmd1和cmd2其一不能成功执行则执行cmd3
cmd1 && cmd2 || cmd3
当cmd为多条命令时,command1 || {command2;command3}
[ -f "$f1" ] && echo 1
相当于
if [条件];then
指令集
fi
[ -f "$f1" ] && echo 1 || echo 0
相当于
if [条件];then
指令集1
else
指令集2
fi
3. 命令组合
命令组合有两种形式: {命令表}
和(命令表)
,前者只在本shell中执行,不产生新的子进程;后者要产生新的子进程来执行命令表。
例1: { cd /tmp; pwd; }
该命令表只能在当前shell下执行,先进入目录tmp,然后执行pwd命令,执行完毕后,当前目录已改变为pwd。
[root@node02 ~]# { cd /tmp; pwd; }
/tmp
[root@node02 tmp]#
已经切换到/tmp目录下了。
例2: (cd /tmp; pwd; )
当前shell要生成一个子shell进程,由该子shell来执行命令表。子shell完成操作后,自然消亡,而其父shell进程的当前路径并没有变化。
[root@node02 ~]# ( cd /tmp; pwd; )
/tmp
[root@node02 ~]#
当前目录未改变。
4. 将标准输出和错误输出改向out文件
$ cmd >out 2>>out
$ cmd >out 2>>&1
5. shell的变量
hell实际上是基于字符串的程序设计语言,但也有变量。shell变量能够而且只能存储正文字符串,即它只有一种类型的变量即串变量。但从赋值的形式上看,则可以分成四种类型的变量或变量形式。变量的名字必须以字母或下划线开头,可以包括字母、数字和下划线。
- 环境变量
Shell执行环境由一系列环境变量组成,这些变量是由shell维护和管理的,变量名由大写字母或数字组成,可被用户重新定义。
CDPATH 执行cd命令时使用的搜索路径;
HOME 用户的home目录;
PATH 寻找命令或可执行文件的搜索路径;
PS1 主命令提示符,默认为“$”;
PS2 从命令提示符,默认为“ >”;
TERM 使用的终端类型。
例:
[root@node02 ~]# echo $TERM
xterm
[root@node02 ~]# echo $PS1
[\u@\h \W]\$
[root@node02 ~]# echo $PS2
>
[root@node02 ~]# echo $HOME
/root
- 用户自定义变量
语法格式:name=string
,赋值号=
两边不允许有空白符
var=value 赋值操作
var = value 相等操作
只读变量 readonly var
。
- 位置变量
当一个shell过程被调用时, shell隐含地为它建立一系列的位置变量。这种位置变量是系统预定义好的,可以直接引用。如命令行的shell过程名本身被指定为位置变量$0,即”$0” 为当前脚本的文件名; 第一个命令参数为$1
,……,第九个命令参数为$9
。
ls / /bin /etc /usr/bin /dev
$0 $1 $2 $3 $4 $5
set
命令和shift
命令
位置变量可以使用set
命令进行强制性赋值。但是$0
不能使用set
来复制
当位置变量个数超出9时,就不能直接引用位置大于9的位置变量了,必须用shift
命令存取。每执行一次shift
命令,删除$1
位置变量,并使其他的所有位置变量向左移动一个位置。
[root@node02 ~]# set first second third fourth
[root@node02 ~]# echo $1 $2
first second
[root@node02 ~]# shift
[root@node02 ~]# echo $1 $2
second third
[root@node02 ~]#
- 预定义的特殊变量
在shell中有一组特殊的变量,其变量名和变量值只有shell本身才可以设置。如:
"$#" 记录传递给shell的自变量个数
"$*" 传递给脚本或函数的所有参数
$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数
"$$" 记录当前shell的进程号
"$?" 取最近一次命令执行后的退出状态
6. 变量默认值替换
{file-my.file.txt} 若 $file 没设定,则使用 my.file.txt 作传回值,空值及非空值不作处理
${file:-my.file.txt} 若 $file 没有设定或为空值,则使用 my.file.txt 作传回值,非空值时不作处理
${file+my.file.txt} 若$file 设为空值或非空值,均使用my.file.txt作传回值,没设定时不作处理
${file:+my.file.txt} 若 $file 为非空值,则使用 my.file.txt 作传回值,没设定及空值不作处理
${file=txt} 若 $file 没设定,则回传 txt ,并将 $file 赋值为txt,空值及非空值不作处理
${file:=txt} 若 $file 没设定或空值,则回传 txt ,将 $file 赋值为txt,非空值时不作处理
${file?my.file.txt} 若 $file 没设定,则将 my.file.txt 输出至 STDERR, 空值及非空值不作处理
${file:?my.file.txt} 若 $file没设定或空值,则将my.file.txt输出至STDERR ,非空值时不作处理
单引号、双引号与特殊字符的恩怨情仇
使用单引号消除被括在单引号中的所有特殊字符的含义,即单引号表示内容照原样不动。
使用双引号能消除被括在双引号中的大部分特殊字符的含义,不能消除的字符有:$
、 ''
、 \
、反引号
。
7. test命令
test expression
与[ expression ]
等价。
字符串比较
test "$user"="jordon" # 不管是何种情况,均加上双引号,以免出错。
整数比较
例:if test $num1 -eq $num2
参数 说明
-eq 等于则为真
-ne 不等于则为真
-gt 大于则为真
-ge 大于等于则为真
-lt 小于则为真
-le 小于等于则为真
8. 字符串比较
-z "字符串"
:字符串为空返回真
-n "字符串"
:字符串为非空返回真
"串1" = "串2"
:若串1等于串2则为真,可以用==
代替=
;
注意:
a. 字符串操作符务必用双引号引起来;
b. 比较符号的两端必须有空格.
不加空格的反例:
[ "a"=="1" ]&& echo 1 || echo 0
1
[ "a" == "1" ]&& echo 1 || echo 0
0
9. 整数比较操作符
在[ ]
以及test
中使用的比较符:-eq
、-gt
、-lt
等;
在(())
以及[[]]
中使用的比较符(数学符号):=
、>
、<
等;
整数比较不用加双引号了;[[]]
用-eq
的写法也是对的,[[]]
用>
写法也可能不对,只会比较第一位,逻辑结果不对;
所以整数比较最好用-eq
、-gt
、-lt
等.
[[ 2 > 1 ]]&& echo 1 || echo 0
1
[[ 2 > 11 ]]&& echo 1 || echo 0
1
[[ 2 > 31 ]]&& echo 1 || echo 0
0
10. for循环
for variable in arg1 arg2 ... argn
do
command
done
读取循环条件时,默认情况下,shell会以空格、制表符、换行符作为分隔符,但遇到以上情况时,需要使用IFS来自定义shell的分隔符。例:假设有一个这样的文件:
1.1.1.1 hadoop01
2.2.2.2 hadoop02
要打印出ip,以下写法是错误的:
#!/bin/bash
for line in `cat nodes`
do echo $line | awk '{print $1 }'
done
输出是
1.1.1.1
hadoop01
2.2.2.2
hadoop02
因为for会以空格作为分隔符,如要以换行符为分隔,应当这么写:
#!/bin/bash
IFS="\n";
for line in `cat nodes`
do echo $line | awk '{print $1 }'
done
即,首先在shell脚本内定义shell的分隔符为换行符。 output:
1.1.1.1
2.2.2.2
11. break 和 continue
break
退出整个循环
continue
退出当前循环
break
和continue
后面均可加整数n,即 break n
, continue n
。
break n
终止最内层开始数的第n个循环的执行。
continue n
命令时,则跳过最里层的 n次循环体的执行,即开始第n个(从内向外数) 循环的下一个循环过程。
什么是第n个?
for var in ... # 第三层
do
for var in ... # 第二层
do
for var in ... # 第一层
do
command
done
done
done
12. exit n 和 return n
在函数中return
功能跟exit
类似,作用是跳出函数; 在函数中使用exit
会退出整个shell脚本,而不是退出函数;
exit n
:退出当前shell程序,n为返回值
return n
:用于函数中,n是函数的返回值,用于判断函数执行是否正确
13. echo 与 echo -n
echo 输出默认是换行的
echo -n输出不带换行符
这一点在计算一个字符串的md5值的时候非常需要注意。echo "string"| md5sum
与 echo -n "string" | md5sum
结果不一致。而echo -n "string" | md5sum
才是正确的结果。
14. 逻辑连接符
a
相当于&&
:与,两端都为真才为真;
-o
相当于||
:或,两端都为假才为假; !
:非.
注意: []
中用-a
、-o
、!
,test
用法和[]
相同; [[]]
中用&&
、||
、!
在单中括号[]中只能用-a
、-o
、!
,不能用&&
,&&
只能在双中括号[[]]
或两条命令之间使用:
[[ -f "$f1" && -f "$f2" ]]
或 [ -f "$f1" ] && [ -f "$f2" ]
15. 脚本中的路径
在脚本里,切忌使用./
等形式的相对变量,使用之后脚本的移植性将变得极差,另外如果脚本放在crontab内运行,相对路径基本无法识别。而应该使用
path=$(cd `dirname $0`;pwd)
来获取当前脚本文件所在的路径,当我们要使用相对路径时,使用${path}/
来代替。
dirname $0 取得当前执行的脚本文件的所在目录
cd `dirname $0` 进入这个目录(切换当前工作目录)
pwd,显示当前工作目录(cd执行后的)
16. 变量的引用
在我们定义一个变量之后,引用一个变量,最好使用${var}
而不是$var
,特别是在后面需要拼接字符串的时候,比如
echo ${time}isout
如果不使用{ }
将变量包裹起来,time和isout会拼接在一块,引用变量即会失败。
17. 目录的权限
- 对某个目录只有读而没有执行权限时,
ll
该目录,可以看到该目录下的文件名,但是不能进入该目录下。所以,执行权限对于目录来说非常重要; - 文件的umask值和目录的umask值; 在默认权限的属性上,目录与文件是不一样的。x权限对于目录是非常重要,但是一般文件的创建则不应该有执行的权限,因为一般文件通常是用于数据的记录,自然不需要执限了。因此, 若umask值为022,新建文件的权限为644,而目录的权限为755,如下所示。
[root@node02 tmp]# mkdir A
[root@node02 tmp]# touch a
[root@node02 tmp]# ll
-rw-r--r-- 1 root root 0 3月 13 20:44 a
drwxr-xr-x 2 root root 6 3月 13 20:44 A
18. bash、sh、./sh和source运行脚本的区别
sh FileName
或bash FileName
作用:打开一个子 shell 来读取并执行FileName 中命令。该 Filename 文件可以无 “执行权限”。
注:运行一个shell脚本时会启动另一个命令解释器。
./FileName
作用: 打开一个子 shell 来读取并执行 FileName 中命令,该 filename 文件需要 “执行权限”。执行时需要使用chmod +x file
加上执行权限,否则会提示无执行权限,注意执行脚本时候或者全目录,或者 ./file.sh ,如果不加的话,linux 默认会从PATH 里去找该 file.sh。
注:运行一个 shell 脚本时会启动另一个命令解释器。
source FileName
作用:在当前 bash 环境下读取并执行 FileName 中的命令。该 filename 文件可以无 “执行权限”。
注:该命令通常用命令 .
来替代。