Quick fix: the missing file is not your script but the interpreter named on its first line, the shebang. Most often the script has Windows (CRLF) line endings, so the system looks for /bin/bash followed by an invisible carriage return. Run sed -i 's/\r$//' script.sh (or dos2unix script.sh), then stop your editor or Git from putting the line endings back.
The confusing part is the wording, and the wording depends on your shell. bash 5.1 and 5.3 say /bin/bash^M: bad interpreter: No such file or directory. bash 5.2, the version in Debian 12, Debian 13 and Ubuntu 24.04, says cannot execute: required file not found instead, and the sh on Debian and Ubuntu only prints ./script.sh: not found. All of them mean the same thing, and all of them have the same three causes.
Why does Linux say “No such file or directory” when the file exists?
When you run ./script.sh, the kernel reads the first line, for example #!/bin/bash, and starts that program with your script as its argument. If the program on that line does not exist, starting it fails with “No such file or directory”, and the shell reports that against the file you typed. The missing file is never your script. It is the one its first line points to.
Compiled programs work the same way. A dynamically linked binary names its own loader (for example /lib64/ld-linux-x86-64.so.2), and if that loader is missing you get the same error for a binary that is plainly there. Two commands show you which case you have:
head -1 script.sh | od -c # the shebang, byte by byte: look for \r before \n
file script.sh # "with CRLF line terminators", or the ELF interpreter for a binary\rbefore\n, or “CRLF line terminators” — Windows line endings. Cause 1.- A clean shebang naming a program you do not have — cause 2.
- An ELF binary whose interpreter is missing or whose architecture is wrong — cause 3.
Which message does my shell print?
The same Windows-edited script (first line #!/bin/bash plus a carriage return), run as ./crlf.sh, gets a different message on each system:
shell system message exit
bash 5.1 Ubuntu 22.04, Rocky / Alma 9 ./crlf.sh: /bin/bash^M: bad interpreter: No such file or directory 126
bash 5.2 Debian 12 and 13, Ubuntu 24.04 ./crlf.sh: cannot execute: required file not found 127
bash 5.3 Fedora 44, Alpine 3.24 ./crlf.sh: /bin/bash^M: bad interpreter: No such file or directory 126
dash (/bin/sh) Debian, Ubuntu ./crlf.sh: not found 127
BusyBox ash (/bin/sh) Alpine ./crlf.sh: not found 127
zsh 5.9 Ubuntu 24.04 ./crlf.sh: bad interpreter: /bin/bash^M: no such file or directory 127
#!/usr/bin/env bash any (GNU coreutils) /usr/bin/env: 'bash\r': No such file or directory 127Two practical consequences. Searching your logs for the familiar “bad interpreter” text finds nothing on Debian 12, Debian 13 or Ubuntu 24.04, because bash 5.2 does not print it. And the exit code changes: the same broken script exits 126 on Rocky and 127 on Debian, which matters if a CI job or a monitoring check tests for one of them. What those codes mean in general is in exit code 127.
Cause 1: how do I remove Windows line endings (the ^M)?
Windows ends each line of a text file with two characters, carriage return and line feed (\r\n). Linux uses only the line feed. A script saved on Windows, pasted through some web editors, or checked out by Git on Windows therefore has a \r at the end of every line, and bash prints that character as ^M. The kernel takes the first line literally, so it looks for a program called /bin/bash\r, which does not exist.
Detect it
file script.sh # ... with CRLF line terminators
head -1 script.sh | od -c # # ! / b i n / b a s h \r \n
grep -c $'\r' script.sh # number of lines that carry a carriage returnfile is not installed on every minimal image; od and grep are.
Fix it
# GNU sed (Debian, Ubuntu, Rocky, Alma, Fedora) and BusyBox sed (Alpine)
sed -i 's/\r$//' script.sh
# every shell script below the current directory
find . -name '*.sh' -exec sed -i 's/\r$//' {} +
# portable version, also fine on macOS
tr -d '\r' < script.sh > script.tmp && mv script.tmp script.sh && chmod +x script.shdos2unix does the same and is easier to remember. It is its own package everywhere:
sudo apt install -y dos2unix # Debian, Ubuntu
sudo dnf install -y dos2unix # Rocky, Alma, RHEL, Fedora
apk add dos2unix # Alpine (BusyBox also has a dos2unix applet)
brew install dos2unix # macOS
dos2unix script.shStop it coming back
Fixing the file on the server lasts until the next deploy. The fix that sticks lives in the repository: a .gitattributes file tells Git to check shell scripts out with LF endings on every machine, Windows included.
*.sh text eol=lf
*.bash text eol=lf
Dockerfile text eol=lfgit add .gitattributes
git add --renormalize . # re-stages files that were committed with CRLF
git commit -m "Force LF line endings for shell scripts"
# in an existing Windows checkout, after committing or stashing your work:
# delete the affected scripts, then restore them from the repository
git checkout -- path/to/script.sh- core.autocrlf is the per-machine setting behind most of these files: with
core.autocrlf=true, which the Git for Windows installer suggests by default, Git converts LF to CRLF on checkout..gitattributesoverrides it for the paths it names, and unlike a local setting it travels with the repository. - Your editor shows the current line ending in the status bar. In VS Code, click
CRLFand chooseLF, or set"files.eol": "\n"; in Notepad++ use Edit → EOL Conversion → Unix (LF).
Why does “bash script.sh” run when “./script.sh” fails?
With bash script.sh you start the interpreter yourself, so the shebang is never looked at and its stray \r does no harm. The rest of the file still has carriage returns, though, and they now break individual lines instead:
script.sh: line 2: $'\r': command not found
script.sh: line 3: cd: $'/tmp\r': No such file or directory
ls: cannot access '/nonexistent'$'\r': No such file or directory$'\r': command not found on an empty line is the giveaway. Strip the carriage returns as above rather than relying on calling bash directly.
Cause 2: what if the shebang names a program that is not installed?
If od -c shows a clean first line, read it and check that the program exists:
head -1 script.sh # e.g. #!/usr/bin/python
ls -l /usr/bin/python # the exact path from the shebang
for c in python3 bash node; do command -v "$c"; done # what this system does have#!/usr/bin/python— Debian 12 and 13, Ubuntu 22.04 and 24.04, and Rocky and Alma 9 name the interpreterpython3and ship no/usr/bin/python. Change the line to#!/usr/bin/env python3, or add the alias:python-is-python3on Debian and Ubuntu,python-unversioned-commandon Rocky and Alma. More in python: command not found.#!/bin/bashon Alpine — Alpine and most images built on it have no bash, only BusyBoxsh, which reports./script.sh: not found. Eitherapk add bash, or change the shebang to#!/bin/shif the script avoids bash-only syntax.#!/usr/bin/env node—envsearches the PATH, so this fails with/usr/bin/env: 'node': No such file or directorywhen Node is not installed, or is installed through nvm for another user, or under cron or sudo with a shorter PATH. See npx: command not found for a system-wide install.- A moved virtualenv — scripts in
venv/bincarry the absolute path of the venv’s Python in their shebang. Rename or copy the venv and every one of them points at a path that no longer exists. Recreate it in its new place instead of moving it:python3 -m venv /srv/app/venv && /srv/app/venv/bin/pip install -r requirements.txt. - Options after env —
#!/usr/bin/env bash -emakes env look for a program literally calledbash -e. Current GNU env says so:use -[v]S to pass options in shebang lines. Write#!/usr/bin/env -S bash -e, or putset -eon the second line.
Cause 3: what if the binary was built for a different system?
For a compiled program, file prints the loader it expects:
file ./program
# ELF 64-bit LSB pie executable, x86-64, ..., interpreter /lib64/ld-linux-x86-64.so.2
uname -m # x86_64 or aarch64: the architecture of this machine- A glibc binary on Alpine. Alpine uses musl, so the glibc loader the binary names does not exist and BusyBox reports
not found. Use a build made for musl or a static build, base the image ondebian:*-sliminstead, or tryapk add gcompat, a compatibility layer that is enough for simple programs. - A 32-bit binary on a 64-bit system. It wants
/lib/ld-linux.so.2. Add 32-bit support:sudo dpkg --add-architecture i386 && sudo apt update && sudo apt install -y libc6:i386on Debian and Ubuntu,sudo dnf install -y glibc.i686on Rocky and Alma. The next message is usually about a missing library; that one is covered in error while loading shared libraries. - The wrong CPU architecture gives a different message:
cannot execute binary file: Exec format error, exit 126. An ARM64 build on an x86-64 server, or the reverse. Download the build that matchesuname -m.
Why does Docker say “exec /entrypoint.sh: no such file or directory”?
Containers produce the same failure with their own wording. Depending on the Docker version, the log shows exec /entrypoint.sh: no such file or directory or the older standard_init_linux.go:228: exec user process caused: no such file or directory (the line number varies). The file is in the image; one of the three causes above is too. In practice it is usually an entrypoint script checked out on Windows with CRLF endings, or a #!/bin/bash script in an Alpine-based image.
docker run --rm --entrypoint sh myimage -c 'head -1 /entrypoint.sh | od -c'Fix the file in the repository and add the .gitattributes rule above, so every future build is clean. If you cannot control where the image is built, strip the carriage returns during the build as a safety net:
COPY entrypoint.sh /entrypoint.sh
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]Which similar errors have a different cause?
bash: ./script.sh: Permission denied, exit 126, means the file lacks its execute bit or sits on anoexecmount. See bash: Permission denied.script.sh: command not foundwithout./means the shell searched your PATH, which does not include the current directory. Run it as./script.sh.
Deploy scripts written on one machine and run on another are where all three causes show up. Running them once on a clean Debian, Ubuntu or Rocky install catches them before production does; on a Standard KVM VPS you can reinstall a different distribution from the panel and test against each.
Frequently asked
What does ^M mean in 'bad interpreter'?
^M is how the terminal shows a carriage return, the extra character Windows puts at the end of every line. '/bin/bash^M: bad interpreter' means the system looked for a program literally named '/bin/bash' plus a carriage return. Strip them with "sed -i 's/\r$//' script.sh" or dos2unix.
How do I convert CRLF to LF on Linux?
Run "sed -i 's/\r$//' file" or install dos2unix and run 'dos2unix file'. For a Git repository, add '*.sh text eol=lf' to .gitattributes and run 'git add --renormalize .' so the scripts stay LF on every machine, Windows included.
Why does bash 5.2 say 'cannot execute: required file not found'?
It is how bash 5.2, the version in Debian 12 and 13 and Ubuntu 24.04, reports a missing interpreter or ELF loader, with exit code 127. bash 5.1 and 5.3 print 'bad interpreter: No such file or directory' and exit 126 instead. The causes are the same: CRLF line endings, a shebang naming a program that is not installed, or a binary built for another system.
Why does 'bash script.sh' run but './script.sh' fail?
Calling bash yourself skips the shebang, so a broken first line no longer matters. If the cause is CRLF line endings, the rest of the file still breaks line by line with errors such as "$'\r': command not found". Convert the file to LF instead of relying on calling bash directly.