Shell脚本对编码和行尾符敏感吗?

24 浏览
0 Comments

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: npm 
where  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文件。

admin 更改状态以发布 2023年5月24日
0
0 Comments

在 JetBrains 的产品(如 PyCharm、PHPStorm、IDEA 等)中,您需要在 CRLF / LF 点击 以在两种行分隔符( \r\n \n )之间切换

enter image description here
enter image description here

0
0 Comments

是的。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风格的行尾。有多种方法可以实现此目的:

  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 -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如何尝试解释这些文件仍然是有价值的。

0