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

Are shell scripts sensitive to encoding and line endings?

提问人:thomasb 提问时间:9/16/2016 最后编辑:Henkethomasb 更新时间:6/10/2023 访问量:64353

问:

我正在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 <command>

where <command> is one of:  (snip commands list)

(snip npm help)

[email protected] /usr/local/lib/node_modules/npm  
: command not found  
: No such file or directory  
: command not found

有些事情我不明白。

  • 似乎它以空行作为命令。 在我的编辑器(VS Code)中,我试图替换为(以防万一产生问题),但它什么也没改变。\r\n\n\r
  • 似乎找不到文件夹 (有或没有指示), 或者也许它不知道命令?dirnamecd
  • 似乎它不明白 .installnpm
  • 真正让我感到奇怪的部分是它仍然运行该应用程序 (如果我手动做了一个)...npm install

无法使其正常工作,并怀疑有什么奇怪的东西 文件本身,我直接在 Mac 上创建了一个新文件,这次使用 vim。 我输入了完全相同的说明,然后......现在它无需任何 问题。
两个文件上的 A 显示差异正好为零。
diff

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

更新

按照已接受的答案的建议,在错误的行之后 结局回来了,我检查了很多东西。 事实证明,自从我从我的 Windows 复制了我的 机器,我有,所以每次我修改 bash 文件,它将行尾重新设置为 。
因此,除了跑步(您还必须 在 Mac 上使用 Homebrew 安装),如果您使用的是 Git,请检查您的文件。
~/.gitconfigautocrlf=true\r\ndos2unix.gitconfig

bash sh 换行符 Carriage-Return 换行符

评论

2赞 user1934428 9/16/2016
如果你在 Linux 上运行一个 shell 脚本,至少到目前为止我遇到的所有 shell 实现,如果它们在某处找到一个 \r,它们都会感到不安。不,你说你已经删除了 \r,我希望你验证它们真的消失了。为了安全起见,您应该在十六进制级别查看您的文件,以确保其中没有其他奇怪的字符。下一步是使用 执行脚本以获取更多信息。sh -x ./run-nw
1赞 Gordon Davisson 9/17/2016
在文本文件中查找奇怪字符的另一个好命令是 。如果文件正常,则它看起来正常(除了每行末尾有一个“$”)。任何异常都应该相当突出。DOS/Windows 文件的行尾将有“^M$”。LC_ALL=C cat -vet /path/to/file
2赞 tripleee 8/24/2021
你不需要安装;该命令就足够了,并且是标准操作系统安装的一部分。下面的答案之一显示了如何使用它,可能值得更多的赞成票。dos2unixtr
1赞 tripleee 9/9/2021
tr无法使用 BOM 修复 UTF-8(无论如何这都是可憎的);也许另请参阅 stackoverflow.com/a/38038099/4957508 了解背景和 stackoverflow.com/questions/45240387/...,了解如何删除它。至少有某些版本可以解决这个问题,但我想不是全部。dos2unix
1赞 tripleee 1/11/2023
另请参阅 stackoverflow.com/questions/45772525/...,它更明确地表述为该主题的规范。当然,答案是相似的。

答:

133赞 Anthony Geoghegan 9/16/2016 #1

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

在这种情况下,回车符(在插入符号或 C 转义表示法中)不被视为空格。Bash 将 shebang 之后的第一行(由单个回车符组成)解释为要运行的命令/程序的名称。^M\r

  • 由于没有名为 的命令,因此它打印^M: command not found
  • 由于没有名为 (或 ) 的目录,因此它打印"src"^Msrc^M: No such file or directory
  • 它传递而不是作为引起抱怨的论据。install^Minstallnpmnpm

输入数据中的 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. 如果您有一个支持 or 选项的实用程序版本,例如 GNU ,则可以运行以下命令来去除尾随回车符:sed-i--in-placesed

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

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

    sed 's/\r$//' filename > filename.unix
    
  4. 同样,翻译过滤器可用于从其输入中删除不需要的字符:tr

    tr -d '\r' <filename >filename.unix
    

Cygwin Bash

使用 Cygwin 的 Bash 端口,可以设置一个自定义选项来忽略行尾的回车符(可能是因为它的许多用户使用本机 Windows 程序来编辑他们的文本文件)。 可以通过运行 为当前 shell 启用此功能。igncrset -o igncr

设置此选项仅适用于当前 shell 进程,因此在获取具有无关回车符的文件时,它非常有用。如果您经常遇到带有 DOS 行结尾的 shell 脚本,并且希望永久设置此选项,则可以设置一个名为(全部大写字母)的环境变量来包含 .Bash 使用此环境变量在启动时(在读取任何启动文件之前)设置 shell 选项。SHELLOPTSigncr

实用程序

该实用程序可用于快速查看文本文件中使用了哪些行尾。以下是它为每种文件类型打印的内容: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

该实用程序的 GNU 版本有一个显示非打印字符的选项。cat-v, --show-nonprinting

该实用程序专门用于在 Unix、Mac 和 DOS 行尾之间转换文本文件。dos2unix

相关链接

维基百科有一篇优秀的文章,涵盖了标记一行文本末尾的许多不同方法,这种编码的历史以及在不同的操作系统、编程语言和互联网协议(例如FTP)中如何处理换行符。

具有经典 Mac OS 行尾的文件

经典 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 如何尝试解释此类文件。

评论

0赞 Prasad Lakmal 2/13/2018
dos2unix 为我完成了这项工作。感谢您节省了数百万小时。
1赞 Alex Cohn 2/17/2019
很好的解释,这里只缺少一小块:这些天有什么真正的理由让真正的 bash 继续被视为行尾的有意义的角色吗?\r
2赞 tripleee 6/13/2019
@AlexCohn 没有令人信服的功能原因,但更改此行为可能会破坏现有脚本。我敢肯定,这一定是被维护者多次提出和拒绝的。如果你能设计一个好的过渡计划,让它现在成为可选的,将来是强制性的,它可能会获得一些支持;但我可以预测很多老前辈会告诉你“教年轻人不要使用 Windows 编辑器”。
0赞 Anthony Geoghegan 6/13/2019
感谢您的澄清,@tripleee.我已经开始研究 Alex 问题的答案,并打算使用带有选项集的 Cygwin Bash 试验脚本,但我已经很久没有方便地访问 Windows 操作系统了。igncr
1赞 Natan Yellin 11/23/2020
@AlexCohn 它不是 bash,而是 Linux 内核。
2赞 Igor Soudakevitch 3/3/2018 #2

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

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

评论

6赞 shellter 4/5/2018
应该注意的是,新用户可能会认为他们也可以这样做,这不是一个好主意,因为他们现在将被删除或至少被截断。使用重定向时,请始终对 和 使用不同的文件名。然后,您可以根据需要重命名。祝大家好运。tr -d '\r' < myFile > myFilemyFile< infile > outFileinfileoutfile
0赞 tripleee 11/14/2018
此外,它的不同寻常之处在于它拒绝接受文件名参数;您必须使用重定向,例如(不是trtr x y <inputfiletr x y inputfile)
14赞 Pedro Lobito 2/26/2019 #3

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

enter image description here enter image description here

评论

1赞 Paulo Merson 10/17/2019
在 Windows 上的 IntelliJ 上,打开“设置”(Ctrl+Alt+S) |编辑 |代码样式。在右侧,选择“Unix 和 macOS (\n)”作为“行分隔符”。这是更改每个文件的设置的替代方法。
0赞 danR 3/6/2019 #4

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

评论

1赞 thomasb 3/6/2019
这绝不是最简单的方法,也不一定会删除 Windows 字符,这些字符是有效的字符。
0赞 thomasb 3/8/2019
没错,但是在 vi/vim 中复制/粘贴并不是我所说的“最简单”:D不过,我会取消反对票。
0赞 danR 3/11/2019
同意,对于像我这样的人来说,这是一种生活小窍门,他们不是 shell 脚本方面的专家:)
0赞 mike3996 4/19/2019
touch是一个程序
3赞 tripleee 4/19/2019 #5

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

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

您首先想要修复导致这些文件名称损坏的原因(可能是创建它们的脚本应该被编辑然后重新运行?),但有时这是不可行的。dos2unix

语法是特定于 Bash 的;如果你有一个不同的 shell,也许你需要使用一些其他的符号。也许另请参阅 sh 和 bash 之间的区别$'\r'

评论

0赞 Anthony Geoghegan 6/5/2019
我没有遇到过这个问题,但鉴于许多用户会从重复的问题到达这里,这个答案值得更大的知名度。我赞成它开始在答案列表中移动。
6赞 dougparnoff 8/20/2020 #6

我试图从 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

评论

0赞 Papillon 4/13/2021
我有同样的问题,dos2unix 没有缓解。你的命令有效!非常感谢!
0赞 Tom Tran 11/13/2021
谢谢你的回答,这个答案必须有更多的投票,这个命令对我来说也很好用
0赞 Farkhod Abdukodirov 5/19/2023
它对我有所帮助,这是因为 Git bash 终端配置!非常感谢:)
3赞 Ido Ran 9/4/2020 #7

当我将 git 与 WSL 一起使用时,我遇到了这个问题。 git 有一个功能,它可以根据您使用的操作系统更改文件的行尾,在 Windows 上,它确保行尾与仅使用 .\r\n\n

您可以通过向 git 根目录添加文件名并添加行来解决此问题,如下所示:.gitattributes

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

在此示例中,目录中的所有文件都只有换行行结束符和文件。configrun.sh

评论

1赞 Tomáš Záluský 1/21/2021
这是一个非常聪明的解决方案,而不会给后续代码带来额外的 s、s 和类似负担。好的文本编辑器,如Notepad++或Idea,不会把它转回crlfs(如果他们这样做了,它会在git commit中出现)。sedtr
0赞 Rafael Parungao 10/15/2020 #8

如果您使用的是像 BBEdit 这样的文本编辑器,则可以在状态栏上执行此操作。有一个选项可以切换。

Change the CRLF to LF using BBEdit

-1赞 Natan Yellin 11/23/2020 #9

为了完整起见,我将指出另一种解决方案,它可以永久解决此问题,而无需一直运行dos2unix:

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

评论

2赞 Chris Dodd 1/7/2021
虽然这适用于 python,但通常不适用于 bash,因为 bash 默认在 IFS 中不包含 \r(因此它将被视为真实字符而不是空格),因此 shebang 行以外的 \r 字符仍然会导致问题
4赞 Jamie Smith 4/2/2021 #10

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

Screenshot of shortcut UI

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

评论

0赞 YGautomo 4/30/2023
谢谢,这是使用 Vs Code 的更简单的解决方案。
5赞 Gordon Davisson 5/20/2021 #11

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

在 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

请注意,正常情况下,当您更换时,您应该尽快将其恢复正常,以避免奇怪的副作用;但在所有这些情况下,它都是命令的前缀,因此它只影响该命令,之后不必重置。IFSread

0赞 Muhammad Tariq 11/18/2021 #12

对于 IntelliJ 用户,这里是编写 Linux 脚本的解决方案。
使用 LF - Unix 和 masOS (\n)

enter image description here

0赞 mercury 4/8/2022 #13

脚本可以相互调用。 一个更好的魔术解决方案是转换文件夹/子文件夹中的所有脚本:

find . -name "*.sh" -exec sed -i -e 's/\r$//' {} +

您也可以使用,但许多服务器默认没有安装它。dos2unix

3赞 Leandro Bardelli 5/6/2022 #14

对于用户来说,这可以通过以下方式解决:Notepad++

enter image description here

评论

1赞 tripleee 10/16/2022
首先,使用 Windows 编辑器通常是问题的根本原因。可能避免这种情况。
0赞 DieOde 5/24/2023 #15

由于这个问题,我已经多次损坏了 bash 脚本。

已经发布了许多关于如何更改文件的解决方案。 不过,我在内置的 vim 方法上没有看到任何可以完成此任务的内容。

使用 shell 脚本打开 vim 并运行此命令

:set ff=unix

然后编辑您的 .git属性以获得永久修复

评论

0赞 pierpy 5/25/2023
在可接受的答案中,有:。我认为这是一种内置功能。无论如何,最好包含有用的链接,支持您的内容。谢谢!:set fileformat=unix
2赞 GaTechThomas 6/10/2023 #16

大量引用 git,但不是重新规范化行尾。只需转到存储库的根目录并运行:

git add --renormalize .

只有需要刷新行结尾的文件才会重新检入。文件似乎没有变化,因为行尾是不可见的。