SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-10-20

如何修复 Bash syntax error near unexpected token

Bash 会指出无法解析的 token。根据具体内容定位原因:误粘贴的 <file>、缺少 then 或 do、CRLF 换行,或将 HTML 网页通过管道传给 Bash。

“syntax error near unexpected token”的含义

Bash 读取到语法在当前位置不允许出现的单词或符号时,会输出 syntax error near unexpected token。引号中的 token 是最有用的线索。它表示 bash 放弃解析时找到的内容,真正的错误位于该行或其上方的某一行。

完整的错误行包含三部分。首先是名称:脚本文件名;如果 bash 从管道或 bash -c 读取命令,则为 bash。GNU bash 手册说明,这个名称 $0 用于警告和错误消息。接下来是行号。最后是 token。该命令会以非零状态退出,因此检查 $? 的脚本会检测到失败。

Bash 一次读取并处理一个命令。它先解析一个命令并运行,然后读取下一个命令。因此,错误出现时,出错行之前的命令可能已经运行。整个 if 块或循环会在执行其中任何内容之前完成解析,因此损坏的块中不会有任何内容运行。

在将输出与其他人的输出比较之前,先确认使用的 bash 版本:

bash --version | head -n 1

截至 October 2026,Ubuntu 软件包存档列出 Ubuntu 24.04 使用 bash 5.2.21,Ubuntu 26.04 使用 bash 5.3。这些是不同的版本。比较 token,不要比较整行。

查找标记

  • newline:粘贴的占位符,例如 <file>,或重定向后没有内容。
  • fi 或 done:缺少 then 或 do,[ ... ] 测试后缺少分号,或代码块为空。
  • then 或 do:bash 需要命令时却出现了关键字,例如在条件中使用了 else。
  • (:文件名包含圆括号、extglob 模式处于关闭状态,或使用 sh 运行了 bash 语法。
  • 标记中包含 \r:使用了 Windows 换行符。
  • curl ... | bash 第 1 行出现 < 或 newline:下载内容是网页,而不是脚本。

设置临时目录

下面的每个示例都可以安全运行。每个示例都会写入一个小文件,然后运行该文件。

mkdir -p ~/syntax-lab
cd ~/syntax-lab

这些示例使用带引号的 heredoc 写入文件。'EOF'两侧的引号会阻止当前输入命令的 shell 执行任何展开,因此每个文件都只包含所显示的文本。

Token newline:粘贴的占位符或末尾重定向

文档通常使用尖括号标记必须替换的值,例如 <file> 或 <your-key>。如果原样粘贴该行,bash 会将尖括号解析为重定向运算符。< 表示“从下一个单词读取标准输入”。> 表示“将标准输出写入下一个单词”。在 cat <file> 中,< 将 file 作为输入。> 后面除了行尾没有任何内容,因此 bash 将行尾解析为令牌:newline。

cat > placeholder.sh <<'EOF'
cat <file>
EOF
bash placeholder.sh
echo "exit status: $?"

输出包含 syntax error near unexpected token,后面跟着 newline,退出状态不为 0。行尾的重定向会产生相同的令牌,例如 echo done > 或 command 2>。通常,这是因为编辑时删除了文件名,却留下了重定向运算符。

修复方法是将占位符替换为实际值:

printf 'hello from notes\n' > notes.txt
cat > placeholder.sh <<'EOF'
cat notes.txt
EOF
bash placeholder.sh

它应输出 hello from notes。如果确实需要将尖括号作为文本,请将其引号括起来。echo '<file>' 会输出尖括号,不执行重定向。

Token fi 和 done:缺少 then、do 或分号

在 if 之后,bash 会收集一组命令作为条件。它会一直收集,直到读到 then。如果忘记 then,bash 就会把接下来的行当作条件的一部分。随后,bash 仍在等待 then 时读到了 fi,因此报错 token 为 fi。

cat > no-then.sh <<'EOF'
if [ -f /etc/passwd ]
  echo "this server has a passwd file"
fi
EOF
bash no-then.sh
echo "exit status: $?"

输出包含 syntax error near unexpected token 和 fi。查看行号。错误在第 1 行,但 bash 报告第 3 行,因为它是在第 3 行发现错误的。

缺少分号会产生相同的 token。在 if [ -f /etc/passwd ] then 中,[ 是一个普通命令,因此同一行上的 then 会成为该命令的另一个参数。Bash 仍未读到真正的 then,因此再次报告 fi。使用 ; 结束测试,或将 then 单独放在一行:

cat > no-then.sh <<'EOF'
if [ -f /etc/passwd ]; then
  echo "this server has a passwd file"
fi
EOF
bash no-then.sh

输出应为 this server has a passwd file。

循环的工作方式相同。while 会一直收集条件,直到读到 do,因此缺少 do 时,bash 会报告 done:

cat > no-do.sh <<'EOF'
while read -r line
  echo "read: $line"
done < notes.txt
EOF
bash no-do.sh
echo "exit status: $?"

Token 为 done。将第 1 行改为 while read -r line; do,脚本就会输出 read: hello from notes。

空主体

块中只有注释时也会失败。注释不是命令,bash 要求 then 和 fi 之间,或 do 和 done 之间至少包含一个命令。

cat > empty-body.sh <<'EOF'
if [ -f /etc/passwd ]; then
  # fill this in later
fi
EOF
bash empty-body.sh
echo "exit status: $?"

Token 为 fi。将空命令 : 放入主体中。它不执行任何操作,但会成功,这正是占位符所需的行为:

cat > empty-body.sh <<'EOF'
if [ -f /etc/passwd ]; then
  : # fill this in later
fi
EOF
bash empty-body.sh
echo "exit status: $?"

退出状态现在为 0。

Tokens then 和 do:命令应出现的位置却出现了关键字

当 token 本身是 then 或 do 时,bash 在需要命令的位置遇到了该关键字。常见原因是为 else 指定了条件。只有 if 和 elif 接受条件。else 只接受命令,因此 bash 会将其后的测试表达式当作普通命令处理,而后面的 then 没有可归属的对象。

cat > else-test.sh <<'EOF'
if [ -d /no/such/dir ]; then
  echo "first branch"
else [ -f /etc/passwd ]; then
  echo "this server has a passwd file"
fi
EOF
bash else-test.sh
echo "exit status: $?"

输出在第 3 行包含 syntax error near unexpected token 和 then。两个 echo 都不会执行,因为 bash 会先解析整个 if 块,然后才执行其中的内容。将 else 改为 elif 后,脚本会输出 this server has a passwd file:

cat > else-test.sh <<'EOF'
if [ -d /no/such/dir ]; then
  echo "first branch"
elif [ -f /etc/passwd ]; then
  echo "this server has a passwd file"
fi
EOF
bash else-test.sh

同样的规则也解释了 do。while 或 until 的条件中至少需要包含一个命令,因此两个单词之间没有任何内容的 while do 会使 bash 将 do 作为命令名。在这两种情况下,都应检查 bash 报告的行及其上一行,查找失去对应项或条件的关键字。

文件名中的 Token (:圆括号

在 bash 中,( 和 ) 是运算符。它们可以启动子 shell;跟在名称后面时,则表示函数定义。( 出现在文件名中间时,bash 无法将其解析为语法的一部分,因此会将 ( 命名为该文件。

cat > parens.sh <<'EOF'
cp report(final).txt /tmp/
EOF
bash parens.sh
echo "exit status: $?"

将名称用引号括起来,或在每个括号前加反斜杠:

printf 'final numbers\n' > 'report(final).txt'
cat > parens.sh <<'EOF'
cp 'report(final).txt' /tmp/
EOF
bash parens.sh
ls -l '/tmp/report(final).txt'

ls 应显示复制后的文件。在交互式 shell 中,按 Tab 键补全名称时,shell 会自动添加反斜杠。

扩展 glob 模式也使用括号。只有启用 extglob 选项后,类似 !(*.log) 的模式才会被解析。如果该选项处于关闭状态,bash 会将 ( 视为普通名称。运行 shopt extglob 查看当前设置;在脚本中依赖扩展 glob 模式前,请阅读bash glob 和 extglob 模式的工作原理。

Token ( 再次出现:使用 sh 运行 bash 语法

此脚本使用数组,这是 bash 语法:

cat > array.sh <<'EOF'
#!/bin/bash
files=(one.txt two.txt)
echo "second file: ${files[1]}"
EOF
bash array.sh
sh array.sh
echo "exit status: $?"

bash array.sh 输出 second file: two.txt。sh array.sh 在第 2 行失败。#!/bin/bash 行没有帮助,因为内核只会在您直接执行文件时读取该行。输入 sh array.sh 时,您自行选择了解释器,而 sh 会将 #! 行作为注释处理。

检查服务器上的 sh 使用哪个 shell:

ls -l /bin/sh
readlink -f /bin/sh

如果 readlink 输出 /usr/bin/dash,则 sh 是 dash,即 Debian Almquist shell。Debian 的软件包页面称 dash 是 Debian 上的默认系统 shell。Dash 会拒绝 files=(...) 行。它的错误消息使用自己的措辞,而不是 bash 的措辞,因此应在其中查找 (,而不是查找 bash 的表述。

修复方法是使用 bash 运行文件。您可以直接指定 bash,也可以让文件获得可执行权限,然后由 #! 行选择解释器:

chmod +x array.sh
./array.sh

这里的 +x 使用符号模式,chmod 的数字模式和符号模式介绍了其他形式。./array.sh 和 bash array.sh 最终都会使用 bash,但路径不同;source 与执行 bash 脚本的区别说明了每种情况下由哪个 shell 读取文件。任何将您的文件交给 sh 的方式都会导致相同的失败。定时任务是常见的触发场景,因此如果脚本只在 cron 下失败,请查看cron 任务为何手动运行正常但按计划运行失败。

包含 \r 的令牌:Windows 换行符

Windows 编辑器在每行末尾使用 CRLF(回车符、换行符)。Linux 工具只使用 LF 结束行。Bash 将 LF 视为行尾,但将 CR 视为普通字符。因此,CR 会附加到每行最后一个单词上。这里的 { 会变成 { 加回车符,而不是用于开始函数体的 {。

printf 'greet() {\r\n  echo "hello"\r\n}\r\ngreet\r\n' > crlf.sh
bash crlf.sh
echo "exit status: $?"

输出包含 syntax error near unexpected token。Bash 使用 $'...' 引号形式打印该令牌,因此可以看到隐藏字符,\r 会显示在其中。在其他文件中,同一原因可能表现为单独的 $'\r',表现为名称以 \r 结尾的名称的 command not found,或者当 CR 位于 fi 或 done 上时表现为 unexpected end of file。

使用 cat -A 显示 CR。GNU coreutils 手册说明,-A 会将行尾显示为 $,并将 CRLF 对显示为 ^M$:

cat -A crlf.sh

每行都应以 ^M$ 结尾。使用 GNU sed 删除 CR。在 GNU sed 中,\r 匹配回车符,-i 会直接修改文件:

sed -i 's/\r$//' crlf.sh
cat -A crlf.sh
bash crlf.sh

现在每行都以普通的 $ 结尾,脚本会输出 hello。macOS 上的 sed 对 -i 和 \r 的处理方式不同,因此应在 Linux 服务器上执行此修复。为避免问题再次出现,请将编辑器设置为使用 LF 换行符保存 shell 脚本。

第 1 行出现 <:您将网页通过管道传给了 bash

这是许多 Claude Code 安装报告背后的原因。2026 年 1 月,一名用户在 Claude Code 仓库中提交了 GitHub issue 17813。该用户在 macOS 上运行了 Anthropic 的官方安装程序 curl -fsSL https://claude.ai/install.sh | bash。Bash 输出了 syntax error near unexpected token,并将 < 显示为令牌。下一行引用了它收到的第 1 行内容,该行以 <!DOCTYPE html> 开头。该页面的标题是“你所在的区域无法使用应用 | Claude”。一名评论者指出,语法错误并不是真正的问题。真正的问题是区域限制页面。该 issue 于 2026 年 2 月关闭,状态为不计划处理。

Anthropic 的 Claude Code 设置页面现在会说明此错误。如果安装命令因 syntax error near unexpected token '<' 而失败,该页面会将读者引导至安装故障排查页面。其中的“安装脚本返回 HTML,而不是 shell 脚本”一节说明,安装 URL 返回的是网页、XML 文档或错误状态,而不是脚本。如果错误输出中引用了“你所在的区域无法使用应用”,则表示 Claude Code 在您所在的国家/地区不可用。该页面还将网络和路由问题以及短暂的服务中断列为原因。首先建议等待几分钟,然后再次运行相同的命令。页面还列出了其他安装方法,例如 macOS 上的 brew install --cask claude-code。

无需下载即可复现

您不需要故障服务器即可看到此问题。使用 printf 将一个虚假的 HTML(网页使用的标记语言)页面通过管道传给 bash。第一个页面全部位于同一行中,这符合许多服务器发送页面的方式:

printf '<!DOCTYPE html><html><head><title>Error</title></head></html>\n' | bash
echo "exit status: $?"

输出中会在第 1 行包含 syntax error near unexpected token 和 <。原因如下。对 bash 而言,<!DOCTYPE html> 是一个名为 html 的命令,并带有两个重定向。< 从名为 !DOCTYPE 的文件读取输入。用于闭合标签的 > 是输出重定向,因此后面还需要一个文件名。该行接下来的内容是 <html> 的 <,它是另一个运算符,而不是名称。因此,bash 将 < 作为名称。

现在在每个标签后换行,再发送同一个页面:

printf '<!DOCTYPE html>\n<html>\n<head><title>Error</title></head>\n</html>\n' | bash
echo "exit status: $?"

这次第 1 行正好在 > 后结束,因此令牌是 newline。在通过管道执行安装时,这两个令牌在第 1 行表示同一件事:bash 收到的是页面,而不是脚本。消息以 bash: line 1: 开头,是因为 bash 从管道读取内容,没有可显示的文件名。

如果您将该 HTML 直接粘贴到交互式终端中,可能会先看到 event not found。这是因为在交互式 shell 中,! 会使用感叹号字符启动历史扩展,其处理发生在解析器读取该行之前。

更安全的做法:下载、查看,然后运行

通过管道传给 bash 会在您查看内容之前执行收到的任何内容。请先将脚本保存到文件,查看内容,然后再运行:

curl -fsSL -o install.sh https://claude.ai/install.sh
head -n 5 install.sh
bash -n install.sh && echo "parses as a shell script"
less install.sh
bash install.sh

shell 脚本以 #! 行或 shell 命令开头。如果 head 显示 <!DOCTYPE html> 或 <html>,请停止操作。编辑文件无法解决问题,因为问题出在下载过程。如果软件供应商提供了校验和,请在最后一步之前使用校验和验证下载内容。有关其余 Linux 安装步骤,请参阅在 Linux 桌面和 CLI 中运行 Claude。

检查脚本但不运行:bash -n

bash -n会解析脚本,但不会执行其中的任何命令。Ubuntu 24.04 的 bash 手册这样描述此选项:“读取命令但不执行。可用于检查 shell 脚本是否存在语法错误。”

printf 'if true\n  echo hi\nfi\n' > check-me.sh
bash -n check-me.sh
echo "exit status: $?"
printf 'if true; then\n  echo hi\nfi\n' > check-me.sh
bash -n check-me.sh
echo "exit status: $?"

第一次检查使用 fi 报告 syntax error near unexpected token,状态码不是 0。第二次不输出任何内容,并以 0 退出。两次运行都不会输出 hi,因为两个文件中的内容都没有执行。

bash -n只检查语法,而且只检查 bash 的语法。它不知道某个命令是否存在。它不会执行命令替换,因此无法告诉您 $(...) 会返回什么。它也接受 sh 会拒绝的 bash 专属语法。对于后一种情况,请使用 dash 检查。dash 手册说明,其 -n 选项会读取命令但不运行,并可用于检查 shell 脚本的语法:

dash -n array.sh
echo "exit status: $?"

dash 报告了数组所在的行,状态码不是 0。bash 手册还说明了一个细节:交互式 shell 会忽略此选项,因此在终端中输入 set -n 不会产生任何效果。

再提供一个判断:ShellCheck

ShellCheck 是用于 shell 脚本的静态分析工具。其 README 表示,它的目标之一是指出并解释初学者常见的语法错误;这类错误会让 shell 输出难以理解的错误消息,本文讨论的正是这一类错误。Ubuntu 软件仓库中提供的包名是 shellcheck。

sudo apt update
sudo apt install -y shellcheck
shellcheck --version

截至 2026 年 10 月,Ubuntu 24.04 的软件仓库提供 shellcheck 0.9.0,Ubuntu 26.04 提供 0.11.0。比较它与他人输出的结果前,请先检查版本行。

ShellCheck 会读取 #! 行,以确定脚本使用哪种 shell 编写。为数组脚本添加 sh 头部后,将其交给 ShellCheck:

cat > sh-array.sh <<'EOF'
#!/bin/sh
files=(one.txt two.txt)
echo "second file: ${files[1]}"
EOF
shellcheck sh-array.sh

它会将这些数组行标记为 sh 不支持的语法。这样,在脚本由 dash 执行之前,您就能发现问题。与 bash -n 不同,ShellCheck 还会用通俗的文字解释每个问题,并提供对应页面的链接。提交脚本前运行 shellcheck,首次正式运行任何脚本前运行 bash -n。

JavaScript 的“Unexpected token '<'”是另一种错误

如果消息以 SyntaxError 开头,并且来自浏览器控制台或 Node.js,则它不是 bash 错误。JSON.parse 会在接收到的文本不是有效 JSON(JavaScript Object Notation)时抛出 SyntaxError。当该令牌为 < 时,文本以 < 开头,就像 HTML 页面一样。应在请求端、URL 或服务器响应中修复此问题;修改 shell 脚本无法解决它。

FAQ

“syntax error near unexpected token” 在 bash 中是什么意思?

Bash 读取了一个在当前位置不符合其语法的单词或符号,因此在运行该命令前停止。引号中的 token 是它停止的位置,错误位于该行或上方某一行。newline 表示存在多余的 < 或 >。fi 或 done 表示缺少 then、do 或分号,或者代码块为空。( 表示 bash 无法正确放置括号,包含 \r 的 token 表示文件使用了 Windows 换行符。

为什么脚本可以用 bash 运行,却无法用 sh 运行?

sh script.sh 会使用 /bin/sh 指定的解释器运行文件,并忽略 #!/bin/bash 行,因为您已经自行指定了解释器。运行 readlink -f /bin/sh 查看使用的是哪个 shell。如果是 dash,数组等仅适用于 bash 的语法将无法解析。使用 bash script.sh 运行脚本,或者为脚本添加可执行权限后运行 ./script.sh,这样 #! 行就会选择 bash。

如何修复 syntax error near unexpected token $'\r'?

文件使用了 Windows CRLF 换行符,因此每一行末尾都有一个回车符,bash 会将其视为最后一个单词的一部分。运行 cat -A script.sh,并查看行尾是否存在 ^M$。使用 GNU sed 删除回车符:sed -i 's/\r$//' script.sh。然后将编辑器设置为使用 LF 换行符保存 shell 脚本,避免问题再次出现。

为什么将 curl 的输出通过管道传给 bash 后,会在第 1 行出现 unexpected token '<'?

该 URL 返回的是 HTML 页面,而不是 shell 脚本,因此 bash 尝试运行了该页面。对 bash 而言,HTML 标签的 < 是重定向运算符。对于 Claude Code 安装程序,Anthropic 的故障排查页面说明,这表示安装 URL 返回了网页、XML 文档或错误状态。如果输出包含 “App unavailable in region”,则 Claude Code 在您所在的国家/地区不可用。否则,请等待几分钟后重试。先将脚本下载到文件中,并在运行前查看文件的前几行。