bash - Shell脚本对编码和行尾是否敏感?

标签 bash shell sh

我正在Mac上制作NW.js应用程序,并希望通过双击图标以开发模式运行该应用程序。第一步,我试图使我的shell脚本正常工作。

在Windows上使用VSCode(我想节省时间),我在项目的根目录下创建了一个run-nw文件,其中包含以下内容:

#!/bin/bash

cd "src"
npm install

cd ..
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &


但我得到以下输出:

$ sh ./run-nw

: command not found  
: No such file or directory  
: command not found  
: No such file or directory  

Usage: npm <command>

where <command> is one of:  (snip commands list)

(snip npm help)

npm@3.10.3 /usr/local/lib/node_modules/npm  
: command not found  
: No such file or directory  
: command not found


我真的不明白:


似乎它以空行作为命令。在我的编辑器(VSCode)中,我尝试将\r\n替换为\n(以防\r产生问题),但是它什么也没有改变。
似乎找不到文件夹(带或不带dirname指令),或者它不知道cd命令吗?
似乎无法理解installnpm参数
真正令我感到奇怪的部分是,它仍然可以运行该应用程序(如果我手动执行了npm install)...


由于无法正常工作,并且怀疑文件本身有些奇怪,我这次使用vim在Mac上直接创建了一个新文件。我输入了完全相同的说明,并且...现在可以正常使用了。
两个文件的差异显示完全为零。

有什么区别?什么会使第一个脚本不起作用?我怎么知道?

更新资料

按照接受的答案的要求,在错误的行尾返回之后,我检查了很多事情。事实证明,由于我是从Windows计算机上复制~/.gitconfig的,所以我有autocrlf=true,因此,每次在Windows下修改bash文件时,它将行尾重新设置为\r\n
因此,除了运行dos2unix(必须在Mac上使用Homebrew进行安装)之外,如果您使用的是Git,请检查配置。

最佳答案

是。 Bash脚本在脚本本身及其处理的数据中都对行尾敏感。它们应具有Unix样式的行尾,即每行以换行字符(十进制,ASCII十六进制0A)结尾。

脚本中的DOS / Windows行尾

使用Windows或DOS样式的行尾,每行都以回车符和换行符结尾。如果脚本文件以Windows行尾保存,则Bash会将文件视为

#!/bin/bash^M
^M
cd "src"^M
npm install^M
^M
cd ..^M
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M


注意:我使用脱字符号来表示非打印字符,即^M用于表示回车符(在其他情况下表示为\r);这与cat -v和Vim使用的技术相同。

在这种情况下,回车符(^M\r)不被视为空格。 Bash将shebang之后的第一行(由一个回车符组成)解释为要运行的命令/程序的名称。


由于没有名为^M的命令,因此它将显示: command not found
由于没有名为"src"^M(或src^M)的目录,因此它将打印: No such file or directory
它将install^M而不是install作为参数传递给npm,这导致npm抱怨。


输入数据中的DOS / Windows行尾

像上面一样,如果您有一个包含回车符的输入文件:

hello^M
world^M


那么它将在编辑器中以及将其写入屏幕时看起来完全正常,但是工具可能会产生奇怪的结果。例如,grep将无法找到明显存在的行:

$ grep 'hello$' file.txt || grep -x "hello" file.txt
(no match because the line actually ends in ^M)


附加的文本将改写该行,因为回车将光标移动到该行的开头:

$ sed -e 's/$/!/' file.txt
!ello
!orld


即使在写入屏幕时字符串看起来相同,字符串比较也似乎会失败:

$ a="hello"; read b < file.txt
$ if [[ "$a" = "$b" ]]
  then echo "Variables are equal."
  else echo "Sorry, $a is not equal to $b"
  fi

Sorry, hello is not equal to hello


解决方案

解决方案是将文件转换为使用Unix样式的行尾。有多种方法可以实现:


这可以使用dos2unix程序完成:

dos2unix filename

在功能强大的文本编辑器(Sublime,Notepad ++,不是Notepad)中打开文件并将其配置为保存以Unix行结尾的文件,例如使用Vim,在保存(重新)之前运行以下命令:

:set fileformat=unix

如果您具有支持sed-i选项的--in-place实用程序版本,例如GNU sed,则可以运行以下命令来剥离尾随回车符:

sed -i 's/\r$//' filename


对于其他版本的sed,您可以使用输出重定向来写入新文件。确保为重定向目标使用其他文件名(以后可以重命名)。

sed 's/\r$//' filename > filename.unix

类似地,tr转换过滤器可用于从其输入中删除不需要的字符:

tr -d '\r' <filename >filename.unix



西格温·巴什(Cygwin Bash)

通过Cygwin的Bash端口,可以设置一个自定义的igncr选项,以忽略行尾的回车符(可能是因为其许多用户使用本机Windows程序来编辑其文本文件)。
可以通过运行set -o igncr为当前外壳启用此功能。

设置此选项仅适用于当前的shell进程,因此在寻找带有多余回车符的文件时很有用。如果您经常遇到带有DOS行尾的shell脚本,并希望永久设置此选项,则可以将名为SHELLOPTS(所有大写字母)的环境变量设置为包含igncr。 Bash使用此环境变量在启动时(在读取任何启动文件之前)设置外壳程序选项。

有用的工具

file实用程序对于快速查看文本文件中使用的行尾很有用。这是每种文件类型的打印内容:


Unix行尾:Bourne-Again shell script, ASCII text executable
Mac的行尾:Bourne-Again shell script, ASCII text executable, with CR line terminators
DOS行尾:Bourne-Again shell script, ASCII text executable, with CRLF line terminators


cat实用程序的GNU版本具有一个-v, --show-nonprinting选项,该选项显示非打印字符。

dos2unix实用程序专门用于在Unix,Mac和DOS行尾之间转换文本文件。

有用的链接

Wikipedia的excellent article涵盖了标记文本行末尾的许多不同方式,这种编码的历史以及在不同的操作系统,编程语言和Internet协议(例如FTP)中如何处理换行符。

具有经典Mac OS行尾的文件

使用Classic Mac OS(在OS X之前的版本),每行以回车符(ASCII中的十进制13,十六进制0D)终止。如果脚本文件以这样的行结尾保存,则Bash只会看到一条长行,如下所示:

#!/bin/bash^M^Mcd "src"^Mnpm install^M^Mcd ..^M./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M


由于这条长行以八倍体(#)开头,因此Bash将行(和整个文件)视为一条注释。

注意:2001年,Apple推出了Mac OS X,该操作系统基于BSD衍生的NeXTSTEP操作系统。结果,OS X还使用Unix风格的仅LF的行尾,从那以后,以CR终止的文本文件变得极为罕见。不过,我认为值得展示Bash如何尝试解释此类文件。

关于bash - Shell脚本对编码和行尾是否敏感?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60420086/

相关文章:

bash - awk 根据列值连接

bash - 无法验证应用程序默认凭据

python - 子进程超时 : What to do after TimeoutExpired Exception?

bash nohup 有两个命令

linux - .sh 文件中的 Tar 搜索结果

regex - 转义sed bash脚本变量中的问号字符

linux - 粉碎脚本并输出

linux - 包含特定字符串的 grep 行(一行最多可以写 3 行)

c++ - 执行 bash shell 命令并提取输出 --> 无效文件错误

Java - 在 Centos 中执行 .sh 文件