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

2024-09-30 01:37:51 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在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)中,我尝试用\n替换\r\n(以防\r产生问题),但它没有改变任何东西
  • 它似乎找不到文件夹(有或没有dirname指令),或者它不知道cd命令
  • 似乎它不理解npminstall参数
  • 真正让我感到奇怪的是,它仍然在运行应用程序(如果我手动执行npm install

由于无法使其正常工作,并且怀疑文件本身有什么奇怪的地方,我直接在Mac上创建了一个新文件,这次使用的是vim。我输入了完全相同的指令,然后。。。现在它可以正常工作了。
两个文件上的差异显示完全没有差异

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

更新

按照公认答案的建议,在返回错误的行尾后,我检查了多项内容。事实证明,由于我从Windows机器复制了~/.gitconfig,所以我有autocrlf=true,所以每次我在Windows下修改bash文件时,它都会将行结尾重新设置为\r\n
因此,除了运行dos2unix(您必须在Mac上使用自制软件安装),如果您使用的是Git,请检查您的配置


Tags: installor文件no应用程序npmwindowsmac
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命令的前缀,因此它只影响一个命令,不必在之后重置

对。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在插入符号中或\r在C转义符号中)不被视为空白。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++,而不是Notepad)中打开文件,并将其配置为使用Unix行结尾保存文件,例如使用Vim,在(重新)保存之前运行以下命令:

    :set fileformat=unix
    
  3. 如果您有一个版本的sed实用程序支持-i--in-place选项,例如GNUsed,则可以运行以下命令来剥离尾部回车:

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

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

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

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

Cygwin Bash

对于Cygwin的Bash端口,有一个自定义的igncr选项,可以设置为忽略行结尾中的回车(可能是因为它的许多用户使用本机Windows程序编辑文本文件)。 这可以通过运行set -o igncr为当前shell启用

设置此选项仅适用于当前shell进程,因此在查找包含无关回车的文件时,此选项非常有用。如果您经常遇到具有DOS行结尾的shell脚本,并且希望永久设置此选项,则可以将名为SHELLOPTS(所有大写字母)的环境变量设置为包含igncr。Bash使用此环境变量在启动时(在读取任何启动文件之前)设置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

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

dos2unix实用程序是专门为在Unix、Mac和D之间转换文本文件而编写的OS行结束符

有用的链接

维基百科有一个excellent article涵盖了标记一行文本结尾的许多不同方式、此类编码的历史以及在不同操作系统、编程语言和互联网协议(如FTP)中如何处理新行

具有经典Mac OS行结尾的文件

对于Classic Mac OS(OSX之前),每一行都以回车符终止(十进制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年,苹果推出了基于BSD衍生的NeXTSTEP操作系统的MacOSX。因此,OSX也使用Unix风格的LF-only行结尾,从那时起,以CR结尾的文本文件变得极为罕见。尽管如此,我认为值得展示Bash如何尝试解释这些文件

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

enter image description hereenter image description here

相关问题 更多 >

    热门问题