简介:这份PDF资料聚焦Linux环境下执行可执行文件时提示“No such file or directory”的排查与解决,面向Linux初学者、运维人员及后端开发者,帮助厘清文件明明存在却无法运行的常见困惑。资源共1个PDF文件,压缩包约44KB,内容以文字讲解配合示例命令为主,便于随时查阅与对照实践。资料从检查文件路径与执行权限入手,逐步深入到系统位数与可执行文件位数不匹配这一典型原因,并给出通过uname、file命令确认架构、在Ubuntu上安装32位兼容库的具体思路,同时补充脚本shebang、软链接失效、路径编码等潜在诱因。目前已有22906人学习,适合遇到同类报错时快速定位问题、建立系统化排错思路的读者参考。
1. 文件明明就在眼前,Linux 为什么说 No such file or directory
你ls -l看得清清楚楚,./run.sh就在当前目录,权限也给了chmod +x,一执行却甩回来一句bash: ./run.sh: No such file or directory。更玄学的是,换台机器、换个镜像就正常了。这个报错在 Linux 运维故障案例里出现频率极高,也是很多人排查嵌入式 Linux 项目、国产 Linux 系统时第一个撞上的墙。
它真正想说的往往不是「文件不存在」,而是「我没法按你期望的方式把它跑起来」。内核在execve阶段找不到解释器、动态链接器、目标架构不匹配,都会复用这条 ENOENT 文案,于是文件明明在、报错却说没有,成了典型的黑匣子。这篇笔记就围绕这个报错,把定位手段、根因分类、修复命令和验证方法讲透,适合刚接触 Linux 的开发者,也适合被线上脚本坑过的运维。看完你能自己判断:到底是路径问题、权限问题、解释器问题,还是架构问题。
2. 先分清是 shell 报的还是内核报的:定位手段
2.1 三种报错来源,决定了排查方向
同样是No such file or directory,来源不同,处理方式完全不同。第一种是 shell 自己找不到命令,比如你敲./run.sh但当前目录根本没有这个文件,报错前缀通常是bash:或zsh:。第二种是文件存在、shell 也把控制权交给了内核,但内核在加载阶段失败,报错前缀可能是bash:也可能是程序名本身。第三种是程序已经跑起来,运行中调用open()打开某个配置文件失败,这时报错来自程序自己的日志。
区分方法很直接:先确认文件在不在,再看报错前缀,最后用strace看系统调用停在哪一步。很多人一上来就chmod 777,结果权限给满了还是报同样的错,就是因为根因根本不在权限。
# 第一步:确认文件真实存在,注意大小写和隐藏字符 ls -l ./run.sh # 用 file 看它到底是什么类型,别被扩展名骗了 file ./run.sh # 看前几个字节有没有 BOM 或 CRLF 这类隐藏字符 head -c 32 ./run.sh | xxdls -l确认存在性和权限位,file告诉你这是脚本、ELF 可执行文件还是别的,xxd看头部字节能揪出 Windows 换行符和 BOM。这三条命令基本能在十秒内把「文件到底存不存在、是什么」这件事定死。
2.2 用 strace 把 execve 的失败点抓出来
当文件确实存在、权限也对,报错还在,就该上strace了。它能把execve系统调用的返回值直接摊开给你看,是排查这类问题的后悔药。
# -f 跟踪子进程,-e 只过滤 execve 相关调用,输出更干净 strace -f -e trace=execve ./run.sh 2>&1 | head -20如果输出里出现execve("./run.sh", ...) = -1 ENOENT,说明内核在解析这个文件时失败了。紧接着通常会看到它尝试打开某个解释器路径,比如/bin/bash或/lib64/ld-linux-x86-64.so.2,而那个路径返回 ENOENT。这就把「文件不存在」翻译成了「文件依赖的解释器或链接器不存在」,方向立刻清晰。
参数说明:-f必须加,因为脚本执行会 fork 子进程;-e trace=execve缩小范围,避免输出被淹没;2>&1是因为 strace 默认写 stderr。如果系统没装 strace,用apt install strace或yum install strace补上,这是排查必备工具。
2.3 脚本类报错:shebang 指向的解释器才是关键
对.sh、.py这类脚本,内核读的是第一行 shebang。如果 shebang 写的是#!/bin/bash,而目标系统上 bash 装在/usr/bin/bash,或者干脆没装 bash(比如某些精简的国产 Linux 镜像、Alpine 系),内核就会报 ENOENT,哪怕脚本本身完好无损。
# 看脚本第一行到底指向哪个解释器 head -1 ./run.sh # 确认这个解释器在不在 ls -l /bin/bash /usr/bin/bash 2>&1 # 用 which 或 command -v 查真实路径 command -v bash常见坑是脚本在 Ubuntu 上写的#!/bin/bash,拷到只有/usr/bin/bash的环境就翻车。解决办法要么改 shebang 指向真实路径,要么用#!/usr/bin/env bash让系统自己找。env方式兼容性更好,但要注意env本身也得存在,极简容器里连env都可能没有。
3. 动态链接器与架构不匹配:ELF 文件的隐形杀手
3.1 动态链接器路径写死,换环境就找不到
编译出来的 ELF 可执行文件,头部记录了一个INTERP段,指向动态链接器,典型是/lib64/ld-linux-x86-64.so.2。这个路径是编译时写死的。如果你在 x86_64 的 Ubuntu 上编译,拿到一个只有 musl 或路径不同的系统上跑,内核找不到这个链接器,报的就是 ENOENT。
# 查看 ELF 依赖的解释器和动态库 readelf -l ./myapp | grep interpreter # 或者用 ldd 看依赖,注意 ldd 对不可信文件有风险 ldd ./myapp # 确认链接器文件是否真实存在 ls -l /lib64/ld-linux-x86-64.so.2readelf -l输出里的[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]就是内核要去找的东西。如果这个文件不存在,报错必然是 ENOENT。ldd更直观,但它会实际加载程序,对来源不明的二进制别乱用,用objdump -p或readelf -d更安全。
3.2 架构不匹配:报错文案会骗人
在 ARM 设备上跑 x86_64 的二进制,内核有时报Exec format error,有时在某些内核配置下也报 ENOENT,取决于具体版本。所以看到 ENOENT 别急着否定架构问题。
# 看文件的目标架构 file ./myapp # 看当前系统架构 uname -m # 交叉编译产物在目标板上验证时,先确认这两者一致file输出ELF 64-bit LSB executable, x86-64而uname -m是aarch64,那就是架构不匹配,换对应架构的编译产物即可。嵌入式 Linux 项目里这个问题特别常见,宿主机编译、目标板运行,工具链选错就中招。
3.3 用 patchelf 修链接器路径而不是重编译
如果只是链接器路径不对,又拿不到源码重编译,可以用patchelf直接改 ELF 的 INTERP 段,比重编译快得多。
# 安装 patchelf apt install patchelf # 查看当前解释器 patchelf --print-interpreter ./myapp # 改成目标系统上真实存在的链接器 patchelf --set-interpreter /lib/ld-musl-x86_64.so.1 ./myapp # 改完再验证 patchelf --print-interpreter ./myapp参数说明:--print-interpreter只读不改,先看清楚再动手;--set-interpreter后面跟目标系统上确实存在的链接器绝对路径。改之前备份原文件,改错了二进制可能直接无法加载。这个技巧在把 glibc 程序挪到 musl 环境时特别有用,但要注意 glibc 和 musl 的 ABI 差异,改链接器不等于万事大吉,动态库依赖也得一起处理。
4. 避坑与排查:五条血泪经验
4.1 现象是报错说文件不存在,原因是 Windows 换行符
从 Windows 拷过来的脚本,每行结尾是\r\n。shebang 那行变成#!/bin/bash\r,内核去找一个叫bash\r的解释器,当然找不到,报 ENOENT。现象极具迷惑性,因为ls看文件好好的。
解决:用file看会提示with CRLF line terminators,或者head -c 32 | xxd看到0d 0a。修复用sed -i 's/\r$//' run.sh,或者dos2unix run.sh。养成习惯,跨平台传脚本后先跑一遍dos2unix。
4.2 现象是权限给了还是不行,原因是挂载选项 noexec
文件权限755,shebang 也对,还是报错。检查一下所在分区是不是用noexec挂载的,比如某些/tmp、/data分区出于安全考虑禁用了执行。
解决:mount | grep noexec看挂载选项。如果是 noexec,要么把文件挪到可执行分区,要么重新挂载去掉 noexec,要么用bash run.sh显式调用解释器绕过执行位检查。最后一种最省事,但只对脚本有效,ELF 二进制绕不过去。
4.3 现象是容器里跑不了,原因是基础镜像太精简
Alpine、distroless 这类镜像为了体积砍掉了 bash、glibc 和动态链接器。你的脚本 shebang 写#!/bin/bash,镜像里只有/bin/sh(还是 busybox 的),报 ENOENT。
解决:docker run --rm -it 镜像 sh进去ls /bin看看有啥。要么改 shebang 用#!/bin/sh,要么换基础镜像,要么在 Dockerfile 里补装 bash。distroless 镜像连 shell 都没有,只能静态编译或换镜像。
4.4 现象是软链接指向了不存在的目标,原因是相对路径断了
run.sh是个软链接,指向../bin/run.sh,但目标文件被删了或挪了。ls -l会显示链接指向,但很多人只看文件名不看箭头。
解决:ls -l看箭头指向,readlink -f run.sh解析出最终绝对路径,再确认那个路径存在。软链接的相对路径是相对于链接所在目录解析的,挪动链接或目标都会断。
4.5 现象是 32 位程序在 64 位系统上跑不了,原因是缺 32 位运行库
64 位系统默认不带 32 位动态链接器/lib/ld-linux.so.2,跑 32 位 ELF 就报 ENOENT。
解决:file确认是ELF 32-bit,然后装 32 位兼容库,Debian 系是apt install libc6:i386,RHEL 系是yum install glibc.i686。装完再ldd确认依赖齐了。这个坑在老工业软件、老编译产物上很常见。
5. 把排查固化成脚本:一条命令定位根因
排查多了就会发现,这套流程完全可以固化。我一般会写一个why-noexec.sh,把存在性、类型、shebang、架构、链接器、挂载选项一次性打出来,省得每次手动敲七八条命令。
#!/bin/bash # 用法: ./why-noexec.sh <目标文件> f="$1" [ -z "$f" ] && { echo "用法: $0 <文件>"; exit 1; } echo "== 存在性 ==" ls -l "$f" 2>&1 || { echo "文件不存在"; exit 1; } echo "== 类型 ==" file "$f" echo "== 头部字节(查BOM/CRLF) ==" head -c 32 "$f" | xxd echo "== shebang ==" head -1 "$f" echo "== 架构对比 ==" echo "文件: $(file -b "$f" | grep -oE 'x86-64|ARM|aarch64|80386' | head -1)" echo "系统: $(uname -m)" echo "== 动态链接器 ==" readelf -l "$f" 2>/dev/null | grep -i interpreter || echo "非ELF或静态链接" echo "== 挂载选项 ==" df --output=target "$f" | tail -1 | xargs -I{} mount | grep " {} "逻辑说明:脚本按「存在性 → 类型 → 隐藏字符 → shebang → 架构 → 链接器 → 挂载」的顺序逐层排除,正好对应前面讲的根因分类。file -b去掉文件名只留描述,grep -oE提取架构关键词,df --output=target拿到文件所在挂载点再查 mount 选项。参数就一个目标文件路径,没有多余开关,降低使用门槛。
跑一遍输出,基本能直接告诉你问题出在哪一层。比如 shebang 显示#!/bin/bash但头部字节有0d 0a,那就是 CRLF;架构显示x86-64而系统是aarch64,那就是架构不匹配;链接器那行显示非ELF或静态链接但file说是 ELF,那多半是链接器路径不存在。
进阶一点,可以把这个脚本挂到 CI 的构建后检查里,交叉编译产物在打包前先跑一遍,确认架构和链接器路径符合目标环境,把问题拦在部署之前。我自己的习惯是:任何跨环境交付的二进制或脚本,落地第一件事就是跑这个脚本,而不是等运行时报错再回头查。这套流程帮我省下的时间,远比写脚本本身多。
希望帮到你。
本文还有配套的精品资源,点击获取