$ 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,文件系统层次结构标准:
| 目录 | 名字来源 | 放什么 |
/home | home,家 | 普通用户的家目录,如 /home/alice |
/root | root,根用户 | root 用户的家目录 |
/etc | et cetera,“等等” | 系统和软件的配置文件,如 /etc/nginx/、/etc/hosts |
/var | variable,可变的 | 运行中不断变化的数据:日志 /var/log、网站文件 /var/www、缓存 |
/tmp | temporary,临时 | 临时文件,重启后通常会被清空 |
/usr | 最早是 user;常被解释为 Unix System Resources | 系统安装的程序、库和文档,如 /usr/bin、/usr/lib |
/bin、/sbin | binaries 二进制程序;system binaries 系统程序 | 基本命令和系统管理命令。新系统里它们只是链接,分别指向 /usr/bin 和 /usr/sbin |
/opt | optional,可选的 | 手动安装的第三方软件 |
/dev | devices,设备 | 设备文件,如硬盘 /dev/sda、黑洞 /dev/null |
/proc | process,进程 | 内核提供的虚拟文件,如 /proc/cpuinfo,不占磁盘 |
/mnt、/media | mount,挂载 | 挂载硬盘、U 盘的地方 |
/boot | boot,启动 | 内核和启动引导文件 |
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 | 系统管理命令,通常需要 root | mount、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。
搜索与查找Search
按文件名找文件用 find,按文件内容找用 grep,找命令在哪用 which(见 man)。
find
findman 1
按名称、类型、大小、时间等条件查找文件
find [起始目录] [条件…] [动作]
| 条件 / 动作 | 含义 |
-name "*.log" | 按文件名匹配,通配符要加引号;-iname 忽略大小写 |
-type f | 只找普通文件(file);d 目录(directory),l 符号链接(link) |
-size +100M | 大于 100 MB。单位 k M G;用 - 表示“小于”时按单位向上取整,-1M 只能匹配空文件,找小文件改用 k |
-mtime -7 | 7 天内修改过(modification time);+30 是超过 30 天没改过;-mmin -60 按分钟算 |
-maxdepth 1 | 最多往下找几层,1 表示只看当前目录 |
-user alice | 属于某个用户的文件 |
-empty | 空文件或空目录 |
-not / ! | 取反;-o 表示“或”(or) |
-path "./venv/*" | 按完整路径匹配,常配合 -not 排除目录 |
-exec 命令 {} \; | 对每个结果执行命令,{} 代表文件名 |
-exec 命令 {} + | 把所有结果一次性交给命令,更快 |
-delete | 删除找到的文件 |
$ find . -name "*.log" # 当前目录及所有子目录下的 .log
$ find /etc -iname "*nginx*" # 忽略大小写
$ find . -type d -name node_modules # 只找目录
$ find / -type f -size +1G 2>/dev/null # 全盘找大于 1G 的文件,屏蔽无权限的报错
$ find . -type f -mtime -1 # 最近 24 小时内改过的文件
$ find . -maxdepth 1 -type f # 只看当前这一层的文件
$ find . -name "*.py" -not -path "./venv/*" # 排除 venv 目录
$ find . -name "*.sh" -exec chmod +x {} \; # 给所有 .sh 加执行权限
$ find . -name "*.txt" -exec grep -l "hello" {} + # 哪些 txt 里含有 hello
$ find /tmp -type f -mtime +30 # 先确认 30 天前的文件有哪些……
$ find /tmp -type f -mtime +30 -delete # ……确认无误再删除
注意:-delete 要放在条件的最后面。写在前面会在筛选之前就开始删除。先不加 -delete 跑一遍看结果,是好习惯。
grep
global regular expression printman 1
在文件或输出中搜索匹配的行
名字来自早期 ed 编辑器的命令 g/re/p:在全文(global)中查找匹配 正则表达式regular expression,简称 regex,描述文本模式的规则 的行并打印(print)。
grep [选项] 模式 [文件…]
| 选项 | 含义 |
-iignore-case | 忽略大小写 |
-rrecursive | 递归搜索整个目录 |
-nline-number | 显示行号 |
-vinvert-match | 反向:显示不匹配的行 |
-wword-regexp | 只匹配完整单词 |
-ccount | 只输出匹配的行数 |
-lfiles-with-matches | 只列出包含匹配的文件名 |
-oonly-matching | 只输出匹配到的那部分,而不是整行 |
-Eextended-regexp | 使用扩展正则,+ ? | () 不用加反斜杠 |
-Ffixed-strings | 把模式当普通文字,不解释正则符号 |
-A 3 / -B 3 / -C 3after / before / context | 同时显示匹配行之后 / 之前 / 前后的 3 行 |
--include="*.py" | 递归时只搜索指定类型的文件;--exclude-dir=.git 排除目录 |
$ grep "error" app.log
$ grep -i "error" app.log # Error、ERROR 也算
$ grep -rn "TODO" src/ # 在整个目录里找,带行号
$ grep -rn --include="*.py" "import os" . # 只在 .py 文件里找
$ grep -v "^#" nginx.conf | grep -v "^$" # 去掉注释行和空行,只看有效配置
$ grep -c "404" access.log # 有多少行包含 404
$ grep -l "password" *.conf # 哪些文件里出现了 password
$ grep -E "error|warn" app.log # 包含 error 或 warn
$ grep -w "id" main.py # 匹配 id,但不匹配 idle、uid
$ grep -C 3 "Traceback" app.log # 连同前后 3 行一起显示,方便看上下文
$ grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log # 提取所有 IP 地址
$ ps aux | grep nginx # 在其他命令的输出里筛选
正则表达式速查
| 写法 | 含义 | 例子 |
. | 任意一个字符 | a.c 匹配 abc、a1c |
* | 前一个字符出现 0 次或多次 | ab*c 匹配 ac、abbc |
+ | 1 次或多次(需要 -E) | [0-9]+ 一串数字 |
? | 0 次或 1 次(需要 -E) | colou?r |
^ / $ | 行首 / 行尾 | ^$ 匹配空行 |
[abc] | 方括号里任意一个字符;[^abc] 表示不是这些 | [0-9]、[a-zA-Z] |
a|b | a 或 b(需要 -E) | GET|POST |
( ) | 分组(需要 -E) | (ab)+ |
\ | 转义,让特殊字符变成普通字符 | \. 匹配真正的点 |
提示:模式用单引号或双引号括起来,避免 shell 先把 *、$ 等符号解释掉。
locate
locateman 1
通过预先建好的索引数据库,快速按文件名查找
比 find 快得多,因为它查的是数据库而不是实时扫描磁盘,但刚创建的文件要等数据库更新后才能搜到。Ubuntu 上用 sudo apt install plocate 安装。
$ locate nginx.conf
/etc/nginx/nginx.conf
$ locate -i readme # 忽略大小写
$ sudo updatedb # 立即更新数据库(系统每天会自动更新一次)
xargs
extended argumentsman 1
把标准输入的内容转换成另一个命令的参数
很多命令(如 rm、cp)只接受参数,不读管道输入。xargs 负责把上一个命令的输出变成它们的参数。
| 选项 | 含义 |
-0null | 以空字符分隔输入,配合 find -print0 处理带空格的文件名 |
-n 1max-args | 每次只传 1 个参数 |
-I {}replace | 用 {} 代表每一项,可以放在命令中间 |
-P 4max-procs | 最多 4 个并行执行 |
-rno-run-if-empty | 输入为空时不执行命令 |
$ find . -name "*.tmp" | xargs rm # 删除所有 .tmp
$ find . -name "*.tmp" -print0 | xargs -0 rm # 文件名带空格时,用这对组合更安全
$ xargs -n 1 curl -O < urls.txt # 逐个下载 urls.txt 里的网址
$ ls *.jpg | xargs -I {} cp {} backup/{} # {} 代表每个文件名
$ cat hosts.txt | xargs -P 4 -I {} ping -c 1 {} # 4 个并行 ping
文本处理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/旧/新/g | g = 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:
| 编号 | 名称 | 默认连接到 |
0 | stdinstandard input,标准输入 | 键盘 |
1 | stdoutstandard output,标准输出 | 屏幕,正常的输出结果 |
2 | stderrstandard 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 && B | A 成功了才执行 B |
A || B | A 失败了才执行 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 read | 4 | 读取内容 | 列出目录里的文件名 |
w write | 2 | 修改内容 | 在目录里新建、删除、重命名文件 |
x execute | 1 | 作为程序执行 | 进入目录(cd) |
数字写法是把每组的数值相加:rwx = 4+2+1 = 7,r-x = 4+1 = 5,r-- = 4。所以 rwxr-xr-- 就是 754。常见组合:
| 数字 | 字符 | 典型用途 |
755 | rwxr-xr-x | 目录、可执行脚本:自己可改,别人可读可执行 |
644 | rw-r--r-- | 普通文件:自己可改,别人只读 |
700 | rwx------ | 私人目录,如 ~/.ssh |
600 | rw------- | 私密文件,如 SSH 私钥、含密码的配置 |
777 | rwxrwxrwx | 所有人可读写执行,几乎永远不该用 |
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 bob | Debian/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 / %MEM | CPU 和内存占用百分比 |
VSZ | Virtual Memory Size,虚拟内存大小(KB),包括没真正用到的,参考意义不大 |
RSS | Resident Set Size,常驻内存大小(KB),进程实际占用的物理内存,看这个 |
TTY | TeleTYpewriter(电传打字机),指进程所在的终端;? 表示没有终端(后台服务) |
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 按精确的程序名。
| 信号 | 编号 | 含义 |
SIGTERM | 15 | terminate,终止。kill 的默认信号,请进程收尾后退出 |
SIGKILL | 9 | kill,强制杀死。进程无法拦截,也来不及保存数据,是最后的手段 |
SIGINT | 2 | interrupt,中断。按 Ctrl+C 就是发这个 |
SIGHUP | 1 | hang up,挂断。终端关闭时发给进程;很多服务收到它会重新加载配置 |
SIGSTOP / SIGCONT | 19 / 18 | stop 暂停 / 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 包里。
| 记录类型 | 含义 |
A | Address,域名对应的 IPv4 地址 |
AAAA | 域名对应的 IPv6 地址(IPv6 地址长度是 IPv4 的 4 倍,所以是 4 个 A) |
CNAME | Canonical Name,规范名称,即别名:把一个域名指向另一个域名 |
MX | Mail Exchanger,邮件交换,负责收这个域名邮件的服务器 |
NS | Name Server,负责这个域名解析的 DNS 服务器 |
TXT | Text,文本记录,常用于验证域名所有权 |
$ 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 演习。
软件包管理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:包里有哪些文件