分类
devops

Double quote to prevent globbing and word splitting. shellcheck(SC2086)

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 在处理变量时的两个核心动作:

  1. 单词分割 (Word Splitting):把变量里的内容按空格、Tab 等拆成多个参数。
  2. 通配符展开 (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 是个好教练,但它有时太保守了。

  • 双引号 = “我就要这个字符串,别乱动。”
  • 不加引号 = “帮我拆分并展开它。”

一句话避坑: 如果你想要 *? 起作用,就不能加双引号;如果你怕文件名里的空格把脚本搞炸,请改用数组

想看下如何用数组更安全地处理那种“文件名里带空格”的复杂场景吗?