HOWTO · Bash

解决 Linux Bash 中 syntax error near unexpected token newline 错误

本文介绍如何解决 Linux Bash 中 syntax error near unexpected token newline 错误。

本页内容

要修复 syntax error near unexpected token newline,请检查报错行以及紧邻其前的命令,找出尚未完成的语法结构。未加引号、却被当作普通文本使用的 < 或 > 是常见原因;如果它们是文本,就给文本加引号;如果原本要进行重定向,就补全重定向。

识别 Bash 的 syntax error near unexpected token newline

当 Bash 报告 syntax error near unexpected token newline 时,表示它解析命令时遇到了行尾,而命令语法尚未完整,或者下一个标记无法接在当前结构之后。换行符只是 Bash 发现问题的位置;缺少的部分可能在上一行。仅凭这条消息无法确定具体原因,因此应结合上下文检查命令,不要随意改动换行。

常见线索包括:未加引号的 < 或 > 被 Bash 识别为重定向运算符;重定向缺少目标;引号或分组结构没有闭合;命令拆成多行时缺少有效的续行符。类似错误也可能触发其他诊断信息,例如意外到达文件末尾。把报错措辞当作线索,再确认报错位置附近的实际语法。

检查报错行附近的命令

打开脚本,将报错行与紧邻其前的命令一起查看。对于多行命令,解析器可能直到遇到下一个换行符,才发现缺少参数或闭合标记。检查命令末尾是否有未完成的重定向、引号是否未闭合、是否缺少 ) 或 },以及反斜杠后是否有空格,而不是紧接着换行。

如果命令位于脚本中,可以用编辑器查看源文件,也可以运行 sed -n '12,20p' script.sh 之类的命令,并调整行号范围以包含周围内容。查看源文件有助于定位语法问题;单独运行 cat 并不能诊断问题。如果命令是粘贴到终端中的,请检查粘贴的完整命令;即使 Bash 指向最后一行,错误也可能由更早的行引起。

修复将未加引号的尖括号用作文本

在 Bash 中,未加引号的 < 会重定向标准输入,> 会重定向标准输出。例如,name=<guest> 会被解析为 shell 语法,而不是将字面文本 <guest> 赋给变量。重定向还需要目标,因此像 printf '%s\n' < 这样的未完成写法会让 Bash 找不到文件名。如果尖括号是要显示的字符,请给它们加引号或进行转义;如果你确实想进行重定向,就提供相应的文件或文件描述符。

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

script=$(mktemp)
trap 'rm -f "$script"' EXIT
cat >"$script" <<'BASH'
greet() {
  printf 'Hello, %s! Use <name> as a placeholder.\n' "$1"
}
greet "Sam"
BASH
bash -n "$script" && bash "$script"

这里的 heredoc 会在写入临时文件时保留脚本文本的原样。在脚本中,printf 的格式字符串已加引号,因此 <name> 会被当作普通文本,而不是重定向。示例还定义并闭合了一个函数,然后调用它。脚本运行前会先进行语法检查,shell 退出时,trap 会删除临时文件。

未完成的重定向是一个有用的边界案例:命令中有 <,但后面没有输入文件名。这个独立的诊断示例会将不完整命令写入临时脚本,运行 bash -n,隐藏诊断文本,并打印语法检查的退出状态。数据包没有提供该示例的实测运行输出,因此此示例尚未执行;记录退出状态是为了避免故意写错的脚本导致示例本身失败。

script=$(mktemp)
trap 'rm -f "$script"' EXIT
printf '%s\n' 'printf "%s\n" <' >"$script"
bash -n "$script" 2>/dev/null
status=$?
printf 'incomplete redirection syntax-check exit status: %s\n' "$status"

检查引号、分组结构和续行

确认每个单引号或双引号都有对应的结束引号。例如,printf '%s\n' "Hello 会留下未完成的双引号参数;改为 printf '%s\n' "Hello" 即可闭合引号。未闭合的引号通常会触发意外到达文件末尾或缺少匹配引号之类的诊断,而不一定是这里讨论的换行符错误。不过,当命令跨越多行时,仍应检查引号是否闭合。

分组结构也需要相应的结束符:以 ( 开始的子 shell 需要用 ) 结束,以 { 开始的函数体需要用 } 结束。例如,greet() { printf '%s\n' 'Hello'; } 就包含完整的函数体。根据结构和上下文,缺少结束符可能在后续行或文件末尾才被报告;不要假定每种分组错误都会产生本文讨论的报错措辞。

如果要有意将命令拆成多行,请在换行符前紧接一个反斜杠,且反斜杠后不能有空格。这样 Bash 就会将续行接到当前命令上。在正确闭合的引号字符串中,换行符则属于字符串内容,本身不会自动构成语法错误。请通过清晰的引号和续行来表达意图,不要在未确认参数边界前随意删除换行。

使用 Bash 调试跟踪脚本

如果脚本能通过解析,却出现了意料之外的行为,可以运行 bash -x script.sh,查看 Bash 执行的每条命令。你也可以在要跟踪的部分之前添加 set -x,并在该部分之后添加 set +x。跟踪输出有助于查看展开后的参数及命令的执行顺序;若发生运行时错误,检查错误前最后一条跟踪记录,可以缩小行为偏离预期的位置。

跟踪无法修复语法,也无法显示 Bash 无法解析的命令。检查语法时,请先运行 bash -n script.sh:它会解析脚本,但不会执行其中的命令。如果语法检查报告错误,请回到相关行,修复未完成的语法结构,然后再使用执行跟踪。

验证修正后的命令

编辑完成后,重新检查实际修改的行及其周围的语法结构。如果是脚本,请运行 bash -n script.sh;语法检查通过表示 Bash 已成功解析文件,但不能证明脚本会产生预期结果。接着用正常输入运行脚本,并将实际行为与预期进行比较。如果行为仍不正确,可以运行 bash -x script.sh 跟踪执行的命令。

如果命令是直接输入终端的,请使用安全且有代表性的输入运行修正后的命令。确认作为普通文本的尖括号已加引号、需要重定向的地方已指定目标、引号和分组结构均已闭合,并且续行末尾的反斜杠位置正确。这样可以区分语法解析问题是否已修复,以及之后是否还存在运行时或逻辑问题。