文章目录
- 1. 初识 Shell 解释器
- 1.1 Shell 类型
- 1.2 Shell 的父子关系
- 2. 编写第一个 Shell 脚本
- 3. Shell 脚本语法
- 3.1 脚本格式
- 3.2 注释
- 3.2.1 单行注释
- 3.2.2 多行注释
- 3.3 Shell 变量
- 3.3.1 系统预定义变量(环境变量)
- printenv 查看所有环境变量
- set 查看所有 Shell 变量
- 3.3.2 声明变量
- 字符串变量
- 数组变量
- 关联数组(Map)变量
- 3.3.3 引用变量
- 3.3.4 变量的作用域
- 全局变量
- 局部变量
- 只读变量
- 3.3.5 撤销变量
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]
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)使用。
只读变量
只读变量是定义后无法被修改的变量。使用 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.5 撤销变量
后续不再需要声明的变量时,可以使用 unset
命令来撤销已经声明的变量:
MY_VAR="Some value" # 定义变量
unset MY_VAR # 撤销变量
echo $MY_VAR # 不会输出任何内容
撤销后,该变量不再存在,尝试引用将不会返回任何值,unset
命令不能删除只读变量。