Shell 编程入门指南:从基础到实战2
目录
前言
字符串
if...else 语句
单支:
双支:
多支:
标准文件描述符(FD)
重定向:> (覆盖) 与 >> (追加)
重定向错误输出:2> / 2>>
同时重定向 stdout 与 stderr
POSIX 推荐(经常使用)
将输出丢弃 /dev/null
总结
前言
在 Linux 世界里,“一切皆文件”,而 Shell 则是我们与这些文件、进程、系统资源沟通的桥梁。判断字符串是否相等、检测端口是否被监听、验证文件权限、重定向输出与错误——这些看似琐碎的操作,恰恰构成了日常运维、自动化部署、故障排查的基石。本篇文章通过大量可直接复制运行的示例,把“字符串比较、文件测试、条件分支、重定向与文件描述符”这些高频知识点串成一条清晰的主线:
-
从最简单的
[ \"$a\" = \"$b\" ]
开始,理解测试命令的“真/假”本质; -
用
-f/-d/-r/-w/-x/-s/-e
等选项快速判断文件类型和权限; -
通过单分支、双分支、多分支的
if/elif/else
脚本,把零散的测试组合成有逻辑的业务流程; -
进一步用
>/>>/2>/2>>/&>/&>>
掌握标准输出(FD 1)与标准错误(FD 2)的重定向技巧; -
最终学会把“条件判断 + 重定向”综合运用到端口检测、网络探活、服务安装等真实场景。
字符串
-n 判断是否为非空
-z 判断是否为空
= 判断是否相等
!= 判断是否不相等
示例:判断两个是否相等
[root@localhost shelldemo]# a=123[root@localhost shelldemo]# b=123[root@localhost shelldemo]# [ $a = $b ] && echo \"两个字符串相等\"两个字符串相等[root@localhost shelldemo]# [ $a != $b ] && echo \"两个字符串不相等\"
示例
shell脚本
str=\"hello\"if [ -n \"$str\" ];then echo \"字符串为非空\"else echo \"字符串为空\"fi
运行
[root@localhost shelldemo]# sh hello14.sh 字符串为非空
文件 文件测试运算符 运算符号
-f
[ -f 文件路径 ]
-d
[ -d 目录路径 ]
-s
[ -s 文件路径 ]
-e
[ -e 文件路径 ]
-r
[ -r 文件路径 ]
-w
[ -w 文件路径 ]
-x
[ -x 文件路径 ]
# 判断某一个目录是否可读可写[root@localhost shelldemo]# [ -d /etc ] && [ -r /etc ] && echo \"可读目录\"可读目录# 判断该系统版本是否为英文版[root@localhost shelldemo]# [ $LANG != \"en.US\" ] && echo \"Not en.US\"Not en.US# 判断当前用户是否小于等于5[root@localhost shelldemo]# [ $(who|wc -l) -le 5 ] && echo \"用户太少\" 用户太少[root@localhost shelldemo]# [ $(who|wc -l) < 5 ] && echo \"用户太少\" 用户太少
if...else 语句
单支:
bash
if [ 条件 ]; then 命令fi
示例
# shell脚本if [ -s /etc ];then echo \"文件不为空\"fi# 运行[root@localhost shelldemo]# sh hello16.sh 文件不为空
双支:
bash
if [ 条件 ]; then 命令1else 命令2fi
示例1
# shell脚本if [ $UID -eq 0 ]; then echo \"当前用户是管理员\"else echo \"当前用户不是管理员\" fi# 运行[root@localhost shelldemo]# sh hello17.sh 当前用户是管理员
示例2
# shell脚本num1=$1num2=$2if [ $num1 -gt $num2 ]; then echo \"$num1大于$num2\"else echo \"$num1小于$num2\"fi# 运行[root@localhost shelldemo]# sh hello18.sh 1234 4561234大于456[root@localhost shelldemo]# sh hello18.sh 765 1234567765小于1234567
示例
# shell脚本if netstat -antulp|grep \":80\" > /dev/null 2>&1; then echo \"80端口运行\"else if [ $? -eq 0 ]; then echo \"web网站服务已经运行\" else echo \"启动http服务\" yum install -y httpd > /dev/null systemctl restart httpd fifi# 注:/dev/null 2>&1 将错误日志传输,使得它往后面的if语句运行,yum云安装# 运行[root@localhost shelldemo]# sh hello19.sh 启动http服务file:///mnt/repodata/repomd.xml: [Errno 14] curl#37 - \"Couldn\'t open file /mnt/repodata/repomd.xml\"正在尝试其它镜像。
示例
# shell脚本ip=$1ping -c 2 -i 0.2 -W 3 $ip &> /dev/null#-c 次数 -i ping一次多少秒 -W 反馈结果的时间if [ $? -eq 0 ]; then echo \"$ip is up\"else echo \"$ip is down\"fi # 运行[root@localhost shelldemo]# sh hello20.sh 192.168.10.106192.168.10.106 is up[root@localhost shelldemo]# sh hello20.sh 192.168.10.105192.168.10.105 is down
示例:判断httpd是否安装
# shell脚本rpm -q httpd >rpm.txt 2>&1if [ $? -eq 0 ]; then echo \"安装httpd\"else echo \"没安装httpd\"fi# 测试[root@zardcopy Y]# sh dome1.sh安装httpd
示例:需求:检查用户是否存在 如果不存在就创建新用户,并且设置初始密码为“123456”
# shell脚本read -p \"请输入当前用户名:\" usernamecat /etc/passwd|grep $usernameif [ $? -eq 0 ]; then echo \"用户已创建\"else echo \"用户没有创建\" useradd $username echo \"123\" | passwd --stdin $usernamefi# 测试[root@localhost shelldemo]# sh dome7.sh 请输入当前用户名:test4用户没有创建更改用户 test4 的密码 。passwd:所有的身份验证令牌已经成功更新。[root@localhost shelldemo]# su - test4[test4@localhost ~]$ su - test4密码:上一次登录:六 8月 30 09:41:46 CST 2025pts/2 上[test4@localhost ~]$ su - root密码:上一次登录:六 8月 30 09:26:23 CST 2025从 192.168.10.90pts/2 上
多支:
if [ 条件1 ]; then 命令1elif [ 条件2 ]; then 命令2...else 默认命令fi
示例:判断分数等级
bash
#!/bin/bashscore=$1if [ $score -ge 90 ]; then echo \"优秀\"elif [ $score -ge 80 ]; then echo \"良好\"elif [ $score -ge 60 ]; then echo \"及格\"else echo \"不及格\"fi# 运行[root@localhost shelldemo]# sh hello10.sh 98优秀
示例:写一个脚本,判断文件类型 d f b
# shell脚本read -p \"请输入路径:\" nameif [ -d $name ]; then echo \"这是一个文件夹\"elif [ -f $name ]; then echo \"这是一个文件\"elif [ -b $name ]; then echo \"这是一个设备文件\"else echo \"无法判断\"fi# 输出结果[root@localhost shelldemo]# sh hello21.sh 请输入路径:/root这是一个文件夹[root@localhost shelldemo]# sh hello21.sh 请输入路径:/root/shelldemo/hello21.sh这是一个文件
示例:
# shell脚本# 用date命令记录当前的小时a=$(date +%H)# if判断if [ $a -ge 6 -a $a -lt 11 ]; thenecho \"现在是上午$a点\"elif [ $a -ge 11 -a $a -lt 13 ]; thenecho \"现在是中午$a点\"elif [ $a -ge 13 -a $a -lt 18 ]; thenecho \"现在是下午$a点\"elif [ $a -ge 18 -a $a -lt 24 ]; thenecho \"现在是晚上$a点\"else echo \"现在是凌晨$a点\"fi# 测试[root@localhost shelldemo]# sh dome21.sh 现在是上午08点[root@localhost shelldemo]# date -s \"2025-08-30 12:00:00\"2025年 08月 30日 星期六 12:00:00 CST[root@localhost shelldemo]# sh dome21.sh 现在是中午12点[root@localhost shelldemo]# date -s \"2025-08-30 16:00:00\"2025年 08月 30日 星期六 16:00:00 CST[root@localhost shelldemo]# sh dome21.sh 现在是下午16点
标准文件描述符(FD)
0 = stdin (标准输入) 1 = stdout (标准输出) 2 = stderr (标准错误) 示例(分别演示 stdout/stderr ):
[root@localhost shelldemo]# echo \"out\"out # 到 stdout[root@localhost shelldemo]# echo \"err\" >&2err # 到stderr
重定向:> (覆盖) 与 >> (追加)
> 把 stdout 写到文件(覆盖原内容) >> 把 stdout 追加到文件末尾
[root@localhost shelldemo]# echo \"first\" > file.txt[root@localhost shelldemo]# echo \"second\" > file.txt[root@localhost shelldemo]# cat file.txt #覆盖second[root@localhost shelldemo]# echo \"third\" >> file.txt[root@localhost shelldemo]# cat file.txt #追加secondthird
重定向错误输出:2> / 2>>
[root@localhost shelldemo]# ls /no/such/path 2>err.txt #重覆盖[root@localhost shelldemo]# cat err.txt ls: 无法访问/no/such/path: 没有那个文件或目录[root@localhost shelldemo]# ls /no/same/file 2>>err.txt #追加[root@localhost shelldemo]# cat err.txtls: 无法访问/no/such/path: 没有那个文件或目录ls: 无法访问/no/same/file: 没有那个文件或目录
同时重定向 stdout 与 stderr
有几种常用写法(要注意兼容性与顺序):
POSIX 推荐(经常使用)
command >out.txt 2>&1
含义:先把 stdout 重定向到 out.txt ,再把 stderr(2)重定向到当前 stdout(1)指向的位置(也就
是 out.txt )。结果: stdout 和 stderr 都写入 out.txt 。
# 先传正确,再传错误[root@localhost shelldemo]# bash -c \'echo out; echo err >&2\' >both.txt 2>&1[root@localhost shelldemo]# cat both.txtouterr# 仅仅把错误的传过去[root@localhost shelldemo]# bash -c \'echo out; echo err >&2\' 2>&1 >both.txterr
将输出丢弃 /dev/null
含义 :把不想要的输出重定向到 /dev/null (黑洞)。 示例:只保留 stderr ,丢弃 stdout :
$ some_command > /dev/null
丢弃 stderr :
$ some_command 2> /dev/null
同时丢弃两者:
$ some_command > /dev/null 2>&1# 或(bash):$ some_command &> /dev/null
总结
-
判断的本质:任何测试命令(
[ ]
、[[ ]]
、(( ))
、命令本身)都会返回“退出状态码”——0 为真,非 0 为假;脚本只需根据这个状态码决定下一步动作。 -
文件与字符串测试:记住常用选项(
-f/-d/-e/-r/-w/-x/-s/-n/-z
),就能在一行内完成“存在性、类型、权限、空值”四大类检查。 -
条件结构:
-
单分支
if … then … fi
—— 满足就执行; -
双分支
if … then … else … fi
—— 二选一; -
多分支
if … elif … else … fi
—— 多选一。
-
-
重定向:
-
>
覆盖、>>
追加、2>
定向错误、&>
合并全部; -
/dev/null
是“黑洞”,任何不想保留的输出都可以丢进去; -
顺序决定结果:
>file 2>&1
合并输出与错误到 file;2>&1 >file
则只把错误合并到原 stdout。
-
-
实战套路:
-
端口检测:
netstat -antulp | grep -q \":80\"
; -
网络探活:
ping -c 2 -W 3 $ip &>/dev/null && echo up || echo down
; -
服务安装:先端口检测 → 不存在就
yum/dnf install -y httpd && systemctl enable --now httpd
。
-
把这些基本积木拼接起来,你就能写出既健壮又易维护的自动化脚本,真正做到“让 Shell 替你搬砖,而你专注思考”。