程序员Benothing头像
关注

Bash通配符、转义、引号完全指南:路径扩展与变量展开

开篇:一个真实的工作场景

一个常见的"删错文件"事故是这样发生的:

rm *.txt ~

运维本意是"删除当前目录所有 txt 文件",结果手滑多敲了一个空格——~ 被单独展开成了家目录rm 把家目录整个删了。这起真实事故告诉我们:通配符的展开是 Shell 帮你做的,不是命令自己做的,理解"展开"发生在哪一步,是保命的技能。

再比如你想删除文件名里带空格的文件,直接写 rm file name.txt 会被拆成两个文件;想打印变量却写错引号,输出完全不是你想的那样。今天这篇文章,就把 Bash 的通配符、转义、引号和各类展开机制一次讲透,帮你彻底告别"玄学"命令行。

一、核心认知:先有"展开",后有执行

Bash 执行一条命令时,先做各种展开,再把展开后的结果交给命令。整个流程(简化版)是:

命令行输入
  → 大括号展开 {a,b}
  → 波浪号展开 ~
  → 参数展开 $var
  → 命令替换 $(cmd)
  → 算术展开 $((1+2))
  → 分词(按空白拆分)
  → 文件名展开(通配符 * ? [ ])
  → 交给命令执行

这个顺序解释了几乎所有"为什么这么写不行"的问题。比如 echo * 输出的是当前目录的文件列表,因为 * 在交付给 echo 之前就被 Shell 展开成了文件名。

二、通配符(文件名展开)

语法格式

命令 含通配符的模式

通配符一览

通配符匹配规则示例匹配到的示例文件
*任意长度任意字符(含空)*.loga.logapp.log
?恰好 1 个任意字符file?.txtfile1.txtfilea.txt
[abc]方括号内任一字符file[12].txtfile1.txtfile2.txt
[a-z]字符范围file[a-z].txtfilea.txtfilez.txt
[!abc]排除括号内字符file[!0-9].txtfilea.txt(不含数字)
{a,b,c}大括号展开(不是文件名匹配file{1,2}.txtfile1.txtfile2.txt

实战示例与预期输出

示例 1:批量匹配并查看

假设当前目录有 a.logapp.loga.txt

ls *.log

预期输出:

a.log  app.log

示例 2:方括号精确匹配

ls file[12].txt

预期输出(若只有 file1.txt 存在):

file1.txt

⚠️ 常见错误

  1. 没有匹配到文件时:Bash 默认把模式原样传给命令,导致 ls *.nonexist 报"没有那个文件"。可以开启 nullglob(见知识扩展)让不匹配时展开为空。
  2. * 不匹配隐藏文件:以 . 开头的文件需要显式写 .*
  3. rm * 有风险* 会展开当前目录所有非隐藏文件,建议先 echo * 预览。

三、转义:让特殊字符"现出原形"

反斜杠 \ 让紧跟在后面的字符失去特殊含义,按字面处理。

语法格式

\特殊字符

示例与预期输出

示例:删除文件名带空格的文件

touch "my file.txt"     # 先创建
rm my\ file.txt         # 转义空格
ls                      # 确认已删除

示例:打印美元符号

echo "价格是 \$100"

预期输出:

价格是 $100

注意:\$ 在双引号内转义了 $,避免被当作变量引用。

四、引号:单引号、双引号、反引号

规则总览

引号作用是否展开变量/命令典型用途
'...' 单引号内部所有字符按字面处理❌ 不展开原样输出、含特殊字符的字符串
"..." 双引号保留变量/命令展开,但阻止分词与通配符展开✅ 展开拼接带空格的变量
`...` 反引号命令替换(已不推荐)$(...) 取代

示例与预期输出

示例 1:单引号 vs 双引号

name="linux"
echo '$name 和 $name'
echo "$name$name"

预期输出:

$name 和 $name
linux 和 linux

示例 2:双引号防止分词

dir="/tmp/my dir"
ls "$dir"      # 正确:作为一个整体
ls $dir        # 错误:被拆成 /tmp/my 和 dir 两个参数

示例 3:命令替换的两种写法(推荐 $()

echo "今天是 $(date +%F)"
echo "旧写法:`date +%F`"

预期输出:

今天是 2026-09-16
旧写法:2026-09-16

$(...) 的优势:可嵌套、易读($(echo $(date))),强烈推荐。

引号嵌套速记

  • 单引号里不能再放单引号,可以 '\'' 表示,但更推荐用双引号或改写法;
  • 双引号里可以放单引号:echo "it's ok" ✅;
  • 想输出 " 本身:echo "\""echo '"'

五、变量展开的完整形态

除了最基本的 $var,还有一批控制默认值的高级写法,脚本里非常常用:

写法含义示例(var 未设置时)
${var:-默认值}为空则用默认值(不赋值echo ${port:-8080}8080
${var:=默认值}为空则赋值并输出${port:=8080}$port 为 8080
${var:?报错信息}为空则报错退出${host:?host 未设置}
${#var}变量长度${#name}
${var:2:4}子串截取${str:2:4}
${var#模式} / ${var##模式}删除前缀(最短/最长)${file##*/} 取文件名
${var%模式} / ${var%%模式}删除后缀(最短/最长)${file%.txt} 去扩展名
${var/旧/新}替换第一个匹配${url/http/https}

示例与预期输出

示例:从路径提取文件名和目录

path="/var/log/nginx/access.log"
echo "文件名: ${path##*/}"
echo "目录: ${path%/*}"
echo "去扩展名: ${path%.log}"

预期输出:

文件名: access.log
目录: /var/log/nginx
去扩展名: /var/log/nginx/access

这些写法写脚本时能省大量 sed/awk 调用。

六、知识扩展:改变展开行为的 Shell 选项

Bash 提供一批开关,显著影响展开行为,全部可用 shopt -s 选项 开启:

选项效果示例
nullglob通配符无匹配时展开为空(而不是保留原样)for f in *.png; do ... 不再拿到字面量
failglob无匹配时报错适合脚本中严格检查
globstar** 递归匹配子目录ls **/*.md 匹配所有层级的 md
nocaseglob文件名匹配忽略大小写ls *.TXT 也能匹配 txt
dotglob* 也匹配隐藏文件rm * 会波及隐藏文件,慎用
shopt -s globstar nullglob
ls **/*.conf    # 递归找所有 conf

原理探究:为什么展开顺序重要?举个经典例子——echo $((2+3) * 5) 会报错,因为算术展开先于文件名展开完成,$((2+3)) 变成 5 后,5 * 5 被当作路径模式处理。理解"展开发生在命令执行前、且按固定顺序",很多怪现象都能解释。

七、注意事项与最佳实践

  1. rm 前先 echo 预览echo *.log 看清展开结果再删。
  2. 含空格路径永远加双引号"$file""$dir" 是脚本安全底线。
  3. 需要字面特殊字符用单引号:正则表达式、密码、带 $ 的字符串一律单引号。
  4. 命令替换用 $(...),反引号容易嵌套出错。
  5. ~ 只在开头展开echo ~/dir 有效,echo a~ 输出原样。
  6. 脚本里开 set -u(未定义变量报错),配合 ${var:?} 防手滑。

总结

这一篇揭示了命令行"魔法"的真相:Shell 在命令执行前按固定顺序完成大括号展开、波浪号展开、参数展开、命令替换、算术展开、分词和文件名展开。通配符 * ? [] 负责文件名匹配,反斜杠负责转义,单引号完全字面、双引号保留展开但阻止分词。掌握展开顺序和引号规则,你就能写出安全、可预测的 Bash 命令,避免"删错文件"这类事故。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/m0_66083353/article/details/165631449

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--