写好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变量能够而且只能存储正文字符串,即它只有一种类型的变量即串变量。但从赋值的形式上看,则可以分成四种类型的变量或变量形式。变量的名字必须以字母或下划线开头,可以包括字母、数字和下划线。

  1. 环境变量

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
  1. 用户自定义变量

语法格式:name=string,赋值号=两边不允许有空白符

var=value    赋值操作

var = value 相等操作

只读变量readonly var

  1. 位置变量

当一个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 ~]#
  1. 预定义的特殊变量

在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退出当前循环

breakcontinue后面均可加整数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"| md5sumecho -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 FileNamebash FileName

作用:打开一个子 shell 来读取并执行FileName 中命令。该 Filename 文件可以无 “执行权限”。

注:运行一个shell脚本时会启动另一个命令解释器。

./FileName

作用: 打开一个子 shell 来读取并执行 FileName 中命令,该 filename 文件需要 “执行权限”。执行时需要使用chmod +x file 加上执行权限,否则会提示无执行权限,注意执行脚本时候或者全目录,或者 ./file.sh ,如果不加的话,linux 默认会从PATH 里去找该 file.sh。

注:运行一个 shell 脚本时会启动另一个命令解释器。

source FileName

作用:在当前 bash 环境下读取并执行 FileName 中的命令。该 filename 文件可以无 “执行权限”。

注:该命令通常用命令 .来替代。

原文链接

写好Shell脚本那些不得不知道的细节

上一篇:Kubernetes集群容器引擎切换


下一篇:Web集群案例实战 -- Keepalived 实现 web 服务高可用