41、深入探索Shell脚本编程:基础与高级应用

深入探索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 脚本编程,提高工作效率和解决问题的能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值