深入探索Shell脚本编程:基础与高级应用
1. 基础Shell脚本编程
1.1 读取用户输入
在Shell脚本中,
read
命令可用于读取用户在键盘上输入的内容,并将其存储到指定的变量中。例如:
#!/bin/sh
read value
当脚本运行到
read value
时,
bash
会等待用户输入,并将输入内容存储在
value
变量中。
需要注意的是,
echo
命令的
-n
选项可防止在显示的字符串末尾自动添加换行符。若不使用该选项,
Enter a value
会显示,且值会在后续行中读取,这仅影响显示效果,不影响操作本身。
1.2 调用Shell函数
可以将常用的Shell命令组合成一个函数,并为其命名。之后,通过函数名即可执行这组命令。以下是一个简单的示例,展示了Shell函数的语法:
#!/bin/sh
hello() {
echo -n "Hello, "
echo $1 $2
}
hello Jane Doe
运行此脚本,输出结果为:
Hello, Jane Doe
该脚本定义了一个名为
hello
的Shell函数,该函数期望接收两个参数。在函数体中,这些参数通过
$1
和
$2
引用。函数定义以函数名后跟括号开始,函数体用花括号
{ ... }
括起来。最后一行展示了如何带参数调用Shell函数。
1.3 控制脚本执行流程
在
bash
脚本中,可以使用
if
、
case
、
for
和
while
等特殊命令来控制命令的执行顺序。这些控制语句根据命令的退出状态来决定下一步操作。任何命令执行后都会返回一个退出状态码,通常,退出状态码为零表示命令执行成功,非零则表示命令执行出错。
1.3.1 if-then-else 结构
假设要在使用
vi
编辑器编辑文件之前创建该文件的备份,并且若无法创建备份则避免编辑文件。以下是实现此任务的
bash
脚本:
#!/bin/sh
if cp "$1" "#$1"
then
vi "$1"
else
echo "Operation failed to create a backup copy"
fi
此脚本展示了
if-then-else
结构的语法,
if
命令根据
cp
命令的退出状态来决定下一步操作。若
cp
返回零,脚本使用
vi
编辑文件;否则,显示错误信息并退出。注意,
fi
用于终止
if
命令,忘记使用
fi
是
bash
脚本中常见的错误来源。
1.3.2 test 命令
可以使用
test
命令评估任何表达式,并将表达式的值作为命令的退出状态。例如,若要编写一个仅在文件已存在时才编辑该文件的脚本,可使用
test
命令:
#!/bin/sh
if test -f "$1"
then
vi "$1"
else
echo "No such file exists"
fi
test
命令的简写形式是将表达式放在方括号
[ ... ]
中,上述脚本可改写为:
#!/bin/sh
if [ -f "$1" ]
then
vi "$1"
else
echo "No such file exists"
fi
需要注意的是,方括号两边必须有空格,因为左括号
[
是
test
的别名。
1.3.3 for 循环
for
循环是另一种常见的控制结构。以下脚本用于计算 1 到 10 的整数之和:
#!/bin/sh
sum=0
for i in 1 2 3 4 5 6 7 8 9 10
do
sum=`expr $sum + $i`
done
echo "Sum = $sum"
此示例还展示了如何使用
expr
命令评估表达式。
expr
要求操作数(如加号
+
)两边有空格,否则无法正常工作。
1.3.4 case 语句
case
语句用于根据变量的值执行一组命令。以下是一个示例脚本:
#!/bin/sh
echo -n "What should I do -- (Y)es/(N)o/(C)ontinue? [Y] "
read answer
case $answer in
y|Y|"")
echo "YES"
;;
c|C)
echo "CONTINUE"
;;
n|N)
echo "NO"
;;
*)
echo "UNKNOWN"
;;
esac
将此代码保存为
confirm
文件,并使用
chmod +x confirm
使其可执行。运行
./confirm
后,根据提示输入
y
、
n
或
c
并按
Enter
,脚本将分别显示
YES
、
NO
或
CONTINUE
。
case
命令的一般语法如下:
case $variable in
value1 | value2)
command1
command2
... other commands ...
;;
value3)
command3
command4
... other commands ...
;;
esac
case
命令以
case
开头,以
esac
结尾。不同的代码块用变量的值分隔,后跟右括号,并以双分号
;;
结束。
1.4 bash 的内置命令
bash
有 50 多个内置命令,包括常见的
cd
和
pwd
等,这些命令可在任何
bash
脚本或 shell 提示符下使用。以下是部分
bash
内置命令及其功能的总结:
| 命令 | 功能 |
| — | — |
|
. filename [arguments]
| 使用可选参数从指定文件中读取并执行命令(与
source
命令作用相同) |
|
: [arguments]
| 展开参数但不处理 |
|
[ expr ]
| 评估表达式
expr
,若为真则返回零状态 |
|
alias [name[=value] ... ]
| 允许一个值等于另一个值 |
|
bg [job]
| 将指定作业置于后台,若未指定作业,则将当前执行的命令置于后台 |
|
break [n]
| 退出
for
、
while
或
until
循环,若指定
n
,则退出第
n
层嵌套循环 |
|
cd [dir]
| 将当前目录更改为
dir
|
|
command [-pVv] cmd [arg ... ]
| 运行指定命令
cmd
及其参数,忽略同名的 shell 函数 |
|
continue [n]
| 开始
for
、
while
或
until
循环的下一次迭代,若指定
n
,则开始第
n
层嵌套循环的下一次迭代 |
|
declare [-frxi] [name[=value]]
| 声明指定名称的变量,并可选择为其赋值 |
|
dirs [-l] [+/-n]
| 显示当前记住的目录列表 |
|
echo [-neE] [arg ... ]
| 在标准输出上显示参数 |
|
enable [-n] [-all]
| 启用或禁用指定的内置命令 |
|
eval [arg ... ]
| 连接参数并将其作为命令执行 |
|
exec [command [arguments]]
| 用运行指定命令及其参数的新进程替换当前 shell 实例 |
|
exit [n]
| 以状态码
n
退出 shell |
|
export [-nf] [name[=word]] ...
| 定义指定的环境变量并将其导出到后续进程 |
|
fc -s [pat=rep] [cmd]
| 替换模式
pat
为
rep
后重新执行命令 |
|
fg [jobspec]
| 将指定作业置于前台,若未指定作业,则将最近的作业置于前台 |
|
hash [-r] [name]
| 记住指定命令的完整路径名 |
|
help [cmd ... ]
| 显示指定内置命令的帮助信息 |
|
history [n]
| 显示过去的命令,若指定
n
,则显示过去
n
条命令 |
|
jobs [-lnp] [ jobspec ... ]
| 列出当前活动的作业 |
|
kill [-s sigspec | -sigspec] [pid | jobspec] ...
| 终止指定的进程 |
|
let arg [arg ... ]
| 评估每个参数,若最后一个参数为 0 则返回 1 |
|
local [name[=value] ... ]
| 创建具有指定名称和值的局部变量(用于 shell 函数) |
|
logout
| 退出登录 shell |
|
popd [+/-n]
| 从目录栈中移除指定数量的条目 |
|
pushd [dir]
| 将指定目录
dir
添加到目录栈顶部 |
|
pwd
| 打印当前工作目录的完整路径名 |
若想了解某个特定内置命令的更多信息,可使用
help
命令,例如:
help test
2. 高级Shell脚本编程:使用sed命令
2.1 sed 命令简介
sed
是一种流编辑器,与标准(交互式)编辑器不同,它逐行处理文件,并根据给定的规则进行更改。以下是一个冒号分隔的员工数据库示例,包含唯一 ID 号、姓名、部门、电话号码和地址五个字段:
1218:Kris Cottrell:Marketing:219.555.5555:123 Main Street
1219:Nate Eichhorn:Sales:219.555.5555:1219 Locust Avenue
1220:Joe Gunn:Payables:317.555.5555:21974 Unix Way
1221:Anne Heltzel:Finance:219.555.5555:652 Linux Road
1222:John Kuzmic:Human Resources:219.555.5555:984 Bash Lane
假设该数据库自公司成立以来就存在,包含大量员工信息,且有多个专有脚本依赖此数据库。现在,某地区的电话公司将电话号码前缀
219
改为
260
,因此需要更改数据库中所有使用该前缀的条目。
2.2 使用 sed 命令进行替换
2.2.1 简单替换
若使用命令
sed 's/219/260/'
,结果如下:
1218:Kris Cottrell:Marketing:260.555.5555:123 Main Street
1260:Nate Eichhorn:Sales:219.555.5555:1219 Locust Avenue
1220:Joe Gunn:Payables:317.555.5555:26074 Unix Way
1221:Anne Heltzel:Finance:260.555.5555:652 Linux Road
1222:John Kuzmic:Human Resources:260.555.5555:984 Bash Lane
虽然第一、四、五行的更改是正确的,但第二行中员工 ID 号里的
219
也被更改了,第三行中地址里的
219
也被更改了,这并非我们想要的结果。
2.2.2 全局替换
若使用
sed 's/219/260/g'
,会更改每行中所有的
219
,这会导致员工 ID 号也被更改,不符合需求。
2.2.3 精确匹配替换
为了更精确地定位要替换的
219
,可以指定其必须出现在字段开头(由冒号表示),使用命令
sed 's/:219/:260/'
:
1218:Kris Cottrell:Marketing:260.555.5555:123 Main Street
1219:Nate Eichhorn:Sales:260.555.5555:1219 Locust Avenue
1220:Joe Gunn:Payables:317.555.5555:26074 Unix Way
1221:Anne Heltzel:Finance:260.555.5555:652 Linux Road
1222:John Kuzmic:Human Resources:260.555.5555:984 Bash Lane
虽然准确性有所提高,但第三行仍存在问题。
2.2.4 处理特殊字符
若尝试使用
sed 's/:219./:260./'
,结果如下:
1218:Kris Cottrell:Marketing:260.555.5555:123 Main Street
1219:Nate Eichhorn:Sales:260.555.5555:1219 Locust Avenue
1220:Joe Gunn:Payables:317.555.5555:260.4 Unix Way
1221:Anne Heltzel:Finance:260.555.5555:652 Linux Road
1222:John Kuzmic:Human Resources:260.555.5555:984 Bash Lane
这里的问题是,句点
.
在正则表达式中有特殊含义,表示任意字符,因此
219
后面的任意字符都会被替换为句点。为了指定要匹配的是句点本身,可以使用反斜杠
\
转义,即
sed 's/:219\./:260./'
,结果如下:
1218:Kris Cottrell:Marketing:260.555.5555:123 Main Street
1219:Nate Eichhorn:Sales:260.555.5555:1219 Locust Avenue
1220:Joe Gunn:Payables:317.555.5555:21974 Unix Way
1221:Anne Heltzel:Finance:260.555.5555:652 Linux Road
1222:John Kuzmic:Human Resources:260.555.5555:984 Bash Lane
至此,成功完成了替换任务。
2.3 使用 sed 命令的流程图
graph TD;
A[开始] --> B[读取文件内容];
B --> C[应用替换规则];
C --> D{是否还有未处理的行};
D -- 是 --> B;
D -- 否 --> E[输出处理后的内容];
E --> F[结束];
通过以上示例可以看出,使用
sed
命令时,关键在于准确识别要查找的字符串的唯一位置,以避免不必要的替换。同时,要注意特殊字符的处理,确保替换的准确性。
2.4 sed 命令替换操作总结
在使用
sed
命令进行替换操作时,我们可以总结出以下步骤和要点:
1.
明确替换需求
:确定要替换的字符串以及替换后的目标字符串,同时要考虑数据的上下文和可能出现的特殊情况。
2.
初步尝试简单替换
:使用基本的替换命令
sed 's/old/new/'
查看初步效果,观察是否存在误替换的情况。
3.
考虑全局替换
:如果需要替换每行中的所有匹配项,可以使用
sed 's/old/new/g'
,但要注意是否会影响到不需要替换的部分。
4.
精确匹配定位
:通过指定字符串的位置信息,如在字段开头或结尾等,使用更精确的匹配规则,如
sed 's/:old/:new/'
来提高替换的准确性。
5.
处理特殊字符
:当遇到具有特殊含义的字符时,使用反斜杠
\
进行转义,确保匹配的是字符本身而非其特殊含义。
2.5 sed 命令常见应用场景
除了上述电话号码前缀替换的例子,
sed
命令还有许多其他常见的应用场景:
| 应用场景 | 示例命令 | 说明 |
| — | — | — |
| 删除空行 |
sed '/^$/d' file.txt
| 删除文件
file.txt
中的所有空行 |
| 替换文件中的特定单词 |
sed 's/word/replacement/g' file.txt
| 将文件
file.txt
中所有的
word
替换为
replacement
|
| 在文件的每一行开头添加内容 |
sed 's/^/prefix /' file.txt
| 在文件
file.txt
的每一行开头添加
prefix
|
| 删除文件中的注释行 |
sed '/^#/d' file.txt
| 删除文件
file.txt
中所有以
#
开头的注释行 |
3. 高级 Shell 脚本编程:结合 awk 命令
3.1 awk 命令简介
awk
是一种强大的文本处理工具,它可以对文本进行格式化、统计、筛选等操作。
awk
以行为单位处理文本,将每行分割成多个字段,然后可以对这些字段进行各种操作。
3.2 awk 命令基本语法
awk
命令的基本语法如下:
awk 'pattern { action }' file.txt
-
pattern:用于匹配行的条件,可以是正则表达式或其他条件。 -
action:在匹配到的行上执行的操作,通常是打印或计算。 -
file.txt:要处理的文件。
3.3 awk 命令示例
假设我们有一个包含学生成绩的文件
grades.txt
,内容如下:
John 85
Mary 92
Tom 78
3.3.1 打印特定字段
要打印文件中每行的第一个字段(学生姓名),可以使用以下命令:
awk '{ print $1 }' grades.txt
输出结果为:
John
Mary
Tom
3.3.2 计算总和与平均值
要计算所有学生的成绩总和与平均值,可以使用以下命令:
awk '{ sum += $2 } END { print "Sum: " sum; print "Average: " sum / NR }' grades.txt
输出结果为:
Sum: 255
Average: 85
在这个命令中,
sum
是一个变量,用于累加每个学生的成绩。
END
块在处理完所有行后执行,
NR
表示处理的行数。
3.4 awk 命令结合 sed 命令
awk
和
sed
命令可以结合使用,以实现更复杂的文本处理任务。例如,我们可以先使用
sed
命令对文件进行初步处理,然后再使用
awk
命令进行进一步的统计和分析。
假设我们有一个包含员工信息的文件
employees.txt
,内容如下:
1218:Kris Cottrell:Marketing:219.555.5555:123 Main Street
1219:Nate Eichhorn:Sales:219.555.5555:1219 Locust Avenue
1220:Joe Gunn:Payables:317.555.5555:21974 Unix Way
1221:Anne Heltzel:Finance:219.555.5555:652 Linux Road
1222:John Kuzmic:Human Resources:219.555.5555:984 Bash Lane
我们可以先使用
sed
命令将电话号码前缀
219
替换为
260
,然后再使用
awk
命令统计每个部门的员工数量:
sed 's/:219\./:260./' employees.txt | awk -F: '{ dept[$3]++ } END { for (d in dept) print d ": " dept[d] }'
-
sed 's/:219\./:260./' employees.txt:将文件中电话号码前缀219替换为260。 -
awk -F: '{ dept[$3]++ } END { for (d in dept) print d ": " dept[d] }':以冒号为分隔符,统计每个部门的员工数量并输出。
3.5 awk 命令与 sed 命令结合使用的流程图
graph TD;
A[开始] --> B[使用 sed 命令处理文件];
B --> C[将 sed 处理结果传递给 awk 命令];
C --> D[awk 命令进行统计分析];
D --> E[输出统计结果];
E --> F[结束];
4. 总结与建议
4.1 总结
通过本文的介绍,我们了解了基础和高级 Shell 脚本编程的相关知识。基础部分包括读取用户输入、调用 Shell 函数、控制脚本执行流程以及使用
bash
的内置命令。高级部分则重点介绍了
sed
和
awk
这两个强大的文本处理工具,以及它们在实际场景中的应用。
4.2 建议
- 多实践 :Shell 脚本编程是一门实践性很强的技术,只有通过不断地实践才能熟练掌握各种命令和技巧。可以通过编写一些小脚本解决实际问题来提高自己的编程能力。
-
深入学习命令细节
:对于
sed、awk等命令,要深入学习其各种选项和用法,了解它们的高级特性,以便在实际应用中能够灵活运用。 -
结合使用工具
:
sed和awk可以与其他命令结合使用,发挥更强大的功能。在实际工作中,要根据具体需求选择合适的工具组合。 - 参考文档和示例 :当遇到问题时,及时查阅相关的文档和示例代码,学习他人的经验和技巧,不断积累知识。
希望本文能够帮助你更好地掌握 Shell 脚本编程,提高工作效率和解决问题的能力。
超级会员免费看
327

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



