Double quote to prevent globbing and word splitting. shellcheck(SC2086)
坑,不是所有的变量需要双引号引起来呀!
比如如下
#!/usr/bin/env bash
file="*.md"
/bin/cp "$file" /tmp
set -x
/bin/cp "$file" /tmp
set +x;
/bin/cp: cannot stat '*.md': No such file or directory
+ /bin/cp '*.md' /tmp
/bin/cp: cannot stat '*.md': No such file or directory
+ set +x
我们来问下AI此处为什么不能遵循shellcheck(SC2086)的建议把$file变量用双引号包起来?
你说得对,这确实是一个非常经典的 Bash “深坑”。
ShellCheck 的 SC2086 建议是一个通用型安全准则,旨在防止文件名包含空格、换行符等特殊字符时导致脚本崩溃或安全漏洞。但在你这个特定的场景下,“安全准则”正好杀死了你需要的“路径扩展(Globbing)”功能。
为什么这里不能听 ShellCheck 的?
我们要搞清楚 Bash 在处理变量时的两个核心动作:
- 单词分割 (Word Splitting):把变量里的内容按空格、Tab 等拆成多个参数。
- 通配符展开 (Globbing):把
*.md这种模式匹配成真实的文件列表。
当你给变量加上双引号 "$file" 时,你实际上是给 Bash 下了死命令:
“不管里面有什么,请把它当做一个完整的、字面意思上的字符串传给命令。”
于是,cp 接收到的参数不是 a.md b.md,而是字符串字面量 *.md。除非你真的有一个文件名叫 *.md(带星号),否则 cp 肯定会报错找不到文件。
这种情况下该怎么办?
如果你非要使用通配符匹配,有几种更“优雅”或更“标准”的处理方式:
1. 使用数组(推荐的做法)
数组是处理文件列表的王道,它既能保留通配符的扩展能力,又能完美避开空格带来的单词分割问题。
#!/usr/bin/env bash
# 将匹配到的文件直接存入数组
files=( *.md )
# 检查数组是否为空(防止匹配不到文件时直接把 "*.md" 传给 cp)
if [ -e "${files[0]}" ]; then
/bin/cp "${files[@]}" /tmp
fi
2. 直接在命令中使用通配符
除非路径是动态生成的,否则没必要把通配符存进变量。
/bin/cp *.md /tmp
3. 告诉 ShellCheck “闭嘴”
如果你深知自己在做什么,且确定变量里没有空格等怪异字符,你可以用注释忽略这条特定的警告,这也是老手的常见做法:
# shellcheck disable=SC2086
/bin/cp $file /tmp
总结
ShellCheck 是个好教练,但它有时太保守了。
- 双引号 = “我就要这个字符串,别乱动。”
- 不加引号 = “帮我拆分并展开它。”
一句话避坑: 如果你想要 * 或 ? 起作用,就不能加双引号;如果你怕文件名里的空格把脚本搞炸,请改用数组。
想看下如何用数组更安全地处理那种“文件名里带空格”的复杂场景吗?
