Fixing "bad interpreter" - Shebang and CRLF Issues

Fixing "bad interpreter" - Shebang and CRLF Issues

What is the "bad interpreter" error?

Conclusion: The shell cannot run the interpreter named on the script's shebang line. Almost every case is either CRLF line endings or a wrong shebang path.

You try to run a shell script and hit an error like this:

$ ./deploy.sh
-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory

The file exists and is executable, yet it refuses to run. This error means the kernel cannot start the interpreter declared on line 1 (the #!... shebang).

Narrow it to two causes first

  • You see ^M in the error → CRLF line endings (most common)
  • No ^M, but a path like /usr/bin/python3the shebang path does not exist

This article works through them in that order.

Note that Permission denied is a different problem: a missing execute bit (chmod +x). It is easy to confuse the two, so for the permission side see Permission denied Fix.

Why does it say "No such file or directory"?

Conclusion: The kernel uses the shebang line verbatim as the interpreter path. A trailing \r from CRLF turns /bin/bash into /bin/bash\r, a path that does not exist.

The Linux kernel reads the first line starting with #! and treats the text after #! literally, as the absolute path to an interpreter. That is the root of the error.

Files saved on Windows or by some editors use CRLF (\r\n) line endings instead of LF (\n). The shebang line is then read as:

#!/bin/bash\r
       this \r (CR) is part of the "path"

So the kernel looks for a file literally named /bin/bash + carriage return. No such file exists, so you get No such file or directory, and the CR shows up as ^M in the message.

Even when the name and path are correct, a single invisible \r breaks it. That is why the line "looks right but won't run."

The shebang path itself can also be wrong. For example, you wrote #!/usr/local/bin/python3 but Python only exists at /usr/bin/python3 on that machine.

How do you tell if CRLF is the cause?

Conclusion: Use file to detect the line-ending type and cat -A to reveal ^M at line ends. Both expose CRLF at a glance.

Observe the cause before fixing anything.

Check line endings with file

$ file deploy.sh

When CRLF is present, you see:

deploy.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators

with CRLF line terminators confirms line endings are the cause. A clean file shows only ASCII text executable with no CRLF note.

Reveal line ends with cat -A

$ cat -A deploy.sh | head -3
#!/bin/bash^M$
^M$
echo "deploy start"^M$

cat -A marks line ends with $ and CR with ^M. If every line ends in ^M$, it is CRLF. A clean file shows just $.

Inspect the shebang line precisely

$ head -1 deploy.sh | od -c
0000000   #   !   /   b   i   n   /   b   a   s   h  \r  \n

\r \n together means CRLF. \n alone means LF (clean).

How do you fix CRLF line endings?

Conclusion: dos2unix is the most reliable fix. Without it, use sed -i 's/\r$//' to strip the trailing CR from every line, then re-check with file.

$ dos2unix deploy.sh
dos2unix: converting file deploy.sh to Unix format...

A dedicated tool that converts CRLF to LF. Install it with sudo apt install dos2unix on Ubuntu/Debian or sudo dnf install dos2unix on RHEL-based systems.

Option 2: sed (no extra install)

If dos2unix is not available, sed does the job.

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

s/\r$// means "delete the CR at the end of each line." -i edits the file in place. Use -i.bak to keep a backup if you are cautious.

$ sed -i.bak 's/\r$//' deploy.sh   # keeps deploy.sh.bak

With tr -d '\r', tr only reads from standard input, so you cannot redirect back onto the same file (it would be emptied). Always write to a different file.

$ tr -d '\r' < deploy.sh > deploy.unix.sh   # OK
$ tr -d '\r' < deploy.sh > deploy.sh         # WRONG: ends up empty

Option 3: convert in vim

If the file is already open in an editor, fix it in place.

:set fileformat=unix
:w

Always confirm the fix

$ file deploy.sh
deploy.sh: Bourne-Again shell script, ASCII text executable
$ ./deploy.sh
deploy start

If the CRLF line terminators note is gone, you are done.

How do you fix a wrong shebang path?

Conclusion: Confirm the interpreter actually exists at the shebang path with which. For portability, use #!/usr/bin/env bash.

If there is no ^M and the message shows a path like bash: ./run.sh: /usr/local/bin/python3: bad interpreter, then no interpreter lives at that path.

Confirm the interpreter exists

$ head -1 run.sh
#!/usr/local/bin/python3
$ which python3
/usr/bin/python3

The shebang points at /usr/local/bin/python3, but the real binary is at /usr/bin/python3. The paths disagree.

Solve it with env

Instead of hardcoding the real path, let env find the interpreter on PATH for better portability.

#!/usr/bin/env python3
#!/usr/bin/env bash

env searches PATH for the interpreter, so the script works whether the binary is in /usr/bin or /usr/local/bin. It is the modern, environment-resilient form.

Note that passing arguments to the interpreter through env is limited (older systems cannot pass multiple arguments). Options like set -euo pipefail belong in the script body, not crammed into the shebang.

How do you prevent it from coming back?

Conclusion: Pin .sh files to LF with .gitattributes and set your editor to save as LF. These two steps shut down almost all CRLF reintroduction.

Fixing it once is pointless if CRLF returns on every save or commit. Cut it off at the source.

Pin line endings in Git

Place a .gitattributes at the repository root and pin shell scripts to LF.

*.sh text eol=lf

Now checkouts always use LF, so the file survives edits from Windows machines.

Also check your core.autocrlf setting. On Linux/macOS, input is the safe choice.

$ git config --global core.autocrlf input

core.autocrlf=true is common among Windows developers, but it adds CRLF on checkout, which is a breeding ground for bad interpreter in shell scripts. When you work with scripts, override it explicitly with eol=lf in .gitattributes.

Set your editor to save as LF

  • VS Code: click the CRLF indicator at the bottom-right and switch it to LF. Add an .editorconfig with end_of_line = lf to automate it
  • vim: save with :set fileformat=unix
  • Windows Notepad: do not use it for shell scripts (it tends to add CRLF)

Final checklist

  1. file script.sh shows no CRLF line terminators
  2. The shebang path in head -1 script.sh resolves to a real interpreter via which
  3. ls -l script.sh shows the execute bit (x) is set

With all three in place, bad interpreter will not return.

Next Reading