关于bash:shell脚本对编码和行尾敏感吗?

Are shell scripts sensitive to encoding and line endings?

我正在Mac上制作一个nw.js应用程序,想通过双击一个图标以dev模式运行该应用程序。第一步,我正在努力使我的shell脚本工作。

使用Windows上的vscode(我想获得时间),我在项目根目录下创建了一个run-nw文件,其中包含:

1
2
3
4
5
6
7
#!/bin/bash

cd"src"
npm install

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

但我得到了这个输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ 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)中,我试图用
    替换

    (以防
    造成问题),但没有改变。

  • 似乎找不到文件夹(有或没有dirname指令),或者可能不知道cd命令?
  • 似乎它不理解installnpm的论点。
  • 真正让我吃惊的是,它仍然在运行这个应用程序(如果我手动执行npm install)…

无法使其正常工作,并且怀疑文件本身有什么奇怪的地方,我直接在Mac上创建了一个新的文件,这次使用的是vim。我输入了完全相同的指令,而且…现在它可以毫无问题地工作了。两个文件上的差异正好显示零差异。

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

更新

根据被接受的答案的建议,在错误的行尾返回后,我检查了多个东西。结果发现,由于我从Windows机器上复制了我的~/.gitconfig,我有了autocrlf=true,所以每次我在Windows下修改bash文件时,它都会将行尾重新设置为

。因此,除了运行dos2unix(必须使用Mac上的homebrew安装)之外,如果您使用的是git,请检查配置。


对。bash脚本对行尾敏感,无论是在脚本本身还是在它处理的数据中。它们应该有Unix风格的行尾,即每行以换行符(十进制10,十六进制0A,ASCII)结尾。好的。脚本中的DOS/Windows行尾

对于Windows或DOS样式的行尾,每行都以回车符结尾,后跟换行符。如果使用Windows行尾保存脚本文件,bash将文件视为好的。

1
2
3
4
5
6
7
#!/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表示回车字符(在其他情况下表示为
),这与cat -v和vim使用的技术相同。好的。

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

  • 由于没有名为^M的命令,所以它打印: command not found
  • 由于没有名为"src"^Msrc^M的目录,所以打印: No such file or directory
  • 它通过install^M而不是install作为npm的理由,导致npm抱怨。

输入数据中的DOS/Windows行尾

如上所述,如果您有一个回车的输入文件:好的。

1
2
hello^M
world^M

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

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

附加文本将覆盖行,因为回车将光标移动到行的开头:好的。

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

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

1
2
3
4
5
6
7
$ 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程序来完成:好的。

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

    1
    :set fileformat=unix
  • 如果您有支持-i--in-place选项的sed实用程序版本,例如gnu sed,则可以运行以下命令来删除尾随回车:好的。

    1
    2
    sed -i 's/
    $//'
    filename

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

    1
    2
    sed 's/
    $//'
    filename > filename.unix
  • 同样,可以使用tr转换过滤器从其输入中删除不需要的字符:好的。

    1
    2
    tr -d '
    '
    <filename >filename.unix
  • 塞文温巴什

    对于cygwin的bash端口,有一个自定义的igncr选项,可以设置为忽略行尾的回车(可能是因为它的许多用户使用本机Windows程序编辑文本文件)。设置此选项适用于当前的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行尾之间转换文本文件而编写的。好的。有用的链接

    维基百科有一篇优秀的文章,涵盖了标记一行文本结尾的多种不同方式、此类编码的历史以及新行如何在不同的操作系统、编程语言和互联网协议(如ftp)中处理。好的。带有经典Mac OS行结尾的文件

    对于经典的mac os(pre-os x),每行以回车(十进制13,十六进制0d,ASCII)结束。如果用这样的行尾保存脚本文件,bash只能看到一行这样的长行:好的。

    1
    #!/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

    由于这一长行以octothorpe(#开头),bash将这一行(以及整个文件)视为单个注释。好的。

    注:2001年,苹果推出了基于BSD衍生的NextStep操作系统的MacOSX。因此,OSX也使用了Unix风格的LF-only行结尾,从那时起,以CR结尾的文本文件变得非常罕见。不过,我认为值得展示bash如何尝试解释这些文件。好的。好啊。


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

    1
    2
    $ tr -d '
    '
    < dosScript.py > nixScript.py


    在JetBrains产品(Pycharm、Phpsterm、Idea等)上,您需要在CRLF/LF上使用click在两种类型的行分隔符(


    之间切换。

    enter image description hereenter image description here


    来自一个副本,如果问题是文件的名称在末尾包含^M,则可以用

    1
    2
    3
    4
    5
    for f in *$'
    '
    ; do
        mv"$f""${f%$'
    '}"

    done

    您首先要正确地修复导致这些文件名称损坏的原因(可能创建这些文件的脚本应该是dos2unixed,然后重新运行?)但有时这是不可行的。


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