尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux bin与sbin目录详解:PATH环境变量、历史演进与usr merge合并趋势

Linux bin与sbin目录详解:PATH环境变量、历史演进与usr merge合并趋势 前阵子帮同事排查一个诡异问题他在脚本里写了reboot命令手动执行一切正常但通过 cron 定时跑的时候就是报command not found。排查到最后问题出在他脚本开头的 PATH 环境变量被重置了而/sbin不在这个新 PATH 里。这哥们儿当时就懵了“reboot不是系统自带的吗怎么还会找不到”这个问题看似简单但背后牵扯出来的正是标题里这几个目录——bin、sbin、usr/bin、usr/sbin——它们各自的作用、历史由来以及在现代 Linux 系统里已经发生的变化。很多人用 Linux 好几年天天 cd 来 cd 去却从来没搞明白过这几个目录为什么这么划分为什么有的命令在/bin里有的又在/usr/bin里还有的偏偏要放在/sbin。这篇文章就一次性把这个话题彻底讲透既聊清楚它们之间的职责边界也把这段历史挖出来看看最后再结合现在 systemd 时代的新趋势说说我们日常用机、写脚本时到底该怎么理解这几个目录。1. 命令到底去哪儿了PATH 环境变量才是总开关在拆解四个目录之前先得搞清楚一个前置概念——PATH 环境变量。这是 Unix/Linux 系统里找命令的索引你敲一条命令时shell 不会傻乎乎地全盘搜索而是按照 PATH 里记录的目录顺序一个一个去找找到了就执行找不到就报command not found。1.1 一条命令的执行链路拿最简单的ls来举例。你在终端敲下ls回车shell 会做这些事情检查ls是不是 shell 的内置命令比如cd、export这类就是内置的。如果不是内置命令就按照 PATH 变量里存的目录顺序依次去查找名为ls的可执行文件。如果找到了就直接执行全部目录都找遍了还没有就报错。你可以用which ls或者type -a ls查看ls命令的实际路径。在大多数现代 Linux 发行版上你会看到/usr/bin/ls而/bin/ls可能是一个指向/usr/bin/ls的符号链接。但是放在十年前你看到的很可能是/bin/ls。这里就有一个关键点了不同用户、不同登录方式拿到的 PATH 可能是不一样的。交互式登录的 shell 和 cron 里跑脚本的 shellPATH 就经常不同。我那位同事碰到的就是这种情况——他在系统级 crontab 里写了reboot但 cron 的默认 PATH 往往不包含/sbin自然就找不到了。1.2 普通用户和 root 的 PATH 差异对比一下普通用户和 root 的 PATH你就能看出一些门道了。在一台 Ubuntu 22.04 上普通用户的 PATH 大概是/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binroot 用户的 PATH 也基本上长这个样。但是在很多老系统或者 RHEL 系早期的版本里root 的 PATH 会额外包含/sbin和/usr/sbin而普通用户的 PATH 里可能压根就没有这两个目录。这样设计的目的很明确普通用户平常根本用不到sbin里的管理工具没必要让它们出现在搜索路径里也避免误执行一些危险操作。所以理解这四个目录的第一步就是理解 PATH 这个总开关。目录放在那里是一回事你的 shell 能不能找到它完全是另一回事。2. 四个目录的分工谁放什么谁用得起前面把 PATH 聊清楚了接下来正式进入正题——这四个目录各自是干嘛的。虽然名字长得像但它们的定位差异是很明确的用一句话总结bin放的是所有用户都能用的基本命令sbin放的是系统管理类命令而usr前缀代表这些命令原本属于用户目录里的软件后来形成了第二套更庞大的工具集。2.1/bin: 系统启动必需的最小命令集/bin是 binaries 的缩写里面放的是所有用户都可以执行的基本命令。在老派的 Unix 和早期 Linux 系统中这个目录是系统启动过程中必须用到的——因为系统启动早期/usr分区可能还没有挂载/bin必须待在根文件系统/上里面的工具要保证在只有根分区可用的情况下就能完成基本的系统引导和修复工作。典型成员包括ls、cp、mv、rm、cat、echo、sh、bash这些。可以这么说万一某天你的系统只能进入紧急模式或者单用户模式你还能靠/bin里的这些命令做基本的文件操作和排查问题。2.2/sbin: 系统管理员的地盘sbin里的s是 system 或 superuser 的意思。这个目录专门放系统管理类命令大多数只有 root 用户才有权限执行或者只有 root 用户在执行时才发挥完整功能。像fdisk磁盘分区、mkfs格式化、mount/umount挂载/卸载、ifconfig老牌网卡配置命令、reboot、shutdown、iptables防火墙规则等都住在这里。需要注意的是sbin目录里的命令也有一部分是普通用户可读可执行的但通常需要配合 sudo 提权才能完成真正的管理操作。系统把这个目录单独拎出来其实是一种权限管理的体现——用 PATH 隔离的方式防止普通用户无意中执行到这类命令也防止非 root 的脚本误启用一些关键操作。2.3/usr/bin: 真正的绝大多数用户命令大本营如果你在 Linux 上随手敲which cd、which git、which vim、which python3大概率会得到/usr/bin下的路径。这就是/usr/bin的角色它是系统里用户命令的主仓库。到现代 Linux 系统这里绝大多数你会用到的命令行工具、应用、编程语言解释器其实都被装进了/usr/bin。这跟发行版的包管理器组织方式有关——默认情况下几乎所有软件的二进制文件都会被投放到/usr/bin。所以你也可以粗暴地理解为/usr/bin才是你真正的主战场。2.4/usr/sbin: 非启动关键期的系统管理命令/usr/sbin则更像一个次要的sbin。它也放系统管理命令但这些命令不是系统启动早期必需的而是在系统完全启动起来之后做网络服务管理、守护进程控制、用户管理等操作时用的。典型的有ss查看网络连接、useradd新建用户、groupadd新建用户组、tcpdump抓包工具如果你装的是系统源里的版本等。这些命令同样主要是管理员在使用但因为根文件系统挂载完成、/usr也已经可用之后才派得上用场所以放在/usr/sbin里而不是放进启动阶段就必须待着的/sbin。2.5 四个目录速查对比表目录内容定位使用对象典型命令是否系统启动必需/bin用户基本命令所有用户可执行所有用户ls、cp、mv、sh是/sbin系统管理员命令启动/修复用主要是 rootfdisk、mkfs、mount、reboot是/usr/bin用户命令主仓库绝大多数日常工具所有用户vim、git、python3、gcc否/usr/sbin非启动关键期的系统管理命令主要是 rootuseradd、ss、tcpdump否这个表格看起来很简单但实际操作中很多人会疑惑为什么mount在 CentOS 7 上在/bin里在 Ubuntu 上变成了/usr/bin别急这正是系统演进带来的结果接下来聊聊这段历史。3. 追根溯源为什么会有两套 bin 目录并存理解四个目录的由来必须得回到上世纪 70 年代的 Unix 时代。那时候计算机资源极度匮乏存储空间小得可怜但就是在这种条件下形成了沿用至今的目录设计哲学。3.1 一切从 RK05 磁盘开始DEC 公司当年的 RK05 磁盘单盘容量大概只有 2.5MB今天的手机随手拍一张照片都不止这个数。当时 Unix 系统为了在一台 PDP-11 机器上跑起来系统文件和用户文件不得不分布在多个磁盘上。其中一块磁盘挂载在根目录/用来放系统启动所需的最小核心文件——包括内核、启动脚本以及启动和修复时必需的命令。这批最核心工具就被放进了/bin和/sbin。另一块磁盘则挂载在/usr目录下。3.2/usr的本来面目user 的家目录注意了/usr可不是 Unix System Resources 的缩写它的本意就是 user也就是用户目录。早期 Unix 时代/usr是给用户存放家目录和文件的地方你可以把它理解成今天的/home的老前辈。后来呢磁盘空间还是不够用。操作系统自带的命令越来越多但根目录这块小磁盘放不下了。有人就想了个办法把大部分命令搬到容量更大、挂在/usr下的那块磁盘上。于是/usr/bin诞生了里面装着大量用户的常用程序。这就产生了一分为二的局面根目录/下的/bin保存最小启动工具集保证系统能启动、能修复/usr磁盘上的/usr/bin则存放绝大多数应用程序和用户工具。两个目录里的内容有很多重叠但定位不一样。提示早期还有/usr/sbin同样是为了扩展/sbin的容量把后期加入的、不属于启动必需阶段的管理命令放到/usr磁盘上。3.3 容量升级后为什么不合并到后来磁盘越来越大技术越来越先进很多人就问了既然都这么大了为什么不把/bin和/usr/bin合并非得把这些目录搞得这么麻烦答案在于历史的惯性和向下兼容的承诺。目录结构一旦形成就成了无数脚本、文档、教程、打包脚本的默认前提。今天你去翻任何一个 Linux 发行版的打包规范都能看到各种路径的约定这些约定如果贸然改变会导致成千上万的软件编译安装出错、运维脚本失灵。为了维持稳定Unix 和早期的 Linux 选择容忍重复/bin和/usr/bin就这么一直并存了下来。3.4 交叉复查/bin和/usr/bin内容高度重叠的本质原因既然/usr/bin才是主仓库那/bin里又为什么还有ls、cp这些基础命令其实在现代 Linux 发行版统一布局之前/bin里的工具是刻意从/usr/bin里挑出来的一小部分启动必需品系统在挂载/usr之前只靠根分区里的这些二进制就能运行起来完成最基础的磁盘检查、文件读取和系统恢复。一旦/usr挂载完成系统就直接用/usr/bin里更全的工具集。所以你看到了同一份ls为什么两个目录都有的现象并不是装了两遍而是发行版在打包阶段就把某些关键命令复制或链接到了两边——这背后既是冗余也是系统启动阶段不得已而为之的设计。阶段可用的 bin 目录原因启动早期/usr 未挂载/bin、/sbin根文件系统上必须存在的最小命令集启动完成/usr 已挂载/usr/bin、/usr/sbin完整工具集全部可用4. FHS 标准与 systemd 时代的合并浪潮时间推进到现代 Linux 世界目录演变的节奏突然加快了。一方面有 LSB 和 FHS 这些标准在不断规范目录结构另一方面 systemd 和各大发行版联手掀起了一场usr merge的整合运动让我们标题里的四个目录在今天有了截然不同的形态。4.1 FHS(文件系统层级标准) 的定位FHSFilesystem Hierarchy Standard是 Linux 基金会维护的一套文件系统目录标准它的目标就是规定系统各个目录的用途和摆放规则让不同发行版之间保持一致性。在 FHS 文档里/bin、/sbin、/usr/bin、/usr/sbin四个目录的定位被写得很清楚大致就是我上一节描述的分工。不过FHS 也经历了几个版本。在早期的 FHS 版本里/bin和/sbin是必须要存在的物理目录而在较新的、结合了 usr merge 趋势的布局里/bin变成了/usr/bin的符号链接这一变化正式确认了其实你可以不额外保留一份启动工具集了——因为现代系统的启动流程已经能做到在挂载/usr之前不依赖/bin里的额外工具。4.2 systemd 和 usr merge 带来的物理变化Fedora 在 Fedora 17 那一代开始力推 usr merge随后 Debian 家族包括 Ubuntu在各自的版本里也跟进。所谓 usr merge通俗讲就是把原本散落在根目录/上的/bin、/sbin、/lib全部塞进/usr对应目录下然后让/bin、/sbin、/lib变成指向/usr下对应目录的软链接。合并之后你会在一个现代 Fedora 或 Ubuntu 系统上看到这样的布局$ ls -ld /bin /sbin /lib lrwxrwxrwx 1 root root 7 May 7 10:00 /bin - usr/bin lrwxrwxrwx 1 root root 7 May 7 10:00 /sbin - usr/sbin lrwxrwxrwx 1 root root 7 May 7 10:00 /lib - usr/lib也就是说你在终端里敲cd /bin实际上进的就是/usr/bin敲cd /sbin实质上就是/usr/sbin。传统意义上的四目录物理隔离在绝大多数主流发行版上已经消失了但你访问这些路径的习惯依然被兼容。4.3 合并带来的实际好处那为什么要搞合并动机主要有几个根目录更干净根文件系统/上的顶层目录少了看起来清晰也更容易做只读根目录的部署。启动流程简化以前启动早期需要保证/bin可用现在只要整个/usr能挂载起来所有命令就齐了少了两套目录内容保持一致的同步麻烦。内核和 initramfs 在启动时直接把/usr挂上就不需要再操心两套工具并存带来的版本漂移问题。容错性更好以前万一/bin和/usr/bin里的同一个工具版本不同可能导致脚本在启动早期和后期的行为不一致。合并之后全世界只有一份ls再也不会有这种割裂。容器镜像更精简在容器场景里镜像往往只需要一个完整可用的/usr根目录上那堆链接直接省事又省空间。4.4 但请注意不是所有系统都合并了这里必须提醒一句并非所有 Linux 发行版都做了 usr merge。比如一些坚持传统布局的发行版或者一些独立维护的老派系统/bin依然是一个物理目录里面真的有文件而不是软链接。如果你维护的是一台老旧的 CentOS 6 或者某个国产定制发行版那四个目录还是原原本本的四套物理目录。这也是为什么很多老一点的技术文档里会强调/bin和/usr/bin是两个目录千万别搞混。发行版/系统/bin 是否为符号链接布局类型Fedora 17是指向 usr/binusr mergeDebian 10、Ubuntu 18.10是指向 usr/binusr mergeCentOS 7 / RHEL 7否物理目录内容复制传统布局CentOS 8 / RHEL 8是usr merge现代布局openSUSE Leap 15是usr merge所以写脚本、做自动化运维时不要想当然地以为/bin/ls在所有机器上都存在。在 usr merge 布局的系统上/bin/ls能执行是因为/bin软链接到了/usr/bin在旧式布局的系统上它是实实在在的文件。这两种情况在绝大多数场景下用起来没差异但在做镜像裁剪、chroot 环境、系统修复时就得留个心眼。5. 这些目录在实操中的影响与避坑指南说完了理论和历史最后落到实际操作。这几个目录的差异在以下场景里会真正成为坑我把实际运维中踩过的记得比较深的几个点整理一下。5.1 编写脚本时的 PATH 陷阱这是一个非常经典的坑。cron、systemd timer、PHP 的 exec 函数等场景下环境变量往往是精简过的PATH 可能只包含/usr/bin:/bin不包含/usr/sbin:/sbin。如果你在脚本里直接调用reboot、mount、useradd这类 sbin 命令就会遇到command not found。解决办法有两种在脚本开头显式设置 PATH#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin调用命令时写绝对路径比如直接写/sbin/reboot或/usr/sbin/useradd。但要注意在 usr merge 的系统上/sbin/reboot实际指向/usr/sbin/reboot这层关系没问题在传统布局的系统上你得确认命令确切的位置。推荐先which确认。5.2 如何快速定位一个命令属于哪个分类实际排查问题时我一般这么做# 查看命令的完整路径 which -a mount reboot useradd # 查看命令是物理文件还是符号链接 ls -l $(which mount) # 查看命令所在目录是否在 PATH 中 echo $PATH | tr : \n如果我要写一个安装脚本需要判断当前系统是不是 usr merge 布局可以这样测if [ $(readlink /bin) usr/bin ]; then echo 现代布局: /bin - /usr/bin else echo 传统布局: /bin 是独立目录 fi5.3 自定义软件安装时的目录取舍日常自己编译安装软件时也常遇到一个问题装到/usr/local/bin还是/usr/bin很多发行版的默认 PATH 里/usr/local/bin排在/usr/bin前面。这意味着你装在/usr/local/bin下的程序会覆盖同名的系统默认程序。比如系统自带的 Python 在/usr/bin/python3你自己编译了一个新版 Python 3.12装到了/usr/local/bin/python3。在终端里敲python3优先命中的是/usr/local/bin/python3这可能让你踩版本错乱的坑。一般来说我的建议是用发行版包管理器装软件它自己会处理路径不用操心。自己从源码编译安装优先装到/usr/local下这符合 FHS 对本机管理员自行安装的软件的定位。尽量不要手动往/usr/bin里丢东西包管理器升级时可能直接把你的文件覆盖掉。5.4 容器镜像里的目录策略到了容器世界里这四个目录的区分又被简化了一层。很多基础镜像做完 usr merge 之后根目录上一层就是/usr里的那套东西。我见过很多 Dockerfile 直接往/usr/bin里 COPY 二进制这样没问题因为/bin只是一个链接。但如果你的镜像做得很精简比如用 scratch 从零构建那就完全由你自己决定布局了。这时候可以彻底抛弃老传统只需要一个/bin或者/usr/bin都行只要执行程序的路径和 PATH 环境变量对应上就好。在容器里不需要区分普通命令还是系统管理命令因为整个容器通常就为跑一个进程服务。5.5 辨析bin 目录和bin 文件两个最容易混淆的概念趁这个机会顺便澄清一个问题。标题里说的bin是指存放可执行程序的目录但网络上还有很多人在搜bin 文件怎么打开、keil 生成 bin 文件。这里的 bin 文件指的是二进制格式的数据文件比如单片机固件、光盘镜像文件等和 Linux 系统目录完全是两码事。如果你拿到一个后缀是.bin的文件首先得确认它是什么类型的二进制数据。比如 Keil 编译器生成的是嵌入式固件得用专门的烧录工具写入芯片如果是光盘镜像那就挂载或者用虚拟光驱打开。千万不能拿chmod x就想把它当程序跑大部分.bin文件都不是可执行程序。这种命名上的撞车也导致很多人搜资料时被带偏明明想搜 Linux 目录结构结果搜出一堆单片机烧录教程。5.6 一个典型的排查案例复盘最后复盘一下我开头提到的那个reboot找不到的问题把完整排查思路列出来以后你遇到同类问题可以直接照搬。现象crontab 里定时执行/root/scripts/restart.sh脚本里面有reboot但每次执行都报reboot: command not found日志里没有别的线索。第一步先还原 cron 环境。在脚本第一行加了句env /tmp/cron.env重新跑一次把环境变量导出发现 PATH 只有/usr/bin:/bin确实没有/sbin。第二步确认reboot命令的真实位置。which reboot返回/usr/sbin/reboot在 usr merge 系统上这就是唯一的位置/sbin/reboot其实是个软链接。第三步修复脚本。在脚本开头的#!/bin/bash下面添加了显式的 PATH 声明#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin第四步重新跑一次 cron 任务报错消失问题解决。整个过程其实并不复杂但如果你对sbin目录的定位没有概念很可能要在这一步卡上大半天甚至想破头也想不到是 PATH 的问题。注意不止 cron 会精简 PATHsystemd 的 service 默认环境也是精简过的。如果某些 systemd 服务里调用了外部命令同样建议在 service 文件或脚本里显式设置 PATH不要依赖系统默认值。5.7 在一个系统上实际体验四个目录的异同如果你手头有一台 Linux 机器我建议你亲手做一个小实验把今天的知识串起来。在终端依次执行# 查看四个目录的信息 ls -ld /bin /sbin /usr/bin /usr/sbin # 查看文件系统挂载情况 df -h /bin /usr/bin # 查看 /usr 是否单独分区 mount | grep /usr 在一台现代的 Ubuntu 或 Fedora 上你会看到/bin和/sbin都是软链接/usr/bin和/usr/sbin是物理目录它们都在同一个根文件系统或同一个/usr分区里。如果在老系统上做这个实验你可能会看到/bin和/usr/bin是不同的物理目录/usr甚至可能是独立的挂载点。这种眼见为实的体验比背十遍目录结构说明都管用。最后再说两句我这些年维护过各种各样的 Linux 机器从 CentOS 6 到 Ubuntu 24.04从物理机到容器这个四个目录的问题几乎每隔一段时间就会冒出来一次。说来说去核心就是两句话理解它们的历史由来理解 PATH 的加载机制。至于哪个目录里具体放了什么不同发行版大差不差真到了需要精确知道的场景一个which命令就解决了。不过有一点倒是值得留意随着 usr merge 的全面铺开以后新接触 Linux 的年轻人可能不会再经历两个 ls 在不同目录的年代了。到那时候bin、sbin、usr/bin、usr/sbin这四个名字会继续存在但它们之间的关系会像今天的 IPv4 地址一样——大家都在用但已经没多少人还记得当年划分类地址的逻辑了。能在系统演进到这个阶段之前把这段历史和设计逻辑梳理清楚我觉得还是挺有意义的一件事。
返回列表