同样写一行命令,老司机和新手的区别,往往就藏在 '"`>>>2>&1*{} 这几个不起眼的符号里。这篇文章把 Linux shell 最常用的引号系列 / 重定向系列 / 通配符系列一次性梳理透,并补上 2026 年仍然好用的 Process Substitution / here-string / extglob / noclobber 等进阶姿势。
一、引号系列:' " ` 与不加引号
| 引号 | 含义 | 是否解析变量 | 是否解析命令 | 是否解析通配符 |
|---|
' ' 单引号 | 所见即所得,原样输出 | ❌ | ❌ | ❌ |
" " 双引号 | 解析变量、命令替换,但不展开 {} 序列 / * | ✅ | ✅ | ❌ |
| 不加引号 | 双引号能力 + 通配符 / 花括号展开 | ✅ | ✅ | ✅ |
` ` 反引号 | 优先执行反引号里的命令,把结果嵌入当前命令 | — | ✅ | — |
1.1 经典对比实验
# 单引号:完全原样echo '`hostname` user001 $(whoami) $UID {1..5}'# `hostname` user001 $(whoami) $UID {1..5}# 双引号:解析 $变量 / $() / ``echo "`hostname` user001 $(whoami) $UID {1..5}"# demo-host user001 root 0 {1..5}# 不加引号:再额外展开通配符 / 花括号echo `hostname` user001 $(whoami) $UID {1..5}# demo-host user001 root 0 1 2 3 4 51.2 反引号 vs $()(推荐 $())
echo "今天是 `date +%F` "echo "今天是 $(date +%F)"# 等价,但可读性更好、可嵌套
老脚本里反引号很多,新脚本一律推荐 $(...):支持嵌套、不会被字体误读成单引号。
1.3 什么时候必须加单引号?
# 1) 防止 $ 被 shell 提前解析sed -i 's/$old/$new/g' file.txt # ✗ 变量会被替换sed -i 's/\$old/\$new/g' file.txt # ✓ 转义后才正确# 2) 包含 awk / grep 的复杂表达式awk '{print $1, $NF}' access.log # ✓ 单引号包住整段grep '^\s*#' /etc/sshd_config # ✓ 不被 shell 干扰
二、重定向:> >> 2> 2>&1 <<
"重定向" 三个字的本质:改变标准输入 / 输出 / 错误输出的去向。
2.1 三个标准流
| 流 | 文件描述符 | 默认指向 |
|---|
| stdin(标准输入) | 0 | 键盘 |
| stdout(标准输出) | 1 | 终端 |
| stderr(标准错误) | 2 | 终端 |
2.2 完整速查表
| 符号 | 含义 | 典型用途 |
|---|
> / 1> | 覆盖写入 stdout | 创建 / 清空文件 |
>> / 1>> | 追加写入 stdout | 写日志、追加配置 |
2> | 覆盖写入 stderr | 单独收集错误 |
2>> | 追加写入 stderr | 错误日志 |
&> / >& | stdout + stderr 一起覆盖 | 一锅端 |
&>> | stdout + stderr 一起追加 | 定时任务最常用 |
>file 2>&1 | stdout 重定向到 file,再让 stderr 跟随 1 | POSIX 通用写法 |
< / 0< | 从文件读 stdin | 配合 tr、xargs |
<<EOF | here-doc,多行输入 | 写配置、SQL |
<<<"str" | here-string,把字符串当 stdin | 配合 bc、while read |
2.3 实验:标准输出 vs 错误输出
# 一个不存在的命令eco aaaa# -bash: eco: command not found ← 这条信息走的是 stderr (2)eco aaaa > out.log # stdout 没东西,stderr 仍然喷在屏幕上eco aaaa 2> err.log # 把错误吃到 err.logeco aaaa &> all.log # 全部吃掉
2.4 同时收集正确 & 错误输出(生产最高频写法)
# 方式 1:最容易理解cmd >> app.log 2>> app.log# 方式 2:POSIX 经典 ★(脚本 / 定时任务必背)cmd >> app.log 2>&1# 方式 3:Bash 简写cmd &>> app.log
⚠️ 2>&1 一定要写在 >file后面,否则 stderr 会先继承终端再被重定向,等于没生效:
cmd 2>&1 >> app.log # ✗ 错误顺序,stderr 仍然在屏幕cmd >> app.log 2>&1 # ✓ 正确顺序
2.5 把输出彻底丢掉
cmd > /dev/null 2>&1 # 不想看到任何输出cmd &> /dev/null # Bash 简写
2.6 输入重定向 + xargs
echo "a b c d e f" | xargs -n3# a b c# d e fxargs -n1 < hostlist.txt # 把文件按行喂给后续命令
2.7 here-doc:向文件写多行
cat > config.ini <<EOF[server]host =192.0.2.10port =8080EOF
📌 EOF 只是约定俗成的"结束标记",写成 END / DONE 都行,两边不能有多余空格。
防止 here-doc 里的变量被解析
cat > tpl.txt <<'EOF'当前用户: $USER# 想原样保留 $USER 怎么办?EOF# 给 EOF 加单引号 → 不解析变量,保持模板原样
缩进友好版本 <<-
cat > script.sh <<-'EOF'#!/bin/bashecho "hello"EOF# <<- 会去掉每行开头的 Tab(注意是 Tab,不是空格)
2.8 进阶:Process Substitution <(...)>(...)
原文没讲,但写脚本几乎离不开。
# 直接对比两条命令的输出,不用先生成临时文件diff <(ls /etc) <(ls /backup/etc)# 同时写多个 "管道终点"echo "hello" | tee >(wc -c) >(md5sum) > /dev/null
2.9 防误覆盖:noclobber 与 >|
set -o noclobber # 打开后 > 不会再覆盖已存在文件echo hi > exists.txt # ✗ 报错:cannot overwrite existing fileecho hi >| exists.txt # ✓ 强制覆盖set + o noclobber # 关掉
写关键备份脚本前,强烈建议set -o noclobber 加一层保险。
三、通配符(globbing):批量找文件神器
⚠️ 通配符 ≠ 正则!它只用来匹配文件名和命令行字符串。
3.1 常见符号
| 符号 | 含义 | 例子 |
|---|
* | 任意长度任意字符 | *.log |
? | 任意单个字符 | ?? 匹配两字符文件名 |
[abc] | 集合中任意一个 | [a-c].txt |
[!abc] / [^abc] | 取反 | [!0-9]* 不以数字开头 |
{} | brace expansion,由 shell 直接展开 | {1..10}{a,b,c} |
3.2 花括号 —— 批量生成的"魔法"
echo {a..z} # a b c ... zecho {1..10} # 1 2 3 ... 10echo {01..10} # 01 02 ... 10 ★ 等宽数字echo {1..10..2} # 1 3 5 7 9 ★ 步长echo {a..z..2} # a c e g i ...# 非连续,列表形式echo {alice,bob,carol}echo user-{alice,bob,carol}# user-alice user-bob user-carol3.3 实用小技巧
# 🌟 一键备份(最经典的花括号妙用)cp config.yaml{,.bak}# 等价于:cp config.yaml config.yaml.bak# 一次性创建多个目录mkdir -p project/{src,bin,docs,tests/{unit,integration}}# 批量改后缀(mv 不支持通配,要循环)for f in *.txt; domv "$f" "${f%.txt}.md"; done# 找出 /bin 下 2 个字符的命令ls -l /bin/??3.4 seq —— 类花括号的命令版
seq 1 10# 1 ... 10seq 1 3 10# 1 4 7 10 (起 步 止)seq -w 1 10# 等宽:01 ... 10seq -s, 1 5# 1,2,3,4,5
3.5 通配符 vs 正则(别再混淆!)
| 维度 | 通配符(glob) | 正则(regex) |
|---|
| 谁解释 | shell | 命令本身(grep / sed / awk) |
| 用途 | 匹配文件名 | 匹配字符串 |
* 含义 | "任意多个任意字符" | "前一个字符重复 0 次或多次" |
? 含义 | "任意一个字符" | "前一个字符出现 0 或 1 次" |
. 含义 | 字面意义 . | 任意单个字符 |
3.6 进阶:extglob 扩展通配(Bash 专属)
shopt -s extglobls !(*.log) # 除 .log 外所有文件ls *.@(jpg|png|gif) # 任意图片ls +(abc) # 出现 1 次或多次 abc
Bash 4+ 还支持 globstar:shopt -s globstar 后 **/*.py 递归匹配所有 Python 文件。
四、把三类符号串起来的实战例子
4.1 定时任务模板(运维必背)
# crontab -e* * * * * /opt/scripts/cleanup.sh >> /var/log/cleanup.log 2>&102 * * * /opt/scripts/backup.sh &>> /var/log/backup.log
4.2 一键备份配置目录
ts=$(date +%F)tar -I 'zstd -T0' -cf "/backup/etc-${ts}.tar.zst" /etc/ \ >> /var/log/backup.log 2>&14.3 批量初始化用户家目录
for u in {user001..user010}; domkdir -p /home/$u/{bin,logs,data}chown -R $u:$u /home/$udone4.4 here-doc 写 Nginx 配置
cat > /etc/nginx/conf.d/demo.conf <<'EOF'server { listen 80; server_name demo.example.com; root /var/www/demo; access_log /var/log/nginx/demo.access.log;}EOFnginx -t && systemctl reload nginx
五、避坑清单(生产建议)
✅ 变量带空格一律加双引号:rm -rf "$dir",否则 rm -rf / 真的会发生
✅ 2>&1 永远跟在 >file后面
✅ > 会覆盖原文件,追加请用 >>;脚本前 set -o noclobber 更稳
✅ 大输出不要 > 一个被 tail -f 的文件,可能引起 logrotate 异常
⚠️ 通配符 *不匹配以 . 开头的隐藏文件,需要 shopt -s dotglob
⚠️ {1..10000000} 这种brace expansion 会占内存,慎用,超大序列改用 seq + 管道
⚠️ here-doc 的结束标记不能缩进(除非用 <<- 且只缩进 Tab)
⚠️ 反引号 ` ` 不能嵌套,改用 $()
六、数据脱敏说明
文中主机名 / 用户名 / 文件名 / IP 均为通用示例,与任何真实环境无关:
| 字段 | 脱敏写法 |
|---|
| 主机名 | demo-host(原稿中 oldboy-aliyun-servers、oldboy-85-vip-king-v2 等已隐去) |
| 用户名 | user001 / alice / bob / carol |
| 文件名 | config.yaml / demo.conf / out.log 等无业务含义示例 |
| IP | 192.0.2.10(RFC 5737 文档段) |
| 域名 | demo.example.com(IANA 保留示例域名) |
转发前可用 sed -E 批量替换:
sed -E 's/([0-9]{1,3}\.){3}[0-9]{1,3}/192.0.2.x/g' raw.log > safe.log
七、一句话总结
单引号原样输出、双引号解析变量、反引号 / $() 先执行;> 覆盖、>> 追加、2>&1 合并、&>> 简写;* 找文件、{} 造序列、? 顶一字符。把这三类符号串起管道 |,就是 Linux shell 的"工业能力"。
如果对你有帮助,欢迎点赞 / 在看 / 转发给身边正在啃 Bash 的同学 。