📢该篇博客已经完结,欢迎您的持续关注📢
关于Shell脚本语法的这篇博客到这里就完结了,非常感谢大家的阅读。如果对您有所帮助,请持续关注我的更新。
同时,欢迎您继续阅读我的另一篇文章:Linux常用命令详解,线上问题排查必备。我将持续更新线上问题排查及日常使用的一些Linux常用命令的使用教程,帮助大家更高效地解决问题和提升操作技能。期待与您在下一篇文章中再见!
1. 初识 Shell 解释器
在 Linux 系统中,Shell 是用户与操作系统之间的接口,它充当了命令行界面(CLI,Command Line Interface)和操作系统内核之间的桥梁。用户通过 Shell 输入命令,Shell 则将这些命令解析并执行。Shell 解释器不仅支持用户通过 CLI 输入单个命令,还可以将多个命令放入文件中作为程序执行,这就是 Shell 脚本的基础。
1.1 Shell 类型
Linux 的发行版众多,大多数都会包含多个 Shell,但通常会采用其中一个作为默认 Shell。常见的 Shell 解释器包括:
| Shell 名称 | 描述 |
|---|---|
| Sh | Bourne Shell,早期 Unix 系统中的标准 Shell |
| Bash | Bourne Again Shell,最流行的 Shell,许多 Linux 发行版的默认 Shell |
| Zsh | Z-Shell,功能强大,提供用户友好的特性,Bash 的扩展版本 |
| Csh | C-Shell,语法类似C语言,适合程序员使用 |
| Ksh | Korn Shell,结合了 Bourne Shell 和 C Shell 的特点。 |
| Fish | Friendly Interactive Shell,专为用户友好性设计 |
| Dash | Debian Almquist Shell,轻量级的 Shell,启动速度快,资源占用低,常用于 Debian 系统中的系统脚本和启动脚本 |
以 CentOS7.8 为例,我们要查看系统支持的所有 Shell 类型时,可以通过以下命令查看 /etc/shells 文件:
cat /etc/shells
该文件包含了系统上安装的所有可用 Shell 以及对应的路径:

如果要查看当前用户默认使用的 Shell,可以通过环境变量 $SHELL 来查看:
echo $SHELL

也可以通过查看 /etc/passwd 文件,/etc/passwd 文件中包含了系统中每个用户的信息,在用户ID记录的第7个字段中列出了默认的 Shell 程序。使用 grep 命令查找当前用户的条目:
grep "^$(whoami):" /etc/passwd
该命令会输出类似于以下格式的信息:

其中最后一个字段(/bin/bash)就是当前用户的默认 Shell。
1.2 Shell 的父子关系
Shell 不仅是一种命令行界面(CLI),它还是一个持续运行的复杂交互式程序。当用户登录到系统或打开一个终端窗口时,默认的 Shell(如 Bash、Zsh 等)会被启动,系统会自动运行默认的 Shell 程序,这个 Shell 作为 父Shell 持续运行并等待用户的命令输入。
当用户在 父Shell 中输入 /bin/bash 或其他等效的命令时,系统会创建一个新的Shell实例,这个新的 Shell 称为 子Shell。
子Shell的特性如下:
- 独立性:子Shell相对于父Shell是独立的,它可以有自己的环境变量、目录、作业控制等。
- 层次结构:可以创建多个子Shell,每个子Shell又可以创建自己的子Shell,从而形成一个层次结构。父子关系可以用树状结构表示。
- 退出行为:当子Shell退出时,它会将控制权返回给父Shell,父Shell可以继续执行后续的命令。如果子Shell 在后台运行,则父Shell 也可以继续运行其他命令而不受影响。
父Shell与子Shell的流程演示:

-
当我们登录到系统时,Linux 会启动一个默认的 Shell 程序(例如 Bash),这个 Shell 就是 父Shell。通过
ps -f命令可以查看到它的进程ID是16229(PID),运行的是 bash shell 程序(CMD)。 -
在 父Shell 中输入
/bin/bash命令后,系统会创建一个新的Shell实例,这个新的 Shell 称为 子Shell 。尽管窗口没有变化,但实际上我们已经进入了一个新的 Shell 环境,再次使用ps -f命令,可以查看到 子Shell 的进程信息(第二行):进程 ID 是16299(PID),运行的是 bash shell 程序(CMD)。 -
使用
echo $PPID命令,可以看到 子Shell 的父进程ID(PPID)与 父Shell 的 PID 相同,这确认了 子Shell 是由 父Shell 创建的。 -
如果我们想返回到 父Shell ,只需在 子Shell 中输入
exit命令, 子Shell 就会终止。此时, 父Shell 继续运行,等待我们的输入。 -
如果在 父Shell 中输入
exit命令,父Shell 将会终止,整个终端会话结束,用户将被登出控制台终端。
2. 编写第一个 Shell 脚本
Tips:Shell 可以将多个命令串起来,一次执行完成。如果要多个命令一起运行,可以把它们放在同一行中,彼此间用分号隔开。
对 Shell 解释器有了基本的认识后,我们就可以编写第一个 Shell 脚本了。
创建 Shell 脚本最基本的方式是使用分号(;)将多个命令串在一起,Shell 会按顺序逐个执行命令,例如:
命令1; 命令2; 命令3
这样,Shell 会先执行命令1,完成后再执行命令2,最后执行命令3。此外,如果我们希望在前一个命令成功执行后再执行下一个命令,可以使用 && 连接:
命令1 && 命令2 && 命令3
这样,只有当命令1成功执行(返回状态为0)时,命令2才会执行。这种办法只要不超过最大命令行字符数(255)限制,就能将任意多个命令串连在一起使用。
此外,我们可以将多个命令写入到文本文件中,每次执行时,直接执行文件即可,不需要每次手动输入这些命令。例如:
-
使用
vi或其他文本编辑器创建脚本文件:vi hello_world.sh -
编辑脚本,在文件中输入以下内容,按
Esc键,输入:wq然后按Enter保存并退出。#!/bin/bash echo 'HelloWOlrd' -
通过以下命令运行脚本:
sh hello_world.sh -
运行后,应该会看到以下输出:

3. Shell 脚本基本语法
3.1 脚本格式
每个 Shell 脚本文件的第一行以 #! 开头,后跟解释器的路径,例如 /bin/bash、/bin/sh,这告诉系统用哪个 Shell 解释器来执行脚本,格式为:
#!/bin/bash
3.2 注释
3.2.1 单行注释
Shell 脚本中单行注释以 # 开头,表示注释一行,直到行尾。示例:
# 这是一个注释
echo "Hello, World!" # 输出文本
3.2.2 多行注释
Shell 脚本没有专门的多行注释语法,要实现多行注释,可以使用 # 注释每一行:
# 这是一段多行注释
# 可以写很多内容
# 每一行前都要加 #
还可以通过 Here Document (here doc) 方式实现多行注释的效果,格式如下:
command <<DELIMITER
多行文本
...
DELIMITER
command是你想要执行的命令。DELIMITER是一个自定义的标识符,可以是任意字符串,用于标记文本块的开始和结束。
由于冒号 : 是一个空命令,可以优化为 : + 空格 + 单引号 的格式实现多行注释:
: '
这是注释的部分。
可以有多行内容。
'
3.3 Shell 变量
Shell 脚本中的变量包括系统预定义变量(环境变量)和用户自定义变量;按照类型又分为局部变量、全局变量和只读变量。
3.3.1 系统预定义变量(环境变量)
系统预定义变量是由 Linux 操作系统提供的变量,包含了系统的配置信息和环境设置。这些变量可以直接在 Shell 脚本和命令行中使用。常见的环境变量如下:
| 环境变量 | 说明 |
|---|---|
XDG_SESSION_ID | 当前用户会话的唯一标识符,可用于跟踪用户会话 |
HOSTNAME | 当前系统的主机名,用于在网络中识别计算机,可以通过修改 /etc/hostname 文件来更改它 |
TERM | 当前终端类型,例如 xterm-256color,影响终端的功能和外观 |
SHELL | 显示用户的默认 Shell 程序的路径,例如 /bin/bash |
HISTSIZE | 控制命令历史记录的大小,通常为1000 |
USER | 当前登录用户的用户名,例如 root |
PATH | 定义 Shell 查找可执行程序的路径。当用户输入命令时,系统会在这些路径中查找相应的可执行文件 |
PWD | 显示当前工作目录的绝对路径 |
LANG | 系统的语言设置,影响程序的输出和本地化设置,例如 zh_CN.UTF-8 |
SHLVL | 用于指示当前的 Shell 嵌套级别。每当启动一个新的 Shell 实例,该值就会增加 |
HOME | 当前用户的主目录,例如 /root。 |
SSH_CONNECTION | 当通过SSH连接到远程服务器时,提供有关连接的信息,包括客户端和服务器的IP地址及端口 |
LESSOPEN | 用于设置 less 命令的预处理器,允许在查看文件时进行处理,例如文本高亮 |
SSH_CLIENT | 类似于 SSH_CONNECTION,SSH客户端的IP地址、源端口和服务端口 |
printenv 查看所有环境变量
在 Linux 操作系统中可以使用 printenv 命令以键值对的形式输出当前所有的环境变量。
-
基本用法:
printenv运行这个命令会列出所有当前的环境变量及其值,以键值对的形式显示。例如:
USER=john HOME=/home/john SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -
查看特定变量:
printenv VARIABLE_NAME可以通过指定变量名来查看某个特定环境变量的值。例如:
printenv PATH这将只输出
PATH环境变量的值:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
set 查看所有 Shell 变量
set 命令是 Linux 的一个 Shell 内建命令,用于设置、显示或取消 Shell 的环境变量和 Shell 选项。 Shell 变量是指在 Shell 环境中定义的任何变量。它们可以是系统预定义变量(如 $PATH)或者用户自定义变量。
-
显示所有变量:
set运行此命令会列出当前 Shell 环境中的所有变量(包括环境变量和用户自定义变量)及其值。输出通常包含各个变量的名称和对应的值。例如:
BASH=/bin/bash BASH_VERSION='5.0.17(1)-release' USER=john ... -
设置变量:
set VARIABLE_NAME=value直接使用
set可以为变量赋值,但这种方式只在当前 Shell 会话中有效。示例:set myvar=Hello echo $myvar # 输出 Hello
3.3.2 声明变量
用户可以在 Shell 脚本中自定义变量存储数据、结果或其他信息,并在脚本上下文中引用。
Shell 脚本声明变量需要遵循以下规则:
-
格式:变量名和等号之间不能有空格。例如,
VAR=value是正确的,而VAR = value是错误的。 -
命名规则:
- 只包含字母、数字和下划线:变量名可以包含字母(大小写敏感)、数字和下划线(_),但不能包含其他特殊字符。
- 不能以数字开头:变量名不能以数字开头,但可以包含数字。例如,
VAR1是有效的,而1VAR是无效的。 - 避免使用 Shell 关键字:不应使用 Shell 的关键字(如
if、then、else、fi、for、while等)作为变量名,以免引起混淆。 - 使用大写字母表示常量:常量的变量名通常使用大写字母,例如
PI=3.14。 - 避免使用特殊符号:尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。
- 避免使用空格:变量名中不应包含空格,因为空格通常用于分隔命令和参数。
字符串变量
在 Shell 中,变量默认被视为字符串类型,支持使用单引号 ' 和双引号 " 来定义字符串:
-
使用单引号
'定义:单引号中的所有字符都被视为字面值,完全不进行变量替换或转义my_string='Hello, $USER!' echo "$my_string" # 输出: Hello, $USER!在这个例子中,
$USER被视为普通文本,不会被替换为实际的用户名。 -
使用双引号
"定义:双引号中的变量会被替换为对应的变量值,某些转义字符(如\n、\t等)也会被解析my_string="Hello, $USER!" echo "$my_string" # 输出: Hello, Alice!
对字符串变量常用的操作如下:
-
双引号
"实现多个字符串拼接:greeting="Hello" name="Alice" full_greeting="$greeting, $name!" echo "$full_greeting" # 输出: Hello, Alice! -
使用
${#var}来获取字符串的长度:length=${#my_string} echo "字符串长度: $length" # 输出: 字符串长度: 13 -
使用
${var:start:length}截取字符串:substring=${my_string:7:5} # 从索引 7 开始,长度为 5 echo "$substring" # 输出: World如果需要从指定位置截取到末尾,可以使用
${var:start}语法,而不需要指定长度:my_string="Hello, World!" substring=${my_string:7} # 从索引 7 开始截取到末尾 echo "$substring" # 输出: World! -
使用
${var/old/new}来替换字符串中的部分内容:my_string="Hello, World!" new_string=${my_string/World/Bash} echo "$new_string" # 输出: Hello, Bash!
Tips:
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
在单引号字符串中,不能直接包含单独的单引号。如果需要在字符串中使用单引号,可以成对出现,或通过拼接来实现。比如:
echo 'It'\''s a test' # 输出: It's a test
':先结束当前的单引号字符串。\':转义单引号,插入一个字面值的单引号。':再开始一个新的单引号字符串。
数组变量
Shell 只支持一维数组(不支持多维数组),初始化时不需要指定数组大小,元素的下标由 0 开始,其声明的方式如下:
-
使用括号
()定义数组:my_array=(element1 element2 element3) -
使用
declare -a定义数组:declare -a my_array my_array=(element1 element2 element3)
数组支持的常用操作如下:
-
通过
${array[index]}来访问指定下标的数组元素:echo "${my_array[0]}" # 输出: element1 -
使用
${#array[@]}获取数组的长度:length=${#my_array[@]} echo "数组长度: $length" # 输出: 数组长度: 3 -
使用
@或*可以获取数组中的所有元素:echo "${my_array[@]}" # 输出: element1 element2 element3 -
使用
for循环遍历数组的所有元素:for item in "${my_array[@]}"; do echo "$item" done -
通过索引直接修改数组的某个元素:
my_array[1]="new_value" # 修改第二个元素 echo "${my_array[1]}" # 输出: new_value -
使用
+=添加新元素:my_array+=("element4") # 添加新元素 echo "${my_array[@]}" # 输出: element1 element2 element3 element4
Tips:
- 在定义数组时,数组元素之间需要以空格进行分隔
- 访问或打印数组时,元素之间会自动以空格拆分
关联数组(Map)变量
关联数组(也称为字典或哈希表)允许使用非整数索引来存储键值对。使用 declare -A 命令来定义:
declare -A fruits
fruits=(["apple"]="red" ["banana"]="yellow" ["cherry"]="red")
也可以在声明时初始化:
declare -A fruits=(["apple"]="red" ["banana"]="yellow" ["cherry"]="red")
常用操作如下:
-
通过
${map[key]}来访问关联数组中指定 Key 对应的 Value:echo "${fruits[apple]}" # 输出: red -
${!map[@]}在数组前添加感叹号 ! 可以获取数组的所有键:echo "所有水果: ${!fruits[@]}" # 输出: apple banana cherry -
${map[@]}使用@或*可以获取数组中的所有元素:echo "${fruits[@]}" # 输出: red yellow red -
使用
for循环遍历关联数组的所有键及其对应的值:for key in "${!fruits[@]}"; do echo "$key: ${fruits[$key]}" done -
修改元素:
map[key]="new_value" -
新增元素:
map[new_key]="new_value" -
使用
unset命令删除关联数组中的某个元素:unset map[key]
只读变量
只读变量是定义后无法被修改的变量。使用 readonly 命令可以将变量设置为只读,这样在脚本的后续部分尝试修改该变量将会报错。
# 定义只读变量
readonly MY_READONLY_VAR="This variable is read-only"
# 输出变量的值
echo $MY_READONLY_VAR
# 尝试修改只读变量,将报错:cannot assign to read-only variable
MY_READONLY_VAR="New Value"
3.3.3 引用变量
在脚本上下文中,我们可以在变量名称之前加上美元符($)来引用声明的变量以及系统环境变量。以下是几种常用的引用方法:
-
使用
$variable形式引用变量:echo $PATH -
使用花括号
${variable}形式引用的变量:echo "Value: ${my_var}" echo "Value: ${my_var}123" # 输出:Value: Hello, World!123当变量后面紧跟其他字符时,花括号
{}可以使变量边界更清晰。
Tips:
- 对一个变量进行赋值时不使用
$符号- 引用一个变量值时必须使用美元符(
$)- 在赋值语句中使用其他变量的值时,必须使用
$符号#!/bin/bash # 1. 赋值时不使用美元符 value1="Hello" value2="World" # 2. 在赋值语句中使用 value1 的值时,必须使用美元符 greeting="$value1, $value2!" # 3. 引用变量值时使用美元符 echo "$greeting"
3.3.4 变量的作用域
全局变量
全局变量是指允许在整个脚本中(以及在其启动的所有子进程(即子Shell)中)访问的变量,全局变量可以直接在脚本的顶部或函数外部定义:
MY_GLOBAL_VAR="Hello, World!" # 定义全局变量
echo $MY_GLOBAL_VAR # 输出变量的值
可以使用 export 命令将其导出为环境变量,以便在子进程(即子Shell)中访问。
局部变量
局部变量是在特定的作用域内定义的变量,通常是在函数内定义:
my_function() {
local MY_LOCAL_VAR="This is a local variable" # 定义局部变量
echo $MY_LOCAL_VAR # 在函数内访问
}
my_function # 调用函数
echo $MY_LOCAL_VAR # 输出为空,无法访问局部变量
当函数执行完毕后,局部变量会被销毁,无法在函数外部访问。
Tips:使用
local定义的变量是局部的,只在其所在的函数内有效,无法通过export导出为环境变量供子进程(即子Shell)使用。
3.3.5 撤销变量
后续不再需要声明的变量时,可以使用 unset 命令来撤销已经声明的变量:
MY_VAR="Some value" # 定义变量
unset MY_VAR # 撤销变量
echo $MY_VAR # 不会输出任何内容
撤销后,该变量不再存在,尝试引用将不会返回任何值,unset 命令不能删除只读变量。
3.4 Shell 脚本传递参数
在执行 Shell 脚本时,我们可以通过以下形式向脚本传递参数:
./example.sh 参数1 参数2 参数3
在脚本内通过位置参数 $n 来获取参数的值,例如,$0 :执行的文件名(包含文件路径),$1 表示第一个参数,$2 表示第二个参数,当 n>=10 时,需要使用大括号 ${n} 来获取参数。
Shell 脚本还支持如下的特殊变量来操作传递参数:
| 符号 | 说明 |
|---|---|
$n | 引用参数 |
$@ | 获取所有参数(每个参数作为单独的字符串),适合在循环中使用 |
$# | 获取传递给脚本的参数个数 |
$* | 将所有参数作为一个字符串处理 |
$$ | 当前脚本的进程 ID |
$? | 上一个命令的退出状态码,也可以获取上一个函数返回的结果 |
示例如下:
-
创建一个 Shell 脚本
example.sh,添加以下内容:#!/bin/bash echo "第一个参数是: $1" echo "第二个参数是: $2" echo "第三个参数是: $3" # 通过 $@ 获取所有参数 echo "所有参数: $@" # 通过 $# 获取参数的数量 echo "参数的数量: $#" -
运行脚本并传递参数:
./example.sh 参数1 参数2 参数3 -
输出结果:
第一个参数是: hello 第二个参数是: world 第三个参数是: 123 所有参数: hello world 123 参数的数量: 3
3.5 命令替换
命令替换是 Shell 脚本中最有用的特性之一,它允许将一个命令的输出作为另一个命令的输入。在 Shell 脚本或命令行中,可以使用命令替换来动态地获取数据并将其赋给变量,之后就可以随意在脚本中使用,相较于直接在命令行中输入多个命令,命令替换使得脚本更简洁明了。
命令替换有两种主要的写法:
-
使用反引号(
command):result=`command`result:用于存储命令的输出command:希望执行的命令
-
使用美元符号和括号(
$()),这是更推荐的方式:result=$(command)
命令替换示例如下:
-
获取当前日期:
current_date=$(date) echo "当前日期和时间: $current_date" -
获取当前用户:
current_user=$(whoami) echo "当前用户: $current_user" -
计算文件数量
file_count=$(ls | wc -l) echo "当前目录下的文件数量: $file_count" -
嵌套命令:
file_count=$(ls $(pwd) | wc -l) echo "当前目录中的文件数量: $file_count"内层的
ls $(pwd)命令的输出被传递给wc -l命令,以计算当前目录中的文件数量。
Tips:命令替换会创建一个子 Shell 来运行对应的命令。子 Shell 是由运行该脚本的 Shell 所创建出来的一个独立的子 Shell 。正因如此,由该子 Shell 所执行命令无法使用脚本中所创建的变量。
3.6 运算符
运算符用于执行各种操作,Shell 脚本中的运算符分为多种类型,包括算术运算符、关系运算符、逻辑运算符、位运算符、字符串运算符和文件测试运算符等。
3.6.1 特殊运算符
[](测试命令)
[] 是 Shell 中的测试命令,通常用于条件判断。它也称为“test”命令。这个结构用于检查条件表达式的结果,如比较数值、字符串或文件属性。
基本语法如下:
if [ condition ]; then
# 条件为真时执行的命令
fi
Tips:在方括号内外,操作数和运算符之间必须有空格。例如
[ -f file.txt ]是有效的,而[ -ffile.txt ]是无效的。
[[]](测试命令扩展)
[[ ]] 是 Shell 脚本中进行条件测试的推荐方式,比传统的 [ ] 具有更多的功能和灵活性,[[ ]] 支持更复杂的条件表达式,比如正则表达式匹配:
#!/bin/bash
input="abc123"
if [[ $input =~ [a-z]+[0-9]+ ]]; then
echo "Input matches the regex"
fi
$(())(算术扩展)
$(()) 是用于进行算术运算的结构,它允许在 Shell 脚本中直接进行数学计算。这个结构会返回运算的结果,可以直接用于变量赋值或其他表达式中。
基本语法:
result=$(( 算术表达式 ))
3.6.2 算术运算符
算术运算符用于执行基本的数学运算。
| 运算符 | 描述 | 示例 |
|---|---|---|
+ | 加法 | echo $((5 + 3)) |
- | 减法 | echo $((5 - 3)) |
* | 乘法 | echo $((5 * 3)) |
/ | 除法 | echo $((6 / 3)) |
% | 取模(余数) | echo $((5 % 3)) |
3.6.3 关系运算符
关系运算符用于比较两个值,返回真或假。
| 运算符 | 描述 | 示例 |
|---|---|---|
-eq | 等于 | [ $a -eq $b ] |
-ne | 不等于 | [ $a -ne $b ] |
-gt | 大于 | [ $a -gt $b ] |
-lt | 小于 | [ $a -lt $b ] |
-ge | 大于等于 | [ $a -ge $b ] |
-le | 小于等于 | [ $a -le $b ] |
3.6.4 逻辑运算符
逻辑运算符用于执行布尔逻辑操作。
| 运算符 | 描述 | 示例 |
|---|---|---|
&& | 逻辑与 | command1 && command2 |
|| | 逻辑或 | command1 || command2 |
! | 逻辑非 | if [ ! -f file.txt ]; then ... |
3.6.5 位运算符
位运算符用于按位操作整数值。
| 运算符 | 描述 | 示例 |
|---|---|---|
& | 按位与 | expr $a & $b |
| | 按位或 | expr $a | $b |
^ | 按位异或 | expr $a ^ $b |
~ | 按位取反 | expr ~ $a |
<< | 左移 | expr $a << 1 |
>> | 右移 | expr $a >> 1 |
3.6.6 文件测试运算符
文件测试运算符用于检查文件的属性。
| 运算符 | 描述 | 示例 |
|---|---|---|
-e | 检查文件是否存在 | [ -e filename ] |
-f | 检查是否为普通文件 | [ -f filename ] |
-d | 检查是否为目录 | [ -d dirname ] |
-r | 检查文件是否可读 | [ -r filename ] |
-w | 检查文件是否可写 | [ -w filename ] |
-x | 检查文件是否可执行 | [ -x filename ] |
3.6.7 字符串运算符
字符串运算符用于比较字符串。
| 运算符 | 描述 | 示例 |
|---|---|---|
= | 检查字符串是否相等 | [ "$str1" = "$str2" ] |
!= | 检查字符串是否不等 | [ "$str1" != "$str2" ] |
-z | 检查字符串是否为空 | [ -z "$str" ] |
-n | 检查字符串是否非空 | [ -n "$str" ] |
3.6.8 赋值运算符
赋值运算符用于将值赋给变量。
| 运算符 | 描述 | 示例 |
|---|---|---|
= | 赋值 | a=10 |
+= | 追加赋值 | a+=5 (a 的值变为 105) |
4. Shell 脚本流程控制
与 Java、PHP 等语言不同的是,Shell 中的每个控制结构必须包含可执行的代码块,不能为空。
4.1 条件语句
4.1.1 if-then
最基本的条件判断结构就是 if-then 语句,if-then 语句基本语法如下:
if command
then
commands
fi
Bash 中的 if 语句与许多其他编程语言有所不同。在 Bash 中,if 语句会运行 if 后面的那个命令,命令执行后会返回一个退出状态码:
- 退出状态码为 0:表示命令成功执行,此时会执行
then部分的命令。 - 退出状态码为非 0:表示命令执行失败,此时会执行
else(如果有)部分的命令。
示例:
#!/bin/bash
# 检查目录是否存在
if [ -d "/path/to/directory" ]
then
echo "Directory exists."
fi
我们也可以在条件表达式的尾部添加分号;,就可以将 then 关键字放在同一行上了:
#!/bin/bash
# 检查目录是否存在
if [ -d "/path/to/directory" ]; then
echo "Directory exists."
fi
4.1.2 if-then-else
这个结构增加了一个 else 分支,用于处理条件为假的情况。
基本语法:
if [ 条件 ]; then
# 条件为真时执行的代码
else
# 条件为假时执行的代码
fi
示例:
#!/bin/bash
num=-1
if [ $num -gt 0 ]; then
echo "$num is positive"
else
echo "$num is not positive"
fi
4.1.3 if-elif
if-elif 结构允许检查多个条件,每个 elif 可以有不同的条件,类似 Java 中的 if-else if-else。
基本语法:
if [ 条件1 ]; then
# 条件1为真时执行的代码
elif [ 条件2 ]; then
# 条件2为真时执行的代码
else
# 条件都不满足时执行的代码
fi
示例:
#!/bin/bash
num=0
if [ $num -gt 0 ]; then
echo "$num is positive"
elif [ $num -eq 0 ]; then
echo "$num is zero"
else
echo "$num is negative"
fi
4.2 循环
Shell 脚本中提供了 for、while 和 until 命令实现循环处理重复的逻辑。
4.2.1 for 循环
for 循环用于遍历一个集合(例如列表、数组、字符串等)中的每个元素,并对每个元素执行一段代码,比如处理某个目录下的所有文件、系统上的所有用户或是某个文本文件中的所有行。for 循环非常适合处理已知数量的迭代。基本语法如下:
for variable in list; do
commands
done
variable是一个变量,用于存储当前迭代的元素。list是要遍历的元素列表,可以是静态值、命令输出或范围。
使用示例如下:
-
静态值遍历数字:
#!/bin/bash for i in 1 2 3 4 5; do echo "当前数字是 $i" done -
遍历当前目录下的所有
.txt文件名:#!/bin/bash for file in *.txt; do echo "处理文件: $file" done -
使用命令输出作为列表:
#!/bin/bash for file in $(ls); do echo "文件: $file" done使用
$(ls)获取当前目录下的所有文件名。 -
使用
{start..end}语法生成一个范围:#!/bin/bash for i in {1..5}; do echo "数字: $i" done -
遍历数组
#!/bin/bash array=(apple banana cherry) for fruit in "${array[@]}"; do echo "水果: $fruit" done -
嵌套
for循环#!/bin/bash for i in {1..2}; do for j in {1..3}; do echo "外循环: $i, 内循环: $j" done done
4.2.2 while 循环
while 循环在条件为真时持续执行,常用于读取用户输入、读取文件内容。语法如下:
while [ condition ]; do
commands
done
使用示例如下:
-
计数到 5:
#!/bin/bash count=1 while [ $count -le 5 ]; do echo "计数: $count" ((count++)) # 递增计数 done -
逐行读取文件内容并输出到指定文件:
#!/bin/bash # 读取文件的每一行 filename="example.txt" while read -r line; do echo "当前行: $line" done < "$filename" -
读取用户输入,直到满足某个条件为止:
#!/bin/bash # 初始化变量 input="" # 读取用户输入 while [ "$input" != "exit" ]; do read -p "请输入一个命令(输入 'exit' 退出): " input echo "你输入的命令是: $input" done echo "退出循环"
4.2.3 until 循环
until 循环与 while 循环相反,当条件为假时执行。语法如下:
until [ condition ]; do
commands
done
4.2.4 退出循环和跳过当前迭代
和 Java 一样,Shell 也支持在循环体中使用 continue 跳过当前迭代,break 退出循环。
使用 break 退出循环:
#!/bin/bash
for i in {1..5}; do
if [ $i -eq 3 ]; then
echo "遇到 3,退出循环"
break # 退出循环
fi
echo "处理 $i"
done
输出:
处理 1
处理 2
遇到 3,退出循环
使用 continue 跳过当前迭代:
#!/bin/bash
for i in {1..5}; do
if [ $i -eq 3 ]; then
echo "跳过 $i"
continue # 跳过当前迭代
fi
echo "处理 $i"
done
输出:
处理 1
处理 2
跳过 3
处理 4
处理 5
5. Shell 自定义函数
Shell 支持用户将一系列命令封装成一个自定义函数,然后再脚本中重复使用。
5.1 定义函数
定义函数的基本语法如下:
[function] function_name() {
# commands
}
定义函数时可以省略 function 关键字,直接使用 function_name() 格式定义:
my_function() {
echo "Hello World!"
}
5.2 调用函数
函数必须在声明之后才能被调用。如果在函数声明之前调用,Shell 会提示找不到该命令:

定义好函数后,可以直接使用函数名调用:
#!/bin/bash
my_function() {
echo "Hello World!"
}
my_function # 输出: Hello World!
5.3 函数传数
在函数调用时可以向函数传递参数,只需要在函数内使用位置参数 $n 来访问传入的参数:
#!/bin/bash
my_function() {
local param1=$1 # 第一个参数
local param2=$2 # 第二个参数
echo "Param 1: $param1"
echo "Param 2: $param2"
}
my_function "Hello" "World"

也可以使用参数替换的方式为参数设置默认值,三种格式如下:
${parameter:-default}:如果parameter未设置或为空,则使用default值。${parameter-default}:如果parameter未设置(即没有定义),使用default值,但如果parameter是空字符串,则仍然使用其原值。${parameter:+value}:如果parameter被设置且非空,则使用value。
使用样例如下:
-
使用
$n访问多个参数:sum_numbers() { local sum=$(( $1 + $2 + $3 )) # 计算前三个参数的和 echo "The sum is: $sum" } sum_numbers 5 10 15 # 输出: The sum is: 30 -
使用
$@和$*访问所有传递给函数的参数:print_all_params() { echo "All parameters using \$@:" for param in "$@"; do echo "$param" done } print_all_params "one" "two" "three" -
使用
$#来获取传递给函数的参数个数:count_params() { echo "Number of parameters: $#" } count_params "param1" "param2" # 输出: Number of parameters: 2 -
为参数设置默认值:
greet() { local name=${1:-"Guest"} # 如果第一个参数未提供,使用"Guest" echo "Hello, $name!" } # 调用函数 greet # 输出: Hello, Guest! greet "Alice" # 输出: Hello, Alice!${1:-"Guest"}:检查第一个参数$1是否为空。如果为空,则使用默认值"Guest"。- 如果传入一个参数(如
"Alice"),那么name将会被赋值为"Alice"。
5.4 函数返回值
函数的返回值可以通过两种主要方式来处理:返回状态码和输出结果。
5.4.1 使用 return 返回状态码
函数可以使用 return 命令来返回一个状态码(0-255),在调用函数只能通过 $? 来获取 return 命令返回的状态码。
但 return 命令只能返回一个介于 0 到 255 之间的整数,因此通常用来表示函数的执行结果。例如,返回 0 表示成功,非零值表示失败。如果不显示的调用 return 命令,将以函数的最后一条命令的运行结果,作为返回值。
使用示例:
my_function() {
if [ "$1" -gt 0 ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
# 调用函数并检查返回值
my_function 5
echo $? # 输出0,表示成功
5.4.2 使用 $() 捕获 echo 的输出结果
echo 命令可以将函数的计算结果、变量的值等直接打印在标准输出(通常是屏幕上),我们可以在调用函数时使用命令替换来捕获结果。
使用示例如下:
add_numbers() {
local sum=$(( $1 + $2 ))
echo $sum # 输出结果
}
# 捕获函数输出
result=$(add_numbers 5 10)
echo "The sum is: $result" # 输出: The sum is: 15
6. 输入/输出重定向
在 Linux 系统中,每个命令的执行都会涉及输入和输出的处理。每个命令在运行时通常会打开三个文件:
- 标准输入(STDIN):默认来源是终端(键盘),用于接收用户输入。
- 标准输出 (STDOUT):命令的正常输出结果,默认返回到终端(显示屏)。
- 标准错误(STDERR):用于输出错误信息和警告,通常也会显示在终端,以帮助用户识别问题。
通过输入和输出重定向,我们可以灵活地控制命令执行时的输入来源和输出目标。这在处理文件和日志记录时尤为重要。
6.1 基本重定向命令
基本重定向命令可以控制命令输入、输出的来源和去向,例如将命令的输出写入文件、从文件中读取输入或将输出追加到已有文件中。
| 命令 | 说明 |
|---|---|
command > file | 将 command 的标准输出重定向到 file,如果 file 已存在,则覆盖其内容。 |
command < file | 将 file 的内容作为 command 的标准输入。 |
command >> file | 将 command 的标准输出追加到 file 的末尾,如果 file 不存在,则创建它。 |
使用示例:
-
将
ls的输出重定向到文件 list.txt:ls > list.txt -
将 list.txt 的内容作为
cat的输入:cat < list.txt -
将
echo的输出追加到 log.txt:echo "This is a log entry." >> log.txt
Tips:使用
>时会覆盖文件内容,而>>则是追加到文件末尾。
6.2. 文件描述符重定向
除了基本的重定向命令,我们还可以使用文件描述符重定向更灵活地管理输入和输出。标准输入、标准输出和标准错误的描述符如下:
| 文件描述符 | 描述 | 默认用途 |
|---|---|---|
| 0 | 标准输入 (stdin) | 接收输入(通常来自键盘) |
| 1 | 标准输出 (stdout) | 输出结果(默认到终端) |
| 2 | 标准错误 (stderr) | 输出错误信息(默认到终端) |
文件描述符重定向命令格式如下:
| 命令 | 说明 |
|---|---|
n > file | 将文件描述符 n 的输出重定向到 file。 |
n >> file | 将文件描述符 n 的输出追加到 file。 |
n >& m | 将文件描述符 n 的输出重定向到文件描述符 m,合并它们的输出。 |
n <& m | 将文件描述符 m 的输入重定向到文件描述符 n,合并它们的输入。 |
使用示例:
-
将错误信息写入
error.log文件:ls file.txt 2> error.log -
合并输出和错误,将标准输出和标准错误都重定向到
output.txt文件:command > output.txt 2>&1 -
将标准输出和标准错误分别重定向到不同的文件:
command > output.txt 2> error.txt
7. Shell 脚本执行
执行 Shell 脚本的方法有三种主要形式,每种形式在执行环境、变量作用域、退出状态和权限要求等方面都有显著的不同。
7.1 直接调用脚本(子Shell)
直接调用脚本是一种常见的方式,该方式会在一个子 Shell 中运行脚本,因此脚本内定义的变量在外部是不可见的。
通过直接调用脚本的方式运行脚本只需在命令行输入以下格式的命令:
./script_name.sh
./表示当前目录。如果脚本位于其他目录,需要提供相应的路径,比如/path/to/script_name.sh。script_name.sh脚本文件名,后缀是.sh。
这种方式在执行之前,需要确保脚本文件具有可执行权限,可以通过以下命令来设置:
chmod +x script_name.sh
7.2 指定 Shell 解释器(子Shell)
直接调用脚本执行时 Shell 解释器使用的是当前用户默认的解释器,如果我们希望执行时使用指定的 Shell 解释器,通常是使用以下命令:
bash script_name.sh
或者可以使用其他 Shell,例如:
sh script_name.sh # 使用 Bourne Shell
zsh script_name.sh # 使用 Z Shell
ksh script_name.sh # 使用 Korn Shell
与直接调用脚本不同,这种方法不要求脚本具有可执行权限,可以直接运行。此外,脚本会在一个子进程中执行,定义的变量不会影响到当前的 Shell 环境。
7.3 使用 source 或 . 命令执行
除了上述两种方式,还可以使用 source 或 . 命令来执行脚本,格式如下:
source myscript.sh
# 或
. myscript.sh
这种方式的显著特点是脚本在当前的 Shell 环境中执行,而不是在子 Shell 中。即我们在脚本中定义的变量在当前的 Shell 中是可见的,我们可以通过这种方式使用脚本来修改当前 Shell 的环境,例如定义环境变量、函数或别名。
同时,使用 source 执行脚本时,脚本的退出状态也会影响到当前的 Shell。例如在顶级父 Shell 中使用 source 或 . 执行脚本,而脚本中包含 exit 命令,脚本的执行将会导致整个顶级父 Shell 退出。
#!/bin/bash
exit # 退出当前 Shell

55

被折叠的 条评论
为什么被折叠?



