Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 9 min read

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.sh returns No such file or directory even though ls -l script.sh lists it.
  • The message sometimes names a bad interpreter, sometimes does not.
  • The same script runs fine with bash script.sh but 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, not python, on modern distros).
  • Enforce LF line endings for scripts with a .gitattributes rule (*.sh text eol=lf, *.py text eol=lf) so no contributor’s editor can inject \r into a shebang.
  • Lint shebangs in CI: a quick head -1 | cat -A check that fails on ^M or missing interpreters catches this before runtime.
  • Pin and verify binary architecture in download scripts — compare the release asset against uname -m before 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:

  1. A \r (CRLF) glued to the interpreter path on the shebang line.
  2. A shebang pointing to an interpreter that is not installed at that path.
  3. A typo or relative path in the shebang.
  4. An env shebang whose target (python) is not on PATH.
  5. A downloaded ELF binary for the wrong architecture or missing its dynamic loader.
  6. 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.

Free download · 368-page PDF

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.