$ man linux

Linux 命令手册

日常会用到的 Linux 命令,按用途分类。每条都写了用法、常用选项和能直接照抄的示例;第一次出现的缩写和术语会先给出英文全称和中文意思。忘了某个命令时,直接在搜索框里输入命令名或中文用途,比如“端口”“解压”“权限”。

以 Ubuntu / Debian 上的 bash 和 GNU 工具为准。CentOS、Fedora 等发行版几乎一样;macOS 自带的是 BSD 版本,个别选项不同,文中会注明。

怎么读示例

  • $提示符。后面是要输入的命令,$ 本身不用输入
  • # 说明注释,解释这一行做了什么
  • 输出灰色的行是命令打印出来的结果
  • man 1手册章节:1 普通命令,8 系统管理;“内建”:shell 内置,用 help 查看
  • 文件名斜体蓝字是占位符,换成你自己的值
  • 复制示例右上角的按钮只复制命令,不带提示符和注释

开始之前Basics

终端(Terminal)是输入命令的窗口;真正读取并执行命令的程序叫 shell外壳,夹在用户和系统内核之间的命令解释器。Ubuntu 默认用 bashBourne Again SHell,“重生的 Bourne shell”,早期 Bourne shell 的改进版,macOS 默认用 zshZ shell,日常用法几乎一样。

提示符与命令结构

打开终端后看到的这一串就是提示符,它告诉你现在是谁、在哪台机器、在哪个目录:

alice@web01:~/projects$
# alice      当前用户名
# web01      主机名(这台机器的名字)
# ~/projects 当前所在目录,~ 代表家目录 /home/alice
# $          普通用户;如果是 # 说明你是 root(超级用户,拥有全部权限)

一条命令通常由三部分组成:命令 [选项] [参数]。选项改变命令的行为,参数告诉命令要处理什么。

  • 短选项是一个减号加一个字母,如 -l,多个可以合并:-l -a 等于 -la。
  • 长选项是两个减号加单词,如 --all,更好读,但不能合并。
  • Linux 区分大小写:-r 和 -R 可能是两个意思,File.txt 和 file.txt 是两个文件。
$ ls -l -a /etc                  # 命令 + 两个选项 + 一个参数
$ ls -la /etc                    # 短选项合并,效果相同
$ ls --all --human-readable      # 长选项
$ cat "my notes.txt"             # 文件名里有空格,要加引号
$ rm -- -oops.txt                # "--" 表示选项到此结束,后面的 -oops.txt 当文件名处理
提示:看到 command not found,说明这个命令没装或拼错了。Ubuntu 通常会提示该装哪个包,照着 sudo apt install 包名 装上即可。

路径与特殊符号

绝对路径从根目录 / 开始写,在哪儿用都指向同一个位置,如 /home/alice/notes.txt。相对路径从当前目录出发,如 notes.txt、docs/a.md。

符号含义例子
/在开头表示根目录,在中间是目录分隔符/etc/hosts
~当前用户的家目录~/Downloads = /home/alice/Downloads
.当前目录./run.sh 执行当前目录下的脚本
..上一级目录cd ../.. 往上走两级
-在 cd 里表示“上一次所在的目录”cd - 在两个目录间来回切换
.名字以点开头的是隐藏文件,ls 默认不显示~/.bashrc、.git

常见目录都放什么

Linux 没有 C 盘、D 盘,所有东西都挂在一棵以 / 为根的目录树上。各目录的用途大体遵循 FHSFilesystem Hierarchy Standard,文件系统层次结构标准:

目录名字来源放什么
/homehome,家普通用户的家目录,如 /home/alice
/rootroot,根用户root 用户的家目录
/etcet cetera,“等等”系统和软件的配置文件,如 /etc/nginx/、/etc/hosts
/varvariable,可变的运行中不断变化的数据:日志 /var/log、网站文件 /var/www、缓存
/tmptemporary,临时临时文件,重启后通常会被清空
/usr最早是 user;常被解释为 Unix System Resources系统安装的程序、库和文档,如 /usr/bin、/usr/lib
/bin、/sbinbinaries 二进制程序;system binaries 系统程序基本命令和系统管理命令。新系统里它们只是链接,分别指向 /usr/bin 和 /usr/sbin
/optoptional,可选的手动安装的第三方软件
/devdevices,设备设备文件,如硬盘 /dev/sda、黑洞 /dev/null
/procprocess,进程内核提供的虚拟文件,如 /proc/cpuinfo,不占磁盘
/mnt、/mediamount,挂载挂载硬盘、U 盘的地方
/bootboot,启动内核和启动引导文件

man

manualman 1

查看命令的使用手册

系统自带的说明书,最全也最权威。忘了某个选项怎么用,先查它。

$ man ls                         # 打开 ls 的手册,按 q 退出
$ man 5 crontab                  # 指定第 5 节:crontab 配置文件的写法
$ man -k "copy files"            # 不知道命令名时按关键词搜索,等同 apropos
cp (1)               - copy files and directories

在手册里怎么操作

按键和 less 一样:

按键作用
空格 / b向下 / 向上翻一页
/关键词搜索,n 下一个,N 上一个
g / G跳到开头 / 结尾
q退出

章节编号

手册分成若干节。同一个名字可能在不同节里各有一篇,比如 crontab 既是命令(第 1 节),也是配置文件(第 5 节)。不写章节号时,通常先打开第 1 节的那篇。

章节内容例子
1普通用户命令ls、grep、ssh
5配置文件的格式crontab、fstab、ssh_config
8系统管理命令,通常需要 rootmount、useradd、ip

手册页的结构

每篇手册都按固定的几个部分排列,知道了结构就能直接跳到想看的地方:

标题内容
NAME名称和一句话说明
SYNOPSIS用法摘要:[ ] 里的可以省略,... 表示可以写多个
DESCRIPTION详细说明,很多命令的选项也列在这里
OPTIONS选项说明,有的命令单独成节
EXAMPLES示例,不是每篇都有
SEE ALSO相关的命令和文档

其他求助方式

写法用途
命令 --help简短的用法说明,比 man 快
help 命令查看 shell 内建命令(如 cd、export)的帮助,它们没有单独的手册
type 命令说明这是内建命令、别名还是外部程序
which 命令显示将要执行的程序在哪里
whatis 命令一句话介绍,也就是手册的 NAME 部分
tldr 命令tldrToo Long; Didn't Read,太长不看,社区整理的常用示例,需要另外安装
$ ls --help | less               # 帮助太长时交给 less 分页
$ type cd
cd is a shell builtin
$ which python3
/usr/bin/python3
$ whatis ls
ls (1)               - list directory contents

终端快捷键

这些快捷键在 bash 和 zsh 里都能用,熟练后能省很多敲键盘的时间。

按键作用
Tab自动补全命令和路径;按两次列出所有候选
↑ / ↓翻看上一条 / 下一条历史命令
Ctrl+C中断正在运行的命令(发送 SIGINT 信号,见 kill)
Ctrl+Z暂停(挂起)当前命令,它会停住不动,之后用 fg 回到前台或用 bg 在后台继续(见 前后台任务)
Ctrl+D发送 EOFEnd Of File,输入结束;在空行按会退出当前 shell 或 ssh 会话
Ctrl+R反向搜索(reverse search)历史命令,输入几个字就能找到以前敲过的命令,再按一次继续往前找
Ctrl+L清屏,同 clear
Ctrl+A / Ctrl+E光标跳到行首 / 行尾
Ctrl+U / Ctrl+K删除光标前 / 光标后的全部内容(zsh 里 Ctrl+U 默认删除整行)
Ctrl+W删除光标前的一个单词
Alt+.插入上一条命令的最后一个参数,可连按往前翻。macOS 终端需在设置里勾选“将 Option 键用作 Meta 键”,或者先按 Esc 再按 .
Ctrl+S / Ctrl+Q暂停 / 恢复终端输出。误按 Ctrl+S 时终端看起来像卡死了,按 Ctrl+Q 就好
reset终端显示乱码(比如 cat 了一个二进制文件)时,输入它重置终端

文件与目录Files

pwd

print working directoryman 1

显示当前所在目录的完整路径

$ pwd
/home/alice/projects
$ pwd -P                         # -P = physical,如果经过了符号链接,显示真实路径
$ realpath notes.txt             # 顺带一提:查看某个文件的完整绝对路径
/home/alice/projects/notes.txt

cd

change directory内建

切换当前目录

$ cd /etc/nginx                  # 绝对路径
$ cd sites-available             # 相对路径:进入当前目录下的子目录
$ cd ..                          # 回到上一级
$ cd                             # 不带参数:回到家目录,等于 cd ~
$ cd -                           # 回到上一次所在的目录
$ cd "My Documents"              # 目录名有空格要加引号

ls

listman 1

列出目录中的文件

ls [选项] [路径…]

选项含义
-llong长格式:权限、所有者、大小、修改时间
-aall显示全部,包括以 . 开头的隐藏文件
-Aalmost-all同 -a,但不显示 . 和 ..
-hhuman-readable大小用 K、M、G 显示,配合 -l 用
-ttime按修改时间排序,最新的在前
-Ssize按文件大小排序,最大的在前
-rreverse倒序
-Rrecursive递归列出所有子目录
-ddirectory只显示目录本身,不展开里面的内容
-1每行一个文件名,方便交给其他命令处理
$ ls -lh
total 12K
drwxr-xr-x 2 alice alice 4.0K Sep 20 10:12 docs
-rw-r--r-- 1 alice alice 1.2K Sep 21 09:30 notes.txt
lrwxrwxrwx 1 alice alice    9 Sep 21 09:31 latest -> notes.txt
$ ls -la ~                       # 家目录里的所有文件,含隐藏文件
$ ls -t | head                   # 最近修改的 10 个(用 -l 时第一行是 total,只剩 9 个)
$ ls -lhS                        # 按大小排
$ ls -ld /var/log                # 看目录本身的权限,而不是里面的文件
$ ls *.md                        # 只列 .md 文件

ls -l 每一列依次是:权限(首字母 d 是目录、- 是文件、l 是链接,详见 读懂权限)、硬链接数、所有者、所属组、大小、修改时间、文件名。

提示:很多系统预设了别名 ll(通常等于 ls -alF)。没有的话可以自己加,见 alias。

tree

treeman 1

以树状图显示目录结构

系统一般没预装,用 sudo apt install tree 安装。

选项含义
-L 2level只显示 2 层
-ddirectories只显示目录
-aall包括隐藏文件
-I 模式ignore忽略匹配的文件,多个用 | 分隔
$ tree -L 2 -I "node_modules|.git"
.
├── README.md
├── src
│   ├── main.py
│   └── utils.py
└── tests
    └── test_main.py

2 directories, 4 files

mkdir

make directoryman 1

创建目录

选项含义
-pparents自动创建缺少的上级目录;目录已存在也不报错
-vverbose显示创建了什么(很多命令的 -v 都是这个意思:啰嗦模式)
-m 700mode创建时直接设定权限
$ mkdir backup
$ mkdir -p project/src/utils     # 一次建好多层
$ mkdir -p app/{src,tests,docs}  # 大括号展开:同时建 3 个子目录
$ mkdir -m 700 private           # 只有自己能访问

touch

touchman 1

“碰一下”文件:更新时间戳,文件不存在就新建一个空文件

$ touch notes.txt                # 新建空文件(已存在则只更新修改时间)
$ touch a.txt b.txt c.txt        # 一次建多个
$ touch -d "2026-01-01 10:00" old.txt   # -d = date,把时间改成指定值
$ touch -c maybe.txt             # -c = no-create,文件不存在时不新建

cp

copyman 1

复制文件或目录

cp [选项] 源… 目标

选项含义
-rrecursive递归复制,复制目录时必须加
-aarchive归档模式:递归,并保留权限、时间戳和符号链接(保留所有者需要 root)。做备份时用它
-iinteractive目标已存在时先询问
-nno-clobber目标已存在就跳过,不覆盖
-uupdate只在源文件比目标新、或目标不存在时才复制
-vverbose显示复制过程
$ cp notes.txt notes.bak         # 复制一份
$ cp notes.txt ~/backup/         # 复制到目录里,文件名不变
$ cp -r project/ ~/backup/       # 复制整个目录
$ cp -a /etc/nginx ~/nginx-bak   # 完整备份配置目录,保留权限
$ cp *.jpg photos/               # 复制所有 jpg
$ cp config.yml{,.bak}           # 大括号展开,等于 cp config.yml config.yml.bak
注意:cp 默认会直接覆盖同名目标文件,不提示。拿不准时加 -i。

mv

moveman 1

移动文件,也用来重命名

Linux 里没有单独的“重命名”命令,在同一目录下 mv 就是改名。移动目录不需要 -r。

$ mv draft.md final.md           # 重命名
$ mv final.md ~/docs/            # 移动到另一个目录
$ mv *.log logs/                 # 批量移动
$ mv -i a.txt b.txt              # -i:b.txt 已存在时先问
$ mv -n a.txt b.txt              # -n:已存在就不动
提示:批量重命名(比如把所有 .txt 改成 .md)见 常用组合。

rm

removeman 1

删除文件或目录

选项含义
-rrecursive递归删除目录及其中所有内容
-fforce强制删除,不询问,文件不存在也不报错
-iinteractive每删一个都询问
-I删除超过 3 个文件或递归删除时,只询问一次
-vverbose显示删了什么
$ rm old.txt
$ rm -i *.log                    # 逐个确认
$ rm -r build/                   # 删除目录
$ rm -rf node_modules/           # 强制删除整个目录,不提示
$ rm -- -weird-name.txt          # 删除以 - 开头的文件
注意:命令行里删除的文件不进回收站,基本无法恢复。用 rm -rf 前先用 ls 看一眼要删的路径;尤其小心多打的空格,rm -rf / tmp/x 会从根目录开始删。在脚本里写 rm -rf "$DIR/" 时,要确保变量不是空的。

rmdir

remove directoryman 1

删除空目录

只能删除空目录,目录里有东西会报错,所以比 rm -r 安全。

$ rmdir empty-dir
$ rmdir -p a/b/c                 # 依次删除 c、b、a(都必须是空的)

ln

linkman 1

创建链接(快捷方式)

Linux 中每个文件的元数据都存在一个 inodeindex node,索引节点:记录文件大小、权限、数据位置的结构,文件名只是指向它的标签 里。

  • 符号链接(软链接,ln -s):一个指向路径的“快捷方式”。可以指向目录、可以跨分区;原文件删了,链接就失效。日常用的基本都是这种。
  • 硬链接(不带 -s):给同一个 inode 再起一个名字,和原文件完全平等;删除其中一个,另一个照样能用。不能用于目录,不能跨分区。

ln -s 目标(真实文件) 链接名

$ ln -s /opt/app/v2.1 /opt/app/current   # current 指向 v2.1
$ ls -l /opt/app/current
lrwxrwxrwx 1 root root 13 Sep 26 10:00 /opt/app/current -> /opt/app/v2.1
$ ln -sfn /opt/app/v2.2 /opt/app/current # 改为指向新版本(-f 覆盖旧链接;-n 防止在旧链接指向的目录里再建一个)
$ ln -s ~/dotfiles/.vimrc ~/.vimrc       # 常见用法:集中管理配置文件
$ readlink -f /opt/app/current           # 查看链接最终指向哪里
/opt/app/v2.2
提示:参数顺序和 cp 一样:先写“已存在的东西”,再写“要新建的东西”。

stat

statusman 1

查看文件的详细信息

$ stat notes.txt
  File: notes.txt
  Size: 1234        Blocks: 8          IO Block: 4096   regular file
Device: 8,1         Inode: 524301      Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/   alice)   Gid: ( 1000/   alice)
Access: 2026-09-26 09:30:12.000000000 +0800
Modify: 2026-09-21 09:30:00.000000000 +0800
Change: 2026-09-21 09:30:00.000000000 +0800
$ stat -c '%s %n' *.log          # 自定义格式:只看大小和文件名

三个时间:Access(atime,最后读取时间)、Modify(mtime,内容最后修改时间)、Change(ctime,权限、所有者等元数据最后变更时间)。ls -l 和 find -mtime 用的都是 mtime。

file

fileman 1

根据内容判断文件类型

Linux 不靠扩展名判断文件类型。拿到一个不知道是什么的文件时,先用 file 看一下。

$ file notes.txt photo.jpg /usr/bin/ls data
notes.txt:   Unicode text, UTF-8 text
photo.jpg:   JPEG image data, JFIF standard 1.01
/usr/bin/ls: ELF 64-bit LSB pie executable, x86-64
data:        gzip compressed data

输出里的 ELFExecutable and Linkable Format,可执行与可链接格式 是 Linux 可执行程序的格式,相当于 Windows 的 .exe。

查看文件内容Viewing

cat

concatenateman 1

连接文件并输出,最常用来一次性显示短文件

选项含义
-nnumber显示行号
-Ashow-all显示不可见字符:行尾显示 $,Tab 显示 ^I,Windows 换行多出的 \r 显示为 ^M
-ssqueeze-blank连续多个空行只显示一个
$ cat /etc/hostname
$ cat -n script.sh               # 带行号
$ cat part1.txt part2.txt > all.txt   # 合并文件(这才是 concatenate 的本意)
$ cat -A config.ini | head       # 排查“看起来一样但就是不对”的配置文件
$ tac log.txt                    # tac 是 cat 倒过来写:从最后一行往前显示
提示:长文件别用 cat,会刷满整个屏幕,用 less 或 head / tail。

less

lessman 1

分页查看长文件

名字是个玩笑:早期有个分页工具叫 more,less 功能更多,取“less is more(少即是多)”之意。它不会一次把整个文件读进内存,打开几个 GB 的日志也很快。

按键作用
空格 / b向下 / 向上翻一页
j / k 或方向键向下 / 向上一行
g / G跳到开头 / 结尾
/关键词向下搜索;?关键词 向上搜索
n / N下一个 / 上一个搜索结果
&关键词只显示包含关键词的行(再输入 & 回车取消)
F跟随模式,像 tail -f 一样持续显示新内容,Ctrl+C 退出跟随
q退出
$ less /var/log/syslog
$ less -N app.py                 # -N 显示行号
$ less -S data.csv               # -S 长行不折行,用左右方向键横向滚动
$ less +F app.log                # 打开就进入跟随模式
$ journalctl -u nginx | less     # 分页查看其他命令的输出

head / tail

head · tailman 1

显示文件开头 / 结尾的若干行

选项含义
-n 20lines显示 20 行(默认 10 行),也可以简写成 -20
-c 100bytes按字节数截取
tail -ffollow持续输出新增内容,看实时日志必备,Ctrl+C 退出
tail -F同 -f,但日志文件被轮换(改名、重建)后会自动跟上新文件
tail -n +2从第 2 行开始显示到末尾(常用于跳过表头)
$ head data.csv                  # 前 10 行
$ head -n 3 data.csv             # 前 3 行
$ tail -n 50 app.log             # 最后 50 行
$ tail -f /var/log/nginx/access.log   # 实时查看新请求
$ tail -n +2 data.csv            # 去掉第一行表头
$ sed -n '100,120p' big.log      # 想看中间某几行,用 sed 更方便

wc

word countman 1

统计行数、单词数、字节数

$ wc notes.txt
  42  310 2048 notes.txt         # 行数 单词数 字节数
$ wc -l access.log               # -l = lines,只看行数
$ wc -m notes.txt                # -m = chars,字符数(UTF-8 环境下中文按字算)
$ ls | wc -l                     # 当前目录有多少个文件

diff

differenceman 1

比较两个文件的差异

选项含义
-uunified统一格式,和 git diff 一样,最好读
-rrecursive比较两个目录
-qbrief只说哪些文件不同,不显示具体内容
-yside-by-side左右并排显示
-wignore-all-space忽略空白字符的差异
$ diff -u old.conf new.conf
--- old.conf    2026-09-20 10:00:00
+++ new.conf    2026-09-26 10:00:00
@@ -1,3 +1,3 @@
 port=80
-debug=true
+debug=false
 name=web
$ diff -rq dir1/ dir2/           # 两个目录里哪些文件不一样
$ diff <(sort a.txt) <(sort b.txt)   # <(命令) 把命令输出当作临时文件:比较排序后的结果

读法:- 开头的行是旧文件有、新文件删掉的;+ 开头是新增的;@@ -1,3 +1,3 @@ 表示这段在旧文件从第 1 行起 3 行、新文件从第 1 行起 3 行。

sha256sum / md5sum

checksumman 1

计算文件校验值,确认文件完整、没被篡改

SHA-256Secure Hash Algorithm 256-bit,安全散列算法,输出 256 位 和 MD5Message-Digest Algorithm 5,消息摘要算法第 5 版 都会把任意文件算成一串固定长度的“指纹”,文件哪怕改动一个字节,指纹都会完全不同。MD5 已不够安全,校验下载文件时优先用 SHA-256。

$ sha256sum ubuntu-24.04.iso
8d2d1a...e0f3  ubuntu-24.04.iso
$ sha256sum -c SHA256SUMS        # -c = check,按官方给的校验文件逐个核对
ubuntu-24.04.iso: OK
$ md5sum a.bin b.bin             # 快速判断两个文件内容是否相同
macOS:用 shasum -a 256 文件。

文本处理Text

这些命令单独用都很简单,威力在于用管道 | 串起来:一个命令的输出作为下一个命令的输入。管道的详细说明见 管道。

echo / printf

echo · print formatted内建

输出文字;printf 可以按格式输出

$ echo "Hello, $USER"
Hello, alice
$ echo -e "第一行\n第二行"        # -e 解释 \n(换行)、\t(Tab)等转义
第一行
第二行
$ echo -n "不换行"               # -n 末尾不加换行
$ echo "export EDITOR=vim" >> ~/.bashrc   # 往文件末尾追加一行
$ printf "%-8s %5d\n" apple 3 banana 12   # %-8s 左对齐占 8 格,%5d 右对齐占 5 格的整数
apple        3
banana      12

sort

sortman 1

对行排序

选项含义
-nnumeric按数值排序。不加的话按字符排,10 会排在 9 前面
-rreverse倒序
-hhuman-numeric能正确比较 2K、1M、3G 这类大小
-k 2,2key按第 2 列排序(只写 -k 2 表示从第 2 列到行尾)
-t ,field-separator指定列分隔符,默认是空白
-uunique排序并去重
-Vversion-sort按版本号排序,1.10 排在 1.9 后面
$ sort names.txt
$ sort -n scores.txt              # 数值从小到大
$ sort -rn scores.txt             # 从大到小
$ sort -t, -k3,3n data.csv        # CSV 按第 3 列数值排序
$ du -sh * | sort -rh             # 目录大小从大到小
$ sort -u emails.txt > uniq.txt   # 去重后保存

uniq

uniqueman 1

去除或统计重复行

uniq 只处理相邻的重复行,所以几乎总是先 sort 再 uniq。

选项含义
-ccount每行前面加上出现次数
-drepeated只显示有重复的行
-uunique只显示没有重复的行
-iignore-case忽略大小写
$ sort visitors.txt | uniq -c | sort -rn | head   # 出现次数最多的前 10 项(经典组合)
     42 192.0.2.10
     17 198.51.100.7
      9 203.0.113.5
$ sort list.txt | uniq -d                         # 找出重复项

cut

cutman 1

按分隔符或字符位置截取每行的一部分

$ cut -d: -f1 /etc/passwd         # -d 分隔符(delimiter)为 :,-f 取第 1 列(field):所有用户名
$ cut -d, -f1,3 data.csv          # 第 1 和第 3 列
$ cut -d, -f2- data.csv           # 第 2 列到最后
$ cut -c1-8 ids.txt               # -c 按字符(character):每行前 8 个字符
提示:cut 只认单个分隔字符。遇到用多个空格对齐的输出(如 ps、df),用 awk 更合适。

tr

translateman 1

逐字符替换或删除

$ echo "hello" | tr a-z A-Z        # 转大写
HELLO
$ echo "a,b,c" | tr ',' '\n'       # 逗号换成换行
a
b
c
$ tr -d '\r' < win.txt > unix.txt  # -d = delete:删掉 Windows 换行多出的 \r
$ echo "a     b" | tr -s ' '       # -s = squeeze:连续空格压成一个
a b

Windows 的换行是 \r\n,Linux 只用 \n。\r 是 CRCarriage Return,回车,从 Windows 拷来的脚本运行报 $'\r': command not found 就是它造成的。

sed

stream editorman 1

流编辑器:按规则批量修改文本

sed 逐行读取输入,按命令处理后输出。默认只把结果打印到屏幕,不改原文件;加 -i 才会直接修改。

写法含义
s/旧/新/替换(substitute),每行只换第一个
s/旧/新/gg = global,每行全部替换
-iin-place直接修改文件;-i.bak 修改前先备份
-n '10,20p'quiet + print只打印第 10 到 20 行
'/模式/d'删除(delete)匹配的行
-E使用扩展正则,分组可以用 \1 \2 引用
$ sed 's/http:/https:/g' links.txt        # 预览替换结果
$ sed -i 's/http:/https:/g' links.txt     # 确认没问题后直接改文件
$ sed -i.bak 's/8080/9090/' app.conf      # 改之前自动备份为 app.conf.bak
$ sed -n '100,120p' big.log               # 只看第 100~120 行
$ sed '/^#/d' nginx.conf                  # 删除注释行
$ sed '/^$/d' file.txt                    # 删除空行
$ sed -i '3d' file.txt                    # 删除第 3 行
$ sed 's#/usr/local#/opt#g' paths.txt     # 分隔符可以换成 #,替换路径时不用转义 /
$ sed -E 's/([0-9]{4})-([0-9]{2})/\2\/\1/' dates.txt   # 2026-09 改成 09/2026
macOS:BSD 版的 sed 使用 -i 时必须带一个参数,不要备份就写 sed -i '' 's/a/b/' file。

awk

Aho, Weinberger, Kernighanman 1

按列处理文本的小语言,名字取自三位作者姓氏首字母

awk 把每行按空白(或指定的分隔符)切成若干列,$1 是第一列,$0 是整行。常用内置变量:NRNumber of Records,当前行号、NFNumber of Fields,当前行的列数,$NF 就是最后一列。

awk [-F 分隔符] '条件 { 动作 }' 文件

$ awk '{print $1, $3}' data.txt               # 打印第 1、3 列
$ awk -F: '{print $1, $7}' /etc/passwd        # 以 : 分隔:用户名和默认 shell
$ awk '$3 > 100' data.txt                     # 只打印第 3 列大于 100 的行
$ awk '/error/ {print NR": "$0}' app.log      # 包含 error 的行,前面加行号
$ awk '{sum += $2} END {print sum}' sales.txt # 第 2 列求和
$ awk '{sum += $2} END {print sum/NR}' sales.txt   # 求平均值
$ df -h | awk 'NR > 1 {print $5, $6}'         # 跳过表头,只要使用率和挂载点
$ awk '{print $NF}' data.txt                  # 每行的最后一列
$ awk -F, 'NR > 1 {c[$2]++} END {for (k in c) print k, c[k]}' orders.csv   # 按第 2 列分组计数
提示:awk 的程序一定要用单引号括起来,否则 $1 会被 shell 当成变量先替换掉。

tee

teeman 1

把输入同时输出到屏幕和文件

名字来自水管的 T 形三通接头:数据从一头进来,分两路流出。

$ ./build.sh 2>&1 | tee build.log         # 边看输出边保存日志
$ ls -l | tee list.txt | wc -l            # 保存一份,同时继续往下传
$ echo "127.0.0.1 dev.local" | sudo tee -a /etc/hosts   # -a = append,追加到需要 root 权限的文件
为什么不用 sudo echo ... >> /etc/hosts?重定向 >> 是由当前 shell(普通用户)执行的,sudo 只作用于 echo,所以会提示 Permission denied。换成 sudo tee -a 就是由 root 身份写文件。

column

columnman 1

把文本排成整齐的表格

$ column -t -s, data.csv | less -S    # -s 指定分隔符,-t 按表格对齐:在终端里看 CSV
$ mount | column -t                   # 把杂乱的输出对齐

jq

JSON queryman 1

在命令行里格式化、筛选 JSON

JSONJavaScript Object Notation,JavaScript 对象表示法 是接口最常用的数据格式。jq 需要安装:sudo apt install jq。

$ jq . data.json                         # 格式化(美化缩进和颜色)
$ jq '.name' data.json                   # 取字段
$ jq -r '.name' data.json                # -r = raw,输出不带引号的原始字符串
$ jq '.items[].id' data.json             # 取数组里每个元素的 id
$ jq '.items[] | select(.price > 100)' data.json   # 过滤
$ curl -s https://api.github.com/repos/jqlang/jq | jq '.stargazers_count'

管道、重定向与展开Shell

这一节不是某个命令,而是 shell 本身的语法。它们决定了命令的输出去哪、输入从哪来、多个命令怎么组合。

重定向

每个程序启动时都打开了三个“数据通道”,用编号表示,叫 文件描述符File Descriptor,简称 FD:

编号名称默认连接到
0stdinstandard input,标准输入键盘
1stdoutstandard output,标准输出屏幕,正常的输出结果
2stderrstandard error,标准错误屏幕,错误和警告信息

重定向就是把这些通道改接到文件上:

写法作用
命令 > 文件标准输出写入文件,覆盖原内容
命令 >> 文件标准输出追加到文件末尾
命令 2> 文件只把错误信息写入文件
命令 > 文件 2>&1输出和错误都写入同一个文件。2>&1 意思是“2 号通道接到 1 号现在接的地方”
命令 &> 文件bash 的简写,效果同上
命令 < 文件从文件读取标准输入
> /dev/null/dev/null 是个“黑洞”设备,写进去的东西全被丢弃
$ ls > files.txt                          # 覆盖写入
$ date >> run.log                         # 追加一行
$ find / -name "*.conf" 2> /dev/null      # 丢掉 Permission denied 之类的报错
$ ./backup.sh > backup.log 2>&1           # 所有输出都进日志
$ ./noisy.sh > /dev/null 2>&1             # 什么都不显示
$ mysql mydb < dump.sql                   # 把文件内容作为输入
$ : > app.log                             # 清空文件内容,文件本身保留(: 是空命令,bash 和 zsh 通用)
注意顺序:命令 > 文件 2>&1 是对的;命令 2>&1 > 文件 只会把正常输出写进文件,错误仍然打印在屏幕上,因为 2 号在 1 号改接之前就已经接到屏幕了。

Here document:多行输入

<<EOF 把接下来直到 EOF 的几行当作输入,常用于在脚本里生成配置文件。EOF 只是约定俗成的结束标记,换成别的词也行。

$ cat > hello.txt <<EOF
> 你好,$USER
> 今天是 $(date +%F)
> EOF
$ cat hello.txt
你好,alice
今天是 2026-09-26

开头的 > 是 shell 在等你继续输入时显示的提示符,不用输入。如果不想让 $USER 被替换,写成 <<'EOF'。

管道

命令1 | 命令2 把命令 1 的标准输出直接接到命令 2 的标准输入,中间不需要临时文件。可以一直串下去,每一步只做一件事。

$ ls /usr/bin | wc -l                     # /usr/bin 下有多少个程序
$ history | grep ssh                      # 以前执行过哪些 ssh 命令
$ ps aux | grep python                    # 找 python 进程
$ cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -5
    312 192.0.2.10
    188 198.51.100.7

最后一条的读法:取出每行第一列(IP)→ 排序 → 合并相同的并计数 → 按次数从大到小排 → 取前 5。

提示:管道只传递标准输出。想把错误信息也传过去,写 命令 2>&1 | 下一个命令。

组合多条命令

每条命令结束时都会返回一个 退出码exit status:0 表示成功,非 0 表示失败。$? 保存着上一条命令的退出码。

写法含义
A ; B先执行 A,再执行 B,不管 A 成功与否
A && BA 成功了才执行 B
A || BA 失败了才执行 B。注意 A && B || C 在 B 失败时也会执行 C,不完全等于“如果…否则…”
A &A 放到后台运行,立即返回提示符
( A; B )在子 shell 里执行,里面的 cd、变量不会影响当前 shell
$ mkdir build && cd build                    # 建成功了才进入
$ sudo apt update && sudo apt upgrade -y     # 更新索引成功后再升级
$ ping -c1 example.com > /dev/null && echo 通 || echo 不通
$ grep -q root /etc/passwd; echo $?         # -q 不输出,只看退出码
0
$ (cd /var/www && tar -czf ~/site.tgz html) # 执行完仍停留在原来的目录

引号、变量与通配符

shell 在执行命令之前,会先把命令里的一些特殊写法“展开”成实际内容。引号用来控制要不要展开。

写法含义
'单引号'原样保留,里面的 $、* 等都不展开
"双引号"保留空格,但 $变量 和 $(命令) 仍会展开
\转义下一个字符;放在行尾表示命令在下一行继续
$VAR / ${VAR}取变量的值;后面紧跟字母时用大括号,如 ${name}_bak
$(命令)命令替换:用命令的输出替换这里。旧写法是反引号 `命令`
*任意长度的任意字符(不匹配以 . 开头的隐藏文件)
?任意一个字符
[0-9]方括号里的任意一个字符
{a,b,c}大括号展开,生成多个词
{1..5}生成序列 1 2 3 4 5;{a..e} 也可以

*、?、[] 这类文件名通配叫 globglobal 的缩写,得名于早期 Unix 里负责展开通配符的 glob 程序。

$ name="Linux"                            # 变量赋值,等号两边不能有空格
$ echo 'Hello $name'
Hello $name
$ echo "Hello $name"
Hello Linux
$ echo "今天是 $(date +%F),当前目录有 $(ls | wc -l) 个文件"
今天是 2026-09-26,当前目录有 12 个文件
$ ls *.txt                                # 所有 .txt
$ ls log-2026-0?.txt                      # log-2026-01.txt 到 log-2026-09.txt
$ echo {1..5}
1 2 3 4 5
$ touch day{01..03}.md                    # 生成 day01.md day02.md day03.md
$ backup_dir="/backup/$(date +%Y%m%d)"    # 用命令结果拼路径
提示:用变量表示文件名时,养成加双引号的习惯:rm "$file"。不加的话,文件名里有空格就会被拆成两个参数。

权限与用户Permissions

读懂权限

ls -l 输出开头的 10 个字符描述了文件类型和权限,例如 -rwxr-xr--:

-类型
rwx所有者 u
r-x所属组 g
r--其他人 o

第 1 位是类型:- 普通文件,d 目录,l 符号链接。后面每 3 位一组,分别对应 u(user,文件所有者)、g(group,所属组的成员)、o(others,其他所有人)。每组的三位固定是 r、w、x,没有该权限就显示 -。

权限数值对文件对目录
r read4读取内容列出目录里的文件名
w write2修改内容在目录里新建、删除、重命名文件
x execute1作为程序执行进入目录(cd)

数字写法是把每组的数值相加:rwx = 4+2+1 = 7,r-x = 4+1 = 5,r-- = 4。所以 rwxr-xr-- 就是 754。常见组合:

数字字符典型用途
755rwxr-xr-x目录、可执行脚本:自己可改,别人可读可执行
644rw-r--r--普通文件:自己可改,别人只读
700rwx------私人目录,如 ~/.ssh
600rw-------私密文件,如 SSH 私钥、含密码的配置
777rwxrwxrwx所有人可读写执行,几乎永远不该用
umask:新建文件的默认权限由 umaskuser file-creation mode mask,用户文件创建权限掩码 决定。常见值 022 表示从默认权限里去掉组和其他人的写权限,所以新文件是 644、新目录是 755。输入 umask 可以查看当前值。
特殊权限:偶尔会在 x 的位置看到 s 或 t。s 是 SUIDSet User ID,运行时以文件所有者的身份执行,比如 /usr/bin/passwd 靠它让普通用户能修改密码文件;t 是粘滞位(sticky bit),出现在 /tmp 上,表示目录里的文件只有其所有者能删除。

chmod

change modeman 1

修改文件权限

两种写法:数字(一次设定全部权限)和符号(在现有权限上增减)。符号写法是 谁 操作 权限:谁用 u g o a(a = all,所有人),操作用 + 添加、- 去掉、= 设为。

$ chmod +x deploy.sh                 # 加执行权限,之后就能 ./deploy.sh 运行
$ chmod 755 deploy.sh                # rwxr-xr-x
$ chmod 644 index.html               # rw-r--r--
$ chmod 600 ~/.ssh/id_ed25519        # 私钥只能自己读写,权限太宽 ssh 会拒绝使用
$ chmod u+x,go-w script.sh           # 所有者加执行;组和其他人去掉写
$ chmod -R 755 public/               # -R = recursive,递归修改整个目录
$ chmod -R u=rwX,go=rX public/       # 大写 X:只给目录(和本来就可执行的文件)加 x
提示:chmod -R 755 会让目录里所有普通文件也变成可执行。想要“目录 755、文件 644”,用最后一条大写 X 的写法。

chown / chgrp

change owner · change groupman 1

修改文件的所有者和所属组

修改所有者需要 root 权限,所以通常要加 sudo。

$ sudo chown alice notes.txt                   # 所有者改为 alice
$ sudo chown alice:dev notes.txt               # 同时改所有者和组,格式 用户:组
$ sudo chown -R www-data:www-data /var/www/site   # 递归:网站文件交给 web 服务器用户
$ sudo chown :dev report.pdf                   # 只改组
$ sudo chgrp dev report.pdf                    # 效果同上

sudo

superuser doman 8

以超级用户(root)或其他用户的身份执行命令

日常用普通用户登录,只在需要时用 sudo 临时获得 root 权限,这比一直用 root 安全。第一次使用会要求输入你自己的密码(不是 root 密码),之后一段时间内(默认 15 分钟)不用重复输入。能不能用 sudo 由 /etc/sudoers 决定;Ubuntu 上 sudo 组的成员都可以用,CentOS/Fedora 上是 wheel 组。

写法含义
sudo 命令以 root 身份执行一条命令
sudo !!用 sudo 重新执行上一条命令(忘加 sudo 时最常用)
sudo -i切换成 root 的登录 shell,exit 返回
sudo -u bob 命令以 bob 的身份执行
sudo -l列出(list)自己被允许执行哪些命令
sudo visudo安全地编辑 sudoers 文件,保存前会检查语法,避免把自己锁在外面
$ apt install nginx
E: Could not open lock file ... (13: Permission denied)
$ sudo !!
sudo apt install nginx
[sudo] password for alice:
$ sudo -u postgres psql                   # 以 postgres 用户身份打开数据库
提示:输入密码时屏幕上不会显示任何字符,连星号也没有,这是正常的,输完直接回车。

su

substitute userman 1

切换到另一个用户(也常被解释为 switch user)

和 sudo 不同,su 需要输入目标用户的密码。Ubuntu 默认没给 root 设密码,所以 su - 用不了,用 sudo -i 代替。

$ su - bob                        # 切换成 bob,"-" 表示同时加载 bob 的环境变量和家目录
$ su -                            # 切换成 root
$ sudo su - bob                   # 不知道 bob 的密码时,借 sudo 切换
$ exit                            # 回到原来的用户

whoami / id / who

who am I · identity · whoman 1

查看当前用户身份和登录情况

$ whoami                          # 我现在是谁
alice
$ id                              # 用户编号、组编号、所在的所有组
uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),998(docker)
$ id bob                          # 查看别的用户
$ groups                          # 只看自己在哪些组
$ who                             # 当前有谁登录着
$ w                               # 同上,并显示每人在做什么和系统负载
$ last | head                     # 最近的登录记录

UIDUser ID,用户编号 和 GIDGroup ID,组编号 才是系统真正识别用户和组的方式,名字只是方便人看。root 的 UID 是 0,普通用户一般从 1000 开始。

useradd / usermod / passwd

add · modify user · passwordman 8

创建和管理用户

用户信息存在 /etc/passwd,加密后的密码在 /etc/shadow,组信息在 /etc/group。Debian/Ubuntu 另有更友好的 adduser,会一步步提示你填写。

命令作用
useradd -m -s /bin/bash bob新建用户;-m 同时创建家目录,-s 指定登录 shell
adduser bobDebian/Ubuntu 交互式创建,自动建家目录、设密码
passwd bob设置或修改 bob 的密码;不带用户名时修改自己的
usermod -aG sudo bob把 bob 加入 sudo 组;-a = append,-G = groups
usermod -s /bin/zsh bob修改默认 shell
userdel -r bob删除用户,-r 连家目录一起删
groupadd dev新建组
$ sudo useradd -m -s /bin/bash bob
$ sudo passwd bob
$ sudo usermod -aG docker $USER           # 让自己不加 sudo 也能用 docker
$ newgrp docker                           # 只在新开的这个 shell 里生效;想处处生效要重新登录
$ getent passwd bob                       # 查看用户信息
bob:x:1001:1001::/home/bob:/bin/bash
注意:usermod -G 不加 -a 会把用户移出所有没列出的组。给自己用时如果漏了 -a,可能会失去 sudo 权限。

进程与任务Processes

每个正在运行的程序都是一个进程,有一个唯一的编号 PIDProcess ID,进程编号。启动它的进程叫父进程,编号记作 PPIDParent Process ID,父进程编号。

ps

process statusman 1

查看进程快照

ps 有两套选项风格,最常用的两种写法效果差不多:ps aux(BSD 风格,不带减号:a = 所有用户,u = 显示用户等详细列,x = 包括没有终端的后台进程)和 ps -ef(标准风格:-e = every 全部,-f = full 完整格式)。

$ ps aux
USER   PID %CPU %MEM    VSZ    RSS TTY   STAT START  TIME COMMAND
root     1  0.0  0.1 167744  11800 ?     Ss   Sep20  0:04 /sbin/init
alice 4127  2.3  1.5 912340 121044 pts/0 Sl+  10:02  0:31 python3 app.py
$ ps aux | grep nginx                     # 找某个程序的进程
$ ps aux --sort=-%mem | head -6           # 内存占用最多的 5 个(- 表示降序)
$ ps aux --sort=-%cpu | head -6           # CPU 占用最多的 5 个
$ ps -ef --forest                         # 以树状显示父子关系(也可以用 pstree)
$ ps -o pid,ppid,%cpu,%mem,cmd -p 4127    # 只看指定进程的指定列
列含义
%CPU / %MEMCPU 和内存占用百分比
VSZVirtual Memory Size,虚拟内存大小(KB),包括没真正用到的,参考意义不大
RSSResident Set Size,常驻内存大小(KB),进程实际占用的物理内存,看这个
TTYTeleTYpewriter(电传打字机),指进程所在的终端;? 表示没有终端(后台服务)
STAT状态:R 运行中,S 休眠,D 不可中断(通常在等磁盘),Z 僵尸进程(已结束但父进程没回收),T 已暂停

top / htop

table of processesman 1

实时查看系统资源和进程

$ top
top - 10:32:05 up 12 days,  3:41,  2 users,  load average: 0.52, 0.38, 0.30
Tasks: 213 total,   1 running, 212 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.1 us,  1.0 sy,  0.0 ni, 95.6 id,  0.3 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7856.4 total,   3264.1 free,   2150.6 used,   2441.7 buff/cache
$ top -u alice                            # 只看某个用户的进程
$ htop                                    # 更好用的版本,需安装:sudo apt install htop

CPU 一行:us 用户程序(user),sy 内核(system),id 空闲(idle),wa 等待磁盘读写(I/O wait,I/O 即 Input/Output 输入输出;这个值持续偏高往往说明磁盘是瓶颈),st 被宿主机占用(steal,只在虚拟机里出现)。load average 的含义见 uptime。

top 里的按键作用
P / M按 CPU / 内存占用排序
1分别显示每个 CPU 核心
c显示完整命令行
k输入 PID 结束进程(kill)
u只看某个用户
q退出

htop 可以用鼠标点、上下键选择进程,底部有功能键提示:F3 搜索、F5 树状视图、F6 选择排序列、F9 结束进程、F10 退出。

kill / pkill / killall

killman 1

向进程发送信号,最常用来结束进程

kill 其实是“发信号”,只是默认发送的信号会让进程结束。kill 按 PID 发送,pkill 按名称匹配,killall 按精确的程序名。

信号编号含义
SIGTERM15terminate,终止。kill 的默认信号,请进程收尾后退出
SIGKILL9kill,强制杀死。进程无法拦截,也来不及保存数据,是最后的手段
SIGINT2interrupt,中断。按 Ctrl+C 就是发这个
SIGHUP1hang up,挂断。终端关闭时发给进程;很多服务收到它会重新加载配置
SIGSTOP / SIGCONT19 / 18stop 暂停 / continue 继续
$ kill 4127                               # 发送 SIGTERM,请它正常退出
$ kill -9 4127                            # 等几秒还没退出,再强制结束
$ kill -HUP 812                           # 让进程重新加载配置
$ pkill -f "python3 app.py"               # -f 按完整命令行匹配
$ pkill -u bob                            # 结束 bob 的所有进程
$ killall nginx                           # 结束所有叫 nginx 的进程
$ kill -l                                 # 列出所有信号

pgrep

process grepman 1

按名称查找进程的 PID

比 ps aux | grep 干净,不会把 grep 自己也列出来。

$ pgrep nginx
812
813
$ pgrep -a python                         # -a 同时显示完整命令行
4127 python3 app.py
$ pgrep -u alice                          # alice 的所有进程

前后台任务 & jobs fg bg

foreground · background

在一个终端里同时管理多个任务

写法作用
命令 &直接在后台启动,终端可以继续用
Ctrl+Z把正在前台运行的命令暂停(挂起),它会停住不动,直到用 fg 或 bg 恢复
jobs列出当前终端的后台任务及编号
fg %1把 1 号任务调回前台(foreground)
bg %1让暂停的 1 号任务在后台(background)继续运行
disown %1让任务脱离当前终端,关掉终端也不会结束
$ sleep 300 &
[1] 5120
$ vim notes.txt                           # 编辑到一半按 Ctrl+Z
[2]+  Stopped                 vim notes.txt
$ jobs
[1]-  Running                 sleep 300 &
[2]+  Stopped                 vim notes.txt
$ fg %2                                   # 回到 vim 继续编辑
提示:后台任务仍属于当前终端,关闭终端或 ssh 断开时通常会被一起结束。需要长期运行的任务用 nohup 或 tmux。

nohup

no hang upman 1

让命令在退出终端后继续运行

关闭终端时,系统会给其中的进程发送 SIGHUP(挂断信号),进程默认就会退出。nohup 让命令忽略这个信号。没有指定输出位置时,输出会写到当前目录的 nohup.out。

$ nohup python3 train.py > train.log 2>&1 &   # 后台运行,输出写进日志
[1] 6231
$ tail -f train.log                           # 之后随时回来看进度
$ pgrep -af train.py                          # 确认它还在跑(-f 按完整命令行匹配,否则只比对进程名 python3)

tmux

terminal multiplexerman 1

终端复用器:会话常驻、分屏、断线不丢

在 tmux 里运行的程序不依赖你的 ssh 连接:网络断了,重新连上后 tmux attach 就能回到原来的界面,程序一直在跑。安装:sudo apt install tmux。tmux 里的快捷键都要先按前缀键 Ctrl+b,松开后再按下一个键。

命令 / 按键作用
tmux new -s work新建名为 work 的会话(session)
Ctrl+b d离开(detach)会话,程序在后台继续运行
tmux ls列出所有会话
tmux attach -t work回到 work 会话;只有一个会话时直接 tmux a
Ctrl+b c新建窗口(create);Ctrl+b n / p 下一个 / 上一个窗口
Ctrl+b %左右分屏
Ctrl+b "上下分屏
Ctrl+b 方向键在分屏之间切换
Ctrl+b [进入滚动模式查看历史输出,q 退出
exit关闭当前窗格;全部关闭后会话结束
$ ssh web
$ tmux new -s train
$ python3 train.py                        # 开始跑,然后按 Ctrl+b d 离开
[detached (from session train)]
$ exit                                    # 断开 ssh 也没关系
# ……第二天……
$ ssh web
$ tmux attach -t train                    # 回到原来的界面

watch

watchman 1

每隔几秒重复执行一条命令,全屏显示结果

$ watch -n 2 free -h                      # -n = interval,每 2 秒刷新
$ watch -d 'ls -l /tmp'                   # -d = differences,高亮变化的地方
$ watch -n 1 'ss -tn | wc -l'             # 含管道时整条命令用引号括起来

按 Ctrl+C 退出。

time

time内建

测量一条命令运行了多久

$ time python3 script.py
real    0m2.314s
user    0m1.980s
sys     0m0.120s

real 是实际经过的时间;user 是程序自身消耗的 CPU 时间;sys 是内核替它干活(读写文件、网络等)的 CPU 时间。real 远大于 user + sys,通常说明大部分时间花在等待磁盘或网络上。

系统信息与环境System

uname

Unix nameman 1

查看内核和系统架构信息

$ uname -a                                # -a = all,全部信息
Linux web01 6.8.0-45-generic #45-Ubuntu SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
$ uname -r                                # -r = kernel release,内核版本
6.8.0-45-generic
$ uname -m                                # -m = machine,CPU 架构
x86_64
$ cat /etc/os-release                     # 发行版名称和版本(比 uname 更常用)
PRETTY_NAME="Ubuntu 24.04.1 LTS"
VERSION_ID="24.04"
$ hostname                                # 主机名
web01
$ hostname -I                             # 本机所有 IP 地址
192.168.1.20

架构 x86_64(也写作 amd64)是常见的 Intel/AMD 64 位;aarch64(也写作 arm64)是 ARM 64 位,如树莓派、苹果 M 系列芯片上的虚拟机。下载软件时要选对应的版本。LTSLong Term Support,长期支持版 表示这个版本会提供多年的安全更新。

uptime

up timeman 1

系统已运行多久,以及平均负载

$ uptime
 10:32:05 up 12 days,  3:41,  2 users,  load average: 0.52, 0.38, 0.30
$ nproc                                   # 有几个 CPU 核心,用来对照负载
4

load average(平均负载)的三个数字分别是过去 1、5、15 分钟内,平均有多少个进程正在运行或排队等待 CPU(Linux 上还包括正在等待磁盘读写的进程)。拿它和核心数比:4 核机器负载在 4 以下算正常,长期明显超过 4 说明忙不过来了。

free

freeman 1

查看内存使用情况

$ free -h                                 # -h = human,用 Gi、Mi 显示
               total        used        free      shared  buff/cache   available
Mem:           7.7Gi       2.1Gi       3.2Gi       120Mi       2.4Gi       5.3Gi
Swap:          2.0Gi          0B       2.0Gi

判断内存够不够,看 available(可用),而不是 free(完全空闲)。Linux 会把暂时没用的内存拿来做磁盘缓存(buff/cache),程序需要时会立刻让出来,所以 free 很小是正常的。

Swap交换空间 是磁盘上充当内存的区域,内存不够时把不常用的数据挪过去。Swap 用量持续增长、系统变卡,说明物理内存确实不够了。

lscpu 等硬件信息

list CPUman 1

查看 CPU 和其他硬件信息

$ lscpu                                   # CPU 型号、核心数、架构、缓存
$ grep "model name" /proc/cpuinfo | head -1
model name : Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz
$ lspci                                   # 显卡、网卡等 PCI 设备
$ lsusb                                   # USB 设备
$ nvidia-smi                              # NVIDIA 显卡状态和显存占用(需装驱动)

date

dateman 1

显示或格式化日期时间

$ date
Sat Sep 26 10:32:05 CST 2026
$ date +"%Y-%m-%d %H:%M:%S"               # 年-月-日 时:分:秒
2026-09-26 10:32:05
$ date +%F                                # 等于 %Y-%m-%d,常用于文件名
2026-09-26
$ date +%s                                # Unix 时间戳
1790389925
$ date -d @1790389925                     # 时间戳转成日期
$ date -d "yesterday" +%F                 # 昨天;也可以写 "3 days ago"、"+1 week"
2026-09-25
$ timedatectl                             # 查看时区和同步状态
$ sudo timedatectl set-timezone Asia/Shanghai

Unix 时间戳是从 1970-01-01 00:00:00 UTCCoordinated Universal Time,协调世界时 起经过的秒数,程序里记录时间常用它。输出里的 CST 在这里指中国标准时间(China Standard Time,UTC+8)。

macOS:-d 不可用,时间戳转日期写 date -r 1790389925。

环境变量 & export

environment variables

影响程序行为的全局设置

普通变量只在当前 shell 里有效;用 export 导出后成为环境变量,由当前 shell 启动的程序也能读到。最重要的环境变量是 PATH:输入命令时,shell 会依次在 PATH 列出的目录里找同名程序。提示“command not found”但程序明明装了,多半是它所在的目录不在 PATH 里。

变量含义
$HOME家目录
$USER当前用户名
$PATH查找命令的目录列表,用冒号分隔
$PWD当前目录
$SHELL登录时的默认 shell(不一定是当前正在用的)
$EDITOR默认编辑器,crontab -e、git commit 等会用它
$LANG语言和字符编码,如 zh_CN.UTF-8
$ echo $PATH
/usr/local/bin:/usr/bin:/bin
$ env | sort                              # 列出所有环境变量
$ export API_URL=http://localhost:8000    # 设置环境变量(只在当前终端有效)
$ export PATH="$HOME/.local/bin:$PATH"    # 把一个目录加到 PATH 最前面
$ unset API_URL                           # 删除变量
$ DEBUG=1 python3 app.py                  # 只对这一条命令设置变量
$ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc   # 写进配置文件,永久生效
$ source ~/.bashrc                        # 让配置文件立即生效,不用重开终端

~/.bashrc(rc = run commands,启动时要执行的命令)是 bash 每次打开终端时读取的配置文件;zsh 对应的是 ~/.zshrc。source 文件 表示在当前 shell 里执行这个文件,也可以简写成 . 文件。

alias

alias内建

给命令起别名

$ alias ll='ls -alF'
$ alias gs='git status'
$ alias ..='cd ..'
$ alias                                   # 列出所有别名
$ unalias gs                              # 删除别名
$ \ls                                     # 前面加 \ 临时绕过别名,执行原始命令

直接在终端里定义的别名,关掉终端就没了。想永久保留,把 alias 那几行写进 ~/.bashrc(zsh 是 ~/.zshrc),再用 source 加载一次。

history

history内建

查看和重用执行过的命令

$ history | tail -5
 1021  cd /var/www
 1022  git pull
 1023  sudo systemctl reload nginx
$ history | grep docker                   # 找以前用过的 docker 命令
$ !1022                                   # 重新执行第 1022 号命令
$ !!                                      # 重新执行上一条
$ vim !$                                  # !$ 是上一条命令的最后一个参数
$ history -c                              # 清空当前会话的历史记录(bash;zsh 没有这个选项)
提示:找历史命令最快的方法是按 Ctrl+R 再输入关键词。在 Ubuntu 的 bash 默认设置下,以空格开头的命令不会被记入历史,输入含密码的命令时可以利用这一点;zsh 需要先在 ~/.zshrc 里加上 setopt HIST_IGNORE_SPACE。

磁盘与存储Storage

df

disk freeman 1

查看各个分区的空间使用情况

$ df -h                                   # -h = human-readable
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   21G   27G  44% /
/dev/sdb1       200G  150G   41G  79% /data
tmpfs           3.9G     0  3.9G   0% /dev/shm
$ df -h /home                             # 某个目录所在分区
$ df -hT                                  # -T 同时显示文件系统类型(ext4、xfs……)
$ df -i                                   # -i 查看 inode 用量
提示:如果 df -i 显示 inode 用到 100%,即使还有空间也无法新建文件,通常是某个目录里有海量小文件(比如没清理的缓存或会话文件)。

du

disk usageman 1

统计目录和文件实际占用的空间

df 告诉你哪个分区满了,du 告诉你是哪个目录占的。

选项含义
-ssummarize只显示总计,不列出每个子目录
-hhuman-readable易读单位
-d 1max-depth只统计到往下 1 层,等于 --max-depth=1
-aall文件也列出来,不只列目录
$ du -sh ~/Downloads                      # 整个目录多大
3.4G    /home/alice/Downloads
$ du -sh * | sort -rh | head              # 当前目录下最大的 10 项
$ sudo du -h -d 1 /var | sort -rh         # /var 下每个子目录的大小
$ du -sh .[!.]* *                         # 把隐藏文件和目录也算上

lsblk

list block devicesman 8

列出磁盘和分区

硬盘、U 盘这类按“块”读写的存储设备叫块设备。lsblk 以树状显示磁盘、分区和它们的挂载位置,插了新硬盘或 U 盘后先用它看设备名。

$ lsblk
NAME    MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda       8:0    0    50G  0 disk
├─sda1    8:1    0  49.9G  0 part /
└─sda15   8:15   0   106M  0 part /boot/efi
sdb       8:16   1  14.9G  0 disk
└─sdb1    8:17   1  14.9G  0 part
nvme0n1 259:0    0 476.9G  0 disk
$ lsblk -f                                # 显示文件系统类型、UUID、剩余空间
$ sudo blkid                              # 各分区的 UUID 和类型

RM 为 1 表示可移动设备(removable),RO 为 1 表示只读(read-only)。设备名:sda、sdb 是按接入顺序命名的第 1、2 块 SATA/USB 磁盘,sda1 是它的第 1 个分区;nvme0n1 是 NVMeNon-Volatile Memory Express,一种高速固态硬盘接口 固态硬盘。

mount / umount

mount · unmountman 8

挂载 / 卸载文件系统

Linux 里的存储设备必须“挂”到目录树的某个目录(挂载点)上才能访问,之后访问这个目录就是在访问那个设备。卸载的命令是 umount,少一个 n。

$ sudo mkdir -p /mnt/usb
$ sudo mount /dev/sdb1 /mnt/usb           # 把 U 盘分区挂到 /mnt/usb
$ findmnt /mnt/usb                        # 确认挂载情况
$ sudo umount /mnt/usb                    # 用完卸载再拔
umount: /mnt/usb: target is busy.
$ sudo lsof +D /mnt/usb                   # 看看是谁还在用(常见原因:终端还 cd 在里面)
$ sudo mount -o loop ubuntu.iso /mnt/iso  # 挂载 ISO 镜像文件(挂载点目录要先建好)
$ sudo mount -o remount,rw /              # 以可读写方式重新挂载根分区(救援时用)
$ mount | column -t                       # 列出所有已挂载的文件系统

开机自动挂载:/etc/fstab

fstabfile systems table,文件系统表 里的每一行描述一个开机时要挂载的设备。建议用 UUIDUniversally Unique Identifier,通用唯一识别码 而不是 /dev/sdb1 指定设备,因为设备名可能随接入顺序改变。

# 设备                                     挂载点  类型  选项              dump  检查顺序
UUID=3f1c9a2e-5b7d-4e21-9c8a-1d2e3f4a5b6c  /data   ext4  defaults,nofail   0     2
注意:fstab 写错可能导致系统无法启动。改完后先执行 sudo mount -a(按 fstab 挂载全部),没有报错再重启。选项里加 nofail 可以避免设备不在时卡住开机。

fdisk / mkfs

fixed disk · make filesystemman 8

分区 / 格式化

新硬盘的使用流程:fdisk 分区 → mkfs 格式化 → mount 挂载。常见文件系统:ext4fourth extended filesystem,第四代扩展文件系统 是 Linux 最常用的;FAT32File Allocation Table 32,32 位文件分配表 和 exFAT 在 Windows、macOS 上也能读写,适合 U 盘。

$ sudo fdisk -l                           # 列出所有磁盘和分区表
$ sudo fdisk /dev/sdb                     # 交互式分区:n 新建,d 删除,p 查看,w 写入保存,q 不保存退出
$ sudo mkfs.ext4 /dev/sdb1                # 格式化为 ext4
$ sudo mkfs.vfat -F 32 /dev/sdb1          # 格式化为 FAT32
注意:分区和格式化会清空数据。执行前用 lsblk 再三确认设备名,把 /dev/sdb 错写成系统盘 /dev/sda 的后果无法挽回。

服务、日志与定时任务Services

在后台长期运行、提供服务的程序叫 daemon守护进程,名字常以 d 结尾,如 sshd、dockerd。现代 Linux 发行版基本都用 systemdsystem daemon,系统守护进程 来启动和管理它们,每个服务由一个 .service 配置文件描述,systemd 称之为一个 unit(单元)。

systemctl

system controlman 1

启动、停止、查看 systemd 管理的服务

子命令作用
status查看状态:是否在运行、是否开机自启、最近几行日志
start / stop启动 / 停止
restart重启(服务会短暂中断)
reload只重新加载配置,不中断服务(服务需支持)
enable / disable设置 / 取消开机自启。enable --now 顺便立即启动
is-active / is-enabled只返回运行状态 / 是否自启,适合在脚本里判断
daemon-reload修改或新增 .service 文件后,让 systemd 重新读取
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-09-26 08:12:40 CST; 2h 19min ago
$ sudo systemctl restart nginx
$ sudo systemctl reload nginx                 # 改了配置后用它
$ sudo systemctl enable --now docker          # 开机自启并立即启动
$ systemctl list-units --type=service --state=running   # 正在运行的所有服务
$ systemctl --failed                          # 启动失败的服务
$ systemctl cat nginx                         # 查看服务的配置文件内容

status 输出里,Loaded 行的 enabled 表示开机自启;Active 行显示 active (running) 为正常运行,failed 为启动失败,这时去看 journalctl 的日志找原因。

journalctl

journal controlman 1

查看 systemd 收集的系统和服务日志

选项含义
-u nginxunit只看某个服务
-ffollow实时跟踪新日志
-n 100lines最近 100 行
--since / --until时间范围,如 "1 hour ago"、"2026-09-25 10:00"、today
-p errpriority只看 err 及更严重的级别(emerg、alert、crit、err、warning、notice、info、debug)
-bboot本次开机以来;-b -1 是上一次开机
--no-pager直接输出,不进入 less 分页
$ journalctl -u nginx -n 50               # 最近 50 行
$ journalctl -u nginx -f                  # 实时跟踪
$ journalctl -u myapp --since "1 hour ago"
$ journalctl -p err -b                    # 本次开机以来的所有错误
$ journalctl -b -1 -n 100                 # 上次开机最后 100 行,排查意外重启
$ journalctl --disk-usage                 # 日志占了多少空间
$ sudo journalctl --vacuum-time=7d        # 只保留最近 7 天的日志
传统日志文件:很多程序仍然把日志写在 /var/log/ 下,例如 /var/log/syslog(系统日志)、/var/log/auth.log(登录和 sudo 记录)、/var/log/nginx/。可以用 less、tail -f、grep 查看。

dmesg

display message / diagnostic messageman 1

查看内核消息

硬件识别、驱动加载、磁盘错误、内存耗尽杀进程等内核层面的事件都记录在这里。

$ sudo dmesg -T | tail -20                # -T 显示人能看懂的时间
$ sudo dmesg -w                           # -w = follow,持续输出新消息,插拔 U 盘时很有用
$ sudo dmesg -T | grep -i "out of memory" # 程序莫名消失时,查是否被 OOM 杀掉

OOMOut Of Memory,内存耗尽:内存不够时,内核的 OOM Killer 会挑一个占内存多的进程强制结束,以保住整个系统。

crontab

cron tableman 1

设置定时任务

cron 是负责按时间执行任务的后台服务,名字来自希腊语 chronos(时间);crontab 是它的任务表,每个用户各有一份。

$ crontab -e                              # 编辑自己的定时任务(第一次会让你选编辑器,新手选 nano)
$ crontab -l                              # 查看
$ sudo crontab -u bob -l                  # 查看别人的
$ crontab -r                              # 删除全部任务,没有确认!和 -e 只差一个键,小心

每行一个任务,前 5 个字段是时间,后面是要执行的命令:

*分
0–59
*时
0–23
*日
1–31
*月
1–12
*星期
0–7

* 表示“每”,, 列举多个值,- 表示范围,*/n 表示每隔 n。星期里 0 和 7 都是周日。

# 分  时  日  月  周   命令
0     3   *   *   *    /home/alice/bin/backup.sh >> /home/alice/backup.log 2>&1
*/5   *   *   *   *    /usr/bin/python3 /home/alice/check.py
30    9   *   *   1-5  /home/alice/bin/report.sh
0     0   1   *   *    /home/alice/bin/monthly.sh
0     */2 *   *   *    /home/alice/bin/sync.sh
@reboot                /home/alice/bin/start-worker.sh

上面依次是:每天 3:00;每 5 分钟;周一到周五 9:30;每月 1 号零点;每 2 小时整点;每次开机时。拿不准时可以到 crontab.guru 把表达式翻译成人话。

cron 任务不执行的常见原因:cron 运行时的环境变量很少(PATH 通常只有 /usr/bin:/bin),所以命令和文件都要写绝对路径;在命令末尾加 >> 日志 2>&1 才能看到报错;命令里的 % 有特殊含义,要写成 \%(比如 date +\%F)。

网络Network

常见缩写:IPInternet Protocol,网际协议;IP 地址就是网络上的门牌号、TCPTransmission Control Protocol,传输控制协议,可靠传输,网页、SSH 都用它、UDPUser Datagram Protocol,用户数据报协议,不保证送达但快,DNS、视频通话常用、DNSDomain Name System,域名系统,把域名翻译成 IP。端口是一台机器上区分不同服务的编号,如 SSH 用 22,HTTP 用 80,HTTPS 用 443。

ip

ipman 8

查看和配置网卡、IP 地址、路由

老教程里的 ifconfig、route 来自已停止维护的 net-tools 包,新系统可能没装,用 ip 替代。

$ ip a                                    # = ip addr show,查看所有网卡和 IP
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 ...
    inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    inet 192.168.1.20/24 brd 192.168.1.255 scope global dynamic eth0
$ ip -br a                                # -br = brief,每个网卡一行
lo      UNKNOWN  127.0.0.1/8
eth0    UP       192.168.1.20/24
$ ip -4 a                                 # 只看 IPv4
$ ip route                                # 路由表,default via 后面是网关
default via 192.168.1.1 dev eth0
$ sudo ip link set eth0 up                # 启用网卡(down 为禁用)

lo 是本机回环网卡,127.0.0.1(也叫 localhost)永远指向自己。/24 是 CIDRClassless Inter-Domain Routing,无类别域间路由 写法,表示前 24 位是网络号,相当于子网掩码 255.255.255.0,即 192.168.1.0~192.168.1.255 属于同一个局域网。MTUMaximum Transmission Unit,最大传输单元 是一个数据包的最大字节数。

ping

pingman 8

测试能否连通某台主机,以及延迟多少

名字取自声呐探测的“砰”声。它发送 ICMPInternet Control Message Protocol,互联网控制报文协议 回显请求,对方回应就说明网络是通的。

$ ping -c 4 example.com                   # -c = count,只发 4 次;不加会一直发,Ctrl+C 停
PING example.com (93.184.215.14) 56(84) bytes of data.
64 bytes from 93.184.215.14: icmp_seq=1 ttl=56 time=12.3 ms
64 bytes from 93.184.215.14: icmp_seq=2 ttl=56 time=12.1 ms
--- example.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
$ ping -i 0.5 192.168.1.1                 # -i = interval,每 0.5 秒一次

主要看 time(往返延迟)和 packet loss(丢包率)。TTLTime To Live,生存时间,每经过一个路由器减 1 用来防止数据包在网络里无限绕圈。注意:有些服务器出于安全考虑禁止响应 ping,ping 不通不代表网站打不开。

ss

socket statisticsman 8

查看端口监听和网络连接

socket套接字,程序进行网络通信的端点。ss 是老命令 netstat 的替代品,选项基本通用。

选项含义
-t / -utcp / udp只看 TCP / UDP
-llistening只看正在监听(等待连接)的端口
-nnumeric显示数字端口,不把 80 翻译成 http
-pprocesses显示是哪个进程,看别人的进程需要 sudo
-aall监听的和已建立的连接都显示
-ssummary各类连接的统计数
$ sudo ss -tulnp                          # 最常用:所有监听中的端口及对应程序
Netid State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp   LISTEN 0      511          0.0.0.0:80        0.0.0.0:*    users:(("nginx",pid=812,fd=6))
tcp   LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=640,fd=3))
tcp   LISTEN 0      4096       127.0.0.1:5432      0.0.0.0:*    users:(("postgres",pid=901,fd=5))
$ sudo ss -tlnp | grep :8080              # 8080 端口被谁占了
$ ss -tn                                  # 当前已建立的 TCP 连接

监听地址是 0.0.0.0(IPv6 写作 [::])表示所有网卡都能访问,防火墙放行的话外网也能连;127.0.0.1 表示只有本机能访问。服务启动了但外面连不上,先看这里是不是只监听了 127.0.0.1。

lsof

list open filesman 8

列出进程打开的文件

在 Linux 里“一切皆文件”,网络连接、设备也算文件,所以 lsof 能回答“谁在用这个端口/文件”。

$ sudo lsof -i :8080                      # 谁在用 8080 端口
COMMAND  PID  USER  FD  TYPE DEVICE SIZE/OFF NODE NAME
node    4410 alice  23u IPv4  61234      0t0  TCP *:8080 (LISTEN)
$ sudo lsof -p 4127                       # 某个进程打开了哪些文件
$ sudo lsof /var/log/syslog               # 谁打开了这个文件
$ sudo lsof +L1                           # 已被删除但仍被进程占用的文件
提示:df 显示磁盘满了,du 却找不到大文件?多半是某个大日志被删了,但程序还开着它,空间没有释放。用 lsof +L1 找到这个进程,重启它就能释放空间。

curl

client URLman 1

发送网络请求:下载文件、调试接口

URLUniform Resource Locator,统一资源定位符,即网址。curl 默认把响应内容直接打印到屏幕,支持 HTTPHyperText Transfer Protocol,超文本传输协议、HTTPS、FTP 等多种协议,是调试 APIApplication Programming Interface,应用程序编程接口 的首选工具。

选项含义
-o 文件output保存到指定文件
-Oremote-name用网址里的文件名保存
-Llocation跟随重定向。很多下载链接会先跳转,不加 -L 只会拿到一个跳转提示,而不是真正的文件
-Ihead只获取响应头
-s / -Ssilent / show-error不显示进度条 / 但仍显示错误
-ffail服务器返回错误(如 404)时不输出页面内容,直接以失败退出
-vverbose显示完整的请求和响应过程
-X POSTrequest指定请求方法
-Hheader添加请求头
-ddata发送请求体(会自动使用 POST)
-u 用户:密码user基本认证
$ curl https://example.com                        # 打印网页内容
$ curl -I https://example.com                     # 只看响应头:状态码、服务器类型等
HTTP/2 200
content-type: text/html
$ curl -LO https://example.com/files/app.tar.gz   # 下载,保持原文件名
$ curl -L -o app.tgz "https://example.com/download?id=42"   # 网址含 ? & 时加引号
$ curl -s https://api.github.com/users/octocat | jq .name
$ curl -X POST http://localhost:8000/users \
    -H "Content-Type: application/json" \
    -d '{"name": "alice"}'                        # 发送 JSON
$ curl -v https://example.com 2>&1 | less        # 排查 HTTPS 证书、重定向等问题
$ curl ifconfig.me                                # 查看本机的公网 IP
$ curl -fsSL https://example.com/install.sh | sh  # 常见的一键安装写法,执行前最好先看看脚本内容

wget

World Wide Web getman 1

从网上下载文件

和 curl 相比,wget 专注下载:默认就把内容存成文件、自动跟随重定向,还支持断点续传和递归下载整个站点。

$ wget https://example.com/files/big.iso
$ wget -O app.tgz "https://example.com/download?id=42"  # -O 指定保存的文件名
$ wget -c https://example.com/files/big.iso             # -c = continue,断点续传
$ wget -q -O - https://example.com | head               # -q 安静模式,-O - 输出到屏幕
$ wget -r -np -k https://example.com/docs/              # 递归下载,不往上级目录走,并把链接改成本地可用

dig / nslookup

domain information groperman 1

查询 DNS 解析记录

域名解析不对、改了 DNS 不知道生效没,用 dig 查。Ubuntu 上在 dnsutils 包里,CentOS/Fedora 在 bind-utils 包里。

记录类型含义
AAddress,域名对应的 IPv4 地址
AAAA域名对应的 IPv6 地址(IPv6 地址长度是 IPv4 的 4 倍,所以是 4 个 A)
CNAMECanonical Name,规范名称,即别名:把一个域名指向另一个域名
MXMail Exchanger,邮件交换,负责收这个域名邮件的服务器
NSName Server,负责这个域名解析的 DNS 服务器
TXTText,文本记录,常用于验证域名所有权
$ dig example.com +short                  # 只要结果
93.184.215.14
$ dig example.com MX +short               # 查邮件服务器
$ dig @8.8.8.8 example.com                # 指定用 8.8.8.8(Google 的公共 DNS)查询
$ dig -x 8.8.8.8 +short                   # 反向解析:由 IP 查域名
dns.google.
$ nslookup example.com                    # 更老更简单的替代品,Windows 上也有
$ cat /etc/resolv.conf                    # 本机正在用哪个 DNS 服务器

traceroute / mtr

trace routeman 8

显示数据包到达目标经过的每一跳路由器

ping 只能告诉你通不通,traceroute 能看出卡在哪一段。mtrMy TraceRoute,最早叫 Matt's traceroute 把 ping 和 traceroute 结合起来,持续刷新每一跳的丢包率和延迟。

$ traceroute example.com                  # 需安装:sudo apt install traceroute
$ tracepath example.com                   # 通常预装,不需要 root
$ mtr example.com                         # 实时版,q 退出
$ mtr -rw -c 10 example.com               # 发 10 轮后输出报告,适合贴给别人看

某一跳之后全是 * * *,可能是那台路由器不回应探测包,不一定是故障;要看后续的跳和最终目标是否正常。

ssh

Secure Shellman 1

安全外壳协议:加密地远程登录另一台机器

ssh [选项] 用户@主机 [要执行的命令]

选项含义
-p 2222port指定端口,默认 22
-i 私钥文件identity指定用哪个私钥登录
-L 本地端口:目标:目标端口local forward本地端口转发:把本机端口经 ssh 隧道转到远端。“目标”是从服务器那边看的,写 localhost 就是服务器自己
-J 跳板机jump先登录跳板机再跳到目标机
-N不执行远程命令,只做端口转发
-vverbose显示连接过程,排查登录失败用
$ ssh alice@203.0.113.10
$ ssh -p 2222 alice@203.0.113.10
$ ssh -i ~/.ssh/work_key alice@203.0.113.10
$ ssh web "df -h /"                       # 不进入交互界面,只执行一条命令并返回结果
$ ssh -N -L 8080:localhost:80 web         # 浏览器打开本机 localhost:8080 就等于访问服务器上的 80 端口
$ ssh -J bastion alice@10.0.0.5           # 通过跳板机 bastion 登录内网机器
$ exit                                    # 退出登录,或按 Ctrl+D

用配置文件起别名

把常用的服务器写进 ~/.ssh/config,之后 ssh web 就能登录,scp、rsync、git 也都认这个别名。

Host web
    HostName 203.0.113.10
    User alice
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Host bastion
    HostName bastion.example.com
    User alice
第一次连接会问 Are you sure you want to continue connecting,输入 yes 后,对方的主机指纹会记在 ~/.ssh/known_hosts。如果服务器重装过,再连会出现 REMOTE HOST IDENTIFICATION HAS CHANGED 警告;确认是自己重装的,执行 ssh-keygen -R 主机 删除旧指纹即可。

ssh-keygen

SSH key generatorman 1

生成 SSH 密钥对,实现免密码登录

密钥成对出现:私钥(如 id_ed25519)留在自己电脑上,绝不外传;公钥(id_ed25519.pub)放到服务器的 ~/.ssh/authorized_keys 里,或者贴到 GitHub。登录时服务器用公钥验证你持有对应的私钥,不再需要密码。

$ ssh-keygen -t ed25519 -C "alice@laptop" # -t 算法类型,-C 备注;一路回车即可
$ ls ~/.ssh
id_ed25519  id_ed25519.pub  known_hosts
$ ssh-copy-id alice@203.0.113.10          # 把公钥装到服务器上(这一次还要输入密码)
$ cat ~/.ssh/id_ed25519.pub               # 查看公钥,复制到 GitHub 等网站
$ ssh-keygen -R 203.0.113.10              # 删除某台主机的旧指纹

Ed25519基于 Curve25519 椭圆曲线的 EdDSA 签名算法 是目前推荐的密钥类型,比老的 RSARivest–Shamir–Adleman,取自三位发明者姓氏 更短更快。遇到只支持 RSA 的老系统时,用 ssh-keygen -t rsa -b 4096。

scp

secure copyman 1

通过 SSH 在机器之间复制文件

scp [选项] 源 目标

远程路径写成 用户@主机:路径,本地路径照常写。

$ scp report.pdf alice@203.0.113.10:~/docs/     # 本地 → 远程
$ scp alice@203.0.113.10:/var/log/app.log .     # 远程 → 本地当前目录
$ scp -r project/ web:~/                        # -r 复制整个目录;web 是 ssh config 里的别名
$ scp -P 2222 file.txt alice@203.0.113.10:/tmp/ # 端口用大写 -P(ssh 是小写 -p)
提示:文件多、文件大,或者需要反复同步时,用 rsync:只传有变化的部分,加 -P 后中断了还能续传。

rsync

remote syncman 1

增量同步文件和目录,本地或远程都行

选项含义
-aarchive归档模式:递归,保留权限、时间、符号链接等。几乎总是要加
-vverbose显示传输了哪些文件
-zcompress传输时压缩,适合网络慢的时候
-P= --partial --progress:显示进度,保留传了一半的文件以便续传
-ndry-run演习:只显示会做什么,不真的执行
--delete删除目标里有、源里没有的文件,让两边完全一致
--exclude排除文件或目录
-e "ssh -p 2222"指定 ssh 参数,比如非默认端口
$ rsync -av src/ backup/                          # 本地同步
$ rsync -avz src/ web:/srv/app/                   # 推送到远程服务器
$ rsync -avzP web:/var/backups/ ./backups/        # 从远程拉取,显示进度
$ rsync -avn --delete src/ backup/                # 先演习,看看会删哪些文件
$ rsync -av --delete src/ backup/                 # 确认后再执行
$ rsync -av --exclude node_modules --exclude .git ./ web:/srv/app/
$ rsync -av -e "ssh -p 2222" src/ alice@203.0.113.10:/srv/app/
源路径末尾的斜杠有区别:rsync -a src/ dest/ 把 src 里面的内容同步到 dest;rsync -a src dest/ 会在 dest 里再建一个 src 目录。配合 --delete 时写错会删掉不该删的文件,所以先用 -n 演习。

压缩与归档Archives

Linux 里“打包”和“压缩”是两步:tar 把多个文件打包成一个 .tar 文件,gzip、xz 等负责压缩单个文件。所以常见的 .tar.gz(也写作 .tgz)就是先打包再用 gzip 压缩,tar 可以一步完成这两件事。

tar

tape archiveman 1

打包和解包,名字来自早期用磁带备份的年代

选项含义
-ccreate创建归档
-xextract解开归档
-tlist只列出内容,不解开
-vverbose显示处理的每个文件
-f 文件名file指定归档文件名,必须紧跟文件名,所以总是放在选项最后
-z / -j / -J用 gzip(.gz)/ bzip2(.bz2)/ xz(.xz)压缩或解压
-C 目录directory解压到指定目录;打包时表示先进入这个目录再打包
--exclude打包时排除某些文件
$ tar -czvf backup.tar.gz project/        # 打包并压缩:c 创建、z 用 gzip、v 显示、f 文件名
$ tar -xzvf backup.tar.gz                 # 解压到当前目录
$ tar -xf backup.tar.gz                   # 解压时可以不写 z,tar 会自动识别压缩格式
$ tar -xf backup.tar.gz -C /tmp/restore   # 解压到指定目录(目录要先存在)
$ tar -tf backup.tar.gz                   # 先看看里面有什么
$ tar -cJf logs.tar.xz logs/              # 用 xz 压缩,体积更小但更慢
$ tar -czf site.tgz --exclude='*.log' --exclude=node_modules site/
$ tar -xf backup.tar.gz project/README.md # 只解出其中一个文件
记法:压缩 czvf,解压 xzvf,看内容 tzvf,只有第一个字母不同:c 创建、x 解开、t 列表。

gzip / gunzip

GNU zipman 1

压缩或解压单个文件

GNUGNU's Not Unix,“GNU 不是 Unix”,一个递归缩写;Linux 上大部分基础命令都来自 GNU 项目。gzip 默认会替换原文件:压缩后原文件消失,只剩 .gz。

$ gzip access.log                         # 生成 access.log.gz,原文件被删除
$ gzip -k access.log                      # -k = keep,保留原文件
$ gunzip access.log.gz                    # 解压,等于 gzip -d
$ zcat access.log.gz | grep 404           # 不解压直接看内容;还有 zgrep、zless
$ gzip -9 dump.sql                        # 压缩级别 1(最快)~9(最小),默认 6
$ xz -k big.csv                           # xz 压缩率更高;解压用 unxz

zip / unzip

zipman 1

与 Windows、macOS 通用的 .zip 格式

要发给用 Windows 的人时选 zip。可能需要安装:sudo apt install zip unzip。

$ zip -r site.zip site/                   # -r 递归压缩整个目录
$ zip -r site.zip site/ -x "*.git*"       # -x 排除
$ zip -er secret.zip docs/                # -e 加密,会提示输入密码
$ unzip site.zip                          # 解压到当前目录
$ unzip site.zip -d /tmp/site             # -d 解压到指定目录
$ unzip -l site.zip                       # -l 只列出内容
$ unzip -O GBK windows.zip                # Windows 上压缩的中文文件名乱码时用(Ubuntu 版 unzip 支持)

GBK国标扩展,Windows 中文版默认的汉字编码。Linux 用的是 UTF-8,所以 Windows 打包的 zip 解压出来中文名可能乱码。

软件包管理Packages

Linux 装软件一般不去网站下载安装包,而是用包管理器从软件源(repository,官方维护的软件仓库)安装,它会自动处理依赖。不同发行版的包管理器不同:Debian/Ubuntu 用 apt,Fedora/RHEL/Rocky 等用 dnf。

apt

Advanced Package Toolman 8

高级包管理工具,用于 Debian / Ubuntu

命令作用
apt update刷新软件源的索引,知道有哪些新版本。不会升级任何软件
apt upgrade升级所有可升级的软件
apt install 包名安装,可以一次写多个
apt remove 包名卸载,保留配置文件
apt purge 包名卸载并删除配置文件
apt autoremove清理当初作为依赖装上、现在已经没人需要的包
apt search 关键词搜索
apt show 包名查看版本、大小、依赖、描述
apt list --installed列出已安装的包
$ sudo apt update && sudo apt upgrade -y  # 系统更新的标准写法,-y 自动回答 yes
$ sudo apt install nginx git curl
$ sudo apt install ./google-chrome.deb    # 安装本地 .deb 文件,注意要写 ./
$ sudo apt purge nginx && sudo apt autoremove
$ apt search markdown
$ apt list --installed | grep python
$ dpkg -L nginx                           # 这个包往系统里装了哪些文件
$ dpkg -S /usr/bin/curl                   # 这个文件是哪个包装的
curl: /usr/bin/curl

.deb 是 Debian 系的安装包格式,dpkg(Debian package)是更底层的工具,apt 在它之上负责下载和解决依赖。写脚本时常用 apt-get,它的输出格式更稳定,用法和 apt 基本一样。软件源的配置在 /etc/apt/sources.list 和 /etc/apt/sources.list.d/。

dnf / yum

Dandified YUMman 8

用于 Fedora、RHEL、Rocky Linux、AlmaLinux、CentOS

yumYellowdog Updater, Modified,改进版 Yellowdog 更新器 是老版本系统(如 CentOS 7)上的工具,dnf 是它的继任者,用法几乎相同,在新系统上 yum 命令通常直接指向 dnf。安装包格式是 RPMRPM Package Manager,最早叫 Red Hat Package Manager。

$ sudo dnf install nginx
$ sudo dnf remove nginx
$ sudo dnf upgrade                        # 刷新索引并升级全部,相当于 apt update + upgrade
$ dnf search markdown
$ dnf info nginx
$ dnf list installed | grep python
$ dnf provides /usr/bin/dig               # 哪个包提供这个文件
$ rpm -qa | grep kernel                   # query all:所有已安装的 rpm 包
$ rpm -ql nginx                           # query list:包里有哪些文件

终端编辑器Editors

在服务器上改配置文件离不开终端编辑器。新手用 nano,上手没有门槛;vim 几乎所有 Linux 都预装,至少要会打开、修改、保存、退出。

nano

nanoman 1

简单直观的终端文本编辑器

打开后直接就能打字,快捷键列在屏幕底部,^ 表示 Ctrl,M- 表示 Alt。

按键作用
Ctrl+O 然后回车保存(Write Out)
Ctrl+X退出,有未保存的修改时会问要不要保存(Y / N)
Ctrl+W搜索(Where is)
Ctrl+K / Ctrl+U剪切整行 / 粘贴
Ctrl+_跳到指定行号
Alt+U撤销
$ nano ~/.bashrc
$ sudo nano /etc/hosts                    # 改系统文件要加 sudo,否则无法保存
$ nano -l app.conf                        # -l 显示行号

vim

Vi IMprovedman 1

vi 的增强版,功能强大的模式编辑器

vim 有几种模式,新手卡住通常是因为不知道自己在哪个模式:

  • 普通模式(打开时的默认模式):按键是命令,不是输入文字。任何时候按 Esc 都回到这里。
  • 插入模式:按 i 进入,这时才能像普通编辑器一样打字,左下角显示 -- INSERT --。
  • 命令行模式:在普通模式下按 : 进入,用来保存、退出、替换。
按键(普通模式下)作用
i / a / o在光标前插入 / 光标后插入(append)/ 在下方新开一行(open)
Esc回到普通模式
:w保存(write)
:q退出(quit)
:wq 或 :x保存并退出
:q!不保存,强制退出
dd / yy / p剪切(delete)当前行 / 复制(yank)当前行 / 粘贴(paste)
x删除光标处的字符
u / Ctrl+r撤销(undo)/ 重做(redo)
gg / G跳到第一行 / 最后一行;:42 跳到第 42 行
0 / $行首 / 行尾
w / b下一个单词(word)/ 上一个单词(back)
/关键词搜索,n 下一个,N 上一个
:%s/旧/新/g全文替换(% 表示所有行,和 sed 语法相同)
v进入可视模式(visual)选择文字,再按 y 复制或 d 删除
:set nu显示行号(number)
$ vim notes.txt
# 1. 按 i 进入插入模式,输入内容
# 2. 按 Esc 回到普通模式
# 3. 输入 :wq 回车,保存并退出
$ sudo vim /etc/nginx/nginx.conf
$ vim +42 app.py                          # 打开并直接跳到第 42 行
$ vimtutor                                # vim 自带的交互式教程,30 分钟学会基础操作
不小心进了 vim 又出不来:按 Esc,输入 :q!,回车。

常用组合Recipes

把前面的命令串起来,解决日常真正会遇到的问题。

磁盘满了,找出是什么占的空间

$ df -h                                   # 1. 先确认是哪个分区满了
$ sudo du -h -d 1 / 2>/dev/null | sort -rh | head   # 2. 从根目录逐层往下找
$ sudo du -h -d 1 /var | sort -rh | head  # 3. 进入最大的目录继续找
$ sudo find / -xdev -type f -size +500M -exec ls -lh {} + 2>/dev/null   # 或者直接找大于 500M 的文件
$ sudo journalctl --vacuum-size=200M      # 常见元凶之一:systemd 日志

-xdev 表示不跨越到其他分区,避免把挂载的数据盘也扫进来。

端口被占用,找出并结束对应进程

$ sudo ss -tlnp | grep :8080
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=4410,fd=23))
$ kill 4410                               # 先礼后兵
$ kill -9 4410                            # 仍然不退出再强制
$ sudo fuser -k 8080/tcp                  # 或者一步到位(fuser = file user,-k = kill;在 psmisc 包里)

分析访问日志

$ awk '{print $1}' access.log | sort | uniq -c | sort -rn | head      # 访问最多的 IP
$ awk '{print $9}' access.log | sort | uniq -c | sort -rn             # 各 HTTP 状态码的数量
$ awk '$9 == 500' access.log | tail -20                              # 最近 20 条 500 错误
$ awk '{print $7}' access.log | sort | uniq -c | sort -rn | head      # 访问最多的路径
$ tail -f app.log | grep --line-buffered -i error                    # 实时只看错误

这里按 nginx 默认日志格式取列:第 1 列是 IP,第 7 列是请求路径,第 9 列是状态码。其他格式先用 head -3 access.log 数一下是第几列。

在整个项目里批量替换文字

$ grep -rl "old_name" --include="*.py" .                  # 1. 先看看有哪些文件会被改
$ grep -rlZ "old_name" --include="*.py" . | xargs -0 sed -i 's/old_name/new_name/g'   # 2. 执行替换(-Z 配合 -0,文件名带空格也不出错)
$ git diff                                                # 3. 在 git 仓库里的话,检查改动

批量重命名文件

$ for f in *.txt; do mv -- "$f" "${f%.txt}.md"; done      # 所有 .txt 改成 .md
$ for f in *.JPG; do mv -- "$f" "${f,,}"; done            # 文件名转成小写(bash 4+)
$ i=1; for f in *.jpg; do mv -- "$f" "photo_$(printf %03d $i).jpg"; i=$((i+1)); done   # 按序号命名 photo_001.jpg……

${f%.txt} 表示去掉变量末尾的 .txt;${f,,} 把变量转成小写(macOS 自带的 bash 太旧不支持,zsh 里写 ${f:l})。先把 mv 换成 echo mv 跑一遍,确认输出无误再真正执行。

定期清理旧文件

$ find /var/log/myapp -name "*.log" -mtime +7             # 先看 7 天前的日志有哪些
$ find /var/log/myapp -name "*.log" -mtime +7 -delete     # 确认后删除
$ find ~/backups -name "*.tar.gz" -mtime +30 -delete      # 只保留 30 天内的备份

放进 crontab 就能每天自动执行,例如 0 4 * * * find /var/log/myapp -name "*.log" -mtime +7 -delete。

带日期的备份

$ tar -czf ~/backups/site-$(date +%F).tar.gz -C /var/www site   # 生成 site-2026-09-26.tar.gz
$ rsync -avz ~/backups/ web:/data/backups/                      # 再同步一份到另一台机器
$ pg_dump mydb | gzip > mydb-$(date +%F).sql.gz                 # 导出 PostgreSQL 数据库并压缩

让程序在断开 SSH 后继续运行

$ nohup python3 train.py > train.log 2>&1 &      # 方法一:nohup,简单
$ tmux new -s train                            # 方法二:tmux,之后还能回到原界面
$ systemd-run --user --unit=train python3 train.py   # 方法三:交给 systemd 托管(退出登录后还要运行,先执行一次 loginctl enable-linger)

一次性任务用 nohup 或 tmux 就够了;需要开机自启、崩溃自动重启的长期服务,给它写一个 systemd 的 .service 文件,用 systemctl 管理。

其他随手用得上的

$ python3 -m http.server 8000             # 把当前目录共享成网页,局域网里用浏览器访问 IP:8000 下载;用完按 Ctrl+C 关掉
$ openssl rand -base64 18                 # 生成一个随机密码
$ ps aux --sort=-%mem | head -6           # 最占内存的 5 个进程
$ ls -t ~/Downloads | head                # 最近下载的 10 个文件
$ find . -name "*.py" -print0 | xargs -0 cat | wc -l   # 统计 Python 代码总行数
$ cat /proc/loadavg; nproc                # 快速查看负载和核心数
$ curl -s "wttr.in/Beijing?0"             # 在终端看天气(网址含 ? 要加引号,否则 zsh 会报错)