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 <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 命令? - 它似乎不理解
npm
的install
参数。 - 真正让我感到奇怪的是它仍然运行该应用程序 (如果我手动执行
npm install
)...
无法使其正常工作,并怀疑有什么奇怪的 文件本身,我直接在 Mac 上创建了一个新文件,这次使用的是 vim。 我输入了完全相同的指令,并且......现在它无需任何操作即可工作 问题。
两个文件的 diff
显示差异为零。
有什么区别?是什么导致第一个脚本无法工作?我怎样才能找到答案?
更新
按照接受的答案的建议,在错误的行之后 结局回来了,我检查了很多东西。 事实证明,自从我从 Windows 复制了 ~/.gitconfig
机器,我有 autocrlf=true
,所以每次我修改 bash Windows 下的文件,它会将行结尾重新设置为 \r\n
。
因此,除了运行 dos2unix
(您必须 在 Mac 上使用 Homebrew 安装),如果您使用 Git,请检查您的 .gitconfig
文件。
I am making an NW.js app on macOS, and want to run the app in dev mode
by double-clicking on an icon.
In the first step, I'm trying to make my shell script work.
Using VS Code on Windows (I wanted to gain time), I have created a run-nw
file at the root of my project, containing this:
#!/bin/bash
cd "src"
npm install
cd ..
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &
but I get this output:
$ 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
Some things I don't understand.
- It seems that it takes empty lines as commands.
In my editor (VS Code) I have tried to replace\r\n
with\n
(in case the\r
creates problems) but it changes nothing. - It seems that it doesn't find the folders
(with or without thedirname
instruction),
or maybe it doesn't know about thecd
command ? - It seems that it doesn't understand the
install
argument tonpm
. - The part that really weirds me out, is that it still runs the app
(if I did annpm install
manually)...
Not able to make it work properly, and suspecting something weird with
the file itself, I created a new one directly on the Mac, using vim this time.
I entered the exact same instructions, and... now it works without any
issues.
A diff
on the two files reveals exactly zero difference.
What can be the difference? What can make the first script not work? How can I find out?
Update
Following the accepted answer's recommendations, after the wrong line
endings came back, I checked multiple things.
It turns out that since I copied my ~/.gitconfig
from my Windows
machine, I had autocrlf=true
, so every time I modified the bash
file under Windows, it re-set the line endings to \r\n
.
So, in addition to running dos2unix
(which you will have to
install using Homebrew on a Mac), if you're using Git, check your.gitconfig
file.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
是的。 Bash 脚本对行结束敏感,无论是在脚本本身还是在它处理的数据中。它们应该具有 Unix 风格的行结尾,即每行以换行符(十进制 10,ASCII 中十六进制 0A)结束。
脚本中的 DOS/Windows 行结尾
对于 Windows 或 DOS 样式的行结尾,每行都以回车符后跟换行符结束。您可以在
cat -v yourfile
的输出中看到这个原本不可见的字符:在本例中,回车符(脱字符号中的
^M
或\r< C 转义符号中的 /code> )不被视为空格。 Bash 将 shebang 之后的第一行(由单个回车符组成)解释为要运行的命令/程序的名称。
^M
的命令,因此会打印: command not find
src^M
的目录,因此会打印:没有这样的文件或目录
install^M
而不是install
作为参数传递给npm
,这会导致npm
来抱怨。输入数据中的 DOS/Windows 行结尾
像上面一样,如果您有一个带有回车符的输入文件:
那么它在编辑器中以及将其写入屏幕时看起来完全正常,但工具可能会产生奇怪的结果。例如,
grep
将无法找到明显存在的行:(不匹配,因为该行实际上以
^M
结尾)附加文本似乎会覆盖该行,因为回车将光标移至行首:
即使字符串在写入屏幕时看起来相同,字符串比较也会失败:
解决方案
解决方案是将文件转换为使用 Unix 样式的行结尾。有多种方法可以实现此目的:
使用
dos2unix
程序:在可用文本编辑器(Sublime、Notepad++,而不是记事本)中打开文件,并将其配置为保存带有 Unix 行结尾的文件,例如,使用 Vim,在(重新)保存之前运行以下命令:
-i
或--in-place
选项的sed
实用程序版本,例如 GNUsed
,你可以运行以下命令去除尾随回车符:使用其他版本的
sed
,您可以使用输出重定向来写入新文件。请务必为重定向目标使用不同的文件名(稍后可以重命名)。同样,
tr
翻译过滤器可用于从其输入中删除不需要的字符:Cygwin Bash
通过 Cygwin 的 Bash 端口,有一个自定义
igncr
选项,可以设置为忽略行结尾中的回车符(大概是因为许多用户使用本机 Windows 程序来编辑其文本文件)。可以通过运行
set -o igncr
为当前 shell 启用此功能。设置此选项仅适用于当前 shell 进程,因此在获取带有无关回车符的文件时非常有用。如果您经常遇到以 DOS 行结尾的 shell 脚本,并且希望永久设置此选项,则可以设置一个名为
SHELLOPTS
(全部大写字母)的环境变量来包含igncr
。 Bash 使用此环境变量在启动时(在读取任何启动文件之前)设置 shell 选项。有用的实用程序
file
实用程序对于快速查看文本文件中使用了哪些行结尾非常有用。以下是它为每种文件类型打印的内容:Bourne-Again shell 脚本,ASCII 文本可执行文件
Bourne-Again shell 脚本,ASCII 文本可执行文件,带 CR 行终止符
Bourne-Again shell 脚本,ASCII 文本可执行文件,带有 CRLF 行终止符
GNU 版本
cat
实用程序有一个-v, --show-nonprinting
选项,用于显示非打印字符。dos2unix
实用程序专门用于在 Unix、Mac 和 DOS 行结尾之间转换文本文件。有用的链接
维基百科有一篇优秀文章,涵盖了标记文本行结尾的多种不同方式、此类编码的历史以及不同操作系统、编程语言和 Internet 协议(例如 FTP)中如何处理换行符。
具有经典 Mac OS 行结尾的文件
对于 经典 Mac OS(OS X 之前的版本),每一行都是以回车符结束(十进制 13,十六进制 0D,ASCII 码)。如果以这样的行结尾保存脚本文件,Bash 只会看到如下所示的一长行:
由于这一长行以 octothorpe (
#
) 开头,Bash 会处理该行(以及整个文件) )作为单个评论。注:2001 年,Apple 推出了 Mac OS X,它基于 BSD 衍生的 NeXTSTEP 操作系统。因此,OS X 也使用 Unix 风格的仅 LF 行结尾,从那时起,以 CR 结尾的文本文件变得极其罕见。尽管如此,我认为有必要展示 Bash 如何尝试解释此类文件。
Yes. Bash scripts are sensitive to line-endings, both in the script itself and in data it processes. They should have Unix-style line-endings, i.e., each line is terminated with a Line Feed character (decimal 10, hex 0A in ASCII).
DOS/Windows line endings in the script
With Windows or DOS-style line endings , each line is terminated with a Carriage Return followed by a Line Feed character. You can see this otherwise invisible character in the output of
cat -v yourfile
:In this case, the carriage return (
^M
in caret notation or\r
in C escape notation) is not treated as whitespace. Bash interprets the first line after the shebang (consisting of a single carriage return character) as the name of a command/program to run.^M
, it prints: command not found
src^M
, it prints: No such file or directory
install^M
instead ofinstall
as an argument tonpm
which causesnpm
to complain.DOS/Windows line endings in input data
Like above, if you have an input file with carriage returns:
then it will look completely normal in editors and when writing it to screen, but tools may produce strange results. For example,
grep
will fail to find lines that are obviously there:(no match because the line actually ends in
^M
)Appended text will seem to overwrite the line because the carriage return moves the cursor to the start of the line:
String comparison will fail, even though strings appear to be the same when writing to screen:
Solutions
The solution is to convert the file to use Unix-style line endings. There are a number of ways this can be accomplished:
Using the
dos2unix
program:Open the file in a capable text editor (Sublime, Notepad++, not Notepad) and configure it to save files with Unix line endings, e.g., with Vim, run the following command before (re)saving:
If you have a version of the
sed
utility that supports the-i
or--in-place
option, e.g., GNUsed
, you could run the following command to strip trailing carriage returns:With other versions of
sed
, you could use output redirection to write to a new file. Be sure to use a different filename for the redirection target (it can be renamed later).Similarly, the
tr
translation filter can be used to delete unwanted characters from its input:Cygwin Bash
With the Bash port for Cygwin, there’s a custom
igncr
option that can be set to ignore the Carriage Return in line endings (presumably because many of its users use native Windows programs to edit their text files).This can be enabled for the current shell by running
set -o igncr
.Setting this option applies only to the current shell process so it can be useful when sourcing files with extraneous carriage returns. If you regularly encounter shell scripts with DOS line endings and want this option to be set permanently, you could set an environment variable called
SHELLOPTS
(all capital letters) to includeigncr
. This environment variable is used by Bash to set shell options when it starts (before reading any startup files).Useful utilities
The
file
utility is useful for quickly seeing which line endings are used in a text file. Here’s what it prints for for each file type:Bourne-Again shell script, ASCII text executable
Bourne-Again shell script, ASCII text executable, with CR line terminators
Bourne-Again shell script, ASCII text executable, with CRLF line terminators
The GNU version of the
cat
utility has a-v, --show-nonprinting
option that displays non-printing characters.The
dos2unix
utility is specifically written for converting text files between Unix, Mac and DOS line endings.Useful links
Wikipedia has an excellent article covering the many different ways of marking the end of a line of text, the history of such encodings and how newlines are treated in different operating systems, programming languages and Internet protocols (e.g., FTP).
Files with classic Mac OS line endings
With Classic Mac OS (pre-OS X), each line was terminated with a Carriage Return (decimal 13, hex 0D in ASCII). If a script file was saved with such line endings, Bash would only see one long line like so:
Since this single long line begins with an octothorpe (
#
), Bash treats the line (and the whole file) as a single comment.Note: In 2001, Apple launched Mac OS X which was based on the BSD-derived NeXTSTEP operating system. As a result, OS X also uses Unix-style LF-only line endings and since then, text files terminated with a CR have become extremely rare. Nevertheless, I think it’s worthwhile to show how Bash would attempt to interpret such files.
在 JetBrains 产品(PyCharm、PHPStorm、IDEA 等)中,您需要单击
CRLF
/LF
在两者之间切换行分隔符的类型(\r\n
和\n
)。In JetBrains products (PyCharm, PHPStorm, IDEA, etc.), you'll need to click on
CRLF
/LF
to toggle between the two types of line separators (\r\n
and\n
).我试图从 Windows 启动我的 Docker 容器,结果发现:
我正在使用 Git Bash,问题出在 Git 配置上,然后我执行了以下步骤,结果成功了。它将配置 Git 在签出时不转换行结尾:
非常感谢 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$//' 脚本名。嘘
I was trying to startup my Docker container from Windows and got this:
I was using Git Bash and the problem was with the Git config, then I just did the steps below and it worked. It will configure Git to not convert line endings on checkout:
git config --global core.autocrlf input
Many thanks to Jason Harmon in this link:
https://forums.docker.com/t/error-while-running-docker-code-in-powershell/34059/6
Before that, I tried this, that didn't work:
dos2unix scriptname.sh
sed -i -e 's/\r$//' scriptname.sh
sed -i -e 's/^M$//' scriptname.sh
如果您使用
read
命令从DOS/Windows格式(或可能)的文件(或管道)中读取数据,您可以利用事实上,read
会修剪行首和行尾的空格。如果您告诉它回车符是空格(通过将它们添加到 IFS 变量),它会从行尾删除它们。在 bash(或 zsh 或 ksh)中,这意味着您可以将此标准习惯用法:替换
为:(
如果您使用一个没有的外壳支持
$'...'
引用模式(例如,破折号,某些 Linux 发行版上的默认 /bin/sh),或者您的脚本甚至可能以这样的方式运行shell,那么你需要变得更复杂一点:注意,通常情况下,当你改变
\r' read -r somevar # This *will* trim CRIFS
时,你应该尽快将其恢复正常,以避免奇怪的副作用;但在所有这些情况下,它都是read
命令的前缀,因此它只影响该命令,并且之后不必重置。注意:
-r
选项与此无关,通常是一个好主意避免损坏反斜杠。)如果您不使用
IFS=
前缀(例如,因为您想将数据拆分为字段),那么您可以将以下内容替换为:
如果您使用一个没有的外壳支持
$'...'
引用模式(例如,破折号,某些 Linux 发行版上的默认 /bin/sh),或者您的脚本甚至可能以这样的方式运行shell,那么你需要变得更复杂一点:注意,通常情况下,当你改变
IFS
时,你应该尽快将其恢复正常,以避免奇怪的副作用;但在所有这些情况下,它都是read
命令的前缀,因此它只影响该命令,并且之后不必重置。If you're using the
read
command to read from a file (or pipe) that is (or might be) in DOS/Windows format, you can take advantage of the fact thatread
will trim whitespace from the beginning and ends of lines. If you tell it that carriage returns are whitespace (by adding them to theIFS
variable), it'll trim them from the ends of lines.In bash (or zsh or ksh), that means you'd replace this standard idiom:
with this:
If you're using a shell that doesn't support the
$'...'
quoting mode (e.g. dash, the default /bin/sh on some Linux distros), or your script even might be run with such a shell, then you need to get a little more complex:Note that normally, when you change
\r' read -r somevar # This *will* trim CRIFS
, you should put it back to normal as soon as possible to avoid weird side effects; but in all these cases, it's a prefix to theread
command, so it only affects that one command and doesn't have to be reset afterward.(Note: the
-r
option isn't related to this, it's just usually a good idea to avoid mangling backslashes.)If you're not using the
IFS=
prefix (e.g. because you want to split the data into fields), then you'd replace this:with this:
If you're using a shell that doesn't support the
$'...'
quoting mode (e.g. dash, the default /bin/sh on some Linux distros), or your script even might be run with such a shell, then you need to get a little more complex:Note that normally, when you change
IFS
, you should put it back to normal as soon as possible to avoid weird side effects; but in all these cases, it's a prefix to theread
command, so it only affects that one command and doesn't have to be reset afterward.由于正在使用 VS Code,我们可以在右下角看到 CRLF 或 LF,具体取决于所使用的内容,如果单击它,我们可以在它们之间进行更改(在下面的示例中使用 LF):
我们还可以使用命令托盘中的“更改行尾序列”命令。因为它们的功能相同,所以更容易记住。
Since VS Code is being used, we can see CRLF or LF in the bottom right depending on what's being used and if we click on it we can change between them (LF is being used in below example):
We can also use the "Change End of Line Sequence" command from the command pallet. Whatever's easier to remember since they're functionally the same.
来自重复项,如果问题是您的文件名称末尾包含
^M
,您可以将它们重命名为“您正确地想要修复导致这些文件的任何原因”首先要使用损坏的名称(可能创建它们的脚本应该进行 dos2unix 编辑,然后重新运行?),但有时这是不可行的。
$'\r'
语法是 Bash 特定的;如果您有不同的外壳,也许您需要使用其他符号。也许另请参阅 sh 和 bash 之间的区别Coming from a duplicate, if the problem is that you have files whose names contain
^M
at the end, you can rename them withYou properly want to fix whatever caused these files to have broken names in the first place (probably a script which created them should be
dos2unix
ed and then rerun?) but sometimes this is not feasible.The
$'\r'
syntax is Bash-specific; if you have a different shell, maybe you need to use some other notation. Perhaps see also Difference between sh and bash当我将 git 与 WSL 结合使用时,我遇到了这个问题。
git 有一个功能,它可以根据您使用的操作系统更改文件的行结尾,在 Windows 上,它确保行结尾为
\r\n
,这与仅使用 Linux 的 Linux 不兼容<代码>\n。您可以通过将文件名
.gitattributes
添加到 git 根目录并添加如下行来解决此问题:在此示例中,
config
目录中的所有文件都只有行- feed 行结尾和run.sh
文件也是如此。I ran into this issue when I use git with WSL.
git has a feature where it changes the line-ending of files according to the OS you are using, on Windows it make sure the line endings are
\r\n
which is not compatible with Linux which uses only\n
.You can resolve this problem by adding a file name
.gitattributes
to your git root directory and add lines as following:In this example all files inside
config
directory will have only line-feed line ending andrun.sh
file as well.大量参考 git,但没有重新规范化行结尾。只需转到存储库的根目录并运行:
仅重新签入需要刷新行结尾的文件。文件看起来没有任何更改,因为行结尾是不可见的。
Lots of reference to git but not to renormalizing the line endings in place. Just go to the root of your repo and run:
Only the files that need line endings refreshed will be re-checked in. It will appear that the files have no changes, because line endings are invisible.
对于Notepad++用户,可以通过以下方式解决:编辑> EOL 转换 > Unix (LF)
For Notepad++ users, this can be solved by: Edit > EOL Conversion > Unix (LF)
删除不需要的 CR ('\r') 字符的另一种方法是运行
tr
命令,例如:One more way to get rid of the unwanted CR ('\r') character is to run the
tr
command, for example:MAC / Linux 上最简单的方法 - 使用“touch”命令创建一个文件,使用 VI 或 VIM 编辑器打开该文件,粘贴代码并保存。这将自动删除 Windows 字符。
The simplest way on MAC / Linux - create a file using 'touch' command, open this file with VI or VIM editor, paste your code and save. This would automatically remove the windows characters.
如果您使用的是 BBEdit 等文本编辑器,则可以在状态栏上执行此操作。有一个选项可以切换。
If you are using a text editor like BBEdit you can do it at the status bar. There is a selection where you can switch.
脚本可以互相调用。
一个更好的神奇解决方案是转换文件夹/子文件夹中的所有脚本:
您也可以使用 dos2unix,但许多服务器默认情况下没有安装它。
Scripts may call each other.
An even better magic solution is to convert all scripts in the folder/subfolders:
You can use
dos2unix
too but many servers do not have it installed by default.由于这个问题,我已经多次损坏 bash 脚本了。
已经发布了许多关于如何更改文件的解决方案。
不过,我没有看到任何内置的 vim 方法来完成这项任务。
使用 shell 脚本打开 vim 并运行此命令
然后编辑 .gitattributes 以获得永久修复
I've had corrupted bash scripts so many times from this issue.
There are already many solutions posted on how to change the file.
Though, I didn't see any on the built-in vim method to do this task.
Open vim with the shell script and run this command
Then edit your .gitattributes to get a permanent fix
对于IntelliJ用户,这里是编写Linux脚本的解决方案:文件> 行分隔符
使用 LF - Unix 和 macOS (\n)
For IntelliJ users, here is the solution for writing Linux script: File > Line Separators
Use LF - Unix and macOS (\n)
以防万一,如果您(像我一样)需要一个可在 Windows 中编辑但可在 Linux 中运行的脚本(因此该脚本可能有 CRLF,并且我们不能有 2 个版本,一个用于 Linux,另一个用于 Windows),我来到了在Linux中运行脚本的方式如下(
./build.sh
是脚本)Just in case if you (like me) need to have a script editable in Windows, but runnable in Linux (so the script MAY have CRLF, and we cannot have 2 versions, one for Linux, another for Windows), I came to the following way of running the script in Linux (
./build.sh
is the script)如果您收到错误消息
这通常是因为您调用的脚本嵌入了 \r 字符,这反过来表明它具有 Windows 风格的 \r\n 行结尾(换行符),而不是 bash 期望的仅 \n 行结尾
您需要“重置”环境变量以确保您使用默认环境设置。这可以通过在终端中运行以下命令来完成:
您可以通过打开终端来运行这些命令,或者如果您使用的是 WSL,则可以通过连接到 WSL 实例来执行命令。
If you are getting an error saying
It generally happens because the script you're invoking has embedded \r characters, which in turn suggests that it has Windows-style \r\n line endings (newlines) instead of the \n-only line endings bash expects
You need to "reset" the environment variables to ensure that you are using the default environment settings. This can be done by running the following command in your terminal:
You can run these commands by opening a terminal or, if you are using WSL, by connecting to your WSL instance to execute the commands.
为了完整起见,我将指出 另一种解决方案这可以永久解决这个问题,而不需要一直运行 dos2unix:
For the sake of completeness, I'll point out another solution which can solve this problem permanently without the need to run dos2unix all the time: