Decifrando "syntax error near unexpected token"

Decifrando "syntax error near unexpected token"

O que você vai aprender

  • Como ler a mensagem syntax error near unexpected token
  • Por que a posição do token e a causa real frequentemente divergem
  • Como depurar por token: fi / then / done / ( / newline / end of file

Resumo rápido

Em syntax error near unexpected token 'X', o X é onde o bash desistiu do parsing, e o erro real geralmente está antes dele.

  1. Classifique pelo token (fi/then/done = fluxo de controle, ( = parênteses / caracteres especiais, newline/end of file = algo não fechado)
  2. Leia a linha do erro e a linha imediatamente anterior (suspeite do que vem antes do token, não do token em si)
  3. Verifique a sintaxe apenas com bash -n script.sh (encontre a linha sem executar)
  4. Inspecione terminações de linha e caracteres ocultos com cat -A

Premissas

  • Shell: bash (o padrão no Ubuntu / Debian)
  • Alvo: um script .sh que você escreveu, ou um comando digitado em um shell interativo
  • /bin/sh (dash) exibe uma redação diferente (coberto abaixo)

O que é syntax error near unexpected token?

Conclusão: Enquanto o bash analisa seu comando, ele encontrou uma palavra (token) que não pode aparecer legalmente naquela posição. Ele para na etapa de verificação de sintaxe, antes de executar qualquer coisa.

Antes de executar um comando, o bash primeiro o analisa para verificar se as palavras estão dispostas de acordo com sua gramática. No momento em que decide "esta palavra não pode estar aqui legalmente", ele exibe esse erro e não executa nada.

$ echo hello world)
bash: syntax error near unexpected token `)'

Aqui ) é o unexpected token. O bash leu echo hello world normalmente, mas um ) apareceu sem um ( correspondente, então sinalizou um erro de sintaxe.

Enquanto command not found significa "o comando não foi localizado (mas a gramática estava correta)", syntax error significa "isso nem é uma declaração válida". Nada foi executado.

Por que a posição do token difere da causa real?

Conclusão: O bash lê da esquerda para a direita e para no primeiro ponto em que não consegue mais interpretar corretamente. O token reportado é esse ponto de parada; a causa (algo não fechado ou ausente) geralmente está antes dele.

Esta é a maior armadilha deste erro. Considere:

if [ "$x" -gt 0 ]
  echo "big"
fi
$ bash script.sh
script.sh: line 3: syntax error near unexpected token `fi'

O relatório aponta para fi na linha 3, mas a causa real é o then ausente na linha 1. O bash estava esperando then após if [ ... ], recebeu echo em vez disso, depois fi, e só então decidiu que não conseguia mais interpretar a entrada. Portanto, ele reporta o ponto onde parou (fi).

Dica de leitura

Quando o token é uma palavra de fechamento/terminação () / } / fi / done / esac / end of file), suspeite de um problema com o lado de abertura correspondente. Não corrija a posição do token; revise a estrutura antes dele.

Quando o token é fi / then / done

Conclusão: Um separador ausente (; ou nova linha), um then / do ausente, ou um bloco não fechado. Verifique se if-fi e for-done estão emparelhados.

Quando a estrutura de if / for / while / case está quebrada, essas palavras-chave se tornam o unexpected token.

Sem separador antes do then

Um ; ou nova linha é necessário entre a condição e o then.

# NG: sem separador entre ] e then
if [ "$x" = "1" ] then
  echo ok
fi
$ bash script.sh
script.sh: line 3: syntax error near unexpected token `fi'
# OK: separe com ; (ou coloque then na proxima linha)
if [ "$x" = "1" ]; then
  echo ok
fi

do ausente, ou done / fi sem correspondência

# NG: o do esta ausente
for f in *.txt
  echo "$f"
done
$ bash script.sh
script.sh: line 2: syntax error near unexpected token `echo'
script.sh: line 2: `  echo "$f"'

for ... in ... deve ser seguido por do. Sem do, o bash para na linha após a lista in (aqui echo na linha 2) como uma violação de gramática; ele nunca alcança done.

Quando você cola apenas algumas linhas, é fácil deixar um if ou do solto e deixar a estrutura meio aberta. Sempre verifique a abertura e fechamento de um bloco como um par.

Quando o token é ( ou um símbolo

Conclusão: Um caractere especial do shell sem aspas como ( ) & ; | < >. Coloque símbolos em nomes de arquivo ou strings entre aspas.

( e ) são caracteres especiais usados para subshells e definições de função, então escrevê-los sem aspas em um nome de arquivo ou argumento causa um erro de sintaxe.

# NG: os () no nome do arquivo sao lidos como caracteres especiais
$ cp report(final).txt /backup/
bash: syntax error near unexpected token `('

Corrija com aspas ou escape.

# OK: envolva em aspas
$ cp 'report(final).txt' /backup/

# OK: escape com barra invertida
$ cp report\(final\).txt /backup/

O mesmo acontece com & (background) ou ; (separador de comandos).

# NG: & e lido como operador de background
$ echo Tom & Jerry

Sempre coloque entre aspas qualquer símbolo que você pretende passar como texto literal.

Armadilha ao colar

Copiar da web ou de documentos pode transformar " em aspas curvas "smart quotes" ("`" "`"). Elas parecem similares, mas o bash as trata como caracteres diferentes, então a aspas nunca fecha e você recebe um erro de sintaxe. Verifique com cat -A, ou redigite as aspas manualmente.

Quando o token é newline ou end of file

Conclusão: Uma aspas, parêntese, here-document ou bloco de controle não fechado. O bash leu até o final do arquivo sem encontrar o fechamento e parou no terminador.

unexpected token 'newline' ou unexpected end of file é um sinal de que algo aberto nunca foi fechado.

Uma aspas não fechada

$ echo "hello
>

A " aberta nunca é fechada, então o bash trata a próxima linha como continuação da string e continua esperando entrada (o prompt >). Em um script, ele para no final.

echo "hello
echo "world"
$ bash script.sh
script.sh: line 2: unexpected EOF while looking for matching `"'

Um terminador de here-document incompatível

# NG: o terminador nao corresponde (nao esta no inicio da linha / tem espacos iniciais)
cat <<EOF
hello
  EOF

Um terminador de here-document deve ser um rótulo exato começando no início da linha. Indente-o e ele não é reconhecido: o bash exibe um warning: here-document ... delimited by end-of-file (wanted 'EOF') e trata tudo até o final do arquivo como o corpo (isso é um aviso, não um erro de sintaxe, e a linha EOF é impressa como está). Para indentar com tabs, use <<-EOF (remove apenas tabs iniciais, não espaços).

Você está executando com sh?

Conclusão: Executar sintaxe exclusiva do bash ([[ ]], arrays, substituição de processo) com sh script.sh causa um erro de sintaxe. Execute com bash, ou defina o shebang como #!/bin/bash.

No Ubuntu, /bin/sh é o dash, que não entende extensões do bash. Um script que funciona no bash falhará quando executado com sh.

# Um script com arrays e [[ ]] exclusivos do bash
arr=(a b c)
[[ -n "$1" ]] && echo "$1"
# NG: executar com sh (dash)
$ sh script.sh
script.sh: 1: Syntax error: "(" unexpected

O dash exibe Syntax error: "(" unexpected com redação diferente, mas a causa é a mesma: sintaxe que o POSIX sh não suporta. Corrija de uma destas formas.

# 1. Execute explicitamente com bash
$ bash script.sh

# 2. Defina o shebang como bash e execute diretamente
$ head -1 script.sh
#!/bin/bash
$ ./script.sh

./script.sh executa sob o interpretador no shebang, mas sh script.sh ignora o shebang e executa sob o dash. Verifique se você não está acidentalmente prefixando sh. Veja Corrigindo "bad interpreter" para detalhes.

Quando terminações de linha CRLF são a causa

Conclusão: Scripts editados no Windows carregam um \r (CR) no final de cada linha, causando erros de sintaxe e comportamento estranho. Identifique ^M com cat -A e converta com dos2unix.

Um script salvo por um editor Windows usa terminações de linha CRLF (\r\n), deixando um \r invisível no final de cada linha. Ele se gruda em palavras-chave e aspas e dispara erros de sintaxe ou command not found.

# o ^M no final das linhas mostra contaminacao por CR
$ cat -A script.sh
#!/bin/bash^M$
if [ "$x" = "1" ]; then^M$
echo ok^M$
fi^M$

$ marca o final da linha e ^M é o CR. Converta para LF.

# mais curto, se dos2unix estiver disponivel
$ dos2unix script.sh

# caso contrario, remova com sed / tr
$ sed -i 's/\r$//' script.sh

Configure seu editor para salvar com "terminações de linha: LF" e "codificação: UTF-8" para prevenir recorrência. Adicionar *.sh text eol=lf ao .gitattributes também funciona bem.

Checklist de diagnóstico

Conclusão: Trabalhe com "classificar pelo token -> suspeitar do que está antes -> verificar com bash -n -> inspecionar caracteres ocultos com cat -A" e você identificará quase todos os erros syntax error near unexpected token.

Percorra de cima para baixo.

  • [ ] Verificou o nome do token (fi/then/done = fluxo de controle, ( = caractere especial, newline/end of file = não fechado)
  • [ ] Releu a linha antes do token, não a posição do token
  • [ ] Executou bash -n script.sh para verificar apenas a sintaxe e localizar a linha
  • [ ] Verificou que as aberturas e fechamentos de if-fi / for-done coincidem
  • [ ] Verificou que aspas, parênteses e here-documents estão fechados
  • [ ] Colocou caracteres especiais em nomes de arquivo e argumentos entre aspas
  • [ ] Executou com bash script.sh / ./script.sh, não sh script.sh
  • [ ] Verificou ^M (CR) com cat -A e executou dos2unix se presente

Próximas leituras: