news 2026/4/3 4:46:08

shell脚本编程入门(下篇)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
shell脚本编程入门(下篇)

第一章 Shell内置命令

1.1 内置命令介绍

  • Shell 内置命令,就是由Bash Shell自身提供的命令,而不是文件系统中的可执行文件。内置命令无需触发磁盘I/O,也无需创建新进程,执行速度更快,相当于调用当前Shell进程的一个函数,减少上下文切换。
  • 可使用type命令判断一个命令是否为内置命令,语法:type 命令
    [root@node1 ~]# type cdcdis a shellbuiltin# 内置命令[root@node1 ~]# type ifconfigifconfigis /usr/sbin/ifconfig# 可执行脚本文件[root@node1 ~]# type crontabcrontabis /usr/bin/crontab# 可执行脚本文件

1.2 常用内置命令列表

  • alias:为指定命令定义一个别名
  • bg:将作业以后台模式运行
  • bind:将键盘序列绑定到一个readline函数或宏
  • break:退出 for、while、select 或 until 循环
  • builtin:执行指定的shell内建命令
  • caller:返回活动子函数调用的上下文
  • cd:将当前目录切换为指定的目录
  • command:执行指定的命令,无需进行通常的shell查找
  • compgen:为指定单词生成可能的补全匹配
  • complete:显示指定的单词是如何补全的
  • compopt:修改指定单词的补全选项
  • continue:继续执行for、while、select或until循环的下一次迭代
  • declare:声明一个变量或变量类型
  • dirs:显示当前存储目录的列表
  • disown:从进程作业表中删除指定的作业
  • echo:将指定字符串输出到STDOUT
  • enable:启用或禁用指定的内建shell命令
  • eval:将指定的参数拼接成一个命令,然后执行该命令
  • exec:用指定命令替换shell进程
  • exit:强制shell以指定的退出状态码退出
  • export:设置子shell进程可用的变量
  • fc:从历史记录中选择命令列表
  • fg:将作业以前台模式运行
  • getopts:分析指定的位置参数
  • hash:查找并记住指定命令的全路径名
  • help:显示帮助文件
  • history:显示命令历史记录
  • jobs:列出活动作业
  • kill:向指定的进程ID(PID)发送一个系统信号
  • let:计算一个数学表达式中的每个参数
  • local:在函数中创建一个作用域受限的变量
  • logout:退出登录shell
  • mapfile:从STDIN读取数据行,并将其加入索引数组
  • popd:从目录栈中删除记录
  • printf:使用格式化字符串显示文本
  • pushd:向目录栈添加一个目录
  • pwd:显示当前工作目录的路径名
  • read:从STDIN读取一行数据并将其赋给一个变量
  • readarray:从STDIN读取数据行并将其放入索引数组在这里插入代码片

1.3 高频内置命令详解

1.3.1alias:设置命令别名

alias用于给命令创建别名,可将复杂命令简化,提升工作效率;不带任何参数时,会显示当前Shell进程中的所有别名列表。

[root@node1 ~]# aliasaliascp='cp -i'aliasegrep='egrep --color=auto'aliasfgrep='fgrep --color=auto'aliasgrep='grep --color=auto'aliasl.='ls -d .* --color=auto'aliasll='ls -l --color=auto'aliasls='ls --color=auto'aliasmv='mv -i'aliasrm='rm -i'aliaswhich='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

上述为系统默认别名,例如llls -l功能完全一致,就是因为设置了别名。

语法说明

  • 定义别名alias 别名="命令"(单引号、双引号均可)
  • 删除指定别名unalias 别名
  • 删除所有别名unalias -a

提示:上述删除操作均为临时生效,仅作用于当前Shell环境;若需永久删除,需手动修改对应的配置文件。

实操案例

# 定义别名psList,对应命令ps -aux[root@node1 ~]# alias psList="ps -aux"# 查看别名,确认定义成功[root@node1 ~]# alias# 执行别名,等同于执行ps -aux[root@node1 ~]# psList# 删除别名psList[root@node1 ~]# unalias psList

1.3.2 echo:输出字符串

echo是最常用的内置命令,用于在终端输出字符串,默认在末尾添加换行符,核心可实现不换行输出和转义字符解析。

语法说明

  • 默认换行输出:echo 字符串
  • 不换行输出:echo -n 字符串
  • 解析转义字符:echo -e "字符串(含转义字符)"(-e参数用于启用转义解析)

常用转义字符

  • \n:换行
  • \c:清除结尾的换行

实操案例

# 默认输出,自动换行[root@node1 ~]# echo "hello world"hello world# 不换行输出[root@node1 ~]# echo -n "hello world"hello world[root@node1 ~]## 未加-e,转义字符不生效[root@node1 ~]# echo "hello\nworld"hello\nworld# 加-e,解析换行转义字符[root@node1 ~]# echo -e "hello\nworld"hello world

1.3.3 read:读取控制台输入

read用于从标准输入(默认终端控制台)读取数据并赋值给变量,支持设置提示符、读取长度、静默模式和超时时间,也可通过重定向从文件读取数据。

基本语法read (选项) (参数)

选项说明

  • -p prompt:指定读取值时的提示符
  • -n num:读取num个字符(无需按回车确认)
  • -s:静默模式,不显示输入的字符(适合输入密码)
  • -t seconds:设置等待时间(秒),超时自动退出

参数说明
变量:用于存储读取数据的变量名

实操案例

# 案例1:10秒内读取控制台输入的名称,并输出欢迎信息[root@node1 scripts]# vim read_test.sh#!/bin/bashread-t10-p"请输入您的芳名:"nameecho"welcome,$name"# 执行脚本[root@node1 scripts]# ./read_test.sh请输入您的芳名:rapden welcome,rapden# 案例2:读取1个字符,用于确认操作[root@node1 scripts]# vim read2.sh#!/bin/bashread-n1-p"您确定要删除数据吗(请输入y/n):"charprintf"\n"# 换行,避免光标紧跟输入内容echo"您输入的字符:${char}"# 执行脚本[root@node1 scripts]# ./read2.sh您确定要删除数据吗(请输入y/n):y 您输入的字符:y

1.3.4 exit:退出Shell进程

exit用于退出当前Shell环境进程,可返回指定状态码,通过$?变量可获取该状态码,用于判断命令或脚本的执行结果。

语法说明

  • 正常退出:exit(默认返回状态码0,代表执行成功)
  • 错误退出:exit 非0数字(数字范围0~255,代表执行失败,不同数字可区分错误类型)

应用场景

  • 主动结束当前Shell进程
  • 脚本执行出错时,返回对应状态码,便于定位错误(如1代表文件不存在,2代表权限不足)

1.3.5 declare:设置变量属性

declare用于声明Shell变量、设置变量属性,也可显示Shell变量与函数(与set命令效果一致),还可创建关联数组。

语法说明declare [+/-] [aArxif] [变量名称=设置值]

  • +/-:-用于设置变量属性,+用于取消变量属性
  • -a:设置为普通索引数组
  • -A:设置为key-value关联数组(仅Bash支持)
  • -r:设置为只读变量(等同于readonly)
  • -x:设置为全局变量(等同于export)
  • -i:设置为整型变量
  • -f:设置为函数变量,declare -f查询所有函数定义,declare -F查询所有函数名称

实操案例

# 案例1:设置整型变量[root@node1 scripts]# declare -i age=20[root@node1 scripts]# echo $age20[root@node1 scripts]# age=abc # 非数值赋值,整型变量会显示为0[root@node1 scripts]# echo $age0[root@node1 scripts]# declare +i age # 取消整型属性[root@node1 scripts]# age=abc # 可正常赋值字符串[root@node1 scripts]# echo $ageabc[root@node1 scripts]# declare -r age # 设置为只读[root@node1 scripts]# age=123 # 无法修改,提示错误-bash: age:readonlyvariable# 案例2:创建关联数组(键值对数组)[root@node1 scripts]# declare -A student=([name]="rapden" [age]=20 [gender]="male")# 获取指定键的值[root@node1 scripts]# echo ${student[name]}rapden# 获取所有值[root@node1 scripts]# echo ${student[*]}rapden20male[root@node1 scripts]# echo ${student[@]}rapden20male

第2章 函数

Shell函数用于将一系列命令组合在一起,实现代码复用,简化脚本编写与维护,分为系统函数自定义函数两类。

2.1 系统函数

2.1.1 basename:提取文件名

basename用于删除路径中所有前缀(包括最后一个/),提取文件名,支持去除指定后缀。

基本语法basename [string / pathname] [suffix]

  • string / pathname:文件路径或字符串
  • suffix:可选,指定后缀,若添加则去除文件名的该后缀

实操案例

# 提取文件名称(含后缀)[root@node1 scripts]# basename /root/scripts/hello.shhello.sh# 提取文件名称(去除.sh后缀)[root@node1 scripts]# basename /root/scripts/hello.sh .shhello

2.1.2 dirname:提取文件路径

dirname用于从文件绝对路径中去除文件名,提取剩余的目录路径。

基本语法dirname 文件绝对路径

实操案例

# 提取hello.sh文件的目录路径[root@node1 scripts]# dirname /root/scripts/hello.sh/root/scripts

2.2 自定义函数

2.2.1 基本语法

[function]funname[()]{Action;# 函数执行的命令[return int;]# 可选,返回状态码(0~255)}
  • function:可选,用于声明函数,可省略
  • funname:函数名,遵循变量命名规则(字母、数字、下划线,不数字开头)
  • return:可选,用于返回状态码,若省略,默认以最后一条命令的执行结果作为返回值

2.2.2 经验技巧

  • 函数必须在调用前声明,Shell脚本逐行执行,不提前编译
  • 函数返回值只能通过$?变量获取,return后跟数值范围为0~255
  • 函数参数通过$1~$n获取(与脚本参数用法一致),$#获取参数个数

2.2.3 实操案例

需求:编写函数,计算两个输入参数的和,并输出结果及和的平方。

[root@node1 scripts]# vim func_test.sh#!/bin/bash# 声明函数add,计算两个数的和functionadd(){s=$[$1+$2]# $1获取第一个参数,$2获取第二个参数echo$s# 输出和,用于后续获取结果}# 读取用户输入的两个整数read-p"请输入第一个整数:"aread-p"请输入第二个整数:"b# 调用函数,将结果赋值给sum变量sum=$(add$a $b)# 输出结果echo"和:"$sumecho"和的平方:"$[$sum*$sum]# 执行脚本[root@node1 scripts]# ./func_test.sh请输入第一个整数:5 请输入第二个整数:8 和:13 和的平方:169

第3章 正则表达式入门

正则表达式是用于描述、匹配一系列符合特定语法规则的字符串的工具,常用于文本检索、替换。Linux中grepsedawk等文本处理工具均支持正则表达式匹配。

3.1 常规匹配

不包含特殊字符的正则表达式,直接匹配自身对应的字符串,例如匹配包含“atguigu”的行:

[atguigu@hadoop101 shells]$cat/etc/passwd|grepatguigu

3.2 常用特殊字符

3.2.1 锚点匹配

  • ^:匹配一行的开头
  • $:匹配一行的结尾
# 匹配以a开头的行[root@node1 ~]# cat /etc/passwd | grep ^aadm:x:3:4:adm:/var/adm:/sbin/nologin abrt:x:173:173::/etc/abrt:/sbin/nologin# 匹配以nologin结尾的行[root@node1 ~]# cat /etc/passwd | grep nologin$# 匹配空行(^匹配开头,$匹配结尾,中间无字符)[root@node1 ~]# cat /etc/passwd | grep ^$

3.2.2 任意字符匹配

  • .:匹配任意一个字符(除换行符外)
  • *:与前一个字符连用,匹配前一个字符0次或多次
  • .*:匹配任意字符任意次(即匹配所有内容)
# 匹配包含r+两个任意字符+t的行(如rabt、rxt、root等)[root@node1 scripts]# cat daily_archive.sh | grep r..tDEST=/root/archive/$FILE# 匹配包含ro+t的行(如rt、rot、root、rooot等)[root@node1 scripts]# cat /etc/passwd | grep ro*troot:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin

3.2.3 字符区间匹配

[]:匹配某个范围内的一个字符,支持指定单个字符、连续区间。

  • [6,8]:匹配6或8
  • [0-9]:匹配一个0~9的数字
  • [a-z]:匹配一个a~z的小写字母
  • [a-c,e-f]:匹配a~ce~f之间的任意一个字符
  • [0-9]*:匹配任意长度的数字字符串

实操案例:匹配手机号
手机号规则:以1开头,第二位为3-9,后续9位为0-9,共11位。

# 使用-E参数启用扩展正则,支持{}匹配固定次数[root@node1 scripts]# echo "15623456789" | grep -E ^1[3-9][0-9]{9}$15623456789# 匹配成功[root@node1 scripts]# echo "12623456789" | grep -E ^1[3-9][0-9]{9}$# 匹配失败,无输出

第4章 文本处理工具

4.1 cut:文本切割工具

cut用于从文件或标准输入中剪切字节、字符或字段,并输出结果,默认以制表符\t为分隔符。

4.1.1 基本用法

cut[选项参数]filename

4.1.2 选项参数说明

选项参数功能
-f列号,提取指定列(多个列用逗号分隔,连续列用短横线连接)
-d指定分隔符(默认是制表符\t)
-c按字符切割,后续跟数字表示提取第几个字符

4.1.3 实操案例

# 1. 准备测试数据[root@node1 scripts]# vim cut.txtdong shen guan zhen wo wo lai lai le le# 2. 以空格为分隔符,提取第一列[root@node1 scripts]# cut -d " " -f 1 cut.txtdong guan wo lai le# 3. 以空格为分隔符,提取第二、三列[root@node1 scripts]# cut -d " " -f 2,3 cut.txtshen zhen wo lai le# 4. 提取PATH变量中,第3个":"开始后的所有路径[root@node1 scripts]# echo $PATH/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/atguigu/.local/bin:/home/atguigu/bin[root@node1 scripts]# echo $PATH | cut -d ":" -f 3-/usr/local/sbin:/usr/sbin:/home/atguigu/.local/bin:/home/atguigu/bin# 5. 提取IP地址(以ens33网卡为例)[root@node1 scripts]# ifconfig ens33 | grep netmaskinet192.168.200.101 netmask255.255.255.0 broadcast192.168.200.255[root@node1 scripts]# ifconfig ens33 | grep netmask | cut -d " " -f 10192.168.200.101

4.2 awk:文本分析工具

awk是强大的文本分析工具,逐行读取文件内容,以空格为默认分隔符将每行切片,再对切片后的内容进行分析处理,支持条件匹配、数值计算、内置变量等功能。

4.2.1 基本用法

awk[选项参数]'/pattern/{action1} /pattern2/{action2}...'filename
  • pattern:匹配模式,awk会查找符合该模式的行
  • action:匹配成功后执行的命令(如打印、计算)

4.2.2 选项参数说明

选项参数功能
-F指定输入文件的分隔符
-v赋值一个用户定义变量

4.2.3 实操案例

# 1. 准备测试数据(复制系统passwd文件)[root@node1 scripts]# cp /etc/passwd ./# passwd文件格式:用户名:密码:用户id:组id:注释:家目录:shell解析器# 2. 匹配以root开头的行,输出第7列(shell解析器)[root@node1 scripts]# awk -F : '/^root/{print $7}' passwd/bin/bash# 3. 匹配以root开头的行,输出第1列和第7列,用逗号分隔[root@node1 scripts]# awk -F : '/^root/{print $1","$7}' passwdroot,/bin/bash# 4. 输出所有行的第1列和第7列,添加列名和结尾标识[root@node1 scripts]# awk -F : 'BEGIN{print "user shell"}{print $1","$7} END{print "end of file"}' passwduser shell root,/bin/bash bin,/sbin/nologin daemon,/sbin/nologin... end offile# 5. 将用户id(第3列)加1后输出[root@node1 scripts]# awk -v i=1 -F : '{print $3+i}' passwd123...# 6. 统计文件名、每行的行号和列数[root@node1 scripts]# awk -F : '{print "filename:" FILENAME ",linenum:" NR ",col:" NF }' passwdfilename:passwd,linenum:1,col:7 filename:passwd,linenum:2,col:7...# 7. 查找ifconfig输出中的空行行号[root@node1 scripts]# ifconfig | awk '/^$/{print NR}'91827# 8. 提取所有IP地址[root@node1 scripts]# ifconfig | awk '/netmask/{print $2}'172.17.0.1192.168.200.101127.0.0.1

4.2.4 awk内置变量

变量说明
FILENAME当前处理的文件名
NR已读取的记录数(行号)
NF当前行切割后的列数

第5章 综合应用案例

5.1 归档文件(每日自动备份)

需求说明
实现每天对指定目录归档备份,输入一个目录名称(末尾不带/),将目录下所有文件按天归档压缩,归档文件名附加日期,保存到/root/archive目录下。

核心工具:tar(归档压缩),-c归档,-z压缩,生成.tar.gz格式文件。

脚本实现

[root@node1 scripts]# vim daily_archive.sh#!/bin/bash# 功能:每日归档指定目录,保存到/root/archive# 步骤1:判断输入参数个数是否为1if[$#-ne1]thenecho"参数个数错误!应该输入一个参数,作为归档目录名"exit1# 错误退出,状态码1代表参数错误fi# 步骤2:判断输入的目录是否存在if[-d$1]thenecho"目录存在,开始准备归档..."elseecho"目录不存在!"exit2# 错误退出,状态码2代表目录不存在fi# 步骤3:获取目录名称和绝对路径DIR_NAME=$(basename$1)# 提取目录名DIR_PATH=$(cd$(dirname $1);pwd)# 获取目录绝对路径# 步骤4:获取当前日期(格式:年月日,如251022)DATE=$(date+%y%m%d)# 步骤5:定义归档文件名和目标路径FILE=archive_${DIR_NAME}_${DATE}.tar.gzDEST=/root/archive# 若目标目录不存在,创建目录if[!-d$DEST]thenmkdir-p$DESTfi# 步骤6:执行归档压缩echo"开始归档...."tar-czf$DEST/$FILE$DIR_PATH/$DIR_NAME# 步骤7:判断归档是否成功if[$?-eq0]thenecho"归档成功"echo"归档文件名为:$DEST/$FILE"elseecho"归档出现问题!"exit3# 错误退出,状态码3代表归档失败fiexit0# 正常退出

脚本测试与定时任务

# 执行脚本,测试归档(归档../scripts目录)[root@node1 scripts]# ./daily_archive.sh ../scripts目录存在,开始准备归档... 开始归档....tar: Removing leading `/' from member names 归档成功 归档文件名为:/root/archive/archive_scripts_251022.tar.gz# 设置定时任务,每天凌晨2点自动归档/root/scripts目录[root@node1 scripts]# crontab -e# 添加如下内容(0 2 * * * 表示每天凌晨2点执行)02* * * /root/scripts/daily_archive.sh /root/scripts# 查看定时任务[root@node1 scripts]# crontab -l02* * * /root/scripts/daily_archive.sh /root/scripts

5.2 发送消息(快速给指定用户发消息)

需求说明
实现向指定用户快速发送消息,输入用户名作为第一个参数,后续为要发送的消息。需检测:用户是否登录、用户是否开启消息功能、消息是否为空。

核心工具:mesg(查看/开启消息功能)、write(发送消息)。

脚本实现

[root@node1 scripts]# vim send_msg.sh#!/bin/bash# 功能:向指定用户发送消息,需检测用户状态和消息内容# 步骤1:判断参数个数是否至少为2(用户名+消息)if[$#-lt2]thenecho"参数错误!用法:$0用户名 消息内容"exit1fi# 步骤2:提取用户名和消息内容USER=$1shift# 移除第一个参数(用户名),剩余参数作为消息MSG=$*# 步骤3:检测用户是否登录# 查看当前登录用户,过滤目标用户(who | grep -w 精确匹配用户名)if!who|grep-w$USER>/dev/nullthenecho"错误:用户$USER未登录系统!"exit2fi# 步骤4:检测用户是否开启消息功能(mesg查看,允许消息输出为y)# 切换到目标用户执行mesg,捕获输出结果MESG_STATUS=$(su-$USER-c"mesg"2>/dev/null|awk'{print $1}')if["$MESG_STATUS"!="y"]thenecho"错误:用户$USER未开启消息接收功能!"exit3fi# 步骤5:检测消息是否为空if[-z"$MSG"]thenecho"错误:消息内容不能为空!"exit4fi# 步骤6:发送消息(write 用户名 终端,消息通过echo传入)# 获取用户登录的终端(who | grep -w $USER | awk '{print $2}',取第一个终端)TERMINAL=$(who|grep-w$USER|awk'{print $2}'|head-n1)echo"$MSG"|write$USER$TERMINAL# 步骤7:判断消息是否发送成功if[$?-eq0]thenecho"消息发送成功!"elseecho"消息发送失败!"exit5fiexit0

脚本测试

# 测试1:向登录用户rapden发送消息[root@node1 scripts]# ./send_msg.sh rapden 你好,这是测试消息!消息发送成功!# 测试2:用户未登录(错误场景)[root@node1 scripts]# ./send_msg.sh test 测试消息错误:用户test未登录系统!# 测试3:用户未开启消息功能(错误场景)[root@node1 scripts]# ./send_msg.sh rapden 测试消息错误:用户rapden未开启消息接收功能!# 解决方法:切换到rapden用户,执行mesg y开启消息功能[rapden@node1 ~]$ mesg y
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/16 14:19:18

十年前的微信消息收发架构长啥样?

2023 年,微信及 WeChat 的 DAU(月活用户)达到 13.4 亿,微信已经是很多人工作、生活中不可或缺的一个环节。从 2011 年 1 月 21 日上线至今,微信已经走过了 13 个年头,其背后的技术基座与架构也发生了巨大的变化。 这些变化背后,所折射的也正是中国互联网高速发展的黄金…

作者头像 李华
网站建设 2026/3/27 1:28:09

长尾关键词优化在SEO提升策略中的关键作用解析

长尾关键词优化是现代SEO策略中不可或缺的部分。长尾关键词通常由三个以上词语组成,搜索量较低,但更具针对性,能有效吸引目标用户。在这段内容中,将概述长尾关键词的特点及其在提升SEO效果中的关键作用。通过深入分析这些关键词的…

作者头像 李华
网站建设 2026/3/28 9:59:55

简单理解:CAN 收发器 TJA1050 如何将来自微控制器的单端 TTL/CMOS 逻辑信号转换为 CAN 总线所需的差分信号。

TJA1050内部原理图,我想知道他是怎么把单端ttl转换为双端can差分?TJA1050 是一款独立的 CAN 收发器,它的核心功能之一正是将来自微控制器的单端 TTL/CMOS 逻辑信号转换为 CAN 总线所需的差分信号。下面结合其内部结构,解释这个转换…

作者头像 李华
网站建设 2026/4/3 3:13:18

数据工程不求人:用 Python 打通“采集–清洗–入湖–可视化”的一条龙流水线

摘要:在数据驱动的时代,很多开发者和分析师依然陷在 Excel 的泥潭中,手动处理 CSV,复制粘贴,效率低下且极易出错。本文将带你通过 Python 构建一套现代化的数据处理流水线。我们将不仅仅是写脚本,而是引入企业级数据工程的思维:从 AsyncIO 高并发采集,到 Polars 的光速…

作者头像 李华
网站建设 2026/4/3 2:30:04

淘客返利系统的CI/CD流水线搭建:Docker镜像构建与K8s部署实践

淘客返利系统的CI/CD流水线搭建:Docker镜像构建与K8s部署实践 大家好,我是 微赚淘客系统3.0 的研发者省赚客! 在微赚淘客系统3.0的演进过程中,为提升交付效率与系统稳定性,我们基于 GitLab CI Harbor Kubernetes 构建…

作者头像 李华
网站建设 2026/4/1 15:46:36

2026年量子开发者的心理健康自查清单:软件测试从业者的专业指南

随着量子计算技术的商业化加速,2026年软件测试从业者面临前所未有的认知负荷与职业压力。量子系统的高复杂性、非确定性行为测试需求,叠加传统敏捷交付压力,使测试工程师的心理健康风险显著攀升。本清单基于行业前沿研究,提供一套…

作者头像 李华