Shell 脚本编程入门:从基础语法到文本处理实战
一、Shell 概述
Shell 是一个命令行解释器,它接收应用程序或用户输入的命令,然后调用操作系统内核来执行。我们日常输入的 cd、ls 等命令,都需要经过 Shell 解析后再交给 Linux 内核,由内核操作硬件完成最终任务。
整个调用关系可以概括为:外层应用程序 → Shell → Linux 内核 → 硬件。
除了命令解释器这一身份之外,Shell 还是一种功能相当强大的编程语言,具备易编写、易调试、灵活性强等特点,非常适合用来完成系统管理、自动化运维和批处理任务。
1. Linux 提供的 Shell 解析器
Linux 系统中常见的 Shell 解析器包括 sh、bash、csh、zsh 等。其中 bash 是目前使用最广泛的解析器,它在 sh 的基础上进行了大量增强。
2. bash 和 sh 的关系
sh 是 UNIX 早期的标准 Shell,bash 则是在 sh 基础上发展起来的增强版 Shell。在大多数 Linux 发行版中,/bin/sh 通常是指向 bash 的链接,但两者在脚本执行时仍可能存在细微差异。总的来看,bash 兼容 sh 的基本功能,同时增加了命令补全、历史记录、更丰富的编程能力等特性。
3. CentOS 默认的解析器是 bash
在 CentOS 以及主流的 Linux 发行版中,默认使用的 Shell 解析器就是 bash。可以通过下面的命令查看当前用户默认的 Shell 解析器:
echo $SHELL
二、Shell 脚本入门
1. 脚本格式
Shell 脚本通常以 #!/bin/bash 开头,这行内容用于指定脚本使用哪个解析器执行。
#!/bin/bash
2. 第一个脚本
下面创建一个名为 hello.sh 的脚本,内容如下:
#!/bin/bash
echo "hello world"
3. 脚本的常用执行方式
第一种:采用 bash 或 sh 加脚本的相对路径或绝对路径执行,这种方式不需要给脚本赋予可执行权限(不用赋予脚本 +x 权限)。
sh ./hello.sh
sh /root/hello.sh
bash ./hello.sh
bash /root/hello.sh
注意:第一种执行方式的本质是 bash 解析器帮你执行脚本,所以脚本本身不需要执行权限。
第二种:采用输入脚本的绝对路径或相对路径来执行脚本,这种方式必须具有可执行权限(+x)。首先需要给 hello.sh 脚本加上 +x 权限:
chmod +x hello.sh
./hello.sh
注意:第二种执行方式的本质是脚本需要自己执行,所以需要执行权限。
第三种:在脚本路径前加上“.”或者 source 来执行(了解即可)。例如创建一个脚本 test.sh,可以分别使用 sh、bash、./ 和 . 的方式来执行:
sh test.sh
bash test.sh
./test.sh
. ./test.sh
source ./test.sh
前两种方式都是在当前 shell 中打开一个子 shell 来执行脚本内容,当脚本内容结束后,子 shell 关闭,再回到父 shell 中。第三种方式则是在当前 shell 里直接执行脚本内容,无需打开子 shell。这也是为什么每次修改完 /etc/profile 文件以后,需要 source 一下的原因。
开子 shell 与不开子 shell 的区别,主要在于环境变量的继承关系。例如在子 shell 中设置的当前变量,父 shell 是不可见的。
三、变量
3.1 系统预定义变量
系统中已经定义好的变量称为系统预定义变量,常用变量如下:
- $HOME:当前用户的家目录
- $PWD:当前所在目录
- $SHELL:当前用户默认的 Shell 解析器
- $USER:当前登录用户
可以通过 echo 命令查看这些变量的值,例如:
echo $HOME
echo $PWD
echo $SHELL
echo $USER
3.2 自定义变量
自定义变量的基本语法为:
变量名=变量值
定义变量时需要遵循以下规则:
- 变量名可以由字母、数字和下划线组成,但不能以数字开头。
- 等号两侧不能有空格。
- 变量值如果包含空格,需要使用双引号或单引号括起来。
使用变量时,在变量名前加上 $ 符号即可,例如:
name="zhangsan"
echo $name
如果需要撤销变量,可以使用 unset 命令;如果希望变量只读,可以使用 readonly 命令。
unset name
readonly name="lisi"
3.3 特殊变量
3.3.1 $n
基本语法:$n 用于接收脚本执行时传入的第 n 个参数,例如 $1 表示第一个参数,$2 表示第二个参数,以此类推。
案例实操:
#!/bin/bash
echo "第一个参数:$1"
echo "第二个参数:$2"
执行脚本并传入参数:
./test.sh hello world
3.3.2 $#
基本语法:$# 用于获取所有输入参数的个数,常用于循环和参数校验。
案例实操:
#!/bin/bash
echo "参数个数:$#"
3.3.3 $*、$@
基本语法:$* 和 $@ 都代表命令行中所有的参数,但是两者在使用双引号包裹时存在区别。
当它们被双引号“”包含时,$* 会将所有的参数作为一个整体,以“$1 $2 …$n”的形式输出所有参数;$@ 会将各个参数分开,以“$1” “$2”…“$n”的形式输出所有参数。
案例实操:
#!/bin/bash
echo "使用 \$* 输出:$*"
echo "使用 \$@ 输出:$@"
在 for 循环中遍历参数时,使用 "$*" 只会循环一次,而使用 "$@" 会按参数个数循环多次,可以直观体会到两者的区别。
3.3.4 $?
基本语法:$? 用于获取上一条命令的返回状态。如果返回值为 0,说明上一条命令执行成功;如果返回非 0 值,说明执行失败。
案例实操:
ls
echo $?
lssss
echo $?
四、运算符
Shell 中进行运算时,可以使用 $((运算式)) 或 $[运算式] 的形式。
基本语法:
$((运算式))
$[运算式]
案例实操:计算 2 加 3 的结果。
echo $((2+3))
echo $[2+3]
五、条件判断
条件判断的基本语法为:
[ 条件判断式 ]
注意:中括号和条件判断式之间必须有空格。
常用判断条件如下:
- 整数比较:-eq(等于)、-ne(不等于)、-lt(小于)、-le(小于等于)、-gt(大于)、-ge(大于等于)
- 文件权限判断:-r(有读权限)、-w(有写权限)、-x(有执行权限)
- 文件类型判断:-f(文件存在且是普通文件)、-e(文件存在)、-d(文件存在且是目录)
案例实操:
[ 10 -gt 5 ]
echo $?
test -f /etc/passwd
echo $?
[ -w /etc/passwd ]
echo $?
六、流程控制
6.1 if 判断
基本语法:
if [ 条件判断式 ]; then
程序
elif [ 条件判断式 ]; then
程序
else
程序
fi
注意事项:[ 条件判断式 ],中括号和条件判断式之间必须有空格;if 后要有空格。
案例实操:
#!/bin/bash
if [ $1 -ge 18 ]; then
echo "成年人"
elif [ $1 -ge 6 ]; then
echo "未成年人"
else
echo "儿童"
fi
6.2 case 语句
基本语法:
case $变量名 in
"值1")
程序
;;
"值2")
程序
;;
*)
程序
;;
esac
注意事项:case 行尾必须为单词“in”;每一个模式匹配必须以右括号“)”结束;双分号“;;”表示命令序列结束,相当于 Java 中的 break;最后的“*)”表示默认模式,相当于 Java 中的 default。
案例实操:
#!/bin/bash
case $1 in
"1")
echo "星期一"
;;
"2")
echo "星期二"
;;
*)
echo "其他"
;;
esac
6.3 for 循环
基本语法 1:
for 变量 in 值1 值2 值3...
do
程序
done
案例实操:
#!/bin/bash
for i in 1 2 3 4 5
do
echo "第 $i 次循环"
done
基本语法 2:
for ((初始值;循环控制条件;变量变化))
do
程序
done
案例实操:
#!/bin/bash
for ((i=1;i<=5;i++))
do
echo "第 $i 次循环"
done
当 $* 和 $@ 被双引号包含时,$* 会将所有的参数作为一个整体,以“$1 $2 …$n”的形式输出所有参数;$@ 会将各个参数分开,以“$1” “$2”…“$n”的形式输出所有参数。
6.4 while 循环
基本语法:
while [ 条件判断式 ]
do
程序
done
案例实操:
#!/bin/bash
i=1
while [ $i -le 5 ]
do
echo "第 $i 次循环"
i=$((i+1))
done
七、read 读取控制台输入
read 命令用于从控制台读取用户输入,基本语法为:
read [选项] 变量名
常用选项及功能如下:
| 选项 | 功能 |
|---|---|
| -p | 指定读取值时的提示符 |
| -t | 指定读取值时等待的时间(秒),如果不加 -t 表示一直等待 |
案例实操 1:读取用户输入的姓名并输出。
#!/bin/bash
read -p "请输入你的姓名:" name
echo "你好,$name"
案例实操 2:设置等待时间,超时后自动退出。
#!/bin/bash
read -t 10 -p "请输入你的姓名:" name
echo "你好,$name"
八、函数
8.1 系统函数
8.1.1 basename
基本语法:basename 用于获取路径中的文件名部分。
basename 路径 后缀
案例实操:
basename /root/hello.sh
basename /root/hello.sh .sh
8.1.2 dirname
基本语法:dirname 用于获取路径中的目录部分。
dirname 路径
案例实操:
dirname /root/hello.sh
8.2 自定义函数
基本语法:
function 函数名()
{
程序
}
函数名()
{
程序
}
经验技巧:
- 必须在调用函数的地方之前先声明函数,因为 Shell 脚本是逐行运行的,不会像其他语言一样先编译。
- 函数返回值只能通过
$?系统变量获得,可以显式加上return返回;如果不加,则以最后一条命令的运行结果作为返回值。return 后跟数值 n,取值范围为 0-255。
案例实操:
#!/bin/bash
function sum()
{
result=$(($1+$2))
return $result
}
sum 10 20
echo "两数之和为:$?"
九、正则表达式入门
正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在 Linux 中,grep、sed、awk 等文本处理工具都支持通过正则表达式进行模式匹配。
9.1 常规匹配
例如通过 grep 匹配包含某个字符串的行:
cat /etc/passwd | grep root
9.2 常用特殊字符
1. 特殊字符 ^
^ 表示匹配一行的开头。例如匹配以 root 开头的行:
cat /etc/passwd | grep ^root
2. 特殊字符 $
$ 表示匹配一行的结尾。例如匹配以 bash 结尾的行:
cat /etc/passwd | grep bash$
3. 特殊字符 .
. 表示匹配任意一个字符。例如匹配包含 r..t 的行:
cat /etc/passwd | grep r..t
4. 特殊字符 *
* 表示匹配它前面的字符出现零次或多次。例如匹配包含 ro*t 的行:
cat /etc/passwd | grep ro*t
5. 字符区间(中括号):[ ]
[ ] 表示匹配字符区间内的任意一个字符。例如匹配以 a、b、c 中任意一个字符开头的行:
cat /etc/passwd | grep ^[abc]
6. 特殊字符 \
\ 表示转义符,用来转义特殊字符。例如匹配包含 $ 的行:
grep '\$' 文件名
十、文本处理工具
10.1 cut
cut 的工作就是“剪”,具体来说就是在文件中负责剪切数据。cut 命令从文件的每一行剪切字节、字符和字段,并将这些字节、字符和字段输出。
基本语法:
cut [选项参数] 文件
常用选项:
- -f:指定要提取的列号,多个列号之间用逗号分隔。
- -d:指定字段分隔符,默认分隔符为制表符。
案例实操:以冒号作为分隔符,提取 /etc/passwd 文件的第一列和第七列。
cut -d ":" -f 1,7 /etc/passwd
10.2 awk
awk 是一个强大的文本分析工具。它把文件逐行读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。
基本语法:
awk [选项参数] '/pattern1/{action1} /pattern2/{action2}...' filename
其中,pattern 表示 awk 在数据中查找的内容,也就是匹配模式;action 表示在找到匹配内容时所执行的一系列命令。
案例实操:
# 数据准备
cp /etc/passwd ./
passwd 数据的含义
用户名:密码(加密过后的):用户id:组id:注释:用户家目录:shell解析器
搜索 passwd 文件以 root 关键字开头的所有行,并输出该行的第 7 列
awk -F : '/^root/{print $7}' passwd
搜索 passwd 文件以 root 关键字开头的所有行,并输出该行的第 1 列和第 7 列,中间以 "," 号分割
awk -F : '/^root/{print $1","$7}' passwd
注意:只有匹配了 pattern 的行才会执行 action
只显示 /etc/passwd 的第一列和第七列,以逗号分割
并在所有行前面添加列名 user,shell,在最后一行添加 lanzhishujia,/bin/bigdata
awk -F : 'BEGIN{print "user, shell"} {print $1","$7} END{print "lanzhishujia,/bin/bigdata"}' passwd
注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行
将 passwd 文件中的用户 id 增加数值 1 并输出
awk -v i=1 -F : '{print $3+i}' passwd
3. awk 的内置变量
awk 提供了一些常用的内置变量,例如 FILENAME 表示文件名,NR 表示已读的记录数(行号),NF 表示当前记录中的字段数。
案例实操:
# 统计 passwd 文件名、每行的行号、每行的列数
awk -F : '{print "filename:" FILENAME ",linenum:" NR ",col:"NF}' passwd
查询 ifconfig 命令输出结果中的空行所在的行号
ifconfig | awk '/^$/{print NR}'
切割 IP
ifconfig ens33 | awk '/netmask/ {print $2}'
十一、总结
Shell 既是一个命令行解释器,也是一门适合自动化运维的编程语言。学习 Shell 脚本时,可以按照下面的主线循序渐进地掌握:
- 基础认知:理解 Shell 的作用、bash 和 sh 的关系,以及脚本最常用的三种执行方式。
- 变量体系:掌握系统预定义变量、自定义变量,以及 $n、$#、$*、$@、$? 等特殊变量的含义和区别。
- 运算与判断:熟练使用运算符、条件判断语法,注意中括号两侧必须有空格。
- 流程控制:掌握 if、case、for、while 四种控制结构,能够根据实际场景选择合适写法。
- 交互与函数:通过 read 读取控制台输入,并使用系统函数或自定义函数提升脚本复用性。
- 文本处理:理解正则表达式的基础用法,以及 cut、awk 两个常用文本处理工具。
整体来说,Shell 脚本学习的重点在于理解命令执行方式、变量与参数的传递规则,以及常用流程控制和文本处理工具的组合运用。多写、多运行、多对照输出结果是快速掌握 Shell 脚本的有效方式。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)