Fixing the `$'\r': command not found` error in Bash scripts with forensic diagnosis and pipeline-level prevention
Back to blog

Fixing the `$'\r': command not found` error in Bash scripts with forensic diagnosis and pipeline-level prevention

6/7/2026 · 3 min · Infrastructure

When an apparently correct script fails on Linux with erratic messages such as \$'\\r': command not found, syntax error near unexpected token, or \$'{\\r'', the root cause almost always resides in the encoding and line-termination layer, rather than the Bash logic itself.

In this article, I document the production-grade procedure I utilize to:

  1. Technically prove the origin of the failure;
  2. Correct the affected files without unintended side effects;
  3. Shield the deployment pipeline to ensure the incident does not recur.

1) Root cause: CRLF (Windows) persistence in Linux runtimes#

The structural difference is straightforward yet critical:

When Bash encounters a \r (carriage return) at the end of a line, it interprets this extra byte as part of the command token. The result is a "command not found" error for a non-existent command named \r, or structural syntax errors in code that visually appears perfect.

Classic field-observed errors:

./deploy.sh: line 2: $'\r': command not found
./deploy.sh: line 18: syntax error near unexpected token `$'{\r''

2) Reliable forensic diagnosis (moving beyond assumptions)#

Before attempting a fix, validate the evidence. I utilize four distinct verification methods.

2.1 file: objective detection of line terminators#

file script.sh

If the output contains with CRLF line terminators, the diagnosis is definitive.

2.2 cat -v: visualizing non-printable characters#

cat -v script.sh | sed -n '1,40p'

If you see ^M at the end of lines, that ^M is the literal \r damaging the execution flow.

2.3 od/hexdump: binary proof#

od -An -tx1 -N 80 script.sh
# or
hexdump -C script.sh | head

Look for the byte sequence 0d 0a (CRLF). In a pure Linux-native script, you should only see 0a.

2.4 shebang and permission validation (avoiding confusion)#

head -n 1 script.sh
ls -l script.sh

Ensure the script starts with a valid shebang (#!/usr/bin/env bash) and possesses the correct execution permissions. While this doesn't resolve CRLF, it eliminates false positives during triage.

3) Safe correction: three operational methods#

3.1 preferred method: dos2unix#

This is a dedicated, predictable tool for CRLF -> LF conversion.

dos2unix script.sh

Installation:

# Debian/Ubuntu
sudo apt update && sudo apt install -y dos2unix

# RHEL/CentOS/Alma/Rocky
sudo dnf install -y dos2unix

3.2 universal method without extra packages: sed#

sed -i 's/\r$//' script.sh

This command surgically removes the \r character at the end of each line while preserving all other content.

3.3 pipeline with temporary output: tr#

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

Use this when an explicit comparison between the original and converted file is required before performing the final replacement.

4) Bulk correction of entire directories (with protection)#

In real-world incidents, the issue rarely exists in a single file. To handle bulk conversion safely without affecting binaries:

find . -type f \( -name "*.sh" -o -name "*.bash" -o -name "*.env" \) -print0 \
  | xargs -0 dos2unix

Post-conversion validation:

find . -type f -name "*.sh" -exec file {} \; | grep -i crlf && echo "CRLF still present" || echo "All scripts normalized to LF"

5) Recurrent production case: CRLF + bad interpreter#

Beyond the standard command error, I frequently encounter:

/bin/bash^M: bad interpreter: No such file or directory

This occurs when the \r contaminates the shebang line itself. The correction is identical (LF), followed by a fresh execution test.

6) Definitive hardening in the DevOps workflow#

Remediating a file resolves the current incident. To prevent recurrence, I standardized the repository, editor settings, and Git configuration.

6.1 .editorconfig for EOL governance#

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 for repository normalization#

* text=auto
*.sh text eol=lf
*.bash text eol=lf
*.env text eol=lf
git config --global core.autocrlf input
git config --global core.eol lf

By setting autocrlf to input, Git automatically converts CRLF -> LF during the commit phase, preventing contaminated scripts from entering the repository.

6.4 CI quality gate#

I include an automated validation to break the pipeline if CRLF is detected in shell scripts:

#!/usr/bin/env bash
set -euo pipefail

if git ls-files '*.sh' '*.bash' | xargs file | grep -qi 'CRLF'; then
  echo "ERROR: CRLF-encoded scripts detected"
  git ls-files '*.sh' '*.bash' | xargs file | grep -i 'CRLF'
  exit 1
fi

echo "OK: All shell scripts use LF"

7) Fast incident response runbook#

When the error manifests in production, I execute this exact sequence:

  1. Confirm the Issue:
   file deploy.sh
   cat -v deploy.sh | head
  1. Correct:
   dos2unix deploy.sh
   chmod +x deploy.sh
  1. Validate Syntax:
   bash -n deploy.sh
  1. Execute with Controlled Trace:
   bash -x deploy.sh

If dealing with a release batch, I apply the fix to all scripts and run bash -n checks for integrity:

find scripts -type f -name '*.sh' -exec bash -n {} \;

8) Technical conclusion#

the \$'\\r': command not found error is not a Bash bug - it is a lack of standardization between Windows and Linux environments. In a professional operation, this type of failure must be addressed as a code supply chain issue (editor + VCS + CI), rather than an isolated script incident.

By utilizing evidence-based diagnosis (file, cat -v, hexdump), controlled correction (dos2unix/sed), and pipeline hardening (.editorconfig, .gitattributes, CI gates), this problem effectively disappears from the daily operational cycle.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments