提问人:thomasb 提问时间:9/16/2016 最后编辑:Henkethomasb 更新时间:6/10/2023 访问量:64353
shell 脚本对编码和行尾敏感吗?
Are shell scripts sensitive to encoding and line endings?
问:
我正在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
- 似乎找不到文件夹
(有或没有指示),
或者也许它不知道命令?
dirname
cd
- 似乎它不明白 .
install
npm
- 真正让我感到奇怪的部分是它仍然运行该应用程序
(如果我手动做了一个)...
npm install
无法使其正常工作,并怀疑有什么奇怪的东西
文件本身,我直接在 Mac 上创建了一个新文件,这次使用 vim。
我输入了完全相同的说明,然后......现在它无需任何
问题。
两个文件上的 A 显示差异正好为零。diff
有什么区别?什么会使第一个脚本不起作用?我怎样才能知道?
更新
按照已接受的答案的建议,在错误的行之后
结局回来了,我检查了很多东西。
事实证明,自从我从我的 Windows 复制了我的
机器,我有,所以每次我修改 bash
文件,它将行尾重新设置为 。
因此,除了跑步(您还必须
在 Mac 上使用 Homebrew 安装),如果您使用的是 Git,请检查您的文件。~/.gitconfig
autocrlf=true
\r\n
dos2unix
.gitconfig
答:
是的。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"^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 样式的行尾。有多种方法可以实现此目的:
这可以使用以下程序完成:
dos2unix
dos2unix filename
在功能强大的文本编辑器(Sublime、Notepad++,而不是记事本)中打开文件,并将其配置为保存带有 Unix 行尾的文件,例如,使用 Vim,在(重新)保存之前运行以下命令:
:set fileformat=unix
如果您有一个支持 or 选项的实用程序版本,例如 GNU ,则可以运行以下命令来去除尾随回车符:
sed
-i
--in-place
sed
sed -i 's/\r$//' filename
对于 的其他版本,可以使用输出重定向来写入新文件。请确保对重定向目标使用不同的文件名(以后可以重命名)。
sed
sed 's/\r$//' filename > filename.unix
同样,翻译过滤器可用于从其输入中删除不需要的字符:
tr
tr -d '\r' <filename >filename.unix
Cygwin Bash
使用 Cygwin 的 Bash 端口,可以设置一个自定义选项来忽略行尾的回车符(可能是因为它的许多用户使用本机 Windows 程序来编辑他们的文本文件)。
可以通过运行 为当前 shell 启用此功能。igncr
set -o igncr
设置此选项仅适用于当前 shell 进程,因此在获取具有无关回车符的文件时,它非常有用。如果您经常遇到带有 DOS 行结尾的 shell 脚本,并且希望永久设置此选项,则可以设置一个名为(全部大写字母)的环境变量来包含 .Bash 使用此环境变量在启动时(在读取任何启动文件之前)设置 shell 选项。SHELLOPTS
igncr
实用程序
该实用程序可用于快速查看文本文件中使用了哪些行尾。以下是它为每种文件类型打印的内容: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 如何尝试解释此类文件。
评论
\r
igncr
去除不需要的 CR ('\r') 字符的另一种方法是运行命令,例如:tr
$ tr -d '\r' < dosScript.py > nixScript.py
评论
tr -d '\r' < myFile > myFile
myFile
< infile > outFile
infile
outfile
tr
tr x y <inputfile
tr x y inputfile
)
在 JetBrains 产品(PyCharm、PHPStorm、IDEA 等)上,您需要单击 / 以在两种类型的行分隔符( 和 )之间切换。CRLF
LF
\r\n
\n
评论
MAC / Linux上最简单的方法 - 使用“touch”命令创建一个文件,使用VI或VIM编辑器打开此文件,粘贴代码并保存。这将自动删除 Windows 字符。
评论
touch
是一个程序
来自重复项,如果问题是您的文件名称末尾包含,则可以使用^M
for f in *$'\r'; do
mv "$f" "${f%$'\r'}"
done
您首先想要修复导致这些文件名称损坏的原因(可能是创建它们的脚本应该被编辑然后重新运行?),但有时这是不可行的。dos2unix
语法是特定于 Bash 的;如果你有一个不同的 shell,也许你需要使用一些其他的符号。也许另请参阅 sh 和 bash 之间的区别$'\r'
评论
我试图从 Windows 启动我的 docker 容器并得到这个:
Bash script and /bin/bash^M: bad interpreter: No such file or directory
我正在使用 git bash,问题出在 git 配置上,然后我只是执行了以下步骤,它起作用了。它将 Git 配置为在结账时不转换行尾:
git config --global core.autocrlf input
- 删除本地存储库
- 再次克隆它。
非常感谢 Jason Harmon 在此链接中: https://forums.docker.com/t/error-while-running-docker-code-in-powershell/34059/6
在此之前,我试过这个,但不起作用:
dos2unix scriptname.sh
sed -i -e 's/\r$//' scriptname.sh
sed -i -e 's/^M$//' scriptname.sh
评论
当我将 git 与 WSL 一起使用时,我遇到了这个问题。
git 有一个功能,它可以根据您使用的操作系统更改文件的行尾,在 Windows 上,它确保行尾与仅使用 .\r\n
\n
您可以通过向 git 根目录添加文件名并添加行来解决此问题,如下所示:.gitattributes
config/* text eol=lf
run.sh text eol=lf
在此示例中,目录中的所有文件都只有换行行结束符和文件。config
run.sh
评论
sed
tr
如果您使用的是像 BBEdit 这样的文本编辑器,则可以在状态栏上执行此操作。有一个选项可以切换。
为了完整起见,我将指出另一种解决方案,它可以永久解决此问题,而无需一直运行dos2unix:
sudo ln -s /bin/bash `printf 'bash\r'`
评论
由于使用了 VS Code,我们可以在右下角看到 CRLF 或 LF,具体取决于正在使用的内容,如果我们单击它,我们可以在它们之间进行更改(下面的示例中使用了 LF):
我们还可以使用命令托盘中的“更改行尾序列”命令。任何更容易记住的东西,因为它们在功能上是相同的。
评论
如果您使用该命令从 DOS/Windows 格式的文件(或管道)中读取(或可能是),您可以利用从行的开头和结尾修剪空格的事实。如果你告诉它回车是空格(通过将它们添加到变量中),它将从行尾修剪它们。read
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
对于 IntelliJ 用户,这里是编写 Linux 脚本的解决方案。
使用 LF - Unix 和 masOS (\n)
脚本可以相互调用。 一个更好的魔术解决方案是转换文件夹/子文件夹中的所有脚本:
find . -name "*.sh" -exec sed -i -e 's/\r$//' {} +
您也可以使用,但许多服务器默认没有安装它。dos2unix
对于用户来说,这可以通过以下方式解决:Notepad++
评论
由于这个问题,我已经多次损坏了 bash 脚本。
已经发布了许多关于如何更改文件的解决方案。 不过,我在内置的 vim 方法上没有看到任何可以完成此任务的内容。
使用 shell 脚本打开 vim 并运行此命令
:set ff=unix
然后编辑您的 .git属性以获得永久修复
评论
:set fileformat=unix
大量引用 git,但不是重新规范化行尾。只需转到存储库的根目录并运行:
git add --renormalize .
只有需要刷新行结尾的文件才会重新检入。文件似乎没有变化,因为行尾是不可见的。
评论
sh -x ./run-nw
LC_ALL=C cat -vet /path/to/file
dos2unix
tr
tr
无法使用 BOM 修复 UTF-8(无论如何这都是可憎的);也许另请参阅 stackoverflow.com/a/38038099/4957508 了解背景和 stackoverflow.com/questions/45240387/...,了解如何删除它。至少有某些版本可以解决这个问题,但我想不是全部。dos2unix