无法解决 "syntax error near unexpected token `fi'" - 隐藏控制字符 (CR) / Unicode 空格

unable to solve "syntax error near unexpected token `fi'" - hidden control characters (CR) / Unicode whitespace

我是 bash 脚本编写的新手,我只是在尝试新事物并掌握它。

基本上我正在编写一个小脚本来将文件的内容存储在一个变量中,然后在 if 语句中使用该变量。

通过一步步摸索出存储变量的方法,然后将文件内容存储为变量。我现在正在处理 if 语句。

我的测试 if 语句非常非常基础。我只是想在为我的程序使用更复杂的 if 语句之前掌握语法。

我的 if 语句是:

if [ "test" = "test" ]
then
    echo "This is the same"
fi

简单吧?但是,当我 运行 脚本时出现错误:

syntax error near unexpected token `fi'

我已经从这个网站和其他网站尝试了很多东西,但我仍然收到这个错误,我不确定哪里出了问题。我的电脑从 运行ning 停止脚本会不会是个问题?

编辑完整代码。请注意,我还删除了所有注释掉的代码,只使用了 if 语句,仍然出现同样的错误。

#!/bin/bash
#this stores a simple variable with the content of the file testy1.txt
#DATA=$(<testy1.txt)
#This echos out the stored variable
#echo $DATA
#simple if statement
if [ "test" = "test" ]
then
    echo "has value"
fi

这里,“$test”应该是一个存储该文件内容的变量。

if [ "$test" = "test" ]; then
    echo "This is the same"
fi

意外标记“fi”附近的语法错误 意味着 if 语句未正确打开和关闭,您需要从头开始检查每个 if、for 或 while 语句是否正确打开和关闭。 不要忘记在脚本开头添加:

#!/bin/bash

如果脚本 看起来 正常(您已经平衡了所有引号和括号以及反引号),但发出错误消息,这可能是由于 有趣的字符,即不显示的字符,例如回车returns、垂直制表符等。要诊断这些,请使用

检查您的脚本
od -c script.sh

并查找 \r\v 或其他意外字符。例如,您可以使用 dos2unix script.sh 命令摆脱 \r

顺便问一下,您使用的是什么操作系统和编辑器?

补充 ,它很好地解释了症状并提供了基于实用程序的解决方案 (dos2unix)。有时不需要安装第三方实用程序,因此这里是基于标准实用程序的解决方案tr:

tr -d '\r' < script > script.tmp && mv script.tmp script

这将从输入中删除所有 \r (CR) 字符,将输出保存到临时文件,然后替换原始文件。

  • 虽然这会盲目地删除 \r 个实例,即使它们不是 \r\n (CRLF) 对的一部分,但通常可以安全地假设 \r 个实例确实仅作为一部分出现这样的对。
  • 其他标准实用程序(awksed)的解决方案也是可能的 - 请参阅我的 this answer
    如果您的 sed 实现提供了 -i 就地更新选项,它可能是更简单的选择。

为了诊断问题我建议使用cat -v script,作为它输出很容易在视觉上解析:如果您在输出行的末尾看到 ^M(代表 \r),您就知道您正在处理一个具有 Window 行结尾的文件。


为什么您的脚本如此隐晦地失败了

通常,shell 脚本错误地具有 Windows 样式的 CRLF 行结尾,\r\n,(而不是所需的 Unix 样式的仅 LF 结尾,\n) 并以 开头 #!/bin/bash 以确实表明问题原因的方式失败:

/bin/bash^M: bad interpreter

因为a quick SO search可以证明。 ^M 表示 CR 被认为是解释器路径的一部分,这显然失败了。
(相比之下,如果脚本的 shebang 行是基于 env 的,例如 #!/usr/bin/env bash,则错误消息不同,但仍然指出原因:env: bash\r: No such file or directory

没有 看到这个问题的原因是你 运行 在 Windows Unix 仿真环境 Cygwin 中,这 - 与 Unix 不同 - 允许 shebang 行以 CRLF 结尾(大概是为了支持在 Windows 上调用其他解释器 do期望 CRLF 结尾)。

因此,CRLF 问题直到稍后在您的脚本中才浮出水面,而且在 shebang 行之后 没有空行 的事实进一步混淆了问题:

  • 空的 CRLF 终止行会导致 Bash (4.x) 抱怨如下:"bash: line <n>: $'\r': command not found,因为 Bash 试图执行CR 作为命令(因为它不将其识别为行尾的一部分)。

  • shebang 行后面的注释行没有问题,因为以 CR 结尾的注释行在语法上仍然有效。

  • 最后,if 语句以一种晦涩的方式破坏了命令:

    • 如果您的文件以换行符结尾,通常情况下,您会得到 syntax error: unexpected end of file:

      • 行尾 thenif 标记被 Bash 视为 then\rif\r(即附加 CR),因此不被识别为关键字。 Bash 因此永远看不到 if 复合命令的结尾,并抱怨在看到 if 语句完成之前遇到文件结尾。
    • 因为你的文件没有,所以你得到了syntax error near unexpected token 'fi':

      • 最后的fi,由于后面没有一个CR,识别为关键词Bash,而前面的 then 不是(如解释的)。在这种情况下,Bash 因此在看到 then 之前看到了关键字 fi,并抱怨 fi.
      • 的不当出现

可选背景信息

Shell 看起来 正常但由于字符不可见或仅看起来与所需字符相同而中断的脚本是一个常见问题,通常有一个以下原因之一:

  • 问题 A:文件具有 Windows 样式的 CRLF (\r\n) 行结尾 而不是 Unix 风格的 LF-only (\n) 行结尾——这里就是这种情况。

    • 从 Windows 机器复制文件或使用使用 CRLF 序列保存文件的编辑器是可能的原因。
  • 问题B:文件有非ASCII Unicode白色space和标点符号看起来像普通的白色space,但在技术上是不同的。

    • 一个常见的原因是源代码是从使用非 ASCII 白色 space 和标点符号的网站复制的,用于 显示 目的;
      一个示例是使用 no-break space Unicode 字符(U+00A0;UTF-8 编码 0xc2 0xa0),这在视觉上与正常 (ASCII) space (U+0020).

诊断问题

以下 cat 命令可视化:

  • 所有通常不可见的ASCII控制字符,如\r^M.
  • 所有非 ASCII 字符(假定现在流行的 UTF-8 编码),例如不间断 space Unicode 字符。作为 M-BM- .

^Mcaret notation的一个例子,不是很明显,尤其是多字节字符,但是,^M之后,通常不需要知道确切地 符号代表什么 - 你只需要注意 ^<letter> 序列是否存在(问题 A),或者是否存在于 unexpected 地方(问题 B)。

最后一点很重要:非 ASCII 字符可以是源代码的 合法 部分,例如字符串文字和注释。只有在使用 代替 ASCII 标点符号 时,它们才会成为问题。

LC_ALL=C cat -v script

注意:如果您使用的是 GNU 实用程序,则 LC_ALL=C 前缀是可选的。

问题 A 的解决方案:将行结尾从 CRLF 转换为 LF-only

  • 基于标准或通常可用的默认实用程序的解决方案(trawksedperl),见我的this answer

  • 一个更强大和方便的选项广泛使用的dos2unix实用程序,如果它是已经安装(通常 不是 ),或者安装它是一个选项。
    如何安装它取决于您的平台;例如:

    • 在 Ubuntu 上:sudo apt-get install dos2unix
    • 在 macO 上,安装了 Homebrewbrew install dos2unix

dos2unix script 会将行结尾转换为 LF 并更新文件 script

请注意 dos2unix 还提供其他功能,例如更改文件的字符编码。

问题 B 的解决方案:将 Unicode 标点符号转换为 ASCII 标点符号

注意:标点符号是指白色space和-

等字符

这种情况下的挑战是只应定位 Unicode 标点符号,而应保留其他非 ASCII 字符;因此,使用 iconv 等字符转码实用程序 不是 一个选项。

nws 是一个提供 Unicode-punctuation-to-ASCII-punctuation 转换模式 同时留下非标点符号 Unicode 字符。独自的;例如:

nws -i --ascii script  # translate Unicode punct. to ASCII, update file 'script' in place

安装:

  • 如果您安装了 Node.js,只需 运行 [sudo] npm install -g nws-cli 即可安装它,这会将 nws 放在您的路径中。

  • 否则:见manual installation instructions.

nws 有几个专注于 whitespace 处理的其他功能,包括 CRLF 到 LF 和反之亦然的翻译(--lf--crlf)。