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

时间:2016-09-16 09:05:15

标签: bash shell sh

我正在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,请检查您的配置。

11 个答案:

答案 0 :(得分:47)

是。 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

解决方案

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

  1. 可以使用dos2unix程序完成此操作:

    dos2unix filename
    
  2. 功能文本编辑器(Sublime,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
    
  5. Cygwin bash

    使用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行结尾之间转换文本文件。

    有用的链接

    维基百科有excellent article涵盖了标记文本行结尾的许多不同方式,此类编码的历史以及如何在不同的操作系统,编程语言和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
    

    由于此单行长行以octothorpe(#)开头,因此Bash将行(和整个文件)视为单个注释。

    注意:2001年,Apple推出了基于BSD派生的NeXTSTEP操作系统的Mac OS X.因此,OS X也使用Unix风格的LF专线结束,从那时起,以CR终止的文本文件变得非常罕见。尽管如此,我认为值得展示Bash如何尝试解释这些文件。

答案 1 :(得分:3)

如果您使用 read 命令读取(或可能)DOS/Windows 格式的文件(或管道),您可以利用read 会从行的开头和结尾修剪空白。如果你告诉它回车是空格(通过将它们添加到 IFS 变量),它会从行尾修剪它们。

在 bash(或 zsh 或 ksh)中,这意味着您将替换此标准习语:

IFS= read -r somevar    # This will not trim CR

这样:

IFS=$'\r' read -r somevar    # This *will* trim CR

(注意:-r 选项与此无关,这通常是避免修改反斜杠的好主意。)

如果您不使用 IFS= 前缀(例如,因为您想将数据拆分为字段),那么您可以替换:

read -r field1 field2 ...    # This will not trim CR

这样:

IFS=$' \t\n\r' read -r field1 field2 ...    # This *will* trim CR

如果您使用的 shell 不支持 $'...' 引用模式(例如破折号,某些 Linux 发行版上的默认 /bin/sh),或者您的脚本甚至可能 用这样的 shell 运行,那么你需要稍微复杂一点:

cr="$(printf '\r')"
IFS="$cr" read -r somevar    # Read trimming *only* CR
IFS="$IFS$cr" read -r field1 field2 ...    # Read trimming CR and whitespace, and splitting fields

注意,正常情况下,当你改变IFS时,你应该尽快把它恢复正常,以免出现奇怪的副作用;但在所有这些情况下,它都是 read 命令的前缀,因此它只影响那个命令,之后不必重新设置。

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

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

enter image description here enter image description here

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

对其进行重命名
for f in *$'\r'; do
    mv "$f" "${f%$'\r'}"
done

您应该正确地解决导致这些文件最初名称损坏的原因(可能创建脚本的脚本应该dos2unix然后重新运行?),但是有时这是不可行的。

答案 6 :(得分:0)

我试图从Windows启动我的docker容器并得到了这个信息:

Bash script and /bin/bash^M: bad interpreter: No such file or directory

我使用的是git bash,问题出在git的配置上,然后我按照下面的步骤操作就可以了。它将配置Git在结帐时不转换行尾:

  1. git config --global core.autocrlf input
  2. 删除本地存储库
  3. 再次克隆它。

非常感谢Jason Harmon在此链接中: https://forums.docker.com/t/error-while-running-docker-code-in-powershell/34059/6

在此之前,我尝试了一下,但是没有用:

  1. dos2unix scriptname.sh
  2. sed -i -e 's/\r$//' scriptname.sh
  3. sed -i -e 's/^M$//' scriptname.sh

答案 7 :(得分:0)

我在WSL中使用git时遇到了这个问题。 git具有一项功能,可以根据您使用的操作系统更改文件的行尾,在Windows上,请确保行尾为/r/n,这与仅使用/n的Linux不兼容。

您可以通过在git根目录中添加文件名.gitattributes并添加以下行来解决此问题:

config/* text eol=lf
run.sh text eol=lf

在此示例中,config目录内的所有文件将仅具有换行符结尾,并且run.sh文件也将具有换行符。

答案 8 :(得分:0)

如果使用的是BBEdit之类的文本编辑器,则可以在状态栏上进行操作。您可以在其中进行选择。

Change the CRLF to LF using BBEdit

答案 9 :(得分:0)

出于完整性考虑,我将指出another solution,它可以永久解决此问题,而无需一直运行dos2unix:

sudo ln -s /bin/bash `printf 'bash\r'`

答案 10 :(得分:0)

由于正在使用 VS Code,我们可以在右下角看到 CRLF 或 LF,具体取决于所使用的内容,如果我们点击它,我们可以在它们之间进行更改(以下示例中使用的是 LF):

Screenshot of shortcut UI

我们还可以使用命令托盘中的“更改行尾序列”命令。哪个更容易记住,因为它们在功能上是相同的。