11.1 正则表达式
11.1.1 字符集
正则表达式的字符集包含三类核心要素:
- 普通字符:直接匹配单个字符,如
a
匹配字母a - 范围字符集:
[a-z]
匹配所有小写字母,[0-9A-F]
匹配十六进制数字 - 预定义字符集:
\d
等价于[0-9]
,\w
匹配字母/数字/下划线,\s
匹配空格
特殊符号示例:
[^abc]
表示排除a/b/c的任意字符[a-zA-Z]
覆盖所有英文字母
11.1.2 Shell正则表达式
与通用正则表达式的差异:
- 通配符转义:
*
在Shell中表示任意字符序列,需用\*
匹配字面量星号 - 边界控制:
^
匹配行首,$
匹配行尾,^$
表示空行 - POSIX扩展语法:需添加
-E
参数支持+
和?
等扩展元字符
示例:grep -E 'error|warning' log.txt
匹配包含error或warning的行
11.2 流编辑(sed)
11.2.1 功能及用法
- 在线编辑:逐行处理文本,默认输出到屏幕不修改原文件
- 核心操作:替换(s)、删除(d)、插入(i)、追加(a)
- 典型场景:批量修改配置文件、删除注释行、提取日志关键字段
11.2.2 参数与说明
参数 | 作用 |
---|---|
-n | 抑制默认输出,常与p 命令配合使用 |
-i.bak | 修改文件前创建备份(扩展名可自定义) |
-e | 串联多个编辑命令,如sed -e 's/a/A/' -e 's/b/B/' |
-r | 启用扩展正则表达式(避免转义符\ ) |
11.2.3 脚本命令
- 地址定位:
1,5d
删除1-5行,/error/d
删除含error的行 - 暂存空间:
h
复制到暂存区,G
追加暂存内容 - 条件执行:
3!d
保留第三行,删除其他行
11.2.4 sed示例
- 替换文本:
sed 's/old/new/g' file.txt
全局替换old为new - 删除空行:
sed '/^$/d' input.log
- 行内编辑:
sed '2s/word/WORD/'
仅修改第二行的首个word
11.3 模式搜索与处理(awk)
11.3.1 功能及用法
- 结构化处理:自动按分隔符切分字段(默认空格/Tab)
- 编程能力:支持变量、循环、条件判断,适合生成报表
- 典型场景:统计访问日志IP频次、计算数据列平均值
11.3.2 参数说明
参数 | 功能 |
---|---|
-F ':' | 指定字段分隔符为冒号 |
-f script | 从文件加载awk脚本 |
-v var=val | 定义变量供脚本使用 |
11.3.3 记录和域
- 记录(Record) :默认以换行符分隔,可通过
RS
变量修改 - 字段(Field) :
$1
表示第一列,$NF
表示最后一列
11.3.4 变量
- 内置变量:
NR
当前行号,FILENAME
当前文件名
FS
输入分隔符,OFS
输出分隔符 - 自定义变量:
awk -v count=0 '/pattern/{count++} END{print count}'
11.3.5 操作符
类型 | 运算符 |
---|---|
算术 | + - * / % ** |
逻辑 | `&& |
字符串连接 | "hello" "world 自动拼接 |
11.3.6 控制语句
-
条件分支:
{if ($3 > 100) print "High"; else print "Normal"}
-
循环结构:
BEGIN{for(i=1;i<=5;i++) print i}
11.3.7 常用函数
函数 | 作用 |
---|---|
length($0) | 获取当前行长度 |
substr(str,start,len) | 截取子字符串 |
gsub(/old/,"new") | 全局替换 |
11.3.8 awk程序执行
- 三阶段流程:
BEGIN{}
初始化 → 逐行处理 →END{}
汇总结果 - 调试技巧:
使用print
输出中间变量,或通过awk --debug
启动调试模式
11.4 Bourne shell及其编程
11.4.1 特殊字符
字符 | 作用 | 转义方法 |
---|---|---|
$ | 变量引用 | \$ 或单引号 |
* | 通配符 | \* 或双引号 |
> | 输出重定向 | \> 或引号包裹 |
11.4.2 变量与参数
-
变量类型:
- 用户变量:
name="value"
- 位置参数:
$1
第一个参数,$#
参数总数 - 环境变量:
PATH
,HOME
通过export
导出
- 用户变量:
-
参数扩展:
${var:-default}
变量为空时返回默认值
11.4.3 shell的状态
- 退出状态码:
0
表示成功,非零值表示错误类型 - 获取方式:
$?
获取上条命令的退出状态
11.4.4 shell的调用与变量传递
- 父子进程:
子进程继承父进程环境变量,但不继承普通变量 - 跨脚本传参:
通过export
导出变量,或通过命令行参数传递
11.4.5 shell程序设计
- 流程控制:
if [ condition ]; thencommands elif [ condition2 ]; thencommands elsecommands fi
11.4.6 命令行参数与选项的处理
- getopts用法:
while getopts "a:bc" opt; docase $opt ina) arg=$OPTARG ;;b) flag=1 ;;esac done
11.4.7 shell程序调试
- 调试模式:
sh -x script.sh
显示每条命令执行过程 - 日志记录:
exec > debug.log 2>&1
重定向所有输出到文件
11.4.8 shell脚本格式规范
- Shebang声明:
#!/bin/sh
- 注释规则:以
#
开头,解释复杂逻辑 - 代码缩进:使用4空格增强可读性
- 错误处理:
set -e
遇到错误立即退出
11.5 练习题
11.5.1 思考题
(1)正则表达式可用于模式匹配与搜索,常见的正则表达式有几类?BRE的正则表达式可完全使用于ERE吗?
(2)shell是解释语言还是编译语言?
(3)awk的默认域分隔是什么?如何改变awk的域分隔符?
(4)在shell中如何回到刚离开的工作目录?
11.5.2 填空题
(1)grep -E '[Hh]enr(y|ietta)' file的功能是(____________)
(2)grep -v "^#" /etc/syslog.conf的作用是(____________)
(3)sed -e 's/sysman/System Manager/g' <ifile >ofile的作用是(____________)
(4)sed '/^$/d;/[[:space:]]*$/d;' <ifile >ofile的作用是(____________)
(5)awk -F: '{ print $1 } END { print NF; print NR }' /etc/passwd的作用是(__________)
(6)awk '{ print toupper($0) }' <ifile > ofile的作用是(____________)
11.5.3 综合题
(1)试分别用grep、sed和awk实现:对某个脚本文件ifile(比如/etc/profile)进行如下操作:
① 显示其中的所注释行(含#开始的行,或#号前全是白空格开始的行);
② 显示去除了所有注释行的内容;
③ 显示所有的(去掉注释和空行)有效行。
(2)设计一个菜单shell程序,要求:
① 捕获信号1、2、3、15。
② 当收到信号后在终端上显示:”I Received Signal #”,其中#为收到的信号编号。
③ 其他仿sh03.sh。
(3)设有shell程序,内容为:
#!/bin/bash
pkg="tcsh"
hostnamectl | grep -i Ubuntu >/dev/null 2>&1
if [ $? -eq 0 ]; thencmd_q='dpkg -l '; cmd_i='apt install '
elsecmd_q='rpm -q '; cmd_i='yum install '
fi
$cmd_q $pkg >/dev/null 2>&1
if [ $? -eq 0 ]; then echo "Package: $pkg has been installed!"
else $cmd_i ${pkg}
fi
试写出程序的功能和执行的可能结果。
(4)在UNIX/Linux系统中,环境变量非常重要,试说明(在字符界面下):
① PATH变量的作用,如何得到PATH的值?
② 在UNIX/Linux系统超级用户的环境变量PATH中允许包含当前目录吗?
③ 若在超级用户的当前目录内有可执行文件mypro,如何执行它(写出执行时键盘输入的内容)?
④ 如何使用该命令在后台执行(写出执行时键盘输入的内容)?
(5)设计一个shell程序计算n的阶乘。要求:
① 从命令行接收参数n;
② 在程序开始后立即判断n的合法性,即是否有参数,若有是否为正整数,若非法请给出错误提示;
③ 最后输出计算的结果。
11.5.4 答案
1.思考题
(1)正则表达式可用于模式匹配与搜索,常见的正则表达式有几类?BRE的正则表达式可完全使用在ERE吗?
解:SRE、BRE和ERE;不能。
(2)shell是解释语言还是编译语言?
解:解释语言。
(3)awk的默认域分隔是什么,如何改变awk的域分隔符?
解:白空格;使用-F选项,可以指定域分隔符,而不使用默认。比如-F:将分隔符指定为冒号。
(4)在bshell中如何回到刚离开的工作目录?
解:$ cd ~- 或者 $ cd $OLDPWD
2.填空题
(1)grep -E '[Hh]enr(y|ietta)' file的功能是:(_搜索Henry、henry、Henrietta和henrietta_)。
(2)grep -v "^#" /etc/syslog.conf的作用是:(_显示文件的所有不以"^#"开始的行_)。
(3)sed -e 's/sysman/System Manager/g' <ifile >ofile的作用是:(将ifile中的所有sysman替换为System Manager后保存到ofile)。
(4)sed '/^$/d;/^[[:space:]]*$/d;' <ifile >ofile的作用是:(将删除空行后的ifile内容保存到ofile)。
(5)awk -F: '{ print $1 } END { print NF; print NR }' /etc/passwd的作用是:(显示/etc/passwd 中的所有用户名,并在最后显示/etc/passwd中的域个数和总行数)。
(6)awk '{ print toupper($0) }' <ifile > ofile的作用是:(将ifile的内容全部转换为大写后保存到ofile)。
3.综合题
(1)试分别用grep、sed和awk实现:对某个脚本文件ifile(比如/etc/profile)进行如下操作:
① 显示其中的所注释行(含#开始的行,或#号前全是白空格开始的行);
② 显示去除了所有注释行的内容;
③ 显示所有的(去掉注释和空行)有效行。
解:
# 要求一:显示其中的所注释行(含#开始的行,或#号前全是白空格开始的行)
# grep '^[[:space:]]*#' /etc/profile# sed '/^[[:space:]]*#/!d' < /etc/profile# awk '/^[[:space:]]*#/ { print $0 }' /etc/profile# 要求二:显示去除了所有注释行的内容# grep -v '^[[:space:]]*#' < /etc/profile# sed '/^[[:space:]]*#/d' /etc/profile# awk '!/^[[:space:]]*#/ { print $0 }' < /etc/profile# 要求三:显示所有的(去掉注释和空行)有效行# grep -v -E '(^[[:space:]]*#)|(^[[:space:]]*$)' /etc/profile# sed '/^[[:space:]]*#/d;/^[[:space:]]*$/d' < /etc/profile# awk '!/(^[[:space:]]*#)|(^[[:space:]]*$)/ { print $0 }' /etc/profile
(2)设计一个菜单shell程序,要求:
①捕获信号1、2、3、15;
②当收到信号后在终端上显示:”I Received Signal #”,其中#为收到的信号编号。
③其它仿sh03.sh。
解:实现方法很多,其中一种的代码如下(命名为sh03n.sh):
#!/bin/sh
func1( ){ echo "This is function 1"; }
func2( ){ echo "This is function 2"; }
trap "echo I received SIGNAL: 1 " 1
trap "echo I received SIGNAL: 2 " 2
trap "echo I received SIGNAL: 3 " 3
trap "echo I received SIGNAL: 15 " 15while true; doclearecho -n "
+--------------------------------------------+
| 1. func1 2. func2 0/q/Q.Exit |
+--------------------------------------------+Please get a choice: "; read xcase $x in1) func1;;2) func2;; 0|Q|q) exit $?;;*) echo -e "Invalid input: $x \a";;esacread -p "Press Enter to continue: " x
done
(3)设有shell程序内容为(命名为sh17.sh):
#!/bin/bash
pkg="tcsh"
hostnamectl | grep -i Ubuntu >/dev/null 2>&1
if [ $? -eq 0 ]; thencmd_q='dpkg -l '; cmd_i='apt install '
elsecmd_q='rpm -q '; cmd_i='yum install '
fi
$cmd_q $pkg >/dev/null 2>&1
if [ $? -eq 0 ]; then echo "Package: $pkg has been installed!"
else $cmd_i ${pkg}
fi
试写出程序的功能和执行的可能结果:
解:针对Ubuntu或红帽系统,分别判断软件包tcsh是否已经安装,若没有安装,则安装之。若已经安装则显示
Package: tcsh has been installed!
(4)在UNIX/Linux系统中,环境变量是非常重要,在字符界面下试说明:
①PATH变量的作用,如何得到PATH的值?
②在UNIX/Linux系统的超级用户的环境变量PATH中允许包含当前目录吗?
③若在超级用户的当前目录内有可执行文件mypro,如何执行它(写出执行时键盘输入内容)?
④如何使该命令在后台执行(写出执行时键盘输入内容)?
解:
①PATH为shell命令的搜索路径;可以用echo $PATH显示其值;
②默认情况下,不允许。这样可以保证命令搜索只在PATH规定的路径内进行。
③由②可知,当前路径不在PATH内,所以搜索命令时,当前路径不会被搜索。若要执行当前目录的命令只能带路径执行,方法是
# ./mypro
④在命令的结尾处,回车前使用&符号,比如
# ./mypro &
(5)设计一个shell程序计算n的阶乘。要求:
①从命令行接收参数n;
②在程序开始后立即判断n的合法性,即是否有参数,若有是否为正整数,若非法请给错误提示;
③最后输出计算的结果。
解:方法之一代码如下(命名为sh18.sh):
#!/bin/bash
# 定义用法提示函数
Usage(){ echo -e "Usage:\n$0 Integer"; }# 参数数量检查(无参数时提示用法)
if [ $# -eq 0 ]; then Usageexit 1
fi# 使用awk验证输入是否为纯数字
x=`echo $1 | awk '/^[[:digit:]]+$/ {print $1}'`# 输入合法时执行计算
if [ ! -z "$x" ]; thenf=1 # 初始化阶乘结果x=1 # 初始化循环计数器# 通过while循环累乘计算阶乘while [ $x -le $1 ]; dof=`expr $f \* $x` # 旧式算术运算x=`expr $x + 1` # 计数器递增# 注释的优化方案:f=$((f*x++))doneecho $f # 输出结果
else Usage # 输入非法时提示用法exit 2
fi
总结
掌握BShell编程需要理解正则表达式、流处理工具(sed/awk)以及Shell脚本设计规范。建议通过实际案例(如日志分析、批量重命名)巩固知识,并善用调试工具提高开发效率。