引言:开启 Shell 编程大门
Shell 作为用户与 Linux 内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是批量处理文件、监控系统性能,还是自动化部署软件,Shell 编程都能轻松胜任。
一、Shell 是什么
(1)定义
Shell 是 Linux 系统中的命令行解释器,它就像是一个翻译官,位于用户和系统内核之间。用户通过在终端输入各种命令,这些命令被 Shell 接收并解析,然后 Shell 将其翻译成系统内核能够理解的指令,进而执行相应的操作,并将结果反馈给用户。
例如,当我们在终端输入 “ls” 命令时,Shell 会将这个命令传达给内核,内核执行后返回当前目录下的文件和目录信息,再由 Shell 展示给我们。
(2)常见 Shell 类型
在 Linux 世界里,有多种不同类型的 Shell,其中最常见的当属 bash 和 zsh 。
bash(Bourne-Again Shell)是大多数 Linux 发行版的默认 Shell,具有广泛的兼容性,几乎可以在所有的 Linux 系统上运行。它的语法简单,易于学习,对于初学者来说非常友好,丰富的内建命令和实用工具,能满足日常的各种基本操作需求,比如文件管理、进程控制等。同时,bash 还支持命令行历史记录和命令补全功能,让我们在操作时更加便捷高效。
zsh(Z Shell)则以其丰富的个性化配置和强大的功能而闻名。它拥有更智能的命令补全功能,不仅能补全命令,还能根据上下文自动补全参数、文件名等,甚至在我们拼写错误时,也能智能地给出提示和修正。此外,zsh 还支持各种主题和插件,通过安装不同的主题,我们可以让终端界面变得更加美观炫酷;借助插件,能进一步扩展其功能,如语法高亮、历史命令搜索、Git 快捷操作等,大大提升工作效率 。不过,zsh 的配置相对复杂一些,对于新手可能不太容易上手。
bash 是个很好的起点,它的稳定性和广泛适用性,能帮助你快速熟悉 Shell 编程的基本概念和操作。而当你对 Shell 有了一定的了解,想要追求更个性化、高效的操作体验时,zsh 则能满足你的进阶需求。
二、Shell 编程基础语法
在了解了 Shell 的基本概念后,接下来我们深入学习 Shell 编程的基础语法,这些语法是编写高效、灵活 Shell 脚本的基石。掌握好它们,你就能轻松驾驭 Shell 编程,实现各种复杂的自动化任务。
(1)脚本文件开头
在编写 Shell 脚本时,通常会在文件的第一行写下 “#!/bin/bash”,它的作用是指定该脚本使用的解释器为 Bash,也就是告诉系统,当执行这个脚本时,要调用 /bin/bash 程序来解释执行脚本中的命令。
这就好比我们打开一个文档时,需要指定用 Word、WPS 等相应的软件来打开一样,“#!/bin/bash” 就是在指明脚本的 “打开方式”。如果没有这一行,系统可能无法正确识别该如何执行脚本中的命令,导致脚本无法正常运行 。
(2)运行 Shell 脚本有几种方法
- 作为可执行程序:
chmod a+x myshell.sh
./myshell.sh
chmod a+x myshell.sh - 这条命令用于给 myshell.sh 脚本添加可执行权限
- chmod 是改变文件权限的命令
- a+x 表示给所有用户(所有者、组用户、其他用户)添加执行权限
- myshell.sh 是目标脚本文件名
./myshell.sh - 这条命令用于执行当前目录下的 myshell.sh 脚本
- ./ 表示当前目录
- myshell.sh 是要执行的脚本文件名
执行这两条命令后,系统会运行你的 shell 脚本并执行其中包含的命令。如果脚本有输出内容,会显示在终端上。
. myshell.sh
source myshell.sh
. myshell.sh 是在当前 shell 环境中执行 myshell.sh 脚本的命令,也可以写成 source myshell.sh,两者效果完全相同。
这种执行方式与 ./myshell.sh 的主要区别在于:
- . myshell.sh 会在当前 shell 进程中执行脚本,脚本中定义的变量、函数等会保留在当前 shell 环境中,执行完毕后可以直接使用
- ./myshell.sh 会启动一个新的子 shell 进程来执行脚本,脚本中定义的变量等不会影响当前 shell 环境
通常在需要让脚本中的设置(如环境变量配置)生效到当前终端时,会使用这种方式,比如执行 .bashrc 或 .profile 等配置文件。
- 作为解释器参数
/bin/bash myshell.sh
/bin/bash myshell.sh 是指定使用 /bin/bash 解释器来执行 myshell.sh 脚本的命令。
这条命令的特点和用途:
- 直接指定了脚本解释器为 /bin/bash,忽略脚本第一行的 #! 声明(即 shebang 行)
- 即使脚本没有可执行权限(未执行 chmod +x),也能正常运行
- 会启动一个新的 bash 子进程来执行脚本,脚本中定义的变量不会影响当前 shell 环境
这种方式常用于:
- 临时指定特定版本的 bash 解释器执行脚本
- 运行没有可执行权限的脚本
- 脚本缺少正确的 shebang 声明时强制指定解释器
(3)编辑shell脚本文件并执行
打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 sh(sh代表shell)
#!/bin/bash
echo "Hello World"
(4)变量
1.定义:
Shell 变量无需声明数据类型(如整数、字符串),直接通过 “变量名=值” 的形式存储数据,本质是 “键值对”,用于在脚本中传递、复用信息(比如文件路径、配置参数、计算结果等)。在 Shell 中,变量的定义非常简单,不需要声明变量类型,直接赋值即可。
变量命名需要遵循一定的规则:
- 只包含字母、数字和下划线: 变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符。
- 不能以数字开头: 变量名不能以数字开头,但可以包含数字。
- 避免使用 Shell 关键字: 不要使用Shell的关键字(例如 if、then、else、fi、for、while 等)作为变量名,以免引起混淆。
- 使用大写字母表示常量: 习惯上,常量的变量名通常使用大写字母,例如 PI=3.14。
- 避免使用特殊符号: 尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。
- 避免使用空格: 变量名中不应该包含空格,因为空格通常用于分隔命令和参数。
这里要注意,变量名和等号之间不能有空格。
# 存储字符串
name="张三"
# 存储整数
age=20
# 存储路径
log_path="/var/log/syslog"
特殊变量:
除了我们自定义的变量,Shell 中还有一些特殊变量,它们有着特殊的用途。比如 “0”,它用于获取当前脚本的名称。假设我们有一个名为test.sh的脚本,在脚本中使用echo 0,就会输出 “test.sh”,这在需要根据脚本名进行不同操作的场景中非常有用 。“1”“2”…… 则用于获取脚本执行时传入的参数,“1”表示第一个参数,“2” 表示第二个参数,以此类推。例如,我们执行脚本 “./test.sh arg1 arg2”,在脚本中使用 echo 1,就会输出“arg1”,使用echo 2 会输出 “arg2” 。这些特殊变量在处理命令行参数、实现脚本的灵活性方面发挥着重要作用。
2.变量的使用:$变量名 或 ${变量名}
调用变量时,在变量名前加$;若变量名后紧跟其他字符(如字母、数字),需用${}包裹避免歧义。
# 定义变量
user="Alice"
score=95
# 基本使用:$变量名
echo "用户名:$user" # 输出:用户名:Alice
# 避免歧义:${变量名}
echo "用户分数:${score}分" # 输出:用户分数:95分
(若写$score分,会被识别为“$score分”变量,导致错误)
3.只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
#! /bin/bash
url="www.baidu.com"
readonly url
url="www.google.com"
运行结果:
[toto@centos shell]$ bash test.sh
test.sh:行4: url: 只读变量
4.变量的修改与删除
修改:直接重新赋值(覆盖旧值):
name="Bob"
name="Charlie" # 新值覆盖旧值,最终name为"Charlie"
删除:用unset 变量名删除变量(删除后变量不可用):
unset name # 删除name变量
echo $name # 输出空(变量已不存在)
示例:
#!/bin/sh
Url="https://www.baidu.com"
unset Url
echo $Url
运行结果为空
5.变量赋值:从命令结果获取值(命令替换)
用$(命令)或反引号`命令`,将命令的输出结果赋值给变量(推荐$(命令),兼容性更好)。
示例:
# 获取当前日期(格式:年-月-日)
today=$(date +%Y-%m-%d)
echo "今天日期:$today" # 输出:今天日期:2024-05-20
# 获取当前目录下的文件数
file_count=$(ls | wc -l)
echo "当前目录文件数:$file_count"
6.变量运算:整数计算(无需额外工具)
用$((表达式))直接对变量进行算术运算(支持+ - * / %等运算符)。
示例:
a=10
b=3
# 加法
sum=$((a + b))
# 乘法
product=$((a * b))
# 取余
remainder=$((a % b))
echo "sum: $sum, product: $product, remainder: $remainder"
# 输出:sum: 13, product: 30, remainder: 1
7.变量类型
字符串变量
在 Shell 中,字符串变量是最为常见的一种类型。定义字符串变量时,既可以使用单引号,也可以使用双引号 。比如:
# 使用单引号定义字符串变量
single_quote_str='这是一个使用单引号定义的字符串'
# 使用双引号定义字符串变量
double_quote_str="这是一个使用双引号定义的字符串"
单引号和双引号在定义字符串变量时有着明显的区别。单引号中的内容会被原样输出,其中的变量不会被解析。例如:
name="张三"
single_quote_greeting='你好,$name'
echo $single_quote_greeting
上述代码的输出结果是你好,$name,可以看到,单引号中的$name并没有被替换成实际的变量值。
而双引号则不同,双引号中的变量会被解析成实际的值,还能识别转义字符。例如:
name="李四"
double_quote_greeting="你好,$name\n欢迎来到这里!"
echo -e $double_quote_greeting
这里的-e选项是为了让echo命令识别转义字符,输出结果会是:
你好,李四
欢迎来到这里!
1. 快速获取字符串长度
使用${#字符串名}即可直接返回字符串长度,若将字符串视为单元素数组,${#字符串名[0]}结果完全一致。
# 实例:计算字符串长度
string="abcd"
echo ${#string} # 输出 4(直接获取长度)
echo ${#string[0]} # 输出 4(数组形式获取,结果相同)
2. 精准提取子字符串
语法格式为${字符串名:起始索引:截取长度},注意:第一个字符的索引为 0,若省略 “截取长度”,则默认截取到字符串末尾。
# 实例1:从第2个字符开始,截取4个字符
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo(索引1对应第2个字符"u",截取4位)
# 实例2:从第6个字符截取到末尾
echo ${string:5} # 输出 ob is a great site(索引5对应"o",省略长度则取剩余所有字符)
3. 查找子字符串位置
借助expr index "$字符串名" 目标字符集,可获取字符集中首个出现字符的位置(从 1 开始计数),若未找到则返回 0。
# 实例:查找"i"或"o"的首次出现位置
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4("o"在第4位,比"i"先出现)
注意:此处使用的是反引号 `(键盘左上角,与~同键),而非单引号 ',用于执行 expr 命令并返回结果。
整数变量
在某些 Shell 中,可以使用declare或typeset命令来声明整数变量。一旦声明为整数变量,它就只能存储整数值。例如:
# 使用declare声明整数变量
declare -i num=10
当给整数变量赋非整数值时,Shell 会尝试将其转换为整数。比如:
declare -i num='10.5'
echo $num
上述代码的输出结果是10,可以看出,10.5被转换为了整数10。
数组变量
Shell 支持数组变量,它允许在一个变量中存储多个值,包括整数索引数组和关联数组。
整数索引数组的定义方式如下:
# 定义整数索引数组
int_array=(1 2 3 4 5)
通过索引可以访问数组中的元素,索引从 0 开始。例如,要访问数组中的第三个元素,可以这样做:
echo ${int_array[2]}
关联数组则使用键值对的方式存储数据,定义时需要先声明为关联数组类型。例如:
# 声明关联数组
declare -A assoc_array
# 给关联数组赋值
assoc_array["name"]="王五"
assoc_array["age"]=25
访问关联数组的元素时,使用键来获取对应的值:
echo ${assoc_array["name"]}
echo ${assoc_array["age"]}
环境变量
环境变量是由操作系统或用户设置的特殊变量,对 Shell 的行为和执行环境有着重要影响。常见的环境变量有PATH、HOME、LANG等。
PATH变量包含了操作系统搜索可执行文件的路径。当我们在命令行输入一个命令时,系统会按照PATH变量中列出的路径依次查找对应的可执行文件。例如:
echo $PATH
HOME变量表示当前用户的主目录路径。比如,在 Linux 系统中,普通用户的主目录通常是/home/用户名,通过HOME变量就可以方便地获取这个路径。
LANG变量则用于设置系统的语言环境,影响着系统中各种信息的显示语言。
特殊变量
在 Shell 中,还有一些特殊变量,它们有着特定的含义和用途。
$0表示脚本的名称。比如有一个名为test.sh的脚本,在脚本内部使用$0,就会得到test.sh。
$1、$2等表示脚本的参数。假设我们有一个脚本arg_test.sh,执行时传入两个参数:
./arg_test.sh param1 param2
在脚本内部,$1就代表param1,$2就代表param2。
$#表示传递给脚本的参数数量。继续以上面的例子来说,$#的值就是2。
$?表示上一个命令的退出状态,正常退出时返回值为 0,非 0 则表示命令执行过程中出现了错误。例如,执行一个不存在的命令:
nonexistent_command
echo $?
这里的输出结果就会是一个非 0 的值,具体数值根据系统而定,通常表示命令未找到的错误状态 。
(5)Shell 数组:一维数组的灵活运用
Bash 仅支持一维数组,无大小限制,下标从 0 开始,可灵活定义和访问元素,适合存储批量数据。
1. 三种数组定义方式
数组元素用空格分隔,支持集中定义、分行定义和单独赋值,甚至可使用非连续下标。
# 方式1:集中定义(最常用)
array_name=(value0 value1 value2 value3)
# 方式2:分行定义(适合元素较多的场景)
array_name=(
value0
value1
value2
value3
)
# 方式3:单独赋值(支持非连续下标)
array_name[0]=value0
array_name[2]=value2 # 跳过下标1,直接定义下标2
array_name[5]=value5 # 下标范围无限制,无需连续
2. 数组元素的读取与遍历
读取单个元素:${数组名[下标]}
获取所有元素实现遍历:${数组名[@]}或${数组名[*]}
# 实例:定义并读取数组
array_name=(apple banana cherry date)
# 读取单个元素(下标2对应第3个元素)
echo ${array_name[2]} # 输出 cherry
# 读取所有元素(两种方式等价)
echo ${array_name[@]} # 输出 apple banana cherry date
echo ${array_name[*]} # 输出 apple banana cherry date
# 遍历数组(推荐用@,可正确处理含空格的元素)
for fruit in ${array_name[@]}; do
echo "水果:$fruit"
done
3. 获取数组长度
获取数组元素总数:${#数组名[@]}或${#数组名[*]}
计算某元素的长度:${#数组名[下标]}
# 实例:计算数组及元素长度
array_name=(apple banana cherry)
# 数组元素总数
length=${#array_name[@]}
echo "数组长度:$length" # 输出 3
# 单个元素长度(下标1对应"banana")
elem_length=${#array_name[1]}
echo "元素长度:$elem_length" # 输出
三、Shell 注释:让脚本更易读
注释是脚本的 “说明书”,合理使用注释能提升代码可维护性,避免后期 “看不懂自己写的代码”。
1. 单行注释:用 #标注
以#开头的行均为注释,会被 Shell 解释器忽略,适合简短说明。
# 这是单行注释,用于解释下一行代码的功能
string="hello world" # 定义字符串(行尾注释,补充代码作用)
echo $string # 输出字符串内容
2. 多行注释:三种实用方式
当需要注释大段代码时,无需逐行加#,以下三种方式可快速实现多行注释。
方式 1:Here 文档(推荐)
用:<<标识符和标识符包裹注释内容,标识符可自定义(如 EOF、COMMENT、! 等),建议使用大写字母区分。
# 实例1:用EOF作为标识符
:<<EOF
这是多行注释的第一行
第二行注释内容,可写脚本说明、作者信息等
第三行:本脚本用于处理日志文件
EOF
# 实例2:用!作为标识符
:<<!
注释内容...
支持任意行数
!
方式 2:空函数包裹
将注释内容定义为无调用的函数,函数内代码不会执行,间接实现注释效果。
# 实例:用空函数注释大段代码
comment() {
这是通过函数实现的多行注释
适合临时注释大段代码,后续取消注释只需调用函数即可
echo "这段代码不会执行"
}
方式 3:冒号 + 单引号
:是 Shell 的空命令(执行后无任何操作),配合单引号包裹多行内容,简单高效。
# 实例:冒号+单引号实现多行注释
: '
这是注释内容
无需定义标识符,直接用单引号包裹
适合快速临时注释
'
四、总结
Shell 编程不仅是系统管理员必备的技能,对于开发人员、数据分析师等也有着重要的意义 它能帮助我们自动化繁琐的任务,提高工作效率,让我们有更多的时间和精力去专注于更有价值的工作 。
学习 Shell 编程是一个持续的过程,希望大家在今后的学习和工作中,能够不断地实践和探索,将所学知识运用到实际项目中 。你可以尝试编写更复杂的脚本,探索自动化运维、服务器管理、数据处理等更多的应用场景 。