Bash: "No such file or directory" on a file that exists

Bash says "No such file or directory" for a script that exists? The missing file is the shebang interpreter — usually CRLF line endings. Three fixes.

Error String

bash: ./script.sh: No such file or directory

Tldr

When the script is present but Bash still says "No such file or directory", the missing file is usually the interpreter in the shebang, not the script. CRLF line endings on the shebang line are the most common cause.

Cause

Bash reads the shebang and execs whatever path it finds. If the path ends with a literal \r (Windows line endings) it becomes "/usr/bin/env bash\r", which does not exist. Other causes: shebang points to /bin/bash on a system that has it at /usr/local/bin/bash, or the interpreter package is not installed.

Repro

# Written on Windows, saved with CRLF:
#!/usr/bin/env bash␍
echo hi
# ./script.sh
# bash: ./script.sh: No such file or directory

Fix

# Strip CRLF:
sed -i 's/\r$//' script.sh

# Or use dos2unix:
dos2unix script.sh

# Configure your editor to use LF line endings for shell scripts.

Explanation

The hidden \r is invisible in most editors. Run "file script.sh" — output that mentions "CRLF line terminators" is the giveaway. Set a .gitattributes rule "*.sh text eol=lf" to prevent recurrence.

Related Shellcheck

SC1017

Deep Dive

Heading

Tell the three causes apart in one command

Body

Run `file script.sh`. If the output mentions "CRLF line terminators" the shebang is carrying a stray carriage return and the interpreter path Bash tries to exec literally ends in \r. If the output looks clean, run `head -1 script.sh` and check the interpreter path actually exists with `ls -l /usr/bin/env` or `command -v bash`. If both are fine, the missing file is the script path itself — `ls -l ./script.sh` from the directory you are in, remembering that a relative path is resolved against your current directory, not the script location.

Heading

Why the message names the script and not the interpreter

Body

The kernel returns ENOENT for the exec of the interpreter, and the shell reports the failure against the path you typed. That mismatch is why the error looks impossible: the file is plainly there. The same message appears when the script has a valid shebang but the interpreter is inside a container image or a chroot that does not include it, and when a binary run from the script is dynamically linked against a missing loader.

Heading

Preventing CRLF from coming back

Body

Add `*.sh text eol=lf` to `.gitattributes` so Git normalises line endings on checkout regardless of platform. Set your editor to LF for shell files (`files.eol` in VS Code, `set fileformat=unix` in Vim). If a build step generates scripts on Windows, pipe them through `sed -i 's/\r$//'` before they are packaged.

Checklist

Run `file script.sh` and look for "CRLF line terminators".

Run `head -1 script.sh` and confirm the interpreter path exists on this machine or inside the container.

Confirm the script path itself resolves: `ls -l ./script.sh` from your current directory.

Check the execute bit with `ls -l` — a missing +x gives "Permission denied", not this error, which helps narrow it down.

If it fails only in a container, verify the interpreter is installed in the image (alpine ships ash, not bash).

Faq

Q

How do I check for CRLF without running the script?

A

Run `cat -A script.sh | head -1` — `^M "No such file or directory" But the File Exists — Fix at the end of the shebang line confirms CRLF.

Q

The script exists and has no CRLF — what else?

A

The missing file is likely the interpreter. `#!/bin/bash` fails on images where bash is not installed (alpine) or lives elsewhere; `#!/usr/bin/env bash` is more portable.

Q

Why does `bash script.sh` work but `./script.sh` fail?

A

Invoking bash explicitly bypasses the shebang, so a broken or CRLF-corrupted shebang line is skipped. That is a strong signal the shebang is the problem.