目录结构与文件系统

标准目录结构:FHS

多数 Linux 发行版都遵循大致统一的目录层次约定,通常称为 FHS(Filesystem Hierarchy Standard)。不同发行版在细节上可能存在差异,但核心目录的用途大体一致。

  • /:根目录,整个系统目录树的起点。
  • /bin/sbin:传统上存放系统启动和基本维护所需的可执行文件。
    • /bin 通常面向普通用户和系统基本命令,例如 cpls 等。
    • /sbin 通常存放系统管理相关命令,其中很多命令需要管理员权限才能执行。
  • /usr/bin/usr/sbin:传统上存放系统运行后供用户和管理员使用的大量可执行文件,例如 gitwget 等。通过发行版包管理器安装的软件通常会把可执行文件放在这些目录下。
  • /lib/usr/lib:存放共享库和相关运行时文件。
    • /lib 传统上服务于 /bin/sbin 中的基础程序,也可能包含内核模块目录,例如 /lib/modules
    • /usr/lib 传统上服务于 /usr/bin/usr/sbin 中的程序。
    • 在 64 位或多架构系统中,还可能存在 /lib64/usr/lib64/lib32 等目录。
  • /usr/local:本机管理员手动安装的软件位置,通常用于区别于发行版包管理器维护的软件。例如手动编译安装的软件、手动解压安装的预编译软件,都可以放在这里。
  • /root:root 用户的家目录。
  • /home:普通用户家目录的父目录,例如用户 abc 的家目录通常是 /home/abc
  • /opt:通常用于安装第三方提供的、相对独立的大型软件包,例如某些商业软件或自包含软件。
  • /etc:系统级配置文件目录。它通常存放纯文本配置文件和少量初始化脚本,例如 /etc/nginx/nginx.conf/etc/apt/ 等。
  • /var:存放运行过程中持续变化的数据。
    • /var/log:系统和服务日志。
    • /var/cache:缓存数据。缓存丢失不应导致关键数据丢失。
    • /var/tmp:临时文件,存续时间通常长于 /tmp,但仍可能被系统策略清理。
  • /tmp:临时文件目录,通常允许多用户写入。它可能在重启后被清理,也可能受系统策略限制容量。
  • /mnt:通常用于临时手动挂载文件系统。
  • /media:通常用于自动挂载可移动介质,例如 U 盘、移动硬盘等。

还有一些与系统运行机制密切相关的特殊目录。一般用户不应手动修改这些目录中的内容,除非明确知道相关机制和风险。

  • /boot:存放引导加载器、内核镜像、initramfs 等启动相关文件。
  • /dev:设备文件目录,例如磁盘、终端、随机数设备,以及常见的 /dev/null
  • /proc:虚拟文件系统,提供进程信息和内核状态信息。其内容通常不是普通磁盘文件。
  • /sys:虚拟文件系统,提供设备、驱动和内核对象的信息。
  • /run:运行时数据目录,例如 PID 文件、Socket 文件、锁文件等。通常在系统启动时创建,重启后清空。

可以从三个角度理解这些目录的分工:

  • 使用对象:bin 倾向于普通用户命令,sbin 倾向于系统管理命令。
  • 提供来源:/usr 下的内容通常由发行版或包管理器维护;/usr/local 下的内容通常由本机管理员维护。
  • 系统阶段:传统设计中,/bin/sbin/lib 与系统启动和基础维护有关,而 /usr 下的内容属于更完整的用户空间环境。

不过,这些区分在现代 Linux 发行版中已经不再总是严格成立。由于历史原因,Linux 目录层次中存在一定的重叠;同时,为了简化维护,许多发行版采用了 Usr Merge,即把若干传统根目录合并到 /usr 下,典型形式包括:

1
2
3
4
/bin    ->  /usr/bin
/sbin -> /usr/sbin
/lib -> /usr/lib
/lib64 -> /usr/lib64

因此,实际系统中看到的目录结构可能与教科书式说明略有不同。阅读目录结构时,应同时区分“标准约定”“发行版实现”和“本机管理员的安装习惯”。

目录树与磁盘挂载

Linux 把所有文件组织成从 / 开始的一棵统一目录树。这个目录树是逻辑层面的结构;具体数据存放在哪个磁盘、分区或远程文件系统中,则由挂载关系决定。

可以用 lsblkfindmntdf -h 等命令查看块设备和挂载点。这里不展开命令用法,只关注概念。

基本规则

  • 根目录 / 必须对应一个已经挂载的文件系统。
  • 任意目录都可以作为挂载点,例如把一个分区挂载到 /data
  • 某个路径对应的实际文件系统,可以沿目录树向上查找最近的挂载点来确定。
  • 如果路径自身和其上级目录都没有额外挂载点,则最终会落到根文件系统上。

理解挂载的关键是区分“逻辑目录树”和“物理存储”:

  • 假设根目录 / 位于磁盘 A 的某个分区。
  • 如果 /home 被单独挂载到磁盘 B 的某个分区,那么 /home 及其子目录的内容实际存储在磁盘 B 上。
  • 此时 /etc/bin/var 等没有被额外挂载的目录仍然位于磁盘 A 对应的根文件系统中。
  • 在目录树上,它们仍然表现为连续的父子路径;在物理存储上,它们可能属于完全不同的设备或文件系统。

因此,从任意路径向上追溯,总能找到一个最近的挂载点;这个挂载点对应的文件系统就是该路径实际所在的文件系统。

硬链接不能跨文件系统创建。软链接保存的是路径引用,因此可以跨文件系统,但目标路径是否有效取决于运行时的挂载和路径状态。

如果把一个文件系统挂载到一个已经存在内容的目录上,需要注意:

  • 原目录中的内容不会被删除,也不会被新文件系统覆盖。
  • 挂载期间,该路径下显示的是新挂载文件系统中的内容,原目录内容会被暂时遮蔽。
  • 卸载后,原目录中的内容会重新可见。

因此,实际操作中通常建议选择空目录作为挂载点,以避免混淆。

文件类型与权限

ls -l 输出格式

在目录中查看文件列表时,ls -l 或常见别名 ll 会以长格式显示文件信息。例如:

1
2
drwxrwxr-x 2 abc abc 4096 Sep 30 00:25 test
-rw-r--r-- 1 abc abc 4096 Sep 30 02:25 hello.py

每一行主要包含以下信息:

  1. 文件类型和权限,例如 drwxrwxr-x-rw-r--r--
  2. 链接计数。对普通文件而言通常表示硬链接数;对目录而言与目录项结构有关,不能简单理解为目录下普通文件数量。
  3. 文件所有者。
  4. 文件所属用户组。
  5. 文件大小,单位默认为字节。使用 -h 可以显示更易读的单位。对目录而言,这个大小表示目录项自身占用的空间,而不是其中所有文件大小之和。
  6. 最后修改时间,通常是内容或元数据相关时间中的一种展示结果,不能简单等同于创建时间。
  7. 文件名。以 . 开头的文件名通常默认隐藏,其中 . 表示当前目录,.. 表示上级目录。

第一列的第一个字符表示文件类型,常见取值包括:

  • -:普通文件。
  • d:目录。
  • l:符号链接。
  • c:字符设备,例如终端、键盘、鼠标等抽象设备。
  • b:块设备,例如磁盘、分区等。
  • p:命名管道。
  • s:Socket 文件。

后九个字符分成三组,分别表示三类主体的权限:

1
2
3
rwx rwx rwx
--- --- ---
所有者 用户组 其他用户

其中:

  • 第 1 组:文件所有者的权限。
  • 第 2 组:文件所属组的权限。
  • 第 3 组:其他用户的权限。

每组中的 rwx 分别表示读、写、执行权限;如果对应位置是 -,表示没有该权限。

文件权限与目录权限的区别

rwx 对普通文件的含义相对直接:

  • r:可以读取文件内容。
  • w:可以修改文件内容,但删除文件本身取决于其所在目录的权限。
  • x:可以把该文件作为程序或脚本执行。

rwx 对目录的含义需要从“目录是路径名到 inode 的映射表”这一角度理解:

  • r:可以列出目录项,即查看目录中有哪些文件名。
  • w:可以修改目录项,例如创建、删除、移动、重命名该目录下的文件。
  • x:可以进入或穿过该目录,即进行路径解析。没有目录的 x 权限,即使知道文件名,也通常无法访问其下的文件。

因此,删除一个文件通常不取决于该文件自身是否具有写权限,而取决于用户是否对其所在目录具有写权限和执行权限。

目录权限不会自动决定子目录和子文件的权限。每个文件系统对象都有自己的权限位。例如 /var 对普通用户可能是 r-x,而 /var/tmp 通常允许普通用户写入,并通过粘滞位限制删除行为。

颜色显示

许多发行版默认会为 ls 开启颜色显示,不同颜色用于区分文件类型和权限状态。常见约定包括:

  • 普通文件:通常显示为默认颜色。
  • 目录:通常显示为蓝色。
  • 可执行文件或脚本:通常显示为绿色。
  • 符号链接、设备文件、压缩文件等也可能有不同颜色。

颜色规则由 LS_COLORS 环境变量和 ls 的配置决定,不同发行版或终端主题下可能不同,因此颜色只能作为辅助判断,不能替代 ls -l 给出的文件类型与权限信息。

特殊权限位

除了普通的 rwx 权限,Linux 还支持一些特殊权限或特殊属性。常见的有:

  • setuid:对可执行文件设置后,程序运行时可能以文件所有者身份执行。
  • setgid:对可执行文件或目录有不同含义;用于目录时,新建文件可能继承目录所属组。
  • sticky bit:常用于共享目录,限制删除和重命名他人文件。
  • 不可变属性、只追加属性等:通常通过文件系统扩展属性实现,例如 chattr +ichattr +a,并不属于普通 rwx 权限位本身。

这里重点说明目录上的粘滞位 t。例如 /tmp/var/tmp 对普通用户通常显示为类似:

1
drwxrwxrwt

其中最后的 t 表示 sticky bit。它的作用是:

  • 多个用户可以在该目录中创建文件。
  • 用户通常只能删除或重命名自己拥有的文件。
  • 目录所有者和 root 用户可以删除或重命名目录中的文件。

因此,粘滞位常用于 /tmp 这类多用户共享写入目录,避免用户随意删除其他用户创建的临时文件。

家目录结构与用户级配置

常见隐藏文件和配置文件

用户家目录通常包含大量以 . 开头的隐藏文件或隐藏目录。它们多用于保存用户级配置、状态和历史记录。

常见 Shell 相关文件包括:

  • .bash_profile.bash_login.profile:登录 Shell 启动时可能读取的配置文件。具体读取哪个文件取决于 Shell 类型和启动方式。
  • .bashrc:交互式非登录 Bash 启动时读取的配置文件。常用于别名、函数、提示符、交互式环境变量等配置。
  • .bash_logout:登录 Bash 退出时可能执行的脚本。
  • .bash_history:Bash 命令历史记录。
  • .zshrc.kshrc.mkshrc:其他 Shell 的用户级配置文件。

常见编辑器相关文件包括:

  • .vimrc:Vim 的用户级配置文件。
  • .viminfo:Vim 的历史和状态信息文件。较新的 Vim 版本也可能使用其他位置或文件名。

还有一些图形界面或远程登录相关文件,例如:

  • .Xauthority:X Window 认证信息文件,常见于图形界面或 X11 转发相关场景。

系统级配置通常位于 /etc 下,例如 /etc/profile/etc/bash.bashrc/etc/zsh/ 等。一般来说:

  • 系统级配置影响所有用户,通常由管理员维护。
  • 用户级配置只影响当前用户,通常位于用户家目录下。
  • 用户级配置通常可以覆盖或补充系统级配置。

不同发行版、不同 Shell、不同启动方式对配置文件的读取顺序可能不同。例如 Ubuntu 中常见的 .profile 会在一定条件下主动加载 .bashrc;而某些 CentOS/RHEL 系系统更常见的是 .bash_profile。因此,排查环境变量或别名问题时,需要同时检查登录 Shell、交互式 Shell、图形终端、SSH 登录等场景。

XDG 基础目录规范

由于历史原因,很多程序会直接在家目录中创建各自的隐藏配置文件,导致家目录变得混乱。FreeDesktop.org 提出了 XDG Base Directory Specification,用于规范用户级配置、数据、缓存和运行时文件的位置。

XDG 规范定义了一组环境变量:

  • $XDG_DATA_HOME:用户级数据文件目录。默认值为 $HOME/.local/share
  • $XDG_CONFIG_HOME:用户级配置文件目录。默认值为 $HOME/.config
  • $XDG_STATE_HOME:用户级状态文件目录。默认值为 $HOME/.local/state。这类文件通常需要持久保存,但不一定适合迁移到其他机器。
  • $XDG_CACHE_HOME:用户级缓存目录。默认值为 $HOME/.cache
  • $XDG_RUNTIME_DIR:用户级运行时文件目录,通常由系统登录管理器创建,用于 Socket、PID 文件等短生命周期对象。

目前只有部分软件严格遵循 XDG 规范,例如许多现代命令行工具和 nvim。另一些软件会在保持历史兼容的同时支持 XDG 路径,例如 git 的部分配置。也有一些软件仍然主要使用传统位置,例如 Vim 的 .vimrc

如果采用 XDG 规范,家目录中通常会包含以下目录:

  • ~/.config:用户级配置文件目录,例如 ~/.config/fish~/.config/nvim
  • ~/.local:用户级本地安装和数据目录。(大致替代 /usr/local,因为 /usr/local 需要管理员权限,普通用户无权限时可以安装到此处)
    • ~/.local/bin:用户级可执行文件目录。许多发行版会自动将其加入 PATH,但具体行为不完全一致。
    • ~/.local/lib:用户级库文件目录。
    • ~/.local/include:用户级头文件目录。
    • ~/.local/share:用户级数据文件目录,例如桌面入口、图标、字体、插件、部分应用数据等。
    • ~/.local/state:用户级状态文件目录,例如日志、历史状态、不可移植状态数据等。
  • ~/.cache:用户级缓存目录。

示意结构如下:

1
2
3
4
5
6
7
8
9
~
├── .config/
├── .local/
│ ├── bin/
│ ├── include/
│ ├── lib/
│ ├── share/
│ └── state/
└── .cache/

在实践中,家目录中还可以保留一些非隐藏目录,用于组织用户自己的软件和临时文件:

  • ~/opt:可作为 /opt 的用户级替代,用于存放相对独立的大型软件。
  • ~/software:可用于存放源码包、编译目录、预编译压缩包等。
  • ~/tmp:可作为用户自己的临时目录,例如用于接收 scp 上传文件、存放临时实验数据等。

除了 ~/.local,有些用户也会使用 ~/local 模拟 /usr/local,但是更推荐前者。

为了让这些目录生效,常见做法是在 Shell 配置文件中添加环境变量。例如:

1
2
3
4
5
6
7
8
9
export PATH="$HOME/.local/bin:$PATH"

export LIBRARY_PATH="$HOME/.local/lib:$LIBRARY_PATH"
export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH"

export PKG_CONFIG_PATH="$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH"

export C_INCLUDE_PATH="$HOME/.local/include:$C_INCLUDE_PATH"
export CPLUS_INCLUDE_PATH="$HOME/.local/include:$CPLUS_INCLUDE_PATH"

如果希望用户自行安装的软件优先于系统软件被找到,可以把 ~/.local/bin 放在 PATH 的前面。过高的优先级会导致用户安装的程序覆盖系统默认的程序,应根据实际需求调整。

环境变量

基本概念

环境变量就是系统层面需要给应用层面提供的一个”词典”,每一项由变量名和变量值(字符串)组成,通常包括应用所需要的路径,系统信息等。

环境变量是进程环境的一部分,可以理解为父进程传递给子进程的一组字符串键值对。它常用于保存路径、系统信息、语言区域、运行参数、软件配置入口等。

需要区分几个概念:

  • Shell 变量:当前 Shell 内部使用的变量。
  • 环境变量:被导出的变量,可以被子进程继承。
  • 配置文件:Shell 启动时读取的脚本,用于初始化变量、别名、函数等。

不同 Shell、不同终端会话、不同进程中的环境变量可能不同。手动在当前终端中修改变量通常只影响当前 Shell 及其后续子进程;退出该 Shell 后修改就会失效。若希望长期生效,需要写入相应配置文件。

可以用 env 查看当前进程环境中的环境变量,例如:

1
2
3
4
HOME=/home/username
LOGNAME=username
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash

访问环境变量时通常使用 $ 前缀,例如:

1
2
echo "$HOME"
echo "$PATH"

Shell 变量名区分大小写。按照惯例,环境变量通常使用全大写名称。

PATH 变量

PATH 是最常见、最重要的环境变量之一。它保存一组可执行文件搜索路径,不同路径之间用冒号 : 分隔。当用户输入一个不含斜杠的命令名时,Shell 会按照 PATH 中目录的顺序查找对应可执行文件。

例如:

1
echo "$PATH"

输出可能类似:

1
/usr/local/bin:/usr/bin:/bin:/home/username/.local/bin

可以将 PATH 按行显示:

1
echo "$PATH" | tr ':' '\n'

也可以去掉重复项和空项后显示:

1
echo "$PATH" | tr ':' '\n' | awk '!seen[$0]++ && length'

PATH 添加目录时,可以追加到后面,也可以放到前面:

1
2
PATH="$PATH:$HOME/bin"
PATH="$HOME/.local/bin:$PATH"

两者区别在于优先级:放在前面的目录会更早被查找,因此如果多个目录中存在同名程序,排在前面的版本会被执行。

如果希望修改对当前 Shell 的子进程也生效,需要导出:

1
export PATH="$HOME/.local/bin:$PATH"

如果希望长期生效,可以把这类语句写入 ~/.bashrc~/.profile~/.bash_profile 等文件。具体应该写入哪个文件,取决于 Shell 类型和启动方式。对于 Bash,常见经验是:

  • 与交互式终端相关的配置写入 ~/.bashrc
  • 与登录会话相关、需要影响整个用户会话的配置写入 ~/.profile~/.bash_profile
  • 如果 .profile.bash_profile 中会主动加载 .bashrc,则需要避免重复添加路径。

注意路径展开问题:

  • 在双引号中,$HOME 会展开为家目录路径。
  • ~ 的展开规则更依赖 Shell 语法位置,不适合作为环境变量值中的通用写法。
  • 因此,配置 PATH 时更推荐使用 $HOME,避免最终 PATH 中保留字面量 ~

删除 PATH 中的路径

除了添加路径,有时也需要从 PATH 中移除某些路径,例如撤销某个软件安装脚本加入的路径。可以定义简单函数:

1
2
3
4
5
6
7
path_remove() {
PATH=$(printf '%s' "$PATH" | tr ':' '\n' | grep -Fxv "$1" | paste -sd ':' -)
}

path_remove_prefix() {
PATH=$(printf '%s' "$PATH" | tr ':' '\n' | grep -v "^$1" | paste -sd ':' -)
}

前者删除精确匹配的路径,后者按前缀删除路径。例如:

1
2
path_remove "/opt/anaconda3/bin"
path_remove_prefix "/opt/anaconda3"

第二个函数使用正则匹配前缀,如果路径中包含正则特殊字符,需要额外注意转义问题。对日常使用而言,上述写法通常已经足够。

Windows PowerShell 中的环境变量

Windows PowerShell 也有环境变量机制,但语法不同。查看环境变量可以使用:

1
echo $env:PATH

按路径分隔符拆分并去重显示:

1
$env:PATH -split ';' | Select-Object -Unique

临时设置环境变量:

1
2
$env:ABC = "abc"
$env:PATH += ";D:\path\to\bin"

PowerShell 中设置的环境变量会被后续子进程继承,不需要额外的 export 命令。

持久性修改用户级环境变量可以使用:

1
[System.Environment]::SetEnvironmentVariable("XXX_TOKEN", "sk-xxxxxxxxxxxxxxxx", "User")

这与在 Windows 图形界面中修改用户环境变量类似。需要注意,已经打开的终端通常不会自动获得修改后的持久环境变量,需要重新打开终端或重新登录。

Bash 脚本与进程环境

子进程与变量继承

在 Bash 中执行脚本时,通常会创建一个新的 Shell 进程来运行脚本。这个子进程会继承父进程导出的环境变量,但不会继承普通 Shell 变量;子进程中的修改也不会反向影响父进程。

例如,父 Shell 中定义普通变量:

1
a=1

如果没有 export a,子进程通常无法通过环境变量获得它。若希望子进程继承,需要导出:

1
export a

也可以在定义时直接导出:

1
export b=3

取消变量的导出属性可以使用:

1
export -n a b

Bash 也支持导出函数:

1
2
export -f func_1 func_2
export -fn func_1 func_2

不过,导出函数主要用于 Bash 子进程,跨 Shell 或跨语言程序时不具备通用性。

source 与当前 Shell 执行

如果希望一个脚本修改当前 Shell 的环境,可以使用 source,而不是以普通方式执行脚本:

1
2
source b.sh
. b.sh

其中 .source 的等价写法,点号后面需要有空格。

普通执行脚本时,脚本在子进程中运行;source 脚本时,脚本内容在当前 Shell 中执行。因此,source 常用于加载配置文件、激活虚拟环境、导入 .env 文件等场景。

Shebang 行

Bash 脚本第一行通常写 Shebang 行,用于指定解释器:

1
#!/usr/bin/env bash

也可以直接指定 Bash 路径:

1
#!/bin/bash

二者区别在于:

  • #!/usr/bin/env bash 会根据当前 PATH 查找 bash,可移植性通常更好,尤其适合用户自定义环境或非标准安装路径。
  • #!/bin/bash 直接指定解释器路径,行为更固定。多数 Linux 系统都会提供 /bin/bash,但并非所有类 Unix 环境都保证这一点。

Shebang 行不是 Bash 语法本身的一部分,而是由内核在执行脚本文件时用于选择解释器。若总是通过 bash script.sh 运行脚本,则 Shebang 行不会决定解释器,但仍建议保留。

Bash 语法中的空格问题

Bash 语法对空格非常敏感。变量赋值时,等号两边不能有空格:

1
2
3
name="Bob"

name = "Bob" # error

后一种写法会让 Bash 把 name 当作命令名执行,而不是变量赋值。

条件判断中,[ 实际上是一个命令,] 是它的最后一个参数,因此括号内侧需要空格:

1
2
3
if [ "$name" = "Bob" ]; then
echo "matched"
fi

下面这些写法都是错误的:

1
2
3
4
5
6
7
if ["$name" = "Bob"]; then
echo "matched"
fi

if [ "$name" = "Bob"]; then
echo "matched"
fi

Bash 的很多语法来自历史工具和命令组合,可读性并不总是理想。遇到复杂命令时,可以使用手册页、--help、ShellCheck 或 explainshell 等工具辅助理解。

.env 文件与本地配置

在 Git 仓库中,有些信息不适合写入脚本或纳入版本控制,例如:

  • 平台相关路径。
  • 本地用户名、机器路径。
  • API Key、Token 等敏感信息。

常见做法是使用环境变量和 .env 文件。.env 通常是纯文本文件,每行一个键值对:

1
KEY=value

常见 .env 文件一般不写 export,这样更便于被不同语言或工具解析。但如果只用于 Bash,也可以接受带 export 的写法。

在 Bash 脚本中导入 .env 并自动导出变量,可以写:

1
2
3
set -a
source .env
set +a

其中:

  • set -a 表示后续定义或修改的变量自动具有导出属性。
  • source .env 在当前 Shell 中读取 .env
  • set +a 取消自动导出。

实际项目中通常应把 .env 加入 .gitignore,同时提供 .env.example 作为模板并纳入版本控制。

软件安装方式

本节只讨论 Ubuntu 或 Debian 系 Linux 中较常见的软件安装方式。其他发行版的包管理器和目录约定可能不同,但基本思想类似。

通过包管理器安装

发行版自带包管理器是最标准的软件安装方式。以 Ubuntu 为例,可以使用 apt 安装软件:

1
sudo apt install nginx

通过包管理器安装的优点是:

  • 自动处理依赖关系。
  • 文件位置符合发行版规范。
  • 可以统一升级、卸载和查询。
  • 安全更新可以由发行版维护。

局限性包括:

  • LTS 系统中软件版本可能偏旧,尤其是仍在快速发展的命令行工具、编译器、编辑器、科研软件等。
  • 安装系统级软件通常需要 root 权限。
  • 默认依赖网络软件源;离线安装需要额外准备包文件和依赖。

例如,通过 apt 安装 nginx 后,其文件可能分布在多个目录中:

  • 可执行文件:/usr/sbin/nginx
  • 模块和库文件:/usr/lib/nginx/
  • 配置文件:/etc/nginx/
  • 文档文件:/usr/share/doc/nginx/
  • 服务管理文件:可能位于 /lib/systemd/system//usr/lib/systemd/system/

常用维护命令包括:

1
2
3
4
sudo apt update                 # 更新软件包索引
apt list --upgradeable # 查看可升级的软件包
sudo apt upgrade # 升级已安装软件包
sudo apt autoremove # 删除不再需要的依赖包

aptapt-get 功能有较多重叠。一般而言:

  • apt 更适合交互式使用,输出更友好。
  • apt-get 更适合脚本中使用,接口更稳定。

查询包信息:

1
apt-cache show <package-name>

查询某个已安装包提供了哪些文件:

1
dpkg -L ripgrep

查询某个文件属于哪个包:

1
dpkg -S "$(which rg)"

包名和可执行文件名不一定一致。例如安装包 ripgrep 后,提供的主命令是 rg

如果官方源中没有需要的软件,可能需要添加第三方软件源或 PPA。例如:

1
2
3
sudo add-apt-repository ppa:zhangsongcui3371/fastfetch
sudo apt update
sudo apt install fastfetch

添加 PPA 后,系统通常会:

  • /etc/apt/sources.list.d/ 中添加新的软件源配置。
  • 配置对应仓库的签名密钥。
  • apt update 后把该仓库的软件包索引合并到本地索引中。

需要注意,第三方源会影响系统软件供应链安全和依赖解析。应尽量选择可信来源,并避免添加过多来源不明的软件源。

如果服务器无法直接联网,也可以手动下载 .deb 文件并上传安装:

1
sudo apt install ./pandoc-3.7.0.2-1-amd64.deb

这里的 ./ 或完整路径很重要,否则 apt 可能会把参数解释为包名并尝试从软件源查找。dpkg -i 也可以安装本地 .deb 文件,但它不会像 apt install ./xxx.deb 那样自动处理依赖,因此一般更推荐使用后者。

Windows 长期缺少与 Linux 发行版包管理器完全等价的官方系统级工具。现在 Windows 10/11 提供了 Winget;此外还有 Chocolatey、Scoop 等社区包管理工具。

源码编译安装

有些软件需要从源码编译安装,常见原因包括:

  • 包管理器中的版本过旧。
  • 需要启用特定编译选项。
  • 需要安装到用户目录而非系统目录。
  • 软件本身没有提供适合当前系统的二进制包。

传统上,源码编译安装的默认前缀是 /usr/local。它通常包含:

  • /usr/local/bin/usr/local/sbin:本地安装的可执行文件。
  • /usr/local/lib:本地安装的库文件。
  • /usr/local/include:本地安装的 C/C++ 头文件。
  • /usr/local/share:架构无关的共享数据,例如文档、图标、locale 文件等。
  • /usr/local/share/man:手册页文件。
  • /usr/local/etc:本地安装软件的配置文件。
  • /usr/local/src:可用于存放源码。

普通用户通常没有 /usr/local 的写权限,因此常使用 ~/.local 作为用户级安装前缀。

典型的 autotools 构建流程是:

1
2
3
./configure
make
make install

安装前通常可以查看配置选项:

1
./configure --help

若希望安装到用户目录,可以指定安装前缀:

1
2
3
./configure --prefix="$HOME/.local"
make
make install

不同项目可能使用不同构建系统,例如 CMake、Meson、Ninja、Cargo、Go modules 等,因此具体命令需要以项目文档为准。核心原则是:

  • 明确安装前缀。
  • 避免不必要地把手动安装的软件混入系统包管理器维护的目录。
  • 确保可执行文件、库文件、头文件和 pkg-config 文件所在目录能被后续工具找到。

如果安装到非标准位置,可能需要配置:

  • PATH:让 Shell 找到可执行文件。
  • LD_LIBRARY_PATH 或动态链接器配置:让程序运行时找到共享库。
  • LIBRARY_PATH:让编译器链接阶段找到库。
  • C_INCLUDE_PATHCPLUS_INCLUDE_PATH:让编译器找到头文件。
  • PKG_CONFIG_PATH:让 pkg-config 找到 .pc 文件。

需要注意,LD_LIBRARY_PATH 会影响运行时动态链接搜索顺序,配置过宽可能引入冲突。对于系统级安装,更规范的方式可能是使用 /etc/ld.so.conf.d/ldconfig;对于用户级安装,则通常在必要时临时设置。

预编译二进制安装

很多软件会直接提供预编译二进制包。用户下载后解压,即可得到可执行文件、库文件和配套资源。这类安装方式不由系统包管理器维护,因此安装、升级和卸载都需要用户自行管理。

下载预编译包时需要注意 CPU 架构和系统兼容性。常见架构名称包括:

  • x86_64 = x64 = amd64:常见 64 位 x86 架构。
  • i686 = x86:老旧 32 位 x86 架构。
  • arm64 = aarch64:64 位 ARM 架构,例如部分服务器、移动设备、Apple Silicon 等。

例如下载并解压 clang+llvm 的预编译包:

1
2
mkdir clang
tar -xf clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz -C clang

解压后的目录可能包含类似 /usr/local 的结构,例如 binincludelibshare 等。理论上可以把这些目录合并到 /usr/local

1
sudo cp -R * /usr/local/

普通用户也可以合并到 ~/.local

1
cp -R * ~/.local/

但是这种“合并式安装”会让不同软件的文件混在同一目录树中,不利于独立管理和卸载。更清晰的方式通常是保持软件目录独立,例如:

1
2
3
4
5
~/opt/clang-llvm-10.0.0/
├── bin/
├── include/
├── lib/
└── share/

然后只把需要的 bin 目录加入 PATH

1
export PATH="$HOME/opt/clang-llvm-10.0.0/bin:$PATH"

如果只需要使用其中少数几个可执行文件,也可以在 ~/.local/bin 中创建符号链接,指向软件目录中的可执行文件。这样可以减少对 PATH 的修改,并保留软件目录的独立性。

对于 MATLAB、部分 IDE、商业软件或大型第三方软件,通常更适合安装到 /opt/<name> 或用户级的 ~/opt/<name>。这类软件往往是自包含的,其可执行文件、库、文档和资源主要位于自身目录中。常见结构包括:

  • bin:主程序和辅助可执行文件。
  • lib:软件自带库。
  • etc:软件自身配置。
  • docshare/doc:文档。
  • pluginsresources 等:插件和资源文件。

这种方式的优点是升级和卸载都比较清晰:删除对应目录,并清理少量环境变量、符号链接或用户配置即可。

glibc 版本兼容性

通过源码编译或预编译二进制安装软件时,需要注意 glibc 版本兼容性。glibc 是 GNU C Library,是大多数 Linux 用户空间程序依赖的核心 C 标准库和系统接口库。

如果软件是在较新的系统上编译的,它可能依赖较新的 glibc 符号版本。在旧系统上运行时,可能出现类似错误:

1
fd: /lib64/libc.so.6: version `GLIBC_2.18' not found (required by fd)

常见情况是:

  • 较新的预编译软件在旧发行版上无法运行。
  • 旧系统上的 glibc 版本过低,无法满足新软件的最低要求。
  • 反方向通常问题较少,因为 glibc 通常尽量保持向后兼容。

可以用以下命令查看系统 glibc 版本:

1
ldd --version

也可以查看 libc 中包含的符号版本:

1
strings /lib64/libc.so.6 | grep '^GLIBC_'

不同发行版的 glibc 版本大致如下:

  • CentOS 7:glibc 2.17。(版本相对很低,不满足很多新软件的最低要求)
  • Ubuntu 20.04:glibc 2.31。
  • Ubuntu 22.04:glibc 2.35。
  • Ubuntu 24.04:glibc 2.39。

glibc 是系统基础组件,不建议在已有系统中手动替换或强行升级,这可能导致大量系统程序失效。遇到 glibc 版本过低时,通常更合理的解决方式包括:

  • 使用适配旧系统的二进制版本。
  • 在旧系统上从源码编译。
  • 使用容器、Conda、AppImage 等隔离环境,但这些方案仍可能受到内核或基础库限制。
  • 更换或升级操作系统。

对于仍然使用 CentOS 7 等旧系统的服务器,glibc 版本是安装新工具时最常见的兼容性障碍之一。

补充

Linux 发行版

严格来说,Linux 指的是操作系统内核。一个可用的操作系统还需要 C 标准库、Shell、核心工具、包管理器、服务管理工具、桌面环境等组件。把这些组件与 Linux 内核一起集成、维护和发布的系统称为 Linux 发行版。

常见发行版可以按家族粗略分为:

  • Debian 系:稳定、通用,包管理器通常为 apt
    • Debian:历史悠久,稳定性优先,常用于服务器和基础系统环境。
    • Ubuntu:基于 Debian,桌面和服务器使用都很广泛,文档和社区资料较多,LTS 版本常用于科研和开发环境。
  • Red Hat 系:企业环境常见,包管理器通常为 dnf 或旧版 yum
    • RHEL:Red Hat Enterprise Linux,商业发行版,强调企业级支持和稳定性。
    • CentOS Stream:位于 Fedora 和 RHEL 之间,作为 RHEL 的上游滚动预览分支,不再等同于旧 CentOS Linux。
    • Rocky Linux / AlmaLinux:RHEL 兼容的社区发行版,常作为旧 CentOS Linux 的替代。
  • Fedora 系:更新较快,常作为新技术进入 RHEL 生态前的试验场。
    • Fedora:由社区和 Red Hat 支持,软件版本较新,适合桌面、开发和新技术体验。
  • Arch 系:滚动更新,强调用户自主配置。
    • Arch Linux:极简、滚动更新、文档质量较高,但安装和维护要求较高。
    • Manjaro:基于 Arch,提供更友好的默认配置和图形化安装体验。

对于初学者和科研开发环境,Ubuntu LTS 通常是较稳妥的选择。对于服务器环境,Ubuntu LTS、Debian、RHEL 兼容发行版都很常见。桌面环境比纯命令行环境包含更多图形组件和驱动变量,因此出现兼容性问题的概率也更高。

Linux 内核与操作系统

可以从用户态和内核态理解 Linux 内核的作用:

  • CPU 通常区分用户态和内核态。用户态权限较低,不能直接执行某些特权指令,也不能任意访问硬件和内核内存。
  • 普通应用程序运行在用户态。如果需要读写文件、创建进程、访问网络或与硬件交互,需要通过系统调用请求内核完成。
  • 内核运行在内核态,负责进程调度、内存管理、文件系统、网络协议栈、设备驱动等核心功能。
  • 操作系统是比内核更大的概念。一个完整的 Linux 系统除了内核外,还包括 Shell、系统服务、包管理器、基础命令、C 库和用户空间工具等。

因此,“Linux 是内核”这句话强调的是严格定义;日常语境中的 “Linux 系统” 通常指某个完整 Linux 发行版。