install.sh 报 env: 'bash\r' 不是权限问题:CRLF、dash/noexec 与 cron PATH 的排查笔记
部署核心业务系统时执行 ./install.sh,报错:
env: 'bash\r': No such file or directory
先试 chmod +x 没有任何效果。这不是权限问题,而是跨平台文件传输导致的换行符污染:shebang 行变成 #!/bin/bash\r,内核把 \r 当成解释器路径的一部分,自然找不到这个文件。
四类现象先对齐
| 故障现象 | 根本原因 | 排查手段 |
|---|---|---|
| env: bash\r: No such file | Windows 换行符(CRLF)污染 | file install.sh 或 od -c |
| Permission denied | 挂载点 noexec 限制或文件系统属性 | mount -o remount,exec |
| syntax error near unexpected token | 解释器不兼容(dash vs bash) | ls -l /bin/sh |
| command not found | 环境变量 PATH 污染或依赖缺失 | echo $PATH; which command |
不要盲目重试。用 sh -x install.sh 打开执行追踪,它会打印每一行被执行的命令以及变量展开后的真实形态。脚本里有复杂逻辑判断时,还要确认 set -e 是否导致了非预期的提前退出。
执行前确认三项环境属性
- 解释器一致性:
ls -l /bin/sh。Debian/Ubuntu 上/bin/sh默认指向dash,它对 POSIX 标准要求更严,脚本里用到[[这类 bash 特有语法就会直接报错。 - 文件编码与格式:
file install.sh确认文件格式。如果输出里有CRLF line terminators,先清洗:
sed -i 's/\r$//' install.sh
- 挂载限制:检查执行目录所在的文件系统是否被挂载为
noexec。安全加固过的生产服务器上,/tmp、/var常被限制执行权限。挂载点用mount查看,必要时 remount。
离线环境下脚本经常因为拉不到依赖而崩掉,用 ldd 检查二进制的动态链接库依赖,确认对应的 .so 已经在 LD_LIBRARY_PATH 里。
环境指纹诊断脚本,可以放在安装脚本头部,也可以单独执行:
#!/bin/bash
echo "--- Environment Fingerprint ---"
echo "Shell: $SHELL"
echo "Path: $PATH"
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
echo "User: $(whoami)"
echo "Working Directory: $(pwd)"
echo "Script Syntax Check:"
bash -n install.sh || echo "Syntax Error Detected"
echo "--- End of Report ---"
cron 环境为什么和终端不一样
脚本在终端里跑得没问题,一放进 crontab 就找不到命令、找不到文件、输出为空。原因高度一致:cron 的 PATH 极其精简,一般只有 /usr/bin:/bin,脚本里调用的 /usr/local/bin/python、/opt/some/bin/tool 都不在其中。HOME 变量也可能没被正确设置,脚本里访问 ~/.ssh/config、~/logs 时落到别的位置。用户 shell 配置文件同样不会加载,alias 和环境变量全部缺失。
先用 cron 一样的最小环境手动测一遍:
env -i /bin/bash /path/to/your/script.sh
这样能模拟出接近 cron 的执行环境,脚本缺什么会立刻暴露。修复方向是在脚本开头显式设置 PATH 和 HOME:
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
export HOME=/root
for 循环的隐藏雷区
for ip in $(cat ip_list.txt); do
ping -c 1 $ip >/dev/null && echo $ip 通
done
如果 ip_list.txt 每行末尾带 \r(CRLF),ping 的就是一个错误地址;文件里若有空格或通配符,$(cat ...) 会被拆成多个词,循环变量出乎意料地分裂。更稳的写法是 while 配合 read:
while IFS= read -r ip; do
ping -c 1 "$ip" >/dev/null && echo "$ip 通"
done < ip_list.txt
-r 防止反斜杠被转义,IFS 确保行首尾空格不被吞掉。另外不要在循环体内修改正在被 while 读取的输入文件,while ... done < file 是持续读入的,内容一改,读取位置就可能错乱。
变量引用统一加双引号 "$VAR",依赖检查用 command -v npm || { echo "npm 不存在"; exit 1; } 放在前面。