【第2章】文件管理
文件与目录的日常操作是命令行的地基。这一章覆盖查看、复制移动、删除、分页与磁盘占用统计——注意
rm、mv、重定向覆盖这些不可逆操作的边界。
cat
cat - 连接文件并输出到标准输出,最常用来查看小文件内容
常用命令
cat /etc/hosts打印文件全部内容(适合短文件;长文件用 less)cat file1.txt file2.txt依次拼接输出多个文件cat file1.txt file2.txt > merged.txt把多个文件合并成一个cat -n app.log输出时给所有行加行号cat -A config.txt显示不可见字符(Tab 显示为 ^I、行尾为 $,排查空白字符问题)cat -s readme.txt把连续多个空行压缩成一个cat >> notes.txt从键盘追加输入到文件,Ctrl-D 结束cat <<'EOF' > run.shheredoc 写多行内容到文件(脚本里生成配置文件的惯用法)cat -从标准输入读取(在管道中显式占位)tac app.log | head逆序查看文件(tac 是 cat 的反向版,GNU;macOS 用tail -r)
语法格式
cat [OPTION]... [FILE]...
FILE 为 - 或省略时读标准输入。
OPTIONS
-n所有行编号-b只给非空行编号(覆盖 -n)-s压缩连续空行为一行-A等价-vET:显示所有不可见字符(GNU)-E行尾显示$-TTab 显示为^I-v用^/M-记法显示控制字符-eBSD/macOS 下等价-vE
备注
- 反模式 UUOC(useless use of cat):
cat file | grep foo直接写grep foo file即可;但多文件拼接、heredoc、显式占位管道时 cat 是正解 - 大文件不要 cat 到终端,用 less 分页或 head/tail 取片段;cat 二进制文件可能弄乱终端,出现乱码后敲
reset恢复 cat a.txt b.txt > a.txt会先把 a.txt 截断为空再读,导致内容丢失;合并进原文件用cat b.txt >> a.txt
cp
cp - 复制文件或目录
常用命令
cp file.txt /tmp/复制文件到目标目录cp file.txt file.bak复制并改名(快速做备份)cp -r dir/ /backup/dir/递归复制整个目录cp -a /etc/nginx /etc/nginx.bak归档复制:递归且保留权限、属主、时间戳、符号链接(备份配置目录首选)cp -i file.txt /tmp/目标已存在时先询问再覆盖cp -n file.txt /tmp/目标已存在时不覆盖cp -v *.conf /etc/app/复制并逐条打印cp -p script.sh /usr/local/bin/保留原文件的权限与时间戳cp -u src/*.js dist/只复制比目标新的文件(GNU,增量同步小场景)cp file.txt{,.$(date +%Y%m%d)}改文件前先做带日期的备份(生成 file.txt.20260714)
语法格式
cp [OPTION]... SOURCE DEST
cp [OPTION]... SOURCE... DIRECTORY
多个 SOURCE 时最后一个参数必须是已存在的目录。
OPTIONS
-r/-R递归复制目录-a归档模式,等价-dR --preserve=all:保留链接、权限、时间戳等全部属性-p保留权限、属主、时间戳-i覆盖前询问-f目标无法打开时先删除再重试(强制覆盖)-n不覆盖已存在的目标-v显示复制过程-u仅当源比目标新或目标不存在时才复制(GNU)-L/-P跟随符号链接复制实体 / 保留符号链接本身
备注
- 复制目录时源路径结尾斜杠语义不同:GNU 下
cp -r dir /dst与cp -r dir/ /dst都是在 /dst 下生成 dir,而 macOS/BSD 下dir/表示复制目录内容——跨平台脚本想复制内容统一写dir/. - 普通
cp -r不保留属主和时间戳,备份/迁移用-a;大量文件或需断点续传时改用rsync -a - cp 覆盖目标是直接截断写入,被覆盖内容不可恢复,重要文件先
-i或做备份
df
df - 查看文件系统的磁盘空间占用与剩余情况
常用命令
df -h以人类可读单位(K/M/G)显示所有已挂载文件系统的使用情况,最常用df -h /只查看根分区的空间使用df -h /var/log查看某个目录所在文件系统的空间(排查日志把盘写满)df -i查看 inode 使用情况(磁盘还有空间但报 "No space left on device" 时查这个)df -T同时显示文件系统类型(ext4 / xfs / apfs 等,Linux 用法)df -h -t ext4只显示指定类型的文件系统df -h -x tmpfs -x devtmpfs排除 tmpfs 等内存文件系统,只看真实磁盘(Linux)df -h .查看当前目录所在分区的使用情况df -h | sort -rk 5 | head按使用率排序,快速找出最满的分区
语法格式
df [OPTION]... [FILE]...
不带参数时列出所有已挂载文件系统;指定 FILE 时显示该文件/目录所在文件系统的信息。
OPTIONS
-h人类可读单位(1K=1024),如 1.5G、200M-H人类可读单位,但按 1000 进位(1K=1000)-i显示 inode 使用情况而非块使用情况-T显示文件系统类型(GNU df)-t <type>只显示指定类型的文件系统-x <type>排除指定类型的文件系统-a显示所有文件系统,包括 0 块的伪文件系统(proc、sysfs 等)-k/-m以 KB / MB 为单位显示(脚本中取数常用-k保证格式稳定)--total末尾追加一行总计(GNU df)
备注
- macOS/BSD 的 df 无
-T、--total选项,且默认以 512-byte block 显示,习惯加-h;查看文件系统类型在 macOS 上用mount或diskutil list - df 统计的是文件系统层面的数据,与 du 逐文件累加的结果可能不一致:文件被删除但仍被进程占用句柄时,df 认为空间未释放而 du 看不到该文件,可用
lsof +L1(Linux)定位 - 排查磁盘满的套路:先
df -h定位哪个分区满,再到该分区用du -sh *逐层下钻
du
du - 统计文件与目录占用的磁盘空间
常用命令
du -sh /var/log查看目录总占用(最常用组合:s 只显示总计、h 人类可读)du -sh *查看当前目录下每个文件/子目录的占用,逐层下钻找大文件du -h --max-depth=1 /data只统计一层子目录的占用(GNU 写法;macOS 用du -h -d 1)du -sh * | sort -rh | head -10找出当前目录下占用最大的 10 项du -ah /var/log | sort -rh | head -20连文件一起统计并排出前 20 大du -sh .[!.]* *连隐藏文件/目录一起统计(家目录排查常用)du -sk * | sort -rn以 KB 为单位排序,兼容不支持sort -h的老系统du -sh --exclude='*.log' /app统计时排除某类文件(GNU)du -sh -x /只统计当前文件系统,不跨挂载点(避免把 /proc、其他盘算进来)
语法格式
du [OPTION]... [FILE]...
不指定 FILE 时统计当前目录。默认递归显示每个子目录的占用。
OPTIONS
-s只显示每个参数的总计,不列子目录-h人类可读单位(K/M/G)-a连文件一起显示(默认只显示目录)-c末尾追加一行 total 总计-d N/--max-depth=N只显示到第 N 层目录(--max-depth为 GNU 写法,-d两边通用)-x不跨文件系统边界(跳过其他挂载点)-k/-m以 KB / MB 为单位-L跟随符号链接统计其指向的内容--exclude=PATTERN排除匹配的文件(GNU du)
备注
- du 统计的是磁盘占用(按块分配),不是文件逻辑大小;稀疏文件的 du 结果可能远小于
ls -l看到的大小,加--apparent-size(GNU)可看逻辑大小 - macOS/BSD 的 du 不支持
--max-depth、--exclude等长选项,层级控制用-d N - du 与 df 结果对不上时,通常是「已删除但仍被进程持有的文件」在占空间,见 df 笔记
head
head - 输出文件开头部分(默认前 10 行)
常用命令
head /var/log/system.log显示文件前 10 行(默认)head -n 50 app.log显示前 50 行head -n 1 data.csv只看第一行(快速确认 CSV 表头/字段顺序)head -c 512 dump.bin | xxd取前 512 字节并十六进制查看(确认文件真实格式)head -n 5 *.conf查看多个文件各自的前 5 行,输出带文件名头sort -rn scores.txt | head -3管道中取 Top 3(head 最常见的用法是配合排序取前 N)head -n -5 app.log输出除最后 5 行之外的全部内容(GNU,注意负号)ps aux | head -1; ps aux | grep java先打表头再看目标行的小技巧head -q -n 1 a.csv b.csv多文件时不打印文件名头,只输出内容
语法格式
head [OPTION]... [FILE]...
省略 FILE 时读标准输入;多个 FILE 时每段前打印 ==> file <== 头。
OPTIONS
-n N显示前 N 行-n -N显示除末尾 N 行外的全部(GNU 专有)-c N显示前 N 字节;-c -N为除末尾 N 字节外全部(GNU)-q多文件时不打印文件名头-v总是打印文件名头-N(如head -20)老式写法,等价-n 20,不推荐但常见
备注
-n -N(去掉末尾 N 行)是 GNU 扩展,macOS/BSD 的 head 不支持,跨平台脚本慎用- 与 tail 对照记忆:
tail -n +N是「从第 N 行到末尾」,两者组合可任意切片,如head -n 100 file | tail -n 1取第 100 行 - head 读够 N 行就关闭输入,上游命令会收到 SIGPIPE 提前退出——管道里
set -o pipefail时可能因此看到非零退出码,属正常现象
less
less - 功能完整的分页器,支持前后滚动与搜索,查看大文件首选
常用命令
less app.log分页查看文件,上下键/PgUp/PgDn 滚动,q 退出less -N app.log显示行号less +F app.log进入类似tail -f的跟踪模式,Ctrl-C 停止跟踪回到浏览,按 F 恢复跟踪(可搜索可回翻,比 tail -f 好用)less +/ERROR app.log打开后直接跳到第一个 ERRORless +G app.log打开后直接跳到文件末尾ps aux | less给管道输出分页less -S wide.csv长行不折行、左右方向键横向滚动(看宽表/长 JSON 行)less -i app.log搜索忽略大小写(搜索词含大写时仍精确匹配)less -R colored.log保留 ANSI 颜色转义(看grep --color=always或彩色日志输出)zless app.log.gz直接查看 gzip 压缩文件(less 家族配套命令)
语法格式
less [OPTION]... [FILE]...
常用交互按键:Space/b 前后翻页、g/G 跳文件头/尾、/、? 向下/向上搜索、n/N 下/上一个匹配、&pattern 只显示匹配行、F 跟踪模式、50g 跳到第 50 行、v 调起编辑器、h 帮助、q 退出。
OPTIONS
-N显示行号-i搜索忽略大小写(模式含大写字母时自动恢复区分)-S长行截断不折行,可横向滚动-R原样输出 ANSI 颜色控制序列-F内容不足一屏时直接输出并退出-X退出时不清屏,内容留在终端上-g搜索时只高亮当前匹配项+N/+G/+/pattern打开后跳到第 N 行 / 末尾 / 首个匹配-p pattern等价+/pattern
备注
- less 按需读取,不把整个文件载入内存,几个 GB 的日志也能秒开,这是它优于 cat/编辑器的核心场景
git log、man等默认用 less 分页,行为可通过环境变量LESS定制,常见配置export LESS='-iRFXS'less +F基本可替代tail -f:跟踪中随时 Ctrl-C 停下来搜索回翻;缺点是对被轮转的文件不会像tail -F那样自动重开
more
more - 按页显示文件内容的分页器(功能较少,几乎所有系统都自带)
常用命令
more app.log分页查看文件,空格翻页、q 退出ps aux | more给管道输出分页more +100 app.log从第 100 行开始显示more +/ERROR app.log打开后直接定位到第一个匹配 ERROR 的位置more -d app.log底部显示操作提示(按错键时给出帮助,适合不熟的人)more -s app.log压缩连续空行为一行more -10 app.log每屏只显示 10 行
语法格式
more [OPTION]... FILE...
交互按键:Space 下翻一屏、Enter 下翻一行、/pattern 向下搜索、n 下一个匹配、= 显示当前行号、v 调起编辑器、q 退出。
OPTIONS
-d显示操作提示信息,按错键不响铃而是给提示-s压缩连续空行-f按逻辑行计数而非折行后的屏幕行-p不滚动,先清屏再逐页显示-c从顶部开始逐行覆盖刷新,不滚屏-u抑制下划线(老终端兼容)-N(如-10)设置每屏显示的行数+N从第 N 行开始显示+/pattern从匹配 pattern 处开始显示
备注
- more 只能向前翻(部分新版实现支持有限回退),不能随意上下滚动;日常查看优先用 less,more 的价值在于极简环境(容器、恢复模式)里一定存在
- more 读完文件末尾会自动退出(less 默认停在末尾等待),管道场景想看完自动结束时 more 反而顺手
- macOS 上 more 实际由 less 提供(同一二进制),行为与传统 more 略有差异
mv
mv - 移动文件/目录,或对其重命名
常用命令
mv old.txt new.txt重命名文件mv file.txt /tmp/把文件移动到目标目录(保留原名)mv file1.txt file2.txt dir/ /backup/把多个文件/目录一起移动到最后一个参数指定的目录mv -i file.txt /tmp/目标已存在时先询问是否覆盖(交互式,防误覆盖)mv -n file.txt /tmp/目标已存在时不覆盖、直接跳过mv -v *.log /var/log/archive/移动并逐条打印做了什么mv dir1 dir2若 dir2 不存在则重命名为 dir2;若 dir2 已存在则把 dir1 移入 dir2 下mv -- -weird-name.txt normal.txt用--处理以-开头的文件名mv ~/Downloads/report{_draft,}.md配合 shell 花括号展开快速改名(等价于 mv report_draft.md report.md)
语法格式
mv [OPTION]... SOURCE DEST
mv [OPTION]... SOURCE... DIRECTORY
两个参数且 DEST 不是已存在目录时为重命名;最后一个参数是已存在目录时为移动(可多源)。
OPTIONS
-i覆盖前询问确认-f强制覆盖,不询问(默认行为,主要用于压掉别名里的 -i)-n不覆盖已存在的目标文件-v显示移动过程-b覆盖前给目标文件做备份(GNU,生成~后缀备份)-u仅当源文件比目标新(或目标不存在)时才移动(GNU)-t DIR指定目标目录,源文件放后面(GNU,方便配合 xargs)--选项结束标记,其后参数一律视为文件名
备注
- 同一文件系统内 mv 只改目录项(rename),瞬间完成;跨文件系统时实际是「复制 + 删除」,大文件会慢且中断后可能留下不完整副本
-f、-i、-n同时出现时以最后一个为准;很多系统给 root 配了alias mv='mv -i',脚本里想强制覆盖要显式-f或用command mv- mv 目录时注意目标是否已存在:
mv dir existing当 existing 是已存在目录时是把 dir 移进它里面,与重命名行为不同,操作前先确认
rm
rm - 删除文件或目录(不可恢复)
常用命令
rm file.txt删除单个文件rm -i file.txt删除前逐个询问确认rm -f file.txt强制删除:忽略不存在的文件、不询问(脚本中常用)rm -r dir/递归删除目录及其全部内容rm -rf /tmp/build/强制递归删除,最常用也最危险的组合,敲回车前先确认路径rm -v *.tmp删除并逐条打印删了什么rm -- -file.txt删除以-开头的文件(或用rm ./-file.txt)find . -name '*.log' -mtime +30 -delete批量删除 30 天前的日志(比rm $(find ...)安全,不受文件名空格影响)rm -rI big_dir/删除超过 3 个文件时只询问一次(GNU,比 -i 省事又有兜底)
语法格式
rm [OPTION]... FILE...
删除目录必须加 -r(或 -d 删空目录)。
OPTIONS
-f强制删除,忽略不存在的文件,从不询问-i每个文件删除前询问-I删除超过 3 个文件或递归删除时询问一次(GNU)-r/-R递归删除目录-d删除空目录(等价 rmdir)-v显示删除过程--preserve-root拒绝对/递归删除(GNU 默认开启)--选项结束标记,防止文件名被当成选项
备注
- rm 删除是解除目录项链接,没有回收站;误删后立即停止对该分区写入,恢复概率才高。日常可考虑用 trash 类工具替代交互式删除
- 变量拼路径时务必防空值:
rm -rf "$DIR"/若 $DIR 为空会变成rm -rf /。脚本里用rm -rf "${DIR:?}"/让空值直接报错退出 - macOS/BSD rm 无
-I和--preserve-root;另外文件仍被进程打开时删除不会立刻释放磁盘空间(见 df 笔记)
tail
tail - 输出文件末尾部分,最常用来追日志
常用命令
tail /var/log/system.log显示文件最后 10 行(默认)tail -n 100 app.log显示最后 100 行tail -f /var/log/system.log实时跟踪日志新增内容,Ctrl-C 退出tail -F app.log跟踪且容忍文件被轮转/重建(按文件名重新打开,盯 logrotate 的日志用这个)tail -n 200 -f app.log先显示最后 200 行再继续跟踪tail -f app.log | grep --line-buffered ERROR实时过滤错误日志tail -n +2 data.csv从第 2 行开始输出到末尾(跳过 CSV 表头,注意是+)tail -f access.log error.log同时跟踪多个文件,输出带文件名分隔头tail -c 1024 dump.bin显示最后 1024 字节tail --pid=1234 -f build.log跟踪到指定进程退出后自动停止(GNU)
语法格式
tail [OPTION]... [FILE]...
多个 FILE 时每段输出前会打印 ==> file <== 头。
OPTIONS
-n N显示最后 N 行;-n +N表示从第 N 行开始直到末尾-c N显示最后 N 字节;同样支持+N-f跟踪文件追加内容(follow by descriptor)-F等价-f --retry,按文件名跟踪,文件被轮转后自动重新打开-q多文件时不打印文件名头-v总是打印文件名头-s N配合 -f,设置轮询间隔秒数--pid=PID配合 -f,PID 进程结束后停止跟踪(GNU)
备注
-f与-F的区别是排障关键:-f盯住文件描述符,日志被 logrotate 挪走后就再也读不到新内容;-F会按路径重开新文件。追长期运行的服务日志一律用-F- 老写法
tail -100 file在 GNU 下仍可用但已不推荐,POSIX 标准写法是-n 100 - 管道中
-f后接 grep/awk 要注意缓冲:grep 加--line-buffered,awk 用fflush(),否则输出会攒着不出来
touch
touch - 创建空文件,或更新已有文件的访问/修改时间戳
常用命令
touch newfile.txt文件不存在则创建空文件;已存在则把时间戳更新为当前时间touch a.txt b.txt c.txt一次创建/更新多个文件touch file{1..5}.txt配合 shell 展开批量创建 file1.txt ~ file5.txttouch -c file.txt只更新时间戳,文件不存在时不创建touch -m file.txt只更新修改时间(mtime)touch -a file.txt只更新访问时间(atime)touch -t 202601011200 file.txt把时间戳设为指定时间(格式 [CC]YYMMDDhhmm[.ss])touch -d '2026-07-01 09:00' file.txt用日期字符串设置时间(GNU)touch -r ref.txt file.txt把 file.txt 的时间戳设置成与 ref.txt 相同
语法格式
touch [OPTION]... FILE...
OPTIONS
-a只改访问时间 atime-m只改修改时间 mtime-c/--no-create文件不存在时不创建-t STAMP使用[[CC]YY]MMDDhhmm[.ss]格式指定时间-d STRING使用日期字符串指定时间(GNU;BSD 语义不同)-r FILE参照另一文件的时间戳-h作用于符号链接本身而非其指向的文件
备注
- touch 常见的两大用途:占位创建空文件;配合 make 等基于 mtime 的工具强制触发/跳过重建
- 更新他人文件的时间戳需要对该文件有写权限;任意指定时间戳(-t/-d/-r)需要是文件属主或 root
- 很多 Linux 挂载默认 relatime,atime 并非每次读取都更新,用 atime 做「最近访问」判断要留意挂载选项