shell脚本对编码和行尾是否敏感?

bash shell sh


我正在Mac上制作一个NW.js应用程序,想通过双击图标在dev模式下运行该应用程序。第一步,我想让我的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)中,我尝试用 \n 替换 \r\n \ n(以防 \r 产生问题),但是它什么也没有改变。
  • 似乎找不到文件夹(带或不带 dirname 指令),或者它不知道 cd 命令?
  • 似乎不了解 npminstall 参数
  • 真正让我感到奇怪的部分是,它仍然可以运行该应用程序(如果我手动执行 npm install )...

由于无法正常工作,我怀疑文件本身有问题,所以这次我直接在Mac上创建了一个新的,用vim。我输入了完全相同的说明,然后...........现在它没有任何问题地工作了。
对这两个文件进行对比,可以看出两者的差异性正好为零。

哪些方面会有差异?是什么原因导致第一个脚本无法正常运行?如何才能发现呢?

Update

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




Answer 1 Anthony Geoghegan


是。Bash脚本在脚本本身及其处理的数据中对行尾敏感。它们应具有Unix样式的行尾,即,每行以换行符(十进制10,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

Solutions

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

  1. 这可以使用 dos2unix 程序完成:

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

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

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

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

    sed 's/\r$//' filename > filename.unix
  4. 同样,可以使用 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的一篇出色的文章涵盖了标记文本行结尾的多种不同方式,这种编码的历史以及在不同的操作系统,编程语言和Internet协议(例如FTP)中如何处理换行符。

具有经典Mac OS行尾的文件

使用Classic Mac OS(pre-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如何尝试解释此类文件。




Answer 2 CONvid19


在JetBrains产品(PyCharm,PHPStorm,IDEA等)上,您需要clickCRLF / LF切换两种类型的行分隔符( \r\n\n )。

enter image description here enter image description here




Answer 3 Igor Soudakevitch


消除不需要的CR('\ r')字符的 tr 一种方法是运行tr命令,例如:

$ tr -d '\r' < dosScript.py > nixScript.py



Answer 4 tripleee


来自重复项,如果问题是文件的末尾包含 ^M ,则可以使用

for f in *$'\r'; do
    mv "$f" "${f%$'\r'}"
done

您应该适当地修复导致这些文件的名称最初损坏的原因(可能是创建了文件的脚本应该先进行 dos2unix ed,然后重新运行?),但有时这不可行。

$'\r' 的语法,这是Bash特有的;如果您使用其他外壳,则可能需要使用其他符号。也许也看到sh和bash之间的区别




Answer 5 danR


最简单的方法是在MAC/Linux上--使用 "touch "命令创建一个文件,用VI或VIM编辑器打开这个文件,粘贴你的代码并保存。这将自动删除windows字符。