ShellCheck calls this SC1017
The ShellCheck Error Decoder explains every code on this site, and the SC2086 deep dive is the one most scripts trip first. CRLF is worth catching before either, because a script with carriage returns fails before any of its own logic runs.
The script ran fine on your machine. You copied it to a server through a Windows editor, a web form, a Slack paste or a git checkout with core.autocrlf=true, and now it will not start at all. /bin/bash^M: bad interpreter. Nothing in the script changed except an invisible byte at the end of every line. That byte is also why the less obvious failure is the dangerous one: run the same file as bash script.sh and it does start, with strict mode off.
What Do CRLF Line Endings Break?
Reproduced here on 2026-09-28 (Kali, bash 5.3.9, file 5.47, ShellCheck 0.11.0). Three scripts written with printf '…\r\n' so every line ends in CRLF, the way a Windows editor saves them.
A normal shebang, run directly:
An env shebang fails with a different message for the same reason:
And the one that looks like it worked. sourced.sh is three lines, set -e, cd /tmp, echo done, run through bash directly so the shebang is never read:
set -e became set -e plus a carriage return, an invalid option, so errexit never turned on. cd then failed on a directory named /tmp plus a carriage return, and because errexit was off the script carried on, printed done, and exited 0. In a real deploy script the next line runs in whatever directory you started in.
How Do I See the Carriage Returns?
file names the problem outright:
cat -A shows each carriage return as ^M before the $ that marks the line end:
od -c shows the actual bytes, \r \n at the end of the shebang:
And ShellCheck flags every line:
The Script
Save as crlf-check.sh. It finds every text file with CRLF line endings under the paths you give it, reports each with file's description, and strips them with --apply.
What Does the Script Print?
On the same deploy/ directory, which also holds one clean LF script (ok.sh) that it correctly leaves out:
The dry run exits 1 when it finds anything, so it works as a CI gate or a pre-commit check as it stands. sourced.sh is caught even though file does not call it a shell script, because it has no shebang. That is the file that would have run with strict mode off.
How Do I Stop Git Bringing CRLF Back?
Fixing the files once does not help if the next checkout on a Windows machine with core.autocrlf=true converts them again. A .gitattributes rule overrides every developer's local setting. Run here in a scratch repository (git 2.53.0) with a CRLF script already committed:
i/ is the index, w/ the working tree. After --renormalize the committed copy is LF while the file on disk is still CRLF. After a commit and a fresh checkout both sides are LF:
Commit the .gitattributes file with the renormalized scripts in the same commit, so nobody can check out one without the other.
Line endings are one of the failure modes a standard script template catches before it matters. The safe bash script template covers the others, and The Production Bash Toolkit ships the template with ShellCheck-clean helpers so every new script starts from a known-good file.
Frequently Asked Questions
What does /bin/bash^M: bad interpreter mean?
The script has Windows CRLF line endings. The first line is #!/bin/bash followed by a carriage return (^M) and a newline, so the kernel looks for an interpreter called /bin/bash plus a carriage return. No such file exists, so exec fails with exit code 126 and the shell prints bad interpreter: No such file or directory.
How do I remove ^M characters from a bash script?
Run sed -i 's/\r$//' script.sh, which deletes a carriage return at the end of every line and edits the file in place. dos2unix script.sh does the same if it is installed. tr -d '\r' < in.sh > out.sh also works but removes carriage returns anywhere in the line, not only at the end, and needs a second file.
Why does my script say $'\r': command not found?
Same cause, different entry point. When you run bash script.sh instead of ./script.sh, the shebang is skipped and bash reads each line with its trailing carriage return. An empty line becomes a command named $'\r', cd /tmp becomes cd to a directory named /tmp plus a carriage return, and set -e becomes set with an invalid option, so strict mode silently never turns on.
How do I stop git from converting my scripts to CRLF?
Add a .gitattributes file at the repository root with the line *.sh text eol=lf, commit it, and run git add --renormalize . to fix files already committed with CRLF. The attribute overrides each developer's core.autocrlf setting, so a checkout on Windows still gets LF in shell scripts.
Does ShellCheck detect CRLF line endings?
Yes. ShellCheck reports SC1017 (error): Literal carriage return. Run script through tr -d '\r' on each affected line. A ShellCheck step in CI or a pre-commit hook catches CRLF before the script reaches a server.
Part of the bash snippets collection
Related Scripts
- Bash Error Handling — what
set -euo pipefailis supposed to catch, when it is actually on - Find and Replace With sed — the
sed -ipatterns behind the fix - Search Files for Text With grep — the
grep -rlIsearch the script is built on