少了一对引号,脚本就疯了
那天写了个部署脚本,逻辑很简单:把构建产物从一个目录复制到另一个目录。源目录和目标目录都用变量存,方便复用:
本地测试,路径没空格,跑得好好的。换到正式环境一跑——
终端炸了。
cp 报了一串"找不到文件"的错误,指向一个叫 /home/user/my 的路径,和一个叫 project/build 的路径——把一个完整路径劈成了两半。
我当时第一反应:路径不存在?
我自信满满地开始排查。
- 路径真的存在吗?
ls /home/user/my\ project/build,存在,文件都在。
- 是不是脚本里路径写错了?打开脚本看,
/home/user/my project/build,一字不差,空格也在。
- 是不是 cp 命令的问题?换成
rsync,还是炸。
- 是不是变量赋值时空格被吃了?
echo $SRC_DIR,输出/home/user/my project/build,空格在,没问题。
- 是不是脚本编码问题?查了,UTF-8,没 BOM,没问题。
- 是不是这个路径里的空格是特殊空格(不间断空格、全角空格)?用
cat -A看,就是普通空格。
到这里我已经开始怀疑人生了:变量里明明存的是完整路径,echo 出来也对,为什么 cp 一用就劈成两半?
一个上午就在这种"变量明明是对的、命令明明是对的、但凑一起就炸"的诡异里过去了。中间我还把路径改了名、去掉了空格——果然不炸了。但那是正式环境的目录结构,不能随便改名。
中午,我盯着那行 cp -r $SRC_DIR/* $DEST_DIR/ 发呆,突然反应过来——
问题不在变量,也不在命令,在我没给变量加引号。
shell 在解析命令时,会对未加引号的变量做两件事:分词(word splitting)和通配符展开(globbing)。
所谓分词,就是 shell 把变量值按空格、制表符、换行符拆成多个独立的"词"。我的 $SRC_DIR 值是 /home/user/my project/build,因为没加引号,shell 把它拆成了两个词:/home/user/my 和 project/build。
于是那行命令在 shell 眼里变成了:
——把 my 当成源、把 project/build/* 当成另一个源,然后复制到目标。两个源都不存在,自然报错。
解决简单到令人窒息:
一对双引号,告诉 shell:"这个变量是一个整体,别给我拆。"
跑完,乖乖工作。
折腾一上午换来的几句话,送给每一个"用 shell 变量不加引号"的人:
- shell 里用变量,永远加双引号。 这是最便宜、收益最高的一条习惯。
"$VAR"比$VAR安全一万倍——双引号会保留变量的完整值,阻止分词和通配符展开。除了你明确想让它分词的场景(极少),一律加引号。
- 路径带空格是引号问题的头号触发场景。 没空格时变量不加分词也看不出来,一旦路径带空格、带特殊字符(
$、*、?、~),不加引号必炸。Windows 挂载目录、用户主目录带空格(My Documents)、中文路径,全是雷区。
echo $VAR看不出问题,要echo "$VAR"。 不加引号的 echo 会先分词再合回来,肉眼看像是完整的,其实变量在被使用时已经被拆过。诊断变量值,永远加引号 echo,或者用printf。
- 用 shellcheck 给脚本体检。 shellcheck 是 shell 脚本的 linter,能自动揪出"变量未加引号"这类问题,附带解释为什么该加。写完脚本过一遍 shellcheck,能挡掉九成的引号坑。这是花十秒钟省两小时的典型操作。
- 点赞
- 收藏
- 关注作者
评论(0)