Bash & Python Error Guide: 'No such file or directory' on an Existing Script
Fix 'No such file or directory' when running a script that clearly exists: bad shebang, CRLF in the interpreter line, missing interpreter, or wrong arch binary.
- #automation
- #troubleshooting
- #errors
- #shebang
Overview
It is one of the most confusing errors in shell work: you run ./deploy.sh, the file is right there, ls shows it, and yet you get No such file or directory. The trick is that the kernel is not complaining about your script — it is complaining about the interpreter named on the script’s shebang line. When you execute a script, the kernel reads the first line #!/path/to/interp, then tries to run that interpreter with your script as an argument. If that interpreter path does not exist (or has a hidden \r glued to it), you get ENOENT pointing at the script you launched.
The deceptive form:
./deploy.sh: No such file or directory
Or with bash’s wrapper making it slightly clearer:
bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory
It occurs at exec time, for scripts and ELF binaries alike. For scripts the culprit is the shebang; for binaries it can be a missing dynamic loader. Either way, the file you named exists — something it depends on does not.
Symptoms
./script.shreturnsNo such file or directoryeven thoughls -l script.shlists it.- The message sometimes names a
bad interpreter, sometimes does not. - The same script runs fine with
bash script.shbut not with./script.sh. - A binary you just downloaded fails to exec despite being present and executable.
ls -l ./deploy.sh
-rwxr-xr-x 1 ubuntu ubuntu 412 Jun 23 10:02 ./deploy.sh
./deploy.sh
bash: ./deploy.sh: /usr/bin/python3^M: bad interpreter: No such file or directory
Common Root Causes
1. A \r (CRLF) appended to the interpreter on the shebang line
The file has Windows line endings, so the shebang is literally #!/bin/bash\r. The kernel looks for an interpreter named /bin/bash followed by a carriage return — which does not exist.
head -1 ./deploy.sh | cat -A
#!/bin/bash^M$
The ^M is the carriage return. Strip it:
sed -i 's/\r$//' ./deploy.sh
# or
dos2unix ./deploy.sh
2. The shebang points to an interpreter that is not installed there
The script expects /usr/bin/python but the system only has /usr/bin/python3, or expects /bin/bash on a minimal image that only ships /bin/sh.
head -1 ./report.py
ls -l /usr/bin/python 2>&1
#!/usr/bin/python
ls: cannot access '/usr/bin/python': No such file or directory
The interpreter genuinely is not there. Fix the shebang or use #!/usr/bin/env python3:
sed -i '1s|.*|#!/usr/bin/env python3|' ./report.py
3. A wrong or relative path in the shebang
The shebang has a typo (#!/usr/bin/bsah) or a relative path (#!bin/bash), which the kernel cannot resolve.
head -1 ./build.sh
#!/usr/local/bni/bash
bni is a transposition of bin; correct the path.
4. The interpreter named by env is not on PATH
#!/usr/bin/env python resolves python via PATH. If python is absent (common on modern distros that ship only python3), env fails and you get ENOENT.
head -1 ./tool.py
/usr/bin/env python --version 2>&1
#!/usr/bin/env python
/usr/bin/env: 'python': No such file or directory
Point it at the interpreter that exists (python3), or install a python shim.
5. A downloaded ELF binary is missing its dynamic loader / wrong architecture
For compiled binaries, “No such file or directory” can mean the ELF interpreter (ld-linux) referenced inside the binary is missing — often because the binary is for the wrong architecture (e.g., an arm64 binary on amd64) or a different libc (glibc vs musl).
file ./mytool
./mytool 2>&1
./mytool: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, interpreter /lib/ld-linux-aarch64.so.1
bash: ./mytool: cannot execute: required file not found
This host is amd64; the binary is aarch64. Download the matching architecture build.
6. The shebang line is too long or the interpreter path has a stray space
Some kernels truncate shebangs over ~127 bytes, and a trailing space after the interpreter path becomes part of the name.
head -1 ./long.sh | cat -A
#!/usr/bin/python3 $
The trailing space ( $) makes the interpreter python3 (with a space). Remove it.
Diagnostic Workflow
Step 1: Confirm the file really exists and is readable
ls -l ./script
file ./script
If ls shows it but exec fails, the problem is an interpreter/loader, not the file.
Step 2: Read the shebang with hidden characters exposed
head -1 ./script | cat -A
A trailing ^M (CRLF) or $ preceded by a space is the smoking gun.
Step 3: Verify the named interpreter exists at that exact path
INTERP=$(head -1 ./script | sed 's/^#!//; s/ .*//; s/\r$//')
echo "interpreter: $INTERP"
ls -l "$INTERP"
ls: cannot access confirms the interpreter is the missing file.
Step 4: For env shebangs, resolve the target on PATH
head -1 ./script
/usr/bin/env <interp_name> --version
env: '<name>': No such file or directory means PATH does not contain it.
Step 5: For binaries, check architecture and loader
file ./binary
readelf -l ./binary | grep interpreter
A mismatch between the binary’s arch and uname -m means you have the wrong build.
Example Root Cause Analysis
A teammate commits a migrate.py helper. In CI it fails on the very first step:
./migrate.py: No such file or directory
The file is checked out and present (ls -l migrate.py shows it, executable). Reading the shebang with control characters visible:
head -1 ./migrate.py | cat -A
#!/usr/bin/env python3^M$
The ^M reveals CRLF line endings. The kernel is searching for an interpreter literally named python3\r via env, which does not exist — hence ENOENT on a file that is plainly there. The author edited the file on Windows without a .gitattributes rule to enforce LF.
Fix: normalize line endings and prevent recurrence:
dos2unix ./migrate.py
printf '*.py text eol=lf\n*.sh text eol=lf\n' >> .gitattributes
After conversion, head -1 ./migrate.py | cat -A shows a clean #!/usr/bin/env python3$ and the script executes.
Prevention Best Practices
- Prefer
#!/usr/bin/env <interp>for portability, but verify the target name actually exists on the runtime (python3, notpython, on modern distros). - Enforce LF line endings for scripts with a
.gitattributesrule (*.sh text eol=lf,*.py text eol=lf) so no contributor’s editor can inject\rinto a shebang. - Lint shebangs in CI: a quick
head -1 | cat -Acheck that fails on^Mor missing interpreters catches this before runtime. - Pin and verify binary architecture in download scripts — compare the release asset against
uname -mbefore chmod +x. - When in doubt, invoke explicitly (
bash script.sh,python3 script.py) to confirm whether the script logic works versus the shebang being broken. - See related shell hardening in the Bash & Python automation guides.
Quick Command Reference
# Confirm the file exists
ls -l ./script
file ./script
# Reveal the shebang with hidden chars
head -1 ./script | cat -A
# Does the named interpreter exist?
INTERP=$(head -1 ./script | sed 's/^#!//; s/ .*//; s/\r$//'); ls -l "$INTERP"
# Strip CRLF from the script
dos2unix ./script # or: sed -i 's/\r$//' ./script
# Fix the shebang to env python3
sed -i '1s|.*|#!/usr/bin/env python3|' ./script.py
# Check binary architecture & loader
file ./binary; readelf -l ./binary | grep interpreter
Conclusion
When No such file or directory names a script that clearly exists, the kernel is reporting a missing interpreter, not a missing script. The recurring causes:
- A
\r(CRLF) glued to the interpreter path on the shebang line. - A shebang pointing to an interpreter that is not installed at that path.
- A typo or relative path in the shebang.
- An
envshebang whose target (python) is not on PATH. - A downloaded ELF binary for the wrong architecture or missing its dynamic loader.
- A truncated shebang or a stray trailing space in the interpreter path.
Read the first line with head -1 | cat -A, confirm the interpreter exists at that exact path, and the ENOENT resolves immediately — most often with a dos2unix and a .gitattributes rule to keep \r out for good.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.