shell 脚本对编码和行尾敏感吗?
Posted
技术标签:
【中文标题】shell 脚本对编码和行尾敏感吗?【英文标题】:Are shell scripts sensitive to encoding and line endings? 【发布时间】:2021-09-09 19:30:21 【问题描述】:我正在 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
)...
无法使其正常工作,并且怀疑文件本身有些奇怪,我直接在 Mac 上创建了一个新文件,这次使用 vim。我输入了完全相同的说明,然后……现在它可以正常工作了。 两个文件的差异显示完全零差异。
有什么区别?什么会使第一个脚本不起作用?我怎样才能知道?
更新
按照接受的答案的建议,在错误的行尾出现后,我检查了多项内容。原来,由于我从我的 Windows 机器上复制了我的~/.gitconfig
,所以我有autocrlf=true
,所以每次我在 Windows 下修改 bash 文件时,它都会将行尾重新设置为\r\n
。
因此,除了运行 dos2unix
(您必须在 Mac 上使用 Homebrew 安装)之外,如果您使用的是 Git,请检查您的配置。
【问题讨论】:
如果你在 Linux 上运行一个 shell 脚本,至少到目前为止我遇到的所有 shell 实现,如果他们在某个地方发现了一个 \r,就会感到不安。不,你说你已经删除了\r,我希望你证实它们真的消失了。为了安全起见,您应该以十六进制级别查看文件,以确保其中没有其他奇怪的字符。下一步是使用sh -x ./run-nw
执行脚本,以获取更多信息。
另一个在文本文件中查找奇怪字符的好命令是LC_ALL=C cat -vet /path/to/file
。如果文件是正常的,它会看起来很正常(除了每行末尾有一个“$”)。任何异常都应该很好地突出。 DOS/Windows 文件的行尾将有“^M$”。
你不需要安装dos2unix
; tr
命令就足够了,它是标准操作系统安装的一部分。下面的答案之一显示了如何使用它,并且可能值得更多的支持。
dd
中还有一个功能可以执行此 IIRC,但可以说它太晦涩无法给出答案。
tr
无法使用 BOM 修复 UTF-8(无论如何这都是可憎的);也许另请参阅***.com/a/38038099/4957508 了解背景和***.com/questions/45240387/… 了解如何删除它。至少 一些 版本的dos2unix
可以解决这个问题,但我猜不是全部。
【参考方案1】:
对于 IntelliJ 用户,这里是编写 Linux 脚本的解决方案。 使用 LF - Unix 和 masOS (\n)
【讨论】:
【参考方案2】:我在使用 git 和 WSL 时遇到了这个问题。
git 有一个功能,它可以根据您使用的操作系统更改文件的行尾,在 Windows 上,它确保行尾为 \r\n
,这与仅使用 \n
的 Linux 不兼容。
您可以通过将文件名.gitattributes
添加到您的 git 根目录并添加以下行来解决此问题:
config/* text eol=lf
run.sh text eol=lf
在此示例中,config
目录中的所有文件都将只有换行符换行符和run.sh
文件。
【讨论】:
这是一个非常聪明的解决方案,无需额外的sed
s、tr
s 和类似的代码给后续代码带来负担。 Notepad++ 或 Idea 等优秀的文本编辑器不会将其转回 crlfs(如果他们这样做了,它将在 git commit 时出现)。【参考方案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 不支持 $'...'
引用模式(例如 dash,某些 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
命令的前缀,所以它只影响那个命令,之后不必重新设置。
【讨论】:
【参考方案4】:由于正在使用 VS Code,我们可以在右下角看到 CRLF 或 LF,具体取决于所使用的内容,如果我们单击它,我们可以在它们之间进行更改(在下面的示例中使用了 LF):
我们还可以使用命令托盘中的“更改行尾顺序”命令。因为它们的功能相同,所以更容易记住。
【讨论】:
【参考方案5】:为了完整起见,我指出another solution可以永久解决这个问题,而无需一直运行dos2unix:
sudo ln -s /bin/bash `printf 'bash\r'`
【讨论】:
虽然这适用于 python,但它通常不适用于 bash,因为 bash 默认情况下在 IFS 中不包含 \r (因此它将被视为真实字符而不是空格)所以 \ shebang 行以外的 r 字符仍然会导致问题【参考方案6】:如果您使用的是 BBEdit 之类的文本编辑器,您可以在状态栏中进行操作。有一个可以切换的选项。
【讨论】:
【参考方案7】:我试图从 Windows 启动我的 docker 容器并得到了这个:
Bash script and /bin/bash^M: bad interpreter: No such file or directory
我使用的是 git bash,问题出在 git config 上,然后我只执行了以下步骤,它就成功了。它将配置 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
【讨论】:
我有同样的问题,dos2unix 没有缓解。你的命令有效!非常感谢! 谢谢你的回答,这个答案一定有更多的投票权,这个命令对我也很有效【参考方案8】:是的。 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 样式的行尾。有多种方法可以实现:
这可以使用dos2unix
程序来完成:
dos2unix filename
在有能力的文本编辑器(Sublime、Notepad++,而不是 Notepad)中打开文件并将其配置为以 Unix 行结尾保存文件,例如,使用 Vim,在 (re) 之前运行以下命令保存:
:set fileformat=unix
如果您有支持-i
或--in-place
选项的sed
实用程序版本,例如GNU sed
,您可以运行以下命令来去除尾随回车符:
sed -i 's/\r$//' filename
对于其他版本的sed
,您可以使用输出重定向来写入新文件。确保为重定向目标使用不同的文件名(以后可以重命名)。
sed 's/\r$//' filename > filename.unix
同样,tr
翻译过滤器可用于从其输入中删除不需要的字符:
tr -d '\r' <filename >filename.unix
Cygwin Bash
对于 Cygwin 的 Bash 端口,有一个自定义的 igncr
选项可以设置为忽略行尾的回车(可能是因为它的许多用户使用本地 Windows 程序来编辑他们的文本文件)。
这可以通过运行 set -o igncr
为 current shell 启用。
设置此选项仅适用于 当前 shell 进程,因此在 采购 带有无关回车符的文件时很有用。如果您经常遇到带有 DOS 行尾的 shell 脚本并希望永久设置此选项,您可以设置一个名为 SHELLOPTS
(全大写字母)的环境变量以包含 igncr
。 Bash 在启动时(在读取任何启动文件之前)使用此环境变量设置 shell 选项。
实用工具
file
实用程序可用于快速查看文本文件中使用了哪些行结尾。以下是它为每种文件类型打印的内容:
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,涵盖了标记文本行结尾的多种不同方式、此类编码的历史以及在不同操作系统、编程语言和互联网协议(例如,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 将如何尝试解释此类文件。
【讨论】:
dos2unix 为我完成了这项工作。感谢您节省了数百万小时。 很好的解释,这里只少了一点:这些天有什么真正的理由让真正的 bash 继续治疗 @987654368 @作为行尾有意义的字符? @AlexCohn 没有令人信服的功能原因,但更改此行为可能会破坏现有脚本。我敢肯定这一定是维护者多次提出并拒绝的。如果你能设计一个好的过渡计划,让它现在是可选的,将来是强制性的,它可能会获得一些支持;但我可以预测很多老前辈会告诉你“教年轻人不要使用 Windows 编辑器”。 感谢@tripleee 的澄清。我已经开始研究 Alex 的问题的答案,并打算使用带有igncr
选项集的 Cygwin Bash 来试验脚本,但我已经很长时间没有方便地访问 Windows 操作系统了。
@AlexCohn It isn't bash, it's the Linux kernel.【参考方案9】:
来自重复文件,如果问题是您的文件的名称 末尾包含^M
,您可以将它们重命名为
for f in *$'\r'; do
mv "$f" "$f%$'\r'"
done
您确实希望首先修复导致这些文件名称损坏的任何原因(可能创建它们的脚本应该是 dos2unix
ed 然后重新运行?)但有时这是不可行的。
$'\r'
语法是 Bash 特有的;如果您有不同的外壳,也许您需要使用其他符号。或许也可以看看Difference between sh and bash
【讨论】:
我没有遇到过这个问题,但是考虑到很多用户会从重复的问题中来到这里,这个答案值得更多的关注。我赞成它开始在答案列表中移动。【参考方案10】:在 JetBrains 产品(PyCharm、phpStorm、IDEA 等)上,您需要单击 CRLF
/LF
以切换 在两种类型的行分隔符(\r\n
和 \n
)之间。
【讨论】:
在 Windows 上的 IntelliJ 上,打开设置 (Ctrl+Alt+S) |编辑 |代码风格。在右侧选择 Unix and macOS (\n) 作为 Line Separator。这是更改每个文件设置的替代方法。【参考方案11】:MAC/Linux 上最简单的方法 - 使用“touch”命令创建文件,使用 VI 或 VIM 编辑器打开此文件,粘贴代码并保存。这将自动删除 windows 字符。
【讨论】:
这不是最简单的方法,也不一定会删除 Windows 字符,它们是有效字符。 没错,但在 vi/vim 中复制/粘贴并不是我所说的“最简单”的方法:D 不过,我不会反对。 同意,对于像我这样不是 shell 脚本专家的人来说,这是一种生活黑客 :)touch
是一个程序【参考方案12】:
去除不需要的 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
)这样的重定向以上是关于shell 脚本对编码和行尾敏感吗?的主要内容,如果未能解决你的问题,请参考以下文章