Quando um script aparentemente correto falha no Linux com mensagens como \$'\\r': command not found, syntax error near unexpected token ou \$'{\\r'', o problema quase sempre está na camada de encoding e terminação de linha, não na lógica Bash.
Neste artigo, documentei o procedimento que uso em produção para:
- comprovar tecnicamente a origem da falha;
- corrigir o arquivo sem efeitos colaterais;
- blindar o pipeline para que o incidente não volte.
1) Causa raiz: CRLF (Windows) em runtime Linux
A diferença estrutural é simples, mas crítica:
- Windows grava fim de linha como
CRLF(\r\n); - Linux/Unix espera
LF(\n).
Quando o Bash encontra \r no fim da linha, ele interpreta esse byte extra como parte do token/comando. O resultado é erro de comando inexistente (\r) ou erro sintático em pontos que visualmente parecem corretos.
Exemplo clássico observado em campo:
./deploy.sh: line 2: $'\r': command not found
./deploy.sh: line 18: syntax error near unexpected token `$'{\r''
2) Diagnóstico técnico confiável (sem suposição)
Antes de “consertar”, valide evidência. Eu uso quatro verificações.
2.1 file: detecção objetiva de terminador de linha
file script.sh
Se a saída contiver with CRLF line terminators, o diagnóstico está fechado.
2.2 cat -v: visualização de caracteres não imprimíveis
cat -v script.sh | sed -n '1,40p'
Se aparecer ^M no fim das linhas, esse ^M é o \r que está quebrando a execução.
2.3 od/hexdump: prova binária
od -An -tx1 -N 80 script.sh
# ou
hexdump -C script.sh | head
Procure sequência 0d 0a (CRLF). Em Linux puro, o esperado é apenas 0a.
2.4 Validação de shebang e permissões (não confundir causa)
head -n 1 script.sh
ls -l script.sh
O script deve começar com shebang válido (#!/usr/bin/env bash ou #!/bin/bash) e permissão de execução adequada. Isso não resolve CRLF, mas elimina falso positivo de diagnóstico.
3) Correção segura: três métodos operacionais
3.1 Método preferido: dos2unix
Ferramenta dedicada e previsível para conversão CRLF -> LF.
dos2unix script.sh
Instalação:
# Debian/Ubuntu
sudo apt update && sudo apt install -y dos2unix
# RHEL/CentOS/Alma/Rocky
sudo dnf install -y dos2unix
3.2 Método universal sem pacote extra: sed
sed -i 's/\r$//' script.sh
Esse comando remove apenas \r no final de cada linha, preservando o restante do conteúdo.
3.3 Pipeline com saída temporária: tr
tr -d '\r' < script.sh > script.sh.lf && mv script.sh.lf script.sh
Uso quando quero comparação explícita entre arquivo original e convertido antes do replace final.
4) Correção em lote de diretórios inteiros (com proteção)
Em incidentes reais, o problema raramente vem em um único arquivo. Para tratar em lote sem afetar binários:
find . -type f \( -name "*.sh" -o -name "*.bash" -o -name "*.env" \) -print0 \
| xargs -0 dos2unix
Validação pós-conversão:
find . -type f -name "*.sh" -exec file {} \; | grep -i crlf && echo "ainda existe CRLF" || echo "todos os scripts em LF"
5) Caso recorrente em produção: CRLF + bad interpreter
Além de \$'\\r': command not found, também encontrei:
/bin/bash^M: bad interpreter: No such file or directory
Isso acontece quando o \r contamina até a linha do shebang. A correção é idêntica (LF), seguida de novo teste de execução.
6) Hardening definitivo no fluxo DevOps
Remediar arquivo resolve incidente atual. Para evitar reincidência, padronizei repositório, editor e Git.
6.1 .editorconfig para governança de EOL
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[*.sh]
indent_size = 2
6.2 .gitattributes para normalização no repositório
* text=auto
*.sh text eol=lf
*.bash text eol=lf
*.env text eol=lf
6.3 Configuração Git recomendada no workstation
git config --global core.autocrlf input
git config --global core.eol lf
Com input, o Git converte CRLF -> LF no commit e evita que scripts subam contaminados.
6.4 Gate de qualidade no CI
Incluí validação automática para quebrar pipeline ao detectar CRLF em scripts:
#!/usr/bin/env bash
set -euo pipefail
if git ls-files '*.sh' '*.bash' | xargs file | grep -qi 'CRLF'; then
echo "ERRO: scripts com CRLF detectados"
git ls-files '*.sh' '*.bash' | xargs file | grep -i 'CRLF'
exit 1
fi
echo "OK: todos os scripts em LF"
7) Runbook rápido de resposta a incidente
Quando o erro aparece em produção, executo essa sequência:
# 1) confirmar problema
file deploy.sh
cat -v deploy.sh | head
# 2) corrigir
dos2unix deploy.sh
chmod +x deploy.sh
# 3) validar sintaxe
bash -n deploy.sh
# 4) executar com trace controlado
bash -x deploy.sh
Se for lote, aplico nos scripts do release e repito bash -n em todos:
find scripts -type f -name '*.sh' -exec bash -n {} \;
8) Conclusão técnica
O erro \$'\\r': command not found não é bug de Bash, é dívida de padronização entre Windows e Linux. Em operação profissional, esse tipo de falha deve ser tratado como problema de supply chain de código (editor + VCS + CI), não como incidente isolado de script.
Com diagnóstico por evidência (file, cat -v, hexdump), correção controlada (dos2unix/sed) e hardening de pipeline (.editorconfig, .gitattributes, checks de CI), o problema deixa de existir no dia a dia.
Este post está licenciado sob CC BY-NC.
Comentários
Participe da discussão abaixo.
Comentários ainda não configurados. Adicione as opções do Cusdis em /assets/json/config/blog-comments-config.json.