Shell脚本对编码和行尾符敏感吗?
Shell脚本对编码和行尾符敏感吗?
我正在macOS上制作一个NW.js应用程序,并希望通过双击图标在开发模式下运行应用程序。
首先,我正在尝试使我的shell脚本工作。
在Windows上使用VS Code(我想要节省时间),我在项目的根目录中创建了一个名为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: npmwhere 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
有些事情我不理解。
- 它似乎将空行视为命令。
在我的编辑器(VS Code)中,我尝试将
\\r\\n
替换为\\n
(以防
\\r
会引起问题),但没有改变任何内容。 - 它似乎找不到文件夹
(带有或不带有
dirname
指令),或者可能它不知道
cd
命令? - 它似乎不理解
npm
命令的install
参数。 - 最让我感到奇怪的是,它仍然会运行应用程序
(如果我手动执行了
npm install
)...
无法使其正常工作,并怀疑该文件本身存在一些奇怪的问题,我这次直接在Mac上使用vim创建了一个新文件。
我输入了完全相同的指令,... 现在它可以完美地工作了。
对两个文件进行diff
比较,没有任何不同。
可能的区别是什么?是什么造成第一个脚本无法工作?我该如何找出原因?
更新
根据被接受的答案建议,在错误的行结尾回来后,我进行了多次检查。
事实证明,由于我从Windows机器复制了~/.gitconfig
,
所以我有autocrlf=true
,因此每次在Windows下修改bash文件时,
它都会重新设置行结尾为\\r\\n
。
因此,除了运行dos2unix
(您需要在Mac上使用Homebrew安装)之外,
如果您正在使用Git,请检查您的.gitconfig
文件。
是的。Bash脚本对于行尾非常敏感,无论是脚本本身还是处理的数据。它们应该使用Unix风格的行尾,即每行都以一个换行字符(十进制10,ASCII中的0A)结尾。
脚本中的DOS/Windows行尾
使用Windows或DOS风格的行尾,每行都以回车符后跟一个换行字符终止。您可以在cat -v yourfile
的输出中看到这个前面是不可见的字符:
$ cat -v yourfile #!/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
或在C转义符表示法中为\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 -d '\r'
filename.unix
Cygwin Bash
使用针对Cygwin的Bash端口时,有一个自定义的igncr
选项可以设置为忽略行末的回车符(可能是因为许多用户使用本机Windows程序编辑文本文件)。 可以通过运行set -o igncr
以启用当前shell对此选项的设置。
设置此选项仅适用于当前shell进程,因此在处理具有无关回车符的文件时,它可能很有用。 如果您经常遇到带有DOS行结束符的Shell脚本并希望永久设置此选项,则可以设置一个名为SHELLOPTS
(所有大写字母)的环境变量以包括igncr
。 Bash在启动时使用此环境变量设置Shell选项(在读取任何启动文件之前)。
有用的工具
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行结束符之间转换文本文件。
有用的链接
维基百科有一篇出色的文章,介绍了标记文本行结束的许多不同方式,这些编码的历史以及如何在不同的操作系统,编程语言和Internet协议(例如FTP)中处理换行符。
具有经典Mac OS行结束符的文件
使用Classic Mac OS(OS X之前),每行都以回车符(十进制13,ASCII中的十六进制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推出了基于BSD派生的NeXTSTEP操作系统的Mac OS X。 因此,OS X也使用Unix风格的LF-only行结束符,自那时以来,以CR结尾的文本文件变得极为罕见。 尽管如此,我认为展示Bash如何尝试解释这些文件仍然是有价值的。