bad interpreter

Fix 'bash: ./script.sh: /bin/bash^M: bad interpreter' on Linux

That ^M in the error means your script has Windows line endings. Here's the two-second fix with sed or dos2unix, and how to stop it happening again.

I know this one is infuriating — the file is right there, it's executable, and bash still acts like you're pointing at a ghost.

Here's the real error, in full, the way you actually see it:

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

That little ^M is the whole story. It's a carriage return (CR, or \r) that got wedged between /bin/bash and the newline. Linux expects only a line feed (\n) at the end of the shebang line. When there's a stray \r, the kernel tries to run a program literally named /bin/bash\r, which doesn't exist.

Fix it with one command. Pick whichever you have installed:

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

Or if you've got dos2unix (install it with apt install dos2unix or dnf install dos2unix):

dos2unix deploy.sh

Then run the script again. That's it. If you want to check first, file deploy.sh will say with CRLF line terminators when the file is the problem and ASCII text executable when it's fine.

Why the fix works

The shebang line is special. When you type ./deploy.sh, the kernel reads the first two bytes. If they're #!, it takes everything to the end of that line and treats it as the path to an interpreter. It doesn't strip whitespace. It doesn't trim control characters. It takes the bytes literally. So /bin/bash\r becomes a path it tries to execve(), and the kernel hands you back ENOENT — "No such file or directory" — because no file on your disk is named with a trailing carriage return.

The sed -i 's/\r$//' command strips a CR only when it sits at the very end of a line, so it won't mangle CRs that appear legitimately inside your script. That's why this is safer than a blanket tr -d '\r', which will eat carriage returns in the middle of strings too.

The usual culprits

You didn't write it that way on purpose. It came from somewhere:

  • Copy-paste from a Windows machine. Notepad, Word, and a surprising number of web forms save text with CRLF endings. Someone emails you the script, you paste it into a file on your Linux box, and the damage is baked in.
  • Git on Windows with core.autocrlf=true. This is the big one. Git converts LF to CRLF on checkout. If a shell script is checked out that way, every line ends in \r. Fix it per-repo with a .gitattributes file:
    *.sh text eol=lf
    *.bash text eol=lf
    Then run git add --renormalize . and commit.
  • VS Code with the wrong line-ending setting. Look at the bottom-right corner — it'll say "CRLF" instead of "LF". Click it, switch to LF, save. Set "files.eol": "\n" in settings to make LF the default for new files.
  • WSL interop. You edit a script in Windows Notepad at /mnt/c/scripts/foo.sh and run it from inside WSL. WSL sees the CRLF endings exactly as bash does. Same fix.

Less common variations you might hit

Error says "No such file or directory" but the file is clearly there

If ls -l script.sh shows the file and it's executable, but you still get "No such file or directory" with no ^M visible, you're probably on a 64-bit system running a 32-bit interpreter, or the dynamic linker path is wrong. Check with:

file script.sh
ldd $(which bash)

If file says "ELF 32-bit" and you're on a 64-bit-only distro (common on newer Alpine or minimal containers), install the multilib packages or the 32-bit runtime.

BOM at the start of the file

Some Windows editors (looking at you, older Notepad) prefix the file with a UTF-8 BOM: bytes EF BB BF. That breaks the shebang too, because the file now starts with \xEF\xBB\xBF#! and the kernel doesn't see the #! at all. You'll usually get cannot execute binary file or a weird permission-denied. Strip it with:

sed -i '1s/^\xEF\xBB\xBF//' script.sh

Shebang points to a path that moved

Scripts with #!/usr/bin/python break on systems where Python 3 lives at /usr/bin/python3 and there's no python symlink. Use #!/usr/bin/env python3 instead — env finds the interpreter on $PATH, so it survives upgrades and distro differences. This is why env is standard in scripts you plan to share.

Script runs fine interactively but fails from cron

Not a line-ending problem, but it looks like this error because cron has a minimal $PATH. Your shebang is #!/usr/bin/env bash, cron can't find bash, and the error reads the same. Add PATH=/usr/local/bin:/usr/bin:/bin at the top of your crontab, or hardcode interpreter paths in the script itself.

Stop this from happening again

The permanent fix is a .gitattributes file at the root of any repo containing shell scripts. Drop this in and commit it:

* text=auto
*.sh text eol=lf
*.bash text eol=lf
*.py text eol=lf

That tells Git to normalize everything on commit and check out shell scripts with LF only, on every platform, forever. No more "works on my machine."

For your editor, set the default to LF. VS Code: "files.eol": "\n". Vim: add set fileformat=unix to your .vimrc. Sublime: "default_line_ending": "unix". Do it once and you'll never see ^M in an error message again.

If you're stuck in a Windows-only workflow, install dos2unix anyway and run it as a pre-commit hook. Fifteen seconds of setup saves you an hour of swearing at a script that looks perfect.

Related Errors in Linux & Unix
Permission denied (publickey) Fix 'Permission denied (publickey)' When SSHing Into Ubuntu 22.04 Fixing Filesystem Superblock Corruption in Linux containerd.service: Main process exited, code=exited, status=2/INVALIDARGUMENT Containerd Service Keeps Crashing – Fix It Fast Fix Linux 'bash: /usr/bin/rm: Argument list too long' Error

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.