整体说明
- Shell 脚本是一种用命令行解释器(Shell)来执行的程序
- 常用语自动化重复性的任务、编写复杂的程序流程
Shell 脚本执行相关基础
通常,Shell 脚本文件的扩展名是
.sh,但这不是强制的编写并执行一个 shell 脚本的步骤
第一步,创建文件和编写代码:
1
2
3
4
# 这是一个简单的脚本,用于打印 "Hello, Shell!"
echo "Hello, Shell!"#!/bin/bash: 这被称为 Shebang- 这行代码告诉系统使用
/bin/bash这个解释器来执行这个脚本(建议指定)
- 这行代码告诉系统使用
#: 井号#后的内容是注释echo: 用于在终端上打印文本的命令
第二步,赋予执行权限: 默认情况下,新创建的文件没有执行权限,需要使用
chmod命令来添加它1
chmod +x hello.sh
+x表示添加执行权限
第三步,运行脚本: 现在,可以通过以下方式运行的脚本
1
./hello.sh
./表示在当前目录执行- 若没有执行权限的脚本可以使用
sh ./hello.sh来执行(此时不再使用指定的解释器)- Shebang 只有在直接运行脚本时才生效
变量的使用
变量用于存储数据,在 Shell 脚本中,定义变量不需要声明类型
定义变量
1
2name="Gemini"
age=25- 注意 : 赋值号
=的两边 不能 有空格
- 注意 : 赋值号
使用变量:要使用变量,需要在变量名前面加上美元符号
$1
2
3
4
5
6
7
name="Alice"
echo "我的名字是 $name。"
# 也可以用大括号括起来,这在某些情况下很有用,比如变量名紧跟在其他文本后面
echo "我的名字是 ${name},今年 ${age} 岁。"环境变量的读取:环境变量是系统预设的变量,可以在任何地方访问
- 例如,
$PATH存储了系统命令的搜索路径,$HOME存储了用户的主目录1
echo "我的主目录是 $HOME"
- 例如,
特殊变量
Shell 提供了一些特殊的变量,用于获取脚本运行时的信息
变量名 含义 $0脚本文件名 $1,$2, …命令行参数。 $1是第一个参数,$2是第二个,以此类推。$#传递给脚本的参数个数 $*所有的命令行参数,作为一个字符串 $@所有的命令行参数,每个参数是独立的字符串 $?上一个命令的退出状态码。 0表示成功,非0表示失败。$$当前脚本的进程 ID 特殊变量使用示例
1
2
3
4
5
6
echo "脚本名称: $0"
echo "参数个数: $#"
echo "第一个参数: $1"
echo "所有参数: $*"- 保存为
args.sh,然后运行bash args.sh apple banana cherry,观察输出1
2
3
4脚本名称: hello.sh
参数个数: 3
第一个参数: apple
所有参数: apple banana cherry
- 保存为
条件判断(if 语句)
if语句用于根据条件执行不同的代码块if基本语法1
2
3if [ 条件 ]; then
# 如果条件为真,执行这里的代码
fiif完整语法1
2
3
4
5
6
7if [ 条件 ]; then
# 如果条件为真
elif [ 其他条件 ]; then
# 如果第一个条件为假,且第二个条件为真
else
# 所有条件都为假
fi- 特别注意 :
[和]之间必须有空格
- 特别注意 :
if 语句相关的常用条件运算符号/表达式
- 字符串比较:
=或==: 字符串相等!=: 字符串不相等-z: 字符串为空-n: 字符串不为空
- 数字比较:
-eq: 相等(Equal)-ne: 不相等(Not Equal)-gt: 大于(Greater Than)-lt: 小于(Less Than)-ge: 大于等于(Greater than or Equal)-le: 小于等于(Less than or Equal)
- 文件测试:
-e: 文件或目录存在-f: 是文件-d: 是目录-r: 可读-w: 可写-x: 可执行
- 常用条件表达式示例
1
2
3
4
5
6
7
8
9
10
11
if [ -f "test.txt" ]; then
echo "文件 test.txt 存在。"
else
echo "文件 test.txt 不存在。"
fi
if [ 10 -gt 5 ]; then
echo "10 大于 5。"
fi
循环语句
- 循环用于重复执行一段代码,直到满足特定条件
for 循环
for循环用于遍历列表中的项目1
2
3
4
5
6
7
8
9
10
11
# 遍历一个列表
for fruit in apple banana cherry; do
echo "水果: $fruit"
done
# 遍历数字范围
for i in {1..5}; do
echo "数字: $i"
done
while 循环
当条件为真时,持续执行循环
1
2
3
4
5
6
7
8
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
count=$((count + 1)) # 算术运算
done$((...))是 Shell 中的算术扩展,用于执行数学运算
函数
定义函数方式一:
1
2
3
4function my_func {
# 函数体
echo "这是一个函数。"
}定义函数方式二:
1
2
3
4my_func() {
# 函数体
echo "这也是一个函数。"
}调用函数
1
my_func
函数参数:函数内部的参数使用
$1,$2等来访问,与脚本的命令行参数类似(定义时不需要指定参数)1
2
3
4
5
6
7
8
greet() {
echo "Hello, $1!"
return 0
}
greet "Bob" # 调用函数并传递参数 "Bob"return命令可以返回一个退出状态码,通常0表示成功,非0表示失败
附录:最佳实践(持续更新)
常用实践
- 为了避免因为文件名中含有空格而导致的错误,始终使用双引号来引用变量
- 如
"$name"
- 如
- 使用
$((...))进行算术运算- 例如
$((a+b)),要特别注意是$加双括号
- 例如
- 使用
$(...)来执行命令并获取其输出(注意是$加单括号)- 例如
current_date=$(date),这比老式的反引号 `` 更推荐使用
- 例如
脚本开头的常用设置
在 Shell 脚本的开头,通常会看到一些
set命令,这些设置可以编写更健壮、更可靠的脚本,有效避免一些常见的错误通常,一个健壮的 Shell 脚本开头会包含以下几行:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 遇到错误立即退出
set -e
# 如果管道中的任何一个命令失败,整个管道命令就失败
set -o pipefail
# 打印所有执行的命令,方便调试
# set -x
# 未设置的变量会立即报错退出
set -u- 这些命令可以单独使用,也可以组合在一起,比如
set -e -o pipefail或更简洁的set -euo pipefail
- 这些命令可以单独使用,也可以组合在一起,比如
对常见组合
set -xeo pipefail的详解,包含了以下几个命令set -e或set -o errexit- 含义是如果脚本中的任何命令失败(返回非零退出状态),脚本会立即退出
- 可以防止脚本在遇到错误后继续执行,从而导致意想不到的后果
- 例如,正在删除一个目录,但
rm命令失败了,如果没有-e,脚本会继续执行下面的命令,可能导致数据不一致或更严重的错误
- 例如,正在删除一个目录,但
set -x或set -o xtrace- 含义是在执行每个命令之前,先打印该命令及其所有参数
- 对于 调试 非常有用,当运行脚本时,你会看到每个命令的完整展开形式,帮助你追踪脚本的执行流程和变量的值
- 在调试完成后,通常会注释掉或删除
-x
- 在调试完成后,通常会注释掉或删除
set -o pipefail- 含义是如果管道(
|)中的任何一个命令失败,整个管道命令的退出状态就是失败- 注:不论前面的管道命令是否成功,后面的管道都会执行,且整个管道的状态由最后一个状态决定
- 默认情况下,管道命令的退出状态只取决于最后一个命令
- 问题在于这意味着
command1 | command2,即使command1失败了,只要command2成功,整个命令依然被认为是成功的 pipefail解决了这个问题,确保你不会忽略管道中间的错误
- 问题在于这意味着
- 含义是如果管道(
set -u或set -o nounset- 含义是如果尝试使用一个未设置的变量,脚本会立即报错并退出(注意:如果不设置这个参数是不会报错,也不会退出的,
shell脚本会跳过这行命令) - 这行命令可以避免因拼写错误或变量未正确赋值而引发的问题
- 例如,如果想使用
$user,但无意中写成了$userr,set -u会立即提醒你,而不是让脚本使用一个空值继续执行
- 例如,如果想使用
- 含义是如果尝试使用一个未设置的变量,脚本会立即报错并退出(注意:如果不设置这个参数是不会报错,也不会退出的,
脚本输出
- 将脚本的输出重定向到文件,方便调试和后续查看,例如
my_script.sh > log.txt 2>&1- 这个命令将脚本的 标准输出 和 标准错误 都重定向到一个文件中
> log.txt 2>&1这个命令可以分解为以下几个部分来理解:- 标准输出 (Standard Output,
stdout) :文件描述符为1 - 标准错误 (Standard Error,
stderr) :文件描述符为2 >:标准输出(stdout)重定向- 注意这个操作符的定义是标准输出重定向 ,相当于是
1>的简写
- 注意这个操作符的定义是标准输出重定向 ,相当于是
2>标准错误(stderr)重定向log.txt:这是重定向的目标文件,标准输出的内容将被写入到这个文件中&1:这是一种特殊的写法,表示将文件描述符2(标准错误)重定向到与文件描述符1(标准输出)相同的位置- 总体来看,这行命令将有两个效果:
- 将文件描述符
2(标准错误)重定向到与文件描述符1(标准输出)相同的位置 - 将
1(标准输出)重定向到文件
- 将文件描述符
- 标准输出 (Standard Output,
- TLDR:
my_script.sh > log.txt 2>&1表示 执行my_script.sh,并将所有成功输出和错误输出都发送到log.txt文件中
输出脚本同时打印到屏幕
- 如果想既将输出和错误记录到文件,又同时在屏幕上看到它们,可以使用
tee命令 tee命令的作用是“分流”输入,它将标准输入的内容复制一份到标准输出,同时写入一个或多个文件
将所有输出都分流
使用
tee命令,可以这样写:1
my_script.sh 2>&1 | tee log.txt
my_script.sh 2>&1:首先,将脚本的标准错误(2)合并到标准输出(1)- 这样,所有的成功信息和错误信息都变成了“标准输出”
|:这是一个管道符号,它将前一个命令的标准输出作为下一个命令的标准输入tee log.txt:将管道接收到的所有内容(即脚本的所有输出)打印到屏幕(标准输出),同时将其 写入log.txt文件
只将标准输出分流,错误输出只打印到屏幕
如果只希望将成功信息记录到文件,而错误信息只在屏幕上显示,可以这样:
1
my_script.sh | tee log.txt
- 在这种情况下
- 标准输出会通过管道传输给
tee,并同时显示在屏幕和写入文件; - 标准错误不会被管道捕获 ,它会直接打印到屏幕上
- 标准输出会通过管道传输给
- 在这种情况下
eval 命令的使用
- shell 脚本中,
eval是一个用于执行字符串作为命令的内置命令eval会先对传入的参数进行二次解析 ,然后执行解析后的结果作为 shell 命令
- 具体来说,
eval的功能包括:- 1)将所有参数拼接成一个字符串
- 2)shell 会对这个字符串进行再次解析(包括变量替换、通配符扩展等)
- 3)最后执行解析后的命令
eval 命令使用示例
动态执行命令:当命令需要动态生成时(比如包含变量)
1
2cmd="ls -l"
eval $cmd # 相当于直接执行 `ls -l`处理复杂变量引用:特别是多层变量嵌套时
1
2
3var1="hello"
var2="var1"
eval echo \$$var2 # 输出 hello,相当于 `echo $var1`,并最终等价于 `echo hello`动态生成变量名:
1
2
3
4
5for i in 1 2 3; do
eval "num$i=$i" # 生成变量 num1=1, num2=2, num3=3
done
echo $num1 # 输出 1
echo $num2 # 输出 2
eval 命令使用注意事项
eval会执行任何解析后的命令,使用不当可能带来安全风险(特别是处理用户输入时)- 危险示例:下面的示例中,如果 user_input 包含恶意命令(如
; rm -rf /),会被执行1
2user_input="some input"
eval "echo $user_input"
- 危险示例:下面的示例中,如果 user_input 包含恶意命令(如
由于会进行二次解析,可能导致意想不到的结果,建议谨慎使用
复杂场景下,有时可以用函数或数组替代
eval实现更安全的逻辑