我正在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
命令吗?似乎无法理解
install
的npm
参数真正令我感到奇怪的部分是,它仍然可以运行该应用程序(如果我手动执行了
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/