Shell脚本开头的shebang行看似简单,却常让新手纠结:究竟该用#!/bin/bash更稳妥,还是#!/usr/bin/env bash更具兼容性?这完全取决于你的脚本将在哪些系统环境中运行。

脚本第一行写 #!/bin/bash 还是 #!/usr/bin/env bash?
固定路径的优势在于,大多数主流Linux发行版(例如CentOS、Ubuntu)的/bin/bash确实位于该位置,直接调用通常没有错误。但跨平台场景下问题就来了——某些容器环境或macOS(尤其新版)中,/bin/bash可能根本不存在,或者版本偏低。而#!/usr/bin/env bash会沿$PATH环境变量动态查找最先匹配的bash,通用性与健壮性更强。
一个容易被误判的错误提示:./script.sh: /bin/bash^M: bad interpreter。此时别急着怀疑shebang写错了,这其实是Windows换行符(CRLF)在作祟。用dos2unix script.sh转换一下,或者在编辑器中将换行符改成LF即可解决。
- 生产环境脚本,尤其是涉及CI/CD或多平台部署时,建议统一使用
#!/usr/bin/env bash。 - 如果明确只运行在CentOS/RHEL 7+或Ubuntu 20.04+的机器上,
#!/bin/bash完全够用。 - 注意
#!/bin/sh并不等同于#!/bin/bash:前者调用的是POSIX兼容shell(如dash),不支持数组、[[、$(())等Bash独有特性。
为什么 chmod +x 后依然报 “Permission denied”?
权限问题常被误判。真正原因往往不是没加上执行权限,而是文件系统挂载时带了noexec选项,或者脚本本身存放在NFS、FAT32、NTFS这类不支持Unix权限的分区上。
如何验证?执行mount | grep "$(df . | tail -1 | awk '{print $1}')"查看当前目录所在挂载点是否包含noexec;再通过ls -l script.sh确认x权限位已经存在。
- 临时绕过:直接用
bash script.sh执行。这样不依赖文件权限,但会启动子shell,变量修改无法保留回当前终端。 - 根本解法:重新挂载时去掉
noexec,或者将脚本移到/tmp、/home这类本地ext4/xfs分区。 - 注意:
sudo chmod +x对只读文件系统无效;sudo也改变不了挂载选项的限制。
./script.sh 和 source script.sh 的本质区别是什么?
关键差异在于是否新建Shell进程。./script.sh会fork一个子shell执行,脚本里定义的变量、函数、cd切换全部隔离在子shell内,退出后对当前终端毫无影响。source(或简写为.)则直接在当前shell进程内逐行解释执行,所有更改即时生效。
典型误用场景:你写了一个export PATH=$PATH:/my/bin的脚本,用./setup.sh执行完毕后发现$PATH毫无变化——因为修改只发生在子shell中。
- 适合
./script.sh:独立任务,例如备份、日志清理、批量重命名。 - 适合
source script.sh:初始化环境、加载函数库、设置别名、修改当前shell状态。 - 注意:
source不检查shebang,它直接忽略第一行,完全按当前shell的语法解释执行。
变量赋值时加不加引号?什么时候必须加?
不加引号是Shell脚本中最隐蔽的Bug来源。只要变量值可能包含空格、制表符、通配符(*、?)、路径分隔符或特殊字符,就必须用双引号包围。
错误示例:file="my file.txt"; cp $file /tmp,实际执行的是cp my file.txt /tmp,被拆成两个参数,cp立刻报“missing destination”。正确做法:cp "$file" /tmp。
- 单引号
'$var':完全禁止变量展开,适合纯字面量字符串。 - 双引号
"$var":允许变量展开和命令替换,但保留空格结构。 - 未加引号
$var:触发word splitting和pathname expansion,属于危险操作。 - 例外:在
[[条件判断中,[[ $var == "abc" ]]不加引号也能工作(但加上更安全)。
实际编写脚本时,最容易被忽略的并非语法细节,而是执行上下文——当前工作目录、环境变量继承关系、stdin/stdout是否被重定向、脚本退出码是否被上游调用者检查。这些看似不显眼的细节,恰恰决定了自动化任务的可靠性。
