Shell编程实例:手把手教你写第一个脚本
一、Shell概述——理解Linux的“翻译官”
Shell是用户与Linux内核之间的桥梁。当你输入一条命令,Shell负责解释它,并调用操作系统内核执行。你可以把它想象成一位翻译官,把人类的指令“翻译”成内核能理解的机器语言。
1. Linux支持哪些Shell?
不同的Shell解析器有各自的特点,最常用的是Bash( Again Shell)。查看系统支持哪些Shell:
cat /etc/shells
输出示例:
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/sh
/bin/dash
/usr/bin/dash
Bash 是大多数Linux发行版的默认Shell,也是我们学习的重点。通过以下命令确认当前使用的Shell:
echo $SHELL
# 输出:/bin/bash
2. Shell的双重身份二、第一个Shell脚本——从Hello World开始1. 脚本格式
每个Shell脚本的第一行必须是 #!/bin/bash,这称为,指定脚本由哪个解释器执行。
2. 编写第一个脚本
创建文件 .sh:
vim helloworld.sh
输入以下内容:
#!/bin/bash
echo "Hello shell!"
保存退出(:wq)。
3. 脚本的两种执行方式方式一:用bash或sh执行(脚本不需要执行权限)方式二:直接执行(脚本必须具有可执行权限)
先添加执行权限:
chmod +x helloworld.sh
然后执行:
./helloworld.sh # 相对路径
/home/atguigu/helloworld.sh # 绝对路径
原理:第一种方法是bash解释器帮我们执行脚本,所以脚本本身不需要执行权限;第二种方法是脚本自己作为可执行文件运行,因此必须要有执行权限。
三、变量——脚本的血液1. 系统预定义变量
系统已经定义好的变量,可以直接使用:
使用示例:
echo $PATH # 查看路径
echo $HOME # 查看家目录
查看当前Shell中所有变量:set 命令。
2. 自定义变量语法规则变量命名规则名称由字母、数字、下划线组成,不能以数字开头。环境变量名建议大写。等号两侧不能有空格。变量默认类型是字符串,无法直接进行数值运算。若值包含空格,必须用引号括起来(单引号或双引号)。最右侧分号可有可无,一般都不写。案例演示
# 定义变量A,值为5
A=5
echo $A # 输出:5
# 重新赋值
A=atguigu
echo $A # 输出:atguigu
# 撤销变量A
unset A
echo $A # 输出为空
# 定义只读变量B
readonly B=2
echo $B # 输出:2
B=9 # 报错:bash: B: 只读变量
unset B # 报错:bash: unset: B: 无法取消设定:只读 variable
# 变量默认是字符串
C=1+2
echo $C # 输出:1+2(不是3)
# 值包含空格时,必须用引号
D=I love banzhang # 错误!会报“找不到命令love”
D="I love banzhang"
echo $D # 输出:I love banzhang
环境变量(全局变量)
使用 可以将自定义变量提升为全局环境变量,使其在子进程中生效。
# 定义一个普通变量
B=3
# 编写脚本helloworld.sh,内容如下:
#!/bin/bash
echo "helloworld"
echo $B
# 执行脚本(未export)
./helloworld.sh
# 输出:helloworld(没有打印B,因为B不是环境变量)
# 导出B为环境变量
export B
./helloworld.sh
# 输出:
# helloworld
# 3
注意:环境变量只在当前进程及其子进程中有效,不同终端窗口之间不共享。
3. 特殊变量
变量
描述
$n
$0表示脚本名称,$1~$9表示第1~9个参数,10以上用$案例:参数变量
创建 .sh:
#!/bin/bash
echo '=========$n========='
echo $0
echo $1
echo $2
echo '=========$#========='
echo $#
echo '=========$*========='
echo $*
echo '=========$@========='
echo $@
执行并观察输出:
./parameter.sh a b c d e f g
输出:
=========$n=========
./parameter.sh
a
b
=========$#=========
7
=========$*=========
a b c d e f g
=========$@=========
a b c d e f g
案例:$?的使用
# 正确命令
./helloworld.sh
echo $? # 输出:0
# 错误命令
xxx
echo $? # 输出:127(命令未找到)
四、算术运算符——让脚本会计算
Shell中无法直接进行算术运算,因为变量默认是字符串。必须借助特殊语法。
语法案例
# 计算 (2+3)*4
S=$[(2+3)*4]
echo $S # 输出:20
# 另一种写法
S=$(((2+3)*4))
echo $S # 输出:20
五、条件判断——让脚本有决策能力1. 语法格式
返回值:条件成立(数据非空)返回0(真),否则返回1(假)。
test
echo $? # 输出:1(空字符串,假)
[ ]
echo $? # 输出:1
test abc
echo $? # 输出:0
[ abc ]
echo $? # 输出:0
2. 常用判断条件整数比较(不能直接用 > < 等符号)
$#
获取所有输入参数的个数
$*
所有参数,作为一个整体
$@
所有参数,每个参数独立
上一条命令的返回状态,0表示成功,非0表示失败
案例:参数变量
创建 .sh:
#!/bin/bash
echo '=========$n========='
echo $0
echo $1
echo $2
echo '=========$#========='
echo $#
echo '=========$*========='
echo $*
echo '=========$@========='
echo $@
执行并观察输出:
./parameter.sh a b c d e f g
输出:
=========$n=========
./parameter.sh
a
b
=========$#=========
7
=========$*=========
a b c d e f g
=========$@=========
a b c d e f g
案例:$?的使用
# 正确命令
./helloworld.sh
echo $? # 输出:0
# 错误命令
xxx
echo $? # 输出:127(命令未找到)
四、算术运算符——让脚本会计算
Shell中无法直接进行算术运算,因为变量默认是字符串。必须借助特殊语法。
语法案例
# 计算 (2+3)*4
S=$[(2+3)*4]
echo $S # 输出:20
# 另一种写法
S=$(((2+3)*4))
echo $S # 输出:20
五、条件判断——让脚本有决策能力1. 语法格式
返回值:条件成立(数据非空)返回0(真),否则返回1(假)。
test
echo $? # 输出:1(空字符串,假)
[ ]
echo $? # 输出:1
test abc
echo $? # 输出:0
[ abc ]
echo $? # 输出:0
2. 常用判断条件整数比较(不能直接用 > < 等符号)文件权限判断文件类型判断3. 案例
# 整数比较
[ 22 -lt 23 ]
echo $? # 0
# 文件权限
[ -w helloworld.sh ]
echo $? # 0
# 文件存在
[ -e helloworld.sh ]
echo $? # 0
# 多条件判断(&& 前一个命令成功才执行后一个,|| 前一个失败才执行后一个)
[ atguigu ] && echo OK || echo notOK # 输出:OK
[ ] && echo OK || echo notOK # 输出:notOK
六、流程控制——让脚本智能化1. if判断单分支
if [ 条件判断式 ]; then
程序
fi
或(换行写):
if [ 条件判断式 ]
then
程序
fi
多分支
if [ 条件判断式 ]; then
程序
elif [ 条件判断式 ]; then
程序
else
程序
fi
注意:
案例:年龄判断
#!/bin/bash
if [ $# -eq 0 ]; then
echo "请携带年龄"
elif [ $1 -lt 18 ]; then
echo "未成年人"
elif [ $1 -lt 60 ]; then
echo "成年人"
else
echo "老年人"
fi
执行测试:
chmod +x if.sh
./if.sh 12 # 输出:未成年人
./if.sh 34 # 输出:成年人
./if.sh 66 # 输出:老年人
./if.sh # 输出:请携带年龄
2. case语句
用于多分支选择,类似C语言的。
语法
case $变量名 in
"值1")
程序1
;;
"值2")
程序2
;;
*)
默认程序
;;
esac
注意:
案例:数字对应角色
#!/bin/bash
case $1 in
"1")
echo "banzhang"
;;
2)
echo "cls"
;;
*)
echo "renyao"
;;
esac
执行:
./case.sh 1 # banzhang
./case.sh 2 # cls
./case.sh 3 # renyao
3. for循环语法1:C语言风格
for ((初始值;循环控制条件;变量变化))
do
程序
done
案例:1加到100
#!/bin/bash
sum=0
for((i=1;i<=100;i++))
do
sum=$[sum+$i]
done
echo $sum # 输出:5050
语法2:遍历列表
for 变量 in 值1 值2 值3...
do
程序
done
案例:打印列表
#!/bin/bash
for i in cls mly wls
do
echo "ban zhang love $i"
done
输出:
ban zhang love cls
ban zhang love mly
ban zhang love wls
深入:$* 与 $@ 的区别
案例对比:
#!/bin/bash
echo '=========$*========='
for i in "$*"
do
echo "ban zhang love $i"
done
echo '=========$@========='
for j in "$@"
do
echo "ban zhang love $j"
done
执行:
./for4.sh cls mly wls
输出:
=========$*=========
ban zhang love cls mly wls
=========$@=========
ban zhang love cls
ban zhang love mly
ban zhang love wls
4. while循环语法
while [ 条件判断式 ]
do
程序
done
案例:1加到100
#!/bin/bash
sum=0
i=1
while [ $i -le 100 ]
do
sum=$[sum+$i]
i=$[i+1]
done
echo $sum # 输出:5050
七、read命令——实现交互式输入1. 作用
从终端读取用户输入,存入变量。
2. 语法
read
变量名
常用选项:
3. 案例
#!/bin/bash
read -t 7 -p "Enter your name in 7 seconds: " NAME
echo $NAME
执行后,会等待用户输入最多7秒,输入的内容存入NAME变量,并打印出来。
八、函数——模块化编程1. 自定义函数语法
[ function ] funname()
{
Action;
[return int;]
}
注意事项:
必须在调用函数之前先声明函数(Shell脚本是逐行执行的)。函数返回值通过 $? 获取,可以显式使用 返回数值(0-255),如果不加,则以最后一条命令的执行结果作为返回值。函数可以带参数,参数在函数内部用 $1、$2 等获取。2. 案例:计算两个数的和
#!/bin/bash
sum()
{
SUM=$[$1+$2]
echo $SUM
}
read -p "请输入第一个数值:" n1
read -p "请输入第二个数值:" n2
sum $n1 $n2
九、Shell工具——文本处理三剑客1. cut——列提取工具
cut 命令用于从文件的每一行中剪切出指定的部分。
语法
cut
选项参数
常用选项:
案例
# 准备数据
vim cut.txt
dong shen
guan zhen
wo wo1
lai lai1
le le1
# 1. 切割第一列
cut -d " " -f 1 cut.txt
# 输出:
dong
guan
wo
lai
le
# 2. 切割第二、三列
cut -d " " -f 2,3 cut.txt
# 输出:
shen
zhen
wo1
lai1
le1
# 3. 切割出包含guan的那一行
cat cut.txt | grep guan | cut -d " " -f 1
# 输出:guan
# 4. 从PATH变量中提取第3个冒号之后的所有路径
echo $PATH | cut -d ":" -f 3-
# 输出:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# 5. 提取IP地址(以ifconfig为例)
ifconfig ens33 | grep netmask | cut -d "i" -f 2 | cut -d " " -f 2
# 输出:192.168.10.150
2. awk——强大的文本分析工具
awk 逐行读入文件,以空格为默认分隔符,对每行切片处理。
语法
awk
选项参数
'//{}'
常用选项:
内置变量案例
# 准备数据:复制passwd文件
cp /etc/passwd ./passwd
# 1. 搜索以root开头的行,输出第7列
awk -F: '/^root/{print $7}' passwd
# 输出:/bin/bash
# 2. 输出第1列和第7列,用逗号分隔
awk -F: '/^root/{print $1","$7}' passwd
# 输出:root,/bin/bash
# 3. 在所有行前添加列名,行尾添加自定义内容
awk -F: 'BEGIN{print "user, shell"} {print $1","$7} END{print "atguigu, bin/zuishuai"}' passwd
# 4. 用户ID增加1
awk -v i=1 -F: '{print $3+i}' passwd
# 5. 统计信息
awk -F: '{print "filename:" FILENAME ",linenum:" NR ",col:" NF}' passwd
# 6. 查询空行行号
ifconfig | awk '/^$/ {print NR}'
# 7. 提取IP地址(另一种方法)
ifconfig ens33 | awk -F " " '/inet /{print $2}'
十、正则表达式入门——模式匹配的利器
正则表达式用于描述、匹配符合规则的字符串。在Linux中,grep、sed、awk等都支持正则表达式。
1. 常规匹配
普通字符匹配自身。例如:
cat /etc/passwd | grep atguigu
匹配所有包含“”的行。
2. 常用特殊字符
字符
说明
示例
匹配行首
^a 匹配以a开头的行
匹配行尾
n$ 匹配以n结尾的行
匹配任意单个字符
r...t 匹配r和t之间有3个任意字符
匹配前一个字符0次或多次
ro*t 匹配rt, rot, root等
匹配括号内的任意一个字符
匹配一个数字
转义特殊字符
a\$b 匹配包含a$b的行
实战
# 匹配以a开头的行
cat /etc/passwd | grep ^a
# 匹配以n结尾的行
cat /etc/passwd | grep n$
# 匹配包含r开头,t结尾,中间任意3个字符
cat /etc/passwd | grep r...t
# 匹配ro*t(r后跟0个或多个o,再跟t)
cat /etc/passwd | grep ro*t
# 匹配包含数字的行
cat /etc/passwd | grep [0-9]
# 匹配包含特殊字符$的行(需要转义)
cat /etc/passwd | grep a\$b
3. 经典正则表达式
邮箱正则:
^[a-zA-Z0-9_]+@[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)+$
手机号正则(简单):
^1[3-9]\d{9}$
4. 正则表达式语法补充总结
通过这篇超详细的教程,我们从零开始全面学习了Shell脚本的核心知识:
Shell脚本是Linux运维开发的核心技能之一,掌握它,你将能够:
建议你动手敲一遍所有案例,在实践中加深理解。遇到问题时,善用echo和set -x进行调试。坚持练习,你很快就能成为Shell脚本高手!
下一步:可以尝试编写一个综合脚本,比如备份日志、监控服务器资源、批量创建用户等,将所学知识融会贯通。祝你学习愉快!
























