> 技术文档 > Shell 编程入门指南:从基础到实战2

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 文件存在且大小不为 0 [ -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 ,再把 stderr2)重定向到当前 stdout1)指向的位置(也就

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

总结

  1. 判断的本质:任何测试命令([ ][[ ]](( ))、命令本身)都会返回“退出状态码”——0 为真,非 0 为假;脚本只需根据这个状态码决定下一步动作。

  2. 文件与字符串测试:记住常用选项(-f/-d/-e/-r/-w/-x/-s/-n/-z),就能在一行内完成“存在性、类型、权限、空值”四大类检查。

  3. 条件结构:

    • 单分支 if … then … fi —— 满足就执行;

    • 双分支 if … then … else … fi —— 二选一;

    • 多分支 if … elif … else … fi —— 多选一。

  4. 重定向:

    • > 覆盖、>> 追加、2> 定向错误、&> 合并全部;

    • /dev/null 是“黑洞”,任何不想保留的输出都可以丢进去;

    • 顺序决定结果:>file 2>&1 合并输出与错误到 file;2>&1 >file 则只把错误合并到原 stdout。

  5. 实战套路:

    • 端口检测: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 替你搬砖,而你专注思考”。