终端快捷键
操作 | 快捷键 |
---|---|
跳到行首 | Ctrl + A |
跳到行尾 | Ctrl + E |
向前一个字符 | Ctrl + F / → |
向后一个字符 | Ctrl + B / ← |
向前一个单词 | Alt + F |
向后一个单词 | Alt + B |
删除到行首 | Ctrl + U |
删除到行尾 | Ctrl + K |
删除光标前一个单词 | Ctrl + W |
删除光标后一个单词 | Alt + D |
删除光标所在字符 | Ctrl + D |
粘贴上次删除内容 | Ctrl + Y |
撤销操作 | Ctrl + _ |
上一条历史命令 | Ctrl + P / ↑ |
下一条历史命令 | Ctrl + N / ↓ |
清屏 | Ctrl + L |
快速参数补全 | Tab |
历史命令查询与复用
💡 反向搜索历史命令(Ctrl + R
)不好用,更推荐使用 history
命令配合 grep
进行搜索:
1# 1. 批量查看所有包含 "ssh ku" 的历史命令编号和内容
2history | grep -i "ssh ku"
3
4# 2. 执行指定编号为 n 的历史命令
5!n
6
7# 或打印出指定编号的历史命令内容
8!n:p # 之后可通过「上一条历史命令」恢复历史命令并编辑
此外感叹号 !
也可以用来执行最近的命令:
1# 执行上一次输入的完整命令
2!!
3
4# 重新以 sudo 权限运行上一条命令
5sudo !!
⚠️ !
表达式执行的命令会立即运行,带有风险,建议先用 history
确认要复用的历史命令内容再使用。
多会话一致性与同步机制
默认情况下,Bash 的历史命令 history
不会在不同会话(session)之间实时同步。每个 Bash 会话都维护各自独立的内存缓存,只有在特定操作发生时才会与磁盘上的主历史文件(例如 ~/.bash_history
)进行读写操作:
- 写入主历史文件
- 当关闭当前 session,Bash 会把此会话的历史命令追加或写入到
~/.bash_history
- 手动执行
history -a
(append,推荐 ✅)可立即把当前 session 的历史追加到主文件 - 或
history -w
(write,覆盖,不推荐 ⚠️)会直接覆盖主文件,可能导致历史丢失
- 当关闭当前 session,Bash 会把此会话的历史命令追加或写入到
- 读取主历史文件
- 新开一个 session 时,Bash 会读取已有历史文件作为初始命令历史
- 若当前 session 需要看到其他 session 新增的历史命令,需要主动执行
history -r
(reload,重新读取)来同步
通配符高效操作文件
Globbing:源自 Unix 早期命令 glob(取自 global 的缩写),后来演变为动词 globbing,意指 Shell 对通配符进行的路径名展开。
Wildcard vs Globbing
- Wildcard(通配符):指代用于匹配文件名或路径的特殊字符,如
*
、?
、[]
等 - Globbing(模式匹配):Shell 检测并展开 wildcard 模式的过程,又称「路径名展开(pathname expansion)」
两词在日常口语里常互换使用,但严格来说 wildcard 是原材料,而 globbing 则是加工动作。
⚠️ 与正则表达式区别
Shell globs ≠ 正则表达式(regular expression,简写为 regex)。
- Glob 的星号
*
匹配「任意长度、任意字符」 - Regex 的
*
则是「重复前一个原子 0 次或多次」
在正则表达式领域,atom(原子)并不是普通化学用语的「原子」,而是一个专门术语,指「一个可以被量词(如
*
、+
、?
)作用的最小匹配单元」
- 一个原子可以是:
- 单个普通字符,例如
a
- 一个转义字符,例如
\d
(等价于[0-9]
)- 一个字符类,如
[A-Z]
- 一个括号分组,例如
(abc)
- 一个断言,如
^
或\b
大括号 {}
:批量枚举或批量生成。
1touch file{w1,w2,w3,x1,x2,x3}{.txt,.log}
创建 filew1.txt
、filew1.log
、filew2.txt
、filew2.log
等文件。
星号 *
:匹配任意数量的任意字符(包括零个)。
1ll file*.txt
列出所有以 file
开头,后面跟任意字符并以 .txt
结尾的文件,如 filew1.txt
、filew2.txt
等。
问号 ?
:匹配任意单个字符。
1ll file?1.txt
列出所有以 file
开头,后面跟一个任意字符并以 1.txt
结尾的文件,如 filew1.txt
、filex1.txt
等。
方括号 []
:匹配括号内的任意一个字符。
1ll file[wx][13].txt
列出所有以 file
开头,后面跟 w
或 x
,再跟 1
或 3
并以 .txt
结尾的文件,如 filew1.txt
、filex3.txt
等。
感叹号 !
和插入符 ^
:[!abc]
或 [^abc]
匹配除了 a
、b
、c
之外的任意单个字符。
1ll file*[^13].*
列出所有以 file
开头,后面跟任意字符但不包含 1
或 3
的文件,如 filew2.txt
、filex2.log
等。
使用技巧与注意事项
- 通配符通常与
ls
、cp
、mv
、rm
等命令组合,实现批量文件操作 - 通配符只在命令参数中生效,Shell 会自动扩展匹配的文件名。如果没有匹配项,表达式会被当作文本处理
- 通配符不能被引号包裹,因为引号会「阻止 Shell 提前展开」通配符
- 比如:
ll "*.txt"
会列出名为*.txt
的文件,而不是所有.txt
文件
- 比如:
*
不会匹配以点(.
)开头的隐藏文件,除非开启让通配符匹配隐藏文件的功能(shopt -s dotglob
)- 关闭让通配符匹配隐藏文件的功能:
shopt -u dotglob
- 关闭让通配符匹配隐藏文件的功能:
- 可以用反斜杠
\*
或引号禁用通配特性,使其按普通字符处理
高效查找文件
find vs locate
find
:实时遍历磁盘,支持复杂条件与批量操作,速度受目录规模影响,结果最准确- 使用场景:准确性、实时性或复杂操作
locate
:查询预先索引的数据库(sudo updatedb
),几乎瞬时返回文件名匹配结果,但需等待索引更新,功能相对单一- 使用场景:速度优先且只查文件名
按名称快速定位文件
1# 精确匹配(大小写敏感)
2find . -name "config.toml"
3
4# 模糊匹配且忽略大小写
5find . -iname "*time*.html"
为什么 find . -iname "*time*.html"
可以把通配符放在引号里? 🤔 ✨
关键在于理解:Shell 先展开与程序后匹配的区别。
流程详解:
步骤 | 未加引号 *time*.html | 加引号 "*time*.html" |
---|---|---|
1. Shell 接收命令行 | 立即在当前目录做文件名展开(globbing),如果正好存在 readtime.html 等文件,Shell 会把它们列成参数。若不存在匹配文件,Shell 原样保留 *time*.html | 因为有引号,Shell 不做任何展开,参数保持字面量 *time*.html |
2. 调用 find 程序 | 可能得到一长串已展开的文件名,也可能得到 *time*.html (取决于第 1 步结果) | 始终得到 *time*.html 这一模式字符串 |
3. find 解析 -name/-iname 模式 | 如果收到的是文件名列表,find 会把它们当作多个独立表达式,往往导致语法错误或者仅在当前目录生效,结果预期出路巨大 | find 按设计使用通配符去递归匹配每个文件的「基名」,得到正确结果 |
结论:想让
find
自己处理通配符,就必须阻止 Shell 抢先展开——最简单方式就是用单/双引号或反斜杠\*
进行引用。
查空文件或目录
1# 查空文件/目录
2find . -empty
按时间维度查文件
1# 查找最近 2 小时(=120 分钟)内被修改过的文件
2find . -type f -mmin -120
3
4# 查找 2 小时到 4 小时之间(120~240 min)改动的文件
5find . -type f -mmin +120 -mmin -240
6
7# 查找比 `README.md` 内容修改时间更晚的文件
8find . -newer README.md
Linux 文件有三种主要时间戳:
- 修改时间(modify time):文件内容最后被更改的时间
-mtime
:按天查找文件修改时间-mmin
:按分钟查找文件修改时间-newer
:实际只比较mtime
- 访问时间(access time):上次读取文件内容的时间(如用
cat
查阅,但没改动内容)-atime
:按天查找文件访问时间-amin
:按分钟查找文件访问时间
- 状态改变时间(change time):属性变化时更新时间,例如权限、所有者变化,或者
mtime
被更新时-ctime
:按天查找文件状态改变时间(如权限变更-cmin
:按分钟查找文件状态改变时间(如权限变更)
- 比如:
- 文件新建时,
atime
/mtime
/ctime
都等于创建时间 - 保存内容修改,
mtime
和ctime
都更新 - 仅
chmod
改权限,只有ctime
更新
- 文件新建时,
避免递归遍历查询更快
1# 仅列当前目录的 `.log` 文件
2find . -maxdepth 1 -type f -name "*.log"
3
4# 统计当前目录中 Java 文件数量
5find . -maxdepth 1 -type f -name "*.java" | wc -l
6
7# 搜索当前目录中 `文件大小 > 10 MB` 的文件
8find . -maxdepth 1 -type f -size +10M
按内容搜源码
1# 排除除任意层级下的 `.venv` 目录,再在 `.py` 文件中查 `woff2` 相关内容
2find . -path "*/.venv" -prune -o -type f -name "*.py" -exec grep -nH "woff2" {} +
3
4# 排除除任意层级下的 `dist` 或 `.venv` 目录,再在 `.py` 文件中查 `woff2` 相关内容
5find . \( -path "*/dist" -o -path "*/.venv" \) -prune -o -type f -name "*.py" -exec grep -nH "woff2" {} +
6
7# 或使用 GNU `grep` 代替会更简洁
8grep -rnH --include="*.py" --exclude-dir={dist,.venv} "woff2" .
ps 与 grep 命令
ps
命令用于显示系统中当前运行的进程信息,grep
命令用于在文本中查找匹配的内容,两者经常结合使用(通过管道符 |
)来方便地搜索和筛选出特定的进程信息。比如列出所有包含 "nginx"
关键字的进程:
1ps aux | grep -i "nginx"
✨ 由于 grep
本身也会被 ps
输出(因为执行了 grep
进程),要避免这一行可以用如下写法:
1ps aux | grep -i "[n]ginx"
这样 grep
只会匹配 nginx
进程,而不会把自身(grep nginx
)带入匹配。