shell/shell-脚本.md
2025-03-28 20:10:14 +08:00

290 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<h2><center>Shell 脚本</center></h2>
------
## 一:脚本规范
### 1. 风格规范
开头有“蛇棒”
所谓shebang其实就是在很多脚本的第一行出现的以”#!”开头的注释,他指明了当我们没有指定解释器的时候默认的解释器,一般可能是下面这样:
```shell
#bin/sh
```
除了 bash 之外,可以用下面的命令查看本机支持的解释器:
```bash
[root@wxin ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
```
- 直接使用`./a.sh`来执行这个脚本的时候,如果没有`shebang`,就会默认用`$Shell`指定的解释器,否则就会用`shebang`指定的解释器。
- 上面这种写法可能不太具备适应性,一般我们会用下面的方式来指定:
```shell
#!/usr/bin/env bash
```
### 2. 注释
注释的意义不仅在于解释用途,而在于告诉我们注意事项,就像是一个 README。
具体的来说对于Shell脚本注释一般包括下面几个部分
- shebang
- 脚本的参数
- 脚本的用途
- 脚本的注意事项
- 脚本的写作时间,作者,版权等
- 各个函数前的说明注释
- 一些较复杂的单行命令注释
### 3. 参数规范
这一点很重要,当脚本需要接受参数的时候,一定要先判断参数是否合乎规范,并给出合适的回显,方便使用者了解参数的使用。
至少得判断下参数的个数
```shell
if [[ $# != 2 ]];then
echo "Parameter incorrect."
exit 1
fi
```
### 4. 变量
一般情况下会将一些重要的环境变量定义在开头,确保这些变量的存在。
```shell
source /etc/profile
export PATH=”/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/”
```
- 这种定义方式有一个很常见的用途,最典型的应用就是,当本地安装了很多`java`版本时,可能需要指定一个`java`来用。这时就会在脚本开头重新定义`JAVA_HOME`以及`PATH`变量来进行控制。
- 一段好的代码通常是不会有很多硬编码在代码里的“魔数”的。如果一定要有,通常是用一个变量的形式定义在开头,然后调用的时候直接调用这个变量,这样方便日后的修改。
### 5. 缩进
- 正确的缩进非常重要,尤其是在写函数时,否则在阅读时很容易把函数体跟直接执行的命令搞混。
- 常见的缩进方法主要有`soft tab``hard tab`两种:
1. 所谓`soft tab`就是使用n个空格进行缩进(n通常是2或4)
2. 所谓`hard tab`当然就是指真实的`\t`字符
- 对于`if``for`语句之类的,最好不要把`then``do`这些关键字单独写一行,这样看上去比较丑。
### 6. 命名标准
所谓命名规范,基本包含下面这几点:
- 文件名规范,以`.sh`结尾,方便识别
- 变量名字要有含义,不要拼错
- 统一命名风格,写`Shell`一般用小写字母加下划线
### 7. 编码统一
在写脚本的时候尽量使用`UTF-8`编码能够支持中文等一些奇奇怪怪的字符。不过虽然能写中文但是在写注释以及打log的时候还是尽量英文毕竟很多机器还是没有直接支持中文的打出来可能会有乱码。
### 8. 日志和回显
- 日志的重要性不必多说,能够方便回头纠错,在大型的项目里是非常重要的。
- 如果这个脚本是供用户直接在命令行使用的,那么最好还要能够在执行时实时回显执行过程,方便用户掌控。
- 为了提高用户体验,会在回显中添加一些特效,比如颜色啊,闪烁啊之类的。
### 9. 密码移除
不要把密码硬编写在脚本里,尤其是当脚本托管在类似 Github 这类平台中时。
### 10. 分行
在调用某些程序的时候,参数可能会很长,这时候为了保证较好的阅读体验,我们可以用反斜杠(续行符)来分行:
```shell
./configure \
prefix=/usr \
sbin-path=/usr/sbin/nginx \
conf-path=/etc/nginx/nginx.conf
```
### 11. 代码有效率
在使用命令的时候要了解命令的具体做法,尤其当数据处理量大的时候,要时刻考虑该命令是否会影响效率。
比如下面的两个sed命令
```bash
[root@wxin ~]# sed -n '1p' file
[root@wxin ~]# sed -n '1p;1q' file
```
作用一样,都是获取文件的第一行。但是第一条命令会读取整个文件,而第二条命令只读取第一行。当文件很大的时候,仅仅是这样一条命令不一样就会造成巨大的效率差异。
当然,这里只是为了举一个例子,这个例子真正正确的用法应该是使用`head -n1 file`命令
**勤用双引号**
- 几乎所有的大佬都推荐在使用”$”来获取变量的时候最好加上双引号。
- 不加上双引号在很多情况下都会造成很大的麻烦。
```shell
#!/bin/sh
#已知当前文件夹有一个a.sh的文件
var="*.sh"
echo $var
echo "$var"
```
运行结果如下:
```shell
a.sh
*.sh
```
可以解释为它执行了下面的命令
```shell
echo *.sh
echo "*.sh"
```
在很多情况下,在将变量作为参数的时候,一定要注意上面这一点,仔细体会其中的差异。上面只是一个非常小的例子,实际应用的时候由于这个细节导致的问题实在是太多了。
### 12. 学会查路径
- 很多情况下,会先获取当前脚本的路径,然后以这个路径为基准,去找其他的路径。通常我们是直接用`pwd`以期获得脚本的路径。 不过其实这样是不严谨的,`pwd`获得的是当前`Shell`的执行路径,而不是当前脚本的执行路径。
正确的做法应该是下面这两种:
```shell
script_dir=$(cd $(dirname $0) && pwd)
script_dir=$(dirname $(readlink -f $0 ))
```
应当先`cd`进当前脚本的目录然后再`pwd`,或者直接读取当前脚本的所在路径。
### 13. 代码要简短
这里的简短不单单是指代码长度,而是只用到的命令数。原则上我们应当做到,能一条命令解决的问题绝不用两条命令解决。这不仅牵涉到代码的可读性,而且也关乎代码的执行效率。
最经典的例子如下:
```bash
[root@wxin ~]# cat /etc/passwd | grep root
[root@wxin ~]# grep root /etc/passwd
```
cat 命令最为人不齿的用法就是这样,用的没有任何意义,明明一条命令可以解决,非得加根管道
### 14. 使用新写法
- 尽量使用[[ ]]来代替[ ]
- 尽量使用\$()将命令的结果赋给变量,而不是反引号。
- 在复杂的场景下尽量使用printf代替echo进行回显
### 15. 其他小技巧
- 路径尽量保持绝对路径,不容易出错,如果非要用相对路径,最好用./修饰
- 优先使用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脚本的语法调试使用bash的相关参数进行调试
```shell
sh [参数] 文件名.sh
```
- -n 不要执行script仅查询语法的问题
- -v 在执行script之前先将script的内容输出到屏幕上
- **-x** 将使用的脚本的内容输出到屏幕,该参数经常被使用
```bash
#-v的示例
[root@wxin ~]# sh -v demo.sh
module () { eval `/usr/bin/modulecmd bash $*`
}
#!/bin/bash
case $1 in
"one")
echo "you input number is one"
;;
"two")
echo "you input number is twp"
;;
*)
echo "you input number is other"
;;
esac
you input number is other
```
```bash
#-x的示例
[root@wxin ~]# sh -x demo.sh
+ case $1 in
+ echo 'you input number is other'
you input number is other
[root@wxin ~]# sh -vx demo.sh
```
## 三:脚本运行方式
Linux中Shell脚本的执行通常有4种方式分别为工作目录执行绝对路径执行sh执行Shell环境执行。
### 1. 工作目录执行
工作目录执行,指的是执行脚本时,先进入到脚本所在的目录(此时,称为工作目录),然后使用 ./脚本方式执行
```bash
[root@wxin ~]# ./test.sh
Hello Shell
```
### 2. 绝对路径执行
```bash
[root@wxin ~]# /home/tan/scripts/test.sh
Hello Shell
```
### 3. bash 执行
```bash
#在子shell中执行不需要脚本的可执行权限
[root@wxin ~]# sh test.sh
Hello Shell
[root@wxin ~]# bash test.sh
Hello Shell
```
### 4. 环境执行
Shell环境执行指的是在当前的Shell环境中执行可以使用 . 接脚本 或 source 接脚本。
不需要脚本的可执行权限在当前shell中执行
```bash
[root@wxin ~]# . test.sh
Hello Shell
[root@wxin ~]# source test.sh
Hello Shell
```