行结束符导致脚本执行错误

故障现象

最近同事遇到一个问题,他的一段代码使用 system() 执行文件系统中的一个 shell 脚本时提示 not found 错误,简单记录帮他定位解决的过程。

为简化问题,假设需要执行的脚本文件 echo.sh 在根目录中,内容如下:

#!/bin/sh
echo $PATH

功能很简单,即打印环境变量 $PATH 的值。同事的 C 代码如下:

system("/echo.sh");

执行此语句时,只提示 not found,而没有提示具体什么找不到,需要依次排查几个可疑点。

故障排查

首先可以排除的是找不到脚本文件 echo.sh 本身。因为调用时使用的是绝对路径,且 ls 命令显示脚本文件确实存在,且有可执行权限。

# ls -l /echo.sh
-rwxr-xr-x    1 root     root           21 Nov  2  2018 /echo.sh

由于脚本文件首行指定将 /bin/sh 作为解析器,因此接着排查 /bin/sh 是否指向了正确的 shell。

# ls -l /bin/sh
lrwxrwxrwx    1 root     root            4 Oct 30  2018 /bin/sh -> ash
# ls -l /bin/ash
-rwxrwxrwx    1 root     root            7 Oct 30  2018 /bin/ash -> busybox
# ls -l /bin/busybox
-rwxrwxrwx    1 root     root      2433952 Oct 30  2018 /bin/busybox

进一步验证 shell 没有问题,在 C 代码中显式地使用 sh 执行脚本正常:

system("sh /echo.sh");

为了加速问题定位,直接尝试在系统 shell 下执行 /echo.sh,问题依旧。

之后查看文件内容,使用系统 busybox 集成的功能极其简单的 vi 命令打开脚本文件,注意首行最后的 ^M

#!/bin/sh^M
echo $PATH

由此怀疑是行结束符导致的找不到解析器,使用 busybox 集成的 dos2unix 命令将脚本文件转换成 Unix 风格的行结束符:

# dos2unix /echo.sh

之后再执行就正常了,问题解决。

# /echo.sh
/sbin:/bin:/usr/sbin:/usr/bin:/sbin

其实同样的脚本在较新版本的 bash 中执行时可明确给出错误的原因:

# /echo.sh
sh: /echo.sh: /bin/sh^M: bad interpreter: No such file or directory

因为出现问题的系统中使用的 busybox 版本较老,提示信息不完善,导致上述的几个排查步骤。

查看行结束符

vim 打开文件后,可使用 :set list:e ++ff=unix 显示行结束符,$ 表示换行符(ASCII 码 0x0A),^M 表示回车符(ASCII 码 0x0D)。Unix 风格仅有 $,DOS 风格同时有 ^M$

file 命令查看文件类型时,若为 DOS 风格行结束符,会有 with CRLF line terminators 字样。

# file deploy.sh
deploy.sh: POSIX shell script, ASCII text executable
# file echo.sh
echo.sh: POSIX shell script, ASCII text executable, with CRLF line terminators

小结

  • 交叉编译环境需要特别注意脚本文件的行结束符,将 Windows 下编写的脚本文件部署到 Unix 系统环境之前,最好使用 dos2unix 命令转换,反方向的部署请使用 unix2dos 命令。
  • 可使用 vimfile 查看文件的行结束符。

以上。

comments powered by Disqus