作业脱机管理
- 将作业(进程)切换到后台可以避免由于误操作如[ctrl]+c等导致的job被异常中断的情形,而脱机管理主要是针对终端异常断开的情形。
- 通常使用nohup命令来使得脱机或注销之后,Job依旧可以继续运行。也就是说nohup忽略所有挂断(SIGHUP)信号。
- 如果该方式命令之后未指定&符号,则job位于前台,指定&符号,则job位于后台。
- 不加&
不加&,作业会在一个新的终端运行,当前终端是无法通过
jobs
命令查看到信息,只能通过ps -ef
命令查看进程
由于使用的是sleep命令,并且没有加&,所以ps -ef | grep sleep会出现两个进程,父进程pid不会变,子进程pid会一直在变,因为他会不断的打开新的终端执行sleep这个命令,所以,如果要停止这个作业,需要使用kill 父进程pid命令
nohup sleep
使用jobs
命令,可以查看到当前作业的运行情况(sleep命令后面的数字单位是秒,如果数字写的小,很快就退出了,jobs会查看不到作业)
不指定日志目录的情况下,会将日志写入到当前目录下的
nohup.out
文件中
nohup sleep 3000 &
指定日志目录的方法
注释:
>&
等同于2>&1
和&>
,将正确的和错误的日志,都写入到指定的文件中
nohup sleep 3000 >& ./sleep.log &
命令执行的顺序
&& 具备逻辑判断
command1 && command2
只有在 command1 成功执行后才会执行 command2
|| 具备逻辑判断
command1 || command2
在 command1 没有成功执行时执行command2
; (分号)不具备逻辑判断
command1 ; command2
command1无论是否成功执行,执行完成后,都会去执行commadn2
通配符(元字符)表示的不是本意
常见的通配符
注意与正则稍有不同
字符 | 含义 | 实例 |
* | 匹配0个或多个任意字符 | a*b,a与b之间可以有任意长度的字符,也可以没有。例如:aabcb,ab,azxcb… |
? | 匹配一个任意字符 | a?b,a与b之间必须但也只能存在一个字符,该字符可以是任意字符。例如:aab,abb,acb… |
[list] | 匹配list中的任意单个字符 | a[xyz]b,a与b之间必须但也只能存在一个字符,该字符只能是x或y或z。例如:axb,ayb,azb |
[!list] | 匹配除list中的任意单个字符 | a[!a-z]b,a与b之间必须但也只能存在一个字符,该字符不能是小写字母。例如:aAb,a0b… |
[c1-c2] | 匹配c1-c2间的任意单个字符 | a[0-1]b,a与b之间必须但也只能存在一个字符,该字符只能是数字。例如:a0b,a1b… |
{string1,string2,…} | 匹配string1、string2等中的一个字符串 | a{abc,xyz,opq}b,a与b之间必须但也只能存在一个字符串,字符串只能是abc或xyz或opq。例如:aabcb,axyzb,aopqb… |
Shell 脚本规范
Shell脚本绝大部分命令自己平时也经常使用,但是在写成脚本的时候总觉得写的很难看。而且当我在看其他人写的脚本的时候,总觉得难以阅读。毕竟Shell脚本这个东西不算是正经的编程语言,他更像是一个工具,用来杂糅不同的程序供我们调用。因此很多人在写的时候也是想到哪里写到哪里,基本上都像是一段超长的main函数,不忍直视。同时,由于历史原因,Shell有很多不同的版本,而且也有很多有相同功能的命令需要我们进行取舍,以至于代码的规范很难统一。
考虑到上面的这些原因,我查阅了一些相关的文档,发现这些问题其实很多人都考虑过,而且也形成了一些不错的文章,但是还是有点零散。因此我就在这里把这些文章稍微整理了一下,作为以后我自己写脚本的技术规范。
风格规范
开头有“蛇棒”
所谓shebang其实就是在很多脚本的第一行出现的以”#!”开头的注释,他指明了当我们没有指定解释器的时候默认的解释器,一般可能是下面这样:
#!/bin/bash
上面这种写法可能不太具备适应性,一般我们会用下面的方式来指定:
#!/usr/bin/env bash
除了 bash 之外,可以用下面的命令查看本机支持的解释器:
cat /etc/shells
注释
- 注释的意义不仅在于解释用途,而在于告诉我们注意事项,就像是一个 README。
- 具体的来说,对于Shell脚本,注释一般包括下面几个部分:
- shebang
- 脚本的参数
- 脚本的用途
- 脚本的注意事项
- 脚本的写作时间,作者,版权等
- 各个函数前的说明注释
- 一些较复杂的单行命令注释
参数要规范
- 这一点很重要,当脚本需要接受参数的时候,一定要先判断参数是否合乎规范,并给出合适的回显,方便使用者了解参数的使用。
变量
- 一般情况下会将一些重要的环境变量定义在开头,确保这些变量的存在。
- 一段好的代码通常是不会有很多硬编码在代码里的“魔数”的。如果一定要有,通常是用一个变量的形式定义在开头,然后调用的时候直接调用这个变量,这样方便日后的修改。
缩进
- 因为很多需要缩进的地方(比如if,for语句)都不长,很多人都懒得去缩进,而且很多人不习惯用函数,导致缩进功能被弱化。
- 正确的缩进是很重要的,尤其是在写函数的时候,否则在阅读的时候很容易把函数体跟直接执行的命令搞混。
- 常见的缩进方法主要有"soft tab"和"hard tab"两种:
- 所谓soft tab就是使用n个空格进行缩进(n通常是2或4)
- 所谓hard tab当然就是指真实的"\t"字符
- 对于if和for语句之类的,最好不要把then,do这些关键字单独写一行,这样看上去比较丑。
命名有标准
所谓命名规范,基本包含下面这几点:
- 文件名规范,以.sh结尾,方便识别
- 变量名字要有含义,不要拼错
- 统一命名风格,写 Shell 一般用小写字母加下划线
编码要统一
在写脚本的时候尽量使用 UTF-8 编码,能够支持中文等一些奇奇怪怪的字符。不过虽然能写中文,但是在写注释以及打log的时候还是尽量英文,毕竟很多机器还是没有直接支持中文的,打出来可能会有乱码。
日志和回显
- 日志的重要性不必多说,能够方便回头纠错,在大型的项目里是非常重要的。
- 如果这个脚本是供用户直接在命令行使用的,那么最好还要能够在执行时实时回显执行过程,方便用户掌控。
- 为了提高用户体验,会在回显中添加一些特效,比如颜色啊,闪烁啊之类的。
密码要移除
- 不要把密码硬编写在脚本里。
太长要分行
- 在调用某些程序的时候,参数可能会很长,这时候为了保证较好的阅读体验,我们可以用反斜杠来分行:
./configure \ –prefix=/usr \ –sbin-path=/usr/sbin/nginx \ –conf-path=/etc/nginx/nginx.conf
注意:在反斜杠前有个空格。
代码有效率
- 在使用命令的时候要了解命令的具体做法,尤其当数据处理量大的时候,要时刻考虑该命令是否会影响效率。
- 比如下面的两个sed命令:
sed -n '1p' file
sed -n '1p;1q' file 12
作用一样,都是获取文件的第一行。
但是第一条命令会读取整个文件,而第二条命令只读取第一行。
当文件很大的时候,仅仅是这样一条命令不一样就会造成巨大的效率差异。
当然,这里只是为了举一个例子,这个例子真正正确的用法应该是使用head -n1 file命令
勤用双引号
- 几乎所有的大佬都推荐在使用"$"来获取变量的时候最好加上双引号。
- 不加上双引号在很多情况下都会造成很大的麻烦,
#!/bin/sh # 已知当前文件夹有一个a.sh的文件 var="*.sh" echo $var echo "$var"
运行结果如下:
a.sh *.sh
可以解释为它执行了下面的命令
echo *.sh echo "*.sh"
在很多情况下,在将变量作为参数的时候,一定要注意上面这一点,仔细体会其中的差异。上面只是一个非常小的例子,实际应用的时候由于这个细节导致的问题实在是太多了
学会查路径
- 很多情况下,会先获取当前脚本的路径,然后以这个路径为基准,去找其他的路径。通常我们是直接用 pwd 以期获得脚本的路径。
- 不过其实这样是不严谨的,pwd 获得的是当前Shell的执行路径,而不是当前脚本的执行路径。
- 正确的做法应该是下面这两种
script_dir=$(cd $(dirname $0) && pwd)
script_dir=$(dirname $(readlink -f $0 ))
应当先cd进当前脚本的目录然后再pwd,或者直接读取当前脚本的所在路径。
代码要简短
- 这里的简短不单单是指代码长度,而是只用到的命令数。原则上我们应当做到,能一条命令解决的问题绝不用两条命令解决。这不仅牵涉到代码的可读性,而且也关乎代码的执行效率。
- 最最经典的例子如下
cat /etc/passwd | grep root
grep root /etc/passwd
cat 命令最为人不齿的用法就是这样,用的没有任何意义,明明一条命令可以解决,非得加根管道
使用新写法
这里的新写法不是指有多厉害,而是指可能更希望使用较新引入的一些语法,更多是偏向代码风格
- 尽量使用func( ){ }来定义函数,而不是func{ }
- 尽量使用[[ ]]来代替[ ]
- 尽量使用$()将命令的结果赋给变量,而不是反引号
- 在复杂的场景下尽量使用printf代替echo进行回显
其他小技巧
- 路径尽量保持绝对路径,不容易出错,如果非要用相对路径,最好用
./
修饰 - 优先使用bash的变量替换代替awk sed,这样更加简短
- 简单的if尽量使用
&&
和||
,写成单行。比如[[ x > 2]] && echo x
- 当export变量时,尽量加上子脚本的namespace,保证变量不冲突
- 会使用
trap
捕获信号,并在接受到终止信号时执行一些收尾工作 - 使用
mktemp
生成临时文件或文件夹 - 利用
/dev/null
过滤不友好的输出信息
- 会利用命令的返回值判断命令的执行情况
- 使用文件前要判断文件是否存在,否则做好异常处理
- 不要处理
ls
后的数据(比如ls -l | awk '{ print $8 }'
),
- ls的结果非常不确定,并且平台有关
- 读取文件时不要使用
for loop
而要使用while read
Shell 脚本调试
Shell脚本的语法调试,使用bash的相关参数进行调试
sh [参数] 文件名.sh
- -n 不要执行script,仅查询语法的问题
- -v 在执行script之前,先将script的内容输出到屏幕上
- -x 将使用的脚本的内容输出到屏幕,该参数经常被使用
脚本运行方式
Linux中Shell脚本的执行通常有4种方式,分别为工作目录执行,绝对路径执行,sh执行,Shell环境执行。
工作目录执行
工作目录执行,指的是执行脚本时,先进入到脚本所在的目录(此时,称为工作目录),然后使用 ./脚本
方式执行,这种方式,需要脚本有执行权限,否则会报错Permission denied
绝对路径执行
绝对路径中执行,指的是直接从根目录/到脚本目录的绝对路径,同样也是需要脚本有执行权限
sh 执行
sh执行,指的是用脚本对应的sh或bash来接着脚本执行,所以,脚本不是必须要有执行权限
Shell 环境执行
Shell环境执行,指的是在当前的Shell环境中执行,可以使用
.
接脚本 或source
接脚本
. test.sh
注意,点后面有空格
source test.sh