ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

报错排查 06|命令明明装了却提示 command not found?PATH 失效的 8 种场景

报错排查 06|命令明明装了却提示 command not found?PATH 失效的 8 种场景 command not found是 Linux 里最直白的报错但它经常骗人。我遇到最典型的一次刚apt install完敲命令还是command not found。装第二遍它告诉我「已经是最新版本」。那一瞬间的逻辑矛盾特别让人上火系统说装了shell 说找不到到底谁在撒谎答案是——两个都没撒谎。装是装上了但 shell 不知道去哪找它。〇、先花两分钟shell 到底去哪找命令你敲下一个命令时shell 并不会「满硬盘搜索」。它只去几个固定目录里挨个找找着了就执行找不着就报command not found。这几个目录的清单就存在一个叫PATH的环境变量里$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin怎么读冒号是分隔符每一段是一个目录。shell 会从左到右依次去找。顺带一提上面这个 PATH 里/snap/bin出现了两次。这不是错误只是白找一轮——PATH 里允许重复目录。所以「找不到命令」这件事只有三种可能程序真的不存在存在但它所在的目录不在 PATH 里在 PATH 里但因为别的原因没被执行。第 2 和第 3 种占了绝大多数——这才是查这个报错的正确方向。一、第 1 种真的没装先排除最简单的。别用which用type原因后面第 7 种会讲$ type -a ffmpeg bash: type: ffmpeg: not found $ command -v ffmpeg || echo 确实没有 确实没有怎么读type -a会把「这个名字能找到的所有东西」都列出来——是外部命令、是别名、还是 shell 函数。完全没有输出 真没有。确认没装之后还得知道该装哪个包——很多发行版的包名和命令名不一样比如dig在dnsutils包里# Ubuntu / Debian先查这个文件属于哪个包 $ sudo apt install apt-file sudo apt-file update $ apt-file search bin/ffmpeg ​ # 或者反向查这个包装了哪些命令 $ dpkg -L openssh-client | grep bin/二、第 2 种装了但它在sbin里这是最经典的一种尤其是「ifconfig/route/fdisk找不到」这个场景。但这里有个流传很广的说法要先纠正「/usr/sbin、/sbin不在普通用户的 PATH 里」——这句话在 RHEL 系上成立在 Ubuntu 桌面上不成立。我在自己那台 Ubuntu 26.04.1 上把 PATH 拆开逐行看$ echo $PATH | tr : \n /usr/local/sbin /usr/local/bin /usr/sbin ← 在 /usr/bin /sbin ← 也在 /bin /usr/games /usr/local/games /snap/bin /snap/bin怎么读/usr/sbin和/sbin都明确在 PATH 里。所以在 Ubuntu 桌面上「命令找不到」不能怪 PATH得往别的原因找。顺手验证一个反直觉的事实你在 PATH 里看到/usr/bin和/bin两个目录会以为它们是两套东西——其实它们是同一个目录。数一下文件数就知道$ for d in /usr/bin /bin /usr/sbin /sbin; do echo $d: $(ls $d | wc -l) 个; done /usr/bin: 1507 个 /bin: 1507 个 ← 和上面完全一样 /usr/sbin: 549 个 /sbin: 549 个 ← 也一样怎么读数字一模一样不是巧合——/bin、/sbin、/lib在现代发行版上都只是指向/usr下对应目录的软链接这个变化叫 usrmerge各家发行版在 2019 年前后陆续完成。所以 PATH 里同时列它们俩等于同一个目录被找了两遍。这也顺便解释了上一条为什么type -a ifconfig会打出两条结果。那到底差在哪差在「这个命令有没有装」$ type -a ifconfig ifconfig 是 /usr/sbin/ifconfig ifconfig 是 /sbin/ifconfig怎么读type -a会把「这个名字能找到的所有位置」都列出来。这里看到两条不是有两份程序——/sbin其实是指向/usr/sbin的软链接同一个文件被两个路径各列了一次这一点下一节还会用到。所以正确的排查顺序是先type -a 命令名——有输出就说明装了没输出才是真没装RHEL 系上普通用户没输出、加了sudo就有 → 那才是「sbin 不在 PATH 里」的问题Ubuntu 上普通用户和sudo都不行 → 直接去查包装没装。ifconfig属于net-tools包现代发行版默认不装。它要是出现在你机器上多半是你某次跟着教程手动装的。三、第 3 种sudo把你的 PATH 重置了这个坑特别隐蔽因为它只在sudo后面出现。现象不加sudo能找到命令加了sudo反而command not found。原因在/etc/sudoers里这一行Ubuntu 默认有Defaults secure_path/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/binsecure_path的作用是执行 sudo 时把 PATH 强制换成这里列的这一套——这是安全设计防止普通用户在 PATH 前面塞一个恶意同名程序然后借 sudo 提权执行。副作用是你自己加在 PATH 里的目录比如/opt/myapp/bin在 sudo 下全部消失。验证方式我机器上的真实输出两条命令挨着跑$ echo $PATH | tr : \n /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /usr/games /usr/local/games /snap/bin /snap/bin ​ $ sudo env | grep ^PATH PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin怎么读——把两串逐行对一下差了三处目录你的 PATHsudo的 PATH/usr/games有没有/usr/local/games有没有/snap/bin出现 2 次只出现 1 次这说明secure_path是整体替换、不是追加——它不管你原来有什么直接把 PATH 换成 sudoers 里写死的那一串。所以你自己往 PATH 里加的目录/opt/myapp/bin、~/.local/bin在sudo下一定会消失。secure_path写在/etc/sudoers里我这份在第 11 行$ sudo grep -n secure_path /etc/sudoers 11:Defaults secure_path/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin顺便解答开头提的那个问题你那串 PATH 是哪儿来的对照/etc/environment就清楚了——它是登录时由 PAM 读进来的$ cat /etc/environment PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin比echo $PATH少一个/snap/bin那一个是会话启动后又追加的。解法按推荐程度排# ① 用绝对路径最推荐无副作用 sudo /opt/myapp/bin/tool --version # ② 用 env 显式传递当前 PATH sudo env PATH$PATH tool --version # ③ 改 sudoers 保留 PATH最不推荐会削弱安全设计 sudo visudo # 把 secure_path 那行注释掉四、第 4 种shell 的哈希缓存hash这是最灵异的一种命令刚装完command -v说没有新开一个终端就好了。原因为了提高效率bash 会把「上次找到的命令在哪个路径」记在一张哈希表里。如果你先敲了一个不存在的命令bash 记下这个命令没有再装上它bash 仍然认为找不到。看一眼这张表$ hash 哈希表为空 ← 这个 shell 还没敲过任何外部命令 $ hash hits command 2 /usr/bin/date怎么读hits是命中次数command是缓存的完整路径。空表才是常态——新开的终端什么都没缓存。要复现这个坑得先敲一次不存在的命令让 bash 把「找不到」也一起缓存进去。清掉它$ hash -r # bash $ rehash # zsh判断标志command not found之后/usr/bin/命令名这个绝对路径能跑通但直接敲命令名不行 → 就是这个坑。hash -r清空整张表新开一个终端等价于同样效果因为哈希表不跨会话。五、第 5 种PATH 加错地方或者加错文件「装到~/.local/bin但 PATH 里没有」是很常见的$ ls ~/.local/bin mytool $ echo $PATH | grep -c $HOME/.local/bin 0加 PATH 要改哪个文件取决于你要在什么场合用——这里有个大坑文件什么时候读适合放什么~/.bashrc交互式bash 启动时给「你自己敲命令」用的 PATH~/.profile登录时一次登录会话的环境变量~/.bash_profile登录式 bash同上注意有的系统没这个文件/etc/profile.d/xxx.sh所有用户登录时全局的 PATH推荐放这里/etc/environment更早由 PAM 读取极简的全局变量不支持 shell 语法关键区别~/.bashrc在非交互式 shell 里不读。所以你在.bashrc里加的 PATH✅ 你开终端敲命令有效❌cron 任务里无效、systemd 服务里无效、脚本里bash script.sh也可能无效。这就是「终端里能跑、cron 里报 command not found」的根因Shell 自动化那个专栏会专门讲 crontab 这一块。稳妥写法放/etc/profile.d/mytool.sh# /etc/profile.d/mytool.sh export PATH$PATH:/opt/myapp/binexport 「让这个变量也传给子进程」不加 export 的变量只在本 shell 有效。所以加 PATH 必须写 export。六、第 6 种文件在了、也在 PATH 里但不可执行到这一步报错的形态会变——你会看到Permission denied或bad interpreter而不是command not found。先看权限$ ls -l /opt/myapp/bin/tool -rw-r--r-- 1 root root 812 Sep 27 10:00 /opt/myapp/bin/tool怎么读没有x就不可执行chmod x /opt/myapp/bin/tool还有一种特别隐蔽的脚本是从 Windows 传过来的行尾是\r\n。这会带来两种怪现象报bad interpreter: /bin/bash^M: no such file or directory——bash 路径后面挂了个看不见的^M或者报command not found当 shebang 那行被搞坏时。$ file mytool.sh mytool.sh: ASCII text, with CRLF line terminators ← 问题在这 $ sed -i s/\r$// mytool.sh # 一行修好记住这个CRLF三个字从 Windows 传到 Linux 的脚本报「找不到解释器」时先查它。七、第 7 种被别名或函数遮住了同名的情况下bash 的查找优先级是别名alias shell 函数 内建命令 PATH 里的外部程序所以一个别名可以完全盖掉一个真命令。看全貌$ type -a ls ls 是 /usr/bin/ls ls 是 /bin/ls上面是我机器上的真实输出——注意它没有别名。这本身就是一个活例子我这份输出是在非交互式 shell脚本里跑的而非交互式 shell 不读~/.bashrcUbuntu 默认给ls配的那个彩色别名自然就不在。如果在交互式终端里跑并且存在别名长这样$ type -a ls ls is aliased to ls --colorauto ← 第一行就是最终生效的 ls is /usr/bin/ls怎么读第一行就是最终生效的那个。想绕开别名用真命令$ \ls # 反斜杠开头跳过别名 $ command ls # 强制按外部命令执行这也是为什么排查时要用type -a而不是whichwhich只查 PATH看不到别名和函数会给你一个看起来很正常的假答案。八、第 8 种架构不匹配 / 解释器缺失最后一种报错也很迷惑。现象 A二进制文件明明在、权限也对执行时报No such file or directory。这不是文件不存在而是它依赖的「解释器」或动态库不存在——比如 32 位程序跑在只有 64 位库的系统上或者手动复制进来的二进制缺少ld-linux。$ file /opt/bin/legacy-tool /opt/bin/legacy-tool: ELF 32-bit LSB executable, Intel 80386 ← 32 位程序 $ ldd /opt/bin/legacy-tool | grep not found ← 缺哪个库一目了然怎么读file告诉你架构ldd列出它依赖的库not found的就是缺的。现象 B用 snap 装的命令找不到。snap 的程序不在/usr/bin而在/snap/bin——先看文件在不在$ ls /snap/bin | head desktop-security-center electronic-wechat firefox geckodriver firmware-updater hwctl icloud-for-linux.calendar怎么读这些全是 snap 装的我机器上ls /snap/bin有 24 个。snap 命令找不到时先ls /snap/bin文件在 → 是 PATH 的问题文件不在 → 是这个 snap 本身没装好。再确认这个目录在 PATH 里$ echo $PATH | grep -o /snap/bin | head -1 /snap/bin九、一张速查流程现象最可能的原因一条命令刚装完就找不到绝对路径能跑哈希缓存hash -r只有sudo之后找不到secure_path重置了 PATHsudo env \| grep ^PATH普通用户找不到root 能找到命令在sbinls -l /usr/sbin/命令终端能跑cron/服务里不能PATH 写进了.bashrc改放/etc/profile.d/报Permission denied没有x权限chmod x 文件报bad interpreter ... ^MWindows 行尾sed -i s/\r$// 文件报No such file or directory但文件在架构/依赖库不匹配file 文件ldd 文件同名命令行为怪异被别名/函数盖住type -a 命令速查表文末收藏版看命令到底有没有、是什么 → type -a 命令名 比 which 准能看到别名/函数 确认是否存在 → command -v 命令名 看 PATH 有哪些目录 → echo $PATH | tr : \n 一行一个重复目录一眼看见 清 shell 哈希缓存 → hash -r 看 sudo 下的 PATH → sudo env | grep ^PATH 命令属于哪个包Ubuntu → apt-file search bin/命令名 包装了哪些命令 → dpkg -L 包名 | grep bin/ 加 PATH全局推荐 → /etc/profile.d/xxx.sh 里 export PATH$PATH:/新目录 加 PATH仅当前用户 → ~/.profile 或 ~/.bashrc后者对 cron/服务无效 跳过别名执行真命令 → \命令名 或 command 命令名 查文件架构与依赖 → file 文件 ldd 文件 修 Windows 行尾 → sed -i s/\r$// 文件 snap 装的东西在哪 → ls /snap/bin /bin、/sbin 的真身 → 软链接指向 /usr/bin 和 /usr/sbinusrmerge
返回列表