-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
394 lines (394 loc) · 326 KB
/
Copy pathatom.xml
File metadata and controls
394 lines (394 loc) · 326 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<author>
<name>Code Water</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<id>https://blog.codewater.top/</id>
<link href="https://blog.codewater.top/" rel="alternate"/>
<link href="https://blog.codewater.top/atom.xml" rel="self"/>
<rights>All rights reserved 2026, Code Water</rights>
<subtitle>阿水</subtitle>
<title>Code Water</title>
<updated>2026-07-16T08:32:14.000Z</updated>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="SSH" scheme="https://blog.codewater.top/tags/SSH/"/>
<content>
<![CDATA[<p>每次连服务器输一遍密码,IP 和端口记在备忘录里翻着敲——这套体验其实配置一次就能永久甩掉:密钥登录免掉密码,config 别名免掉记 IP,剩下的就是 <code>ssh myserver</code> 三个词连上任何机器。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。</p><h2 id="ssh-基本连接"><a href="#ssh-基本连接" class="headerlink" title="ssh 基本连接"></a>ssh 基本连接</h2><p><code>ssh</code> 是加密的远程登录协议和客户端命令,连上之后拿到的就是远程机器的 shell:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ssh deploy@192.168.1.100</span><br><span class="line"><span class="comment"># 以 deploy 用户身份连接目标主机,默认端口 22</span></span><br><span class="line"></span><br><span class="line">ssh -p 2222 deploy@192.168.1.100</span><br><span class="line"><span class="comment"># -p:指定端口(小写 p)——注意这个细节,后面讲 scp 时它会变成一个坑</span></span><br></pre></td></tr></table></figure><h2 id="ssh-keygen:生成密钥对"><a href="#ssh-keygen:生成密钥对" class="headerlink" title="ssh-keygen:生成密钥对"></a>ssh-keygen:生成密钥对</h2><p>密钥登录的原理一句话:生成一对钥匙,<strong>公钥放到服务器上,私钥留在本地绝不外传</strong>;连接时客户端用私钥证明身份,服务器用公钥验证,全程不需要密码。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t ed25519 -C <span class="string">"you@example.com"</span></span><br><span class="line"><span class="comment"># -t:密钥类型,ed25519 是当前 OpenSSH 的默认推荐——密钥短、速度快、安全性好</span></span><br><span class="line"><span class="comment"># -C:注释,通常写邮箱,方便在服务器上区分这是谁的哪把钥匙</span></span><br><span class="line"><span class="comment"># 一路回车用默认路径(~/.ssh/id_ed25519),会问要不要设 passphrase:</span></span><br><span class="line"><span class="comment"># 设了更安全(私钥文件被偷也用不了),个人机器图省事可以留空</span></span><br></pre></td></tr></table></figure><p>生成后 <code>~/.ssh/</code> 下多出两个文件:<code>id_ed25519</code> 是私钥(权限 600,谁都别给),<code>id_ed25519.pub</code> 是公钥(就是要放到服务器上的那个)。</p><p>需要管理多台机器、每台用独立密钥时(比如公司服务器一把、自己 VPS 一把、GitHub 再一把),用 <code>-f</code> 给每把钥匙指定自己的文件名,不然后生成的会覆盖先生成的:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t ed25519 -C <span class="string">"you@example.com"</span> -f ~/.ssh/id_ed25519_work</span><br><span class="line"><span class="comment"># -f:指定密钥文件路径和名称,生成 id_ed25519_work(私钥)和</span></span><br><span class="line"><span class="comment"># id_ed25519_work.pub(公钥),不动默认的 id_ed25519</span></span><br><span class="line"></span><br><span class="line">ssh-keygen -t ed25519 -C <span class="string">"you@example.com"</span> -f ~/.ssh/id_ed25519_vps</span><br><span class="line"><span class="comment"># 再给自己的 VPS 单独生成一把,两把钥匙互不干扰</span></span><br></pre></td></tr></table></figure><p>多把钥匙怎么让 ssh 知道连哪台机器用哪把?靠的就是后面 <code>~/.ssh/config</code> 里的 <code>IdentityFile</code> 字段——每台服务器的配置段里指定自己的那把私钥。</p><h2 id="ssh-copy-id:把公钥装到服务器上"><a href="#ssh-copy-id:把公钥装到服务器上" class="headerlink" title="ssh-copy-id:把公钥装到服务器上"></a>ssh-copy-id:把公钥装到服务器上</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id deploy@192.168.1.100</span><br><span class="line"><span class="comment"># 把本地公钥追加到服务器的 ~/.ssh/authorized_keys 里</span></span><br><span class="line"><span class="comment"># 这一步还需要输一次密码——这是最后一次,装好之后就免密了</span></span><br><span class="line"></span><br><span class="line">ssh deploy@192.168.1.100</span><br><span class="line"><span class="comment"># 再连就直接进去了,不再询问密码</span></span><br></pre></td></tr></table></figure><p>没有 <code>ssh-copy-id</code> 的环境(比如从 Windows 连),手动做等价的事:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> ~/.ssh/id_ed25519.pub | ssh deploy@192.168.1.100 <span class="string">"mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"</span></span><br><span class="line"><span class="comment"># 把公钥内容追加到服务器的 authorized_keys 文件</span></span><br></pre></td></tr></table></figure><p>免密不生效时,第一个该查的是服务器端的目录权限——sshd 对权限要求很严格,太宽松会直接拒绝密钥登录:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 700 ~/.ssh</span><br><span class="line"><span class="built_in">chmod</span> 600 ~/.ssh/authorized_keys</span><br><span class="line"><span class="comment"># ~/.ssh 必须 700、authorized_keys 必须 600(或更严)</span></span><br><span class="line"><span class="comment"># 权限位的含义见 chmod 那篇:700 = 只有属主能读写进入</span></span><br></pre></td></tr></table></figure><h2 id="ssh-config:给服务器起别名"><a href="#ssh-config:给服务器起别名" class="headerlink" title="~/.ssh/config:给服务器起别名"></a>~/.ssh/config:给服务器起别名</h2><p>密钥解决了密码,config 解决记 IP。在本地 <code>~/.ssh/config</code> 里给每台服务器写一段:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Host myserver</span><br><span class="line"> HostName 192.168.1.100</span><br><span class="line"> User deploy</span><br><span class="line"> Port 2222</span><br><span class="line"> IdentityFile ~/.ssh/id_ed25519</span><br><span class="line"># Host:你给它起的别名,后面连接时用这个名字</span><br><span class="line"># HostName:真实 IP 或域名</span><br><span class="line"># User/Port:默认用户和端口,连接时就不用每次写</span><br><span class="line"># IdentityFile:指定用哪把私钥,只有一把钥匙时可以省略</span><br></pre></td></tr></table></figure><p>配好之后:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ssh myserver</span><br><span class="line"><span class="comment"># 等价于 ssh -p 2222 -i ~/.ssh/id_ed25519 deploy@192.168.1.100</span></span><br></pre></td></tr></table></figure><p>管理多台服务器时价值更明显——每台一段配置,名字起得有意义(<code>prod-web</code>、<code>test-db</code>),连哪台敲哪个名字,IP 和端口再也不用记。<code>scp</code>、<code>rsync</code> 也认这份配置,别名全程通用。</p><h2 id="scp:基于-SSH-传文件"><a href="#scp:基于-SSH-传文件" class="headerlink" title="scp:基于 SSH 传文件"></a>scp:基于 SSH 传文件</h2><p><code>scp</code> 走 SSH 通道在本地和远程之间拷贝文件,语法和 <code>cp</code> 一致——把其中一端写成 <code>用户@主机:路径</code> 就行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">scp app.tar.gz myserver:/opt/deploy/</span><br><span class="line"><span class="comment"># 上传:本地文件 → 远程目录(这里直接用了 config 里的别名)</span></span><br><span class="line"></span><br><span class="line">scp myserver:/var/log/app.log ./</span><br><span class="line"><span class="comment"># 下载:远程文件 → 本地当前目录</span></span><br><span class="line"></span><br><span class="line">scp -r ./dist myserver:/opt/deploy/</span><br><span class="line"><span class="comment"># -r:整个目录递归拷贝</span></span><br></pre></td></tr></table></figure><p>一个必踩一次的坑:<strong>scp 指定端口用大写 -P</strong>,和 ssh 的小写 <code>-p</code> 不一样——因为 scp 的小写 <code>-p</code> 已经被”保留文件的修改时间和权限位”占用了(man 手册原文就是这么解释的):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">scp -P 2222 app.tar.gz deploy@192.168.1.100:/opt/deploy/</span><br><span class="line"><span class="comment"># -P:端口,大写——写成小写 -p 不会报错,但那是"保留时间戳"的意思,</span></span><br><span class="line"><span class="comment"># 端口会走默认 22,连接失败还不容易看出原因</span></span><br><span class="line"><span class="comment"># 用 ~/.ssh/config 配好别名可以彻底绕开这个坑:端口写在配置里</span></span><br></pre></td></tr></table></figure><p>偶尔传一两个文件 <code>scp</code> 最顺手;大目录、大文件、需要断点续传或增量同步的场景,换 <a href="/rsync%E5%A2%9E%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E6%88%98-%E5%B8%B8%E7%94%A8%E5%8F%82%E6%95%B0%E4%B8%8E%E8%BF%9C%E7%A8%8B%E5%90%8C%E6%AD%A5">rsync</a> 更合适。</p><h2 id="常用命令速查表"><a href="#常用命令速查表" class="headerlink" title="常用命令速查表"></a>常用命令速查表</h2><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>ssh user@host</code></td><td>远程登录,默认端口 22</td></tr><tr><td><code>ssh -p 端口</code></td><td>指定端口(小写 p)</td></tr><tr><td><code>ssh-keygen -t ed25519</code></td><td>生成密钥对,ed25519 是当前默认推荐</td></tr><tr><td><code>ssh-copy-id user@host</code></td><td>把公钥装到服务器,装完免密</td></tr><tr><td><code>chmod 700 ~/.ssh</code> + <code>600 authorized_keys</code></td><td>免密不生效先查这个权限</td></tr><tr><td><code>~/.ssh/config</code></td><td>别名配置,<code>ssh 别名</code> 直连,scp/rsync 通用</td></tr><tr><td><code>scp 文件 host:路径</code></td><td>上传;反过来写就是下载</td></tr><tr><td><code>scp -r</code></td><td>整目录递归拷贝</td></tr><tr><td><code>scp -P 端口</code></td><td>指定端口是大写 P,小写 p 是保留时间戳</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>一次性的配置换永久的顺手:<code>ssh-keygen</code> 生成 ed25519 密钥、<code>ssh-copy-id</code> 装到服务器、<code>~/.ssh/config</code> 起好别名,之后连任何机器都是 <code>ssh 名字</code> 一步到位。传文件 <code>scp</code> 顺手用,记住端口是大写 <code>-P</code>;要增量和断点续传,隔壁 <code>rsync</code> 伺候。</p>]]>
</content>
<id>https://blog.codewater.top/SSH%E5%85%8D%E5%AF%86%E7%99%BB%E5%BD%95%E4%B8%8Escp%E5%AE%9E%E6%88%98-%E5%AF%86%E9%92%A5%E9%85%8D%E7%BD%AE%E4%B8%8E%E4%BC%A0%E6%96%87%E4%BB%B6/</id>
<link href="https://blog.codewater.top/SSH%E5%85%8D%E5%AF%86%E7%99%BB%E5%BD%95%E4%B8%8Escp%E5%AE%9E%E6%88%98-%E5%AF%86%E9%92%A5%E9%85%8D%E7%BD%AE%E4%B8%8E%E4%BC%A0%E6%96%87%E4%BB%B6/"/>
<published>2026-07-14T12:48:27.000Z</published>
<summary>ssh-keygen 生成密钥、ssh-copy-id 免密登录、config 起别名、scp 传文件(-P 是大写)一篇配齐。</summary>
<title>SSH 免密登录与 scp 实战:密钥、config 别名与传文件</title>
<updated>2026-07-16T08:32:14.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="systemd" scheme="https://blog.codewater.top/tags/systemd/"/>
<category term="运维" scheme="https://blog.codewater.top/tags/%E8%BF%90%E7%BB%B4/"/>
<content>
<![CDATA[<p>服务器重启之后服务没起来,明明之前 <code>systemctl start</code> 跑得好好的——因为 <code>start</code> 和 <code>enable</code> 是两件独立的事,只做了前者,服务就只活到下一次重启为止。这类概念没对齐的坑,systemd 里还有几个。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;<a href="/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8E%92%E6%9F%A5%E6%9C%8D%E5%8A%A1%E9%97%AE%E9%A2%98%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">服务器排查那篇</a> 列了 systemctl/journalctl 的基本命令,这篇讲背后的概念和进阶用法。</p><h2 id="systemd-和-unit:先对齐两个词"><a href="#systemd-和-unit:先对齐两个词" class="headerlink" title="systemd 和 unit:先对齐两个词"></a>systemd 和 unit:先对齐两个词</h2><p>systemd 是现代主流 Linux 发行版(Ubuntu 16.04+、CentOS 7+、Debian 8+)的服务管理器,负责开机按依赖顺序拉起服务、运行期间盯着它们的状态。它管理的最小单位叫 unit——服务只是其中最常见的一类(<code>.service</code>),此外还有定时器(<code>.timer</code>)、挂载点(<code>.mount</code>)等类型。日常说”管服务”,操作的就是 service 类型的 unit。</p><h2 id="start-管现在,enable-管开机:两件独立的事"><a href="#start-管现在,enable-管开机:两件独立的事" class="headerlink" title="start 管现在,enable 管开机:两件独立的事"></a>start 管现在,enable 管开机:两件独立的事</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">systemctl start nginx</span><br><span class="line"><span class="comment"># 现在就把服务跑起来——但只管这一次,重启机器后不会自动再起</span></span><br><span class="line"></span><br><span class="line">systemctl <span class="built_in">enable</span> nginx</span><br><span class="line"><span class="comment"># 设置开机自启——但不影响现在,跑这条命令时服务并不会立刻启动</span></span><br><span class="line"></span><br><span class="line">systemctl <span class="built_in">enable</span> --now nginx</span><br><span class="line"><span class="comment"># 两件事一步做完:设置开机自启,并且现在就启动</span></span><br><span class="line"></span><br><span class="line">systemctl <span class="built_in">disable</span> nginx</span><br><span class="line"><span class="comment"># 取消开机自启,同样不影响当前运行状态</span></span><br><span class="line"></span><br><span class="line">systemctl is-enabled nginx</span><br><span class="line"><span class="comment"># 查询是否已设置开机自启,输出 enabled / disabled</span></span><br></pre></td></tr></table></figure><p>两者完全正交:<code>start</code> 了没 <code>enable</code>,重启后服务消失;<code>enable</code> 了没 <code>start</code>,当前这会儿服务并没有跑。部署新服务的标准动作是 <code>enable --now</code>,一次把两头都管上。</p><p><code>stop</code> 的行为也值得知道:它默认先给服务进程发 SIGTERM 请求优雅退出,超时还没退才升级为 SIGKILL——正是 <a href="/kill%E4%BF%A1%E5%8F%B7%E6%9C%BA%E5%88%B6%E4%B8%8Enice%E4%BC%98%E5%85%88%E7%BA%A7%E5%AE%9E%E6%88%98">kill 信号那篇</a> 讲的”先礼后兵”流程,systemd 帮你做好了。</p><h2 id="systemctl-status-输出怎么读"><a href="#systemctl-status-输出怎么读" class="headerlink" title="systemctl status 输出怎么读"></a>systemctl status 输出怎么读</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">● nginx.service - A high performance web server</span><br><span class="line"> Loaded: loaded (/lib/systemd/system/nginx.service; enabled; ...)</span><br><span class="line"> Active: active (running) since Tue 2026-07-14 09:00:12 CST; 2h ago</span><br><span class="line"> Main PID: 1235 (nginx)</span><br><span class="line"> Memory: 12.4M</span><br><span class="line"># Loaded 行看两个信息:unit 文件在哪、有没有设置开机自启(enabled/disabled)</span><br><span class="line"># Active 行是当前状态:active (running) 正常;failed 是启动失败——</span><br><span class="line"># failed 时下面通常直接附着最近几条报错日志,排查从这里入手最快</span><br></pre></td></tr></table></figure><h2 id="unit-文件放哪、怎么改"><a href="#unit-文件放哪、怎么改" class="headerlink" title="unit 文件放哪、怎么改"></a>unit 文件放哪、怎么改</h2><p>unit 文件有两个主要目录,优先级不同:</p><ul><li><code>/lib/systemd/system/</code>(部分发行版是 <code>/usr/lib/systemd/system/</code>):包管理器安装软件时放的,<strong>别直接改</strong>——包升级会把改动覆盖掉</li><li><code>/etc/systemd/system/</code>:管理员自己的地盘,优先级更高——同名 unit 在两边都存在时,以这里的为准</li></ul><p>自己部署一个 Go 服务,最小可用的 unit 文件长这样:</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /etc/systemd/system/myapp.service</span></span><br><span class="line"><span class="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=My Go App</span><br><span class="line"><span class="attr">After</span>=network.target</span><br><span class="line"><span class="comment"># After:等网络就绪后再启动本服务</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">ExecStart</span>=/opt/myapp/myapp</span><br><span class="line"><span class="comment"># 要运行的命令,必须是绝对路径</span></span><br><span class="line"><span class="attr">Restart</span>=<span class="literal">on</span>-failure</span><br><span class="line"><span class="comment"># 进程异常退出时自动拉起,正常 stop 不会触发</span></span><br><span class="line"><span class="attr">User</span>=myapp</span><br><span class="line"><span class="comment"># 用哪个用户身份运行,不写默认 root——生产环境建议专门建个低权限用户</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=multi-user.target</span><br><span class="line"><span class="comment"># enable 时挂到哪个启动目标下,服务类 unit 基本都写这个</span></span><br></pre></td></tr></table></figure><p>改完 unit 文件(新建、修改都算)有一步必做:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">systemctl daemon-reload</span><br><span class="line"><span class="comment"># 让 systemd 重新读取 unit 文件——不跑这条,systemd 继续用内存里的旧配置,</span></span><br><span class="line"><span class="comment"># 你改的内容看起来"完全不生效",这是 unit 文件改了没反应的头号原因</span></span><br><span class="line"></span><br><span class="line">systemctl <span class="built_in">enable</span> --now myapp</span><br><span class="line"><span class="comment"># 然后正常启动</span></span><br></pre></td></tr></table></figure><h2 id="journalctl:按服务、时间、级别过滤日志"><a href="#journalctl:按服务、时间、级别过滤日志" class="headerlink" title="journalctl:按服务、时间、级别过滤日志"></a>journalctl:按服务、时间、级别过滤日志</h2><p>systemd 托管的服务,标准输出和标准错误都被 journald 收集,用 <code>journalctl</code> 查,不用满盘找日志文件:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">journalctl -u myapp</span><br><span class="line"><span class="comment"># -u:只看指定服务的日志,从最早开始分页显示</span></span><br><span class="line"></span><br><span class="line">journalctl -u myapp -f</span><br><span class="line"><span class="comment"># -f:实时跟踪新日志,等价于 tail -f 的体验</span></span><br><span class="line"></span><br><span class="line">journalctl -u myapp -n 100</span><br><span class="line"><span class="comment"># -n:只看最近 100 行</span></span><br><span class="line"></span><br><span class="line">journalctl -u myapp --since <span class="string">"2026-07-14 09:00"</span> --<span class="keyword">until</span> <span class="string">"2026-07-14 10:00"</span></span><br><span class="line"><span class="comment"># --since/--until:按时间段过滤,也接受 "1 hour ago"、"yesterday" 这类相对写法</span></span><br><span class="line"></span><br><span class="line">journalctl -u myapp -p err</span><br><span class="line"><span class="comment"># -p:按级别过滤,err 只看错误及以上(emerg/alert/crit/err)</span></span><br><span class="line"></span><br><span class="line">journalctl -u myapp -b</span><br><span class="line"><span class="comment"># -b:只看本次开机以来的日志——排查"重启后起不来"时配合 -p err 特别好用</span></span><br></pre></td></tr></table></figure><h2 id="常用命令速查表"><a href="#常用命令速查表" class="headerlink" title="常用命令速查表"></a>常用命令速查表</h2><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>systemctl start/stop/restart 服务</code></td><td>启动 / 停止 / 重启(只管当下)</td></tr><tr><td><code>systemctl reload 服务</code></td><td>平滑重载配置,不中断服务(需服务支持)</td></tr><tr><td><code>systemctl enable --now 服务</code></td><td>设置开机自启并立刻启动</td></tr><tr><td><code>systemctl is-enabled 服务</code></td><td>查是否开机自启</td></tr><tr><td><code>systemctl status 服务</code></td><td>状态 + 最近日志,看 Loaded/Active 两行</td></tr><tr><td><code>systemctl daemon-reload</code></td><td>改过 unit 文件后必跑,否则改动不生效</td></tr><tr><td><code>journalctl -u 服务 -f</code></td><td>实时跟踪服务日志</td></tr><tr><td><code>journalctl -u 服务 --since "1 hour ago"</code></td><td>按时间过滤</td></tr><tr><td><code>journalctl -u 服务 -p err -b</code></td><td>本次开机以来的错误日志</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>systemd 的坑基本都是概念错位:<code>start</code> 和 <code>enable</code> 各管一头(记住 <code>enable --now</code>)、unit 文件改在 <code>/etc/systemd/system/</code> 且改完必须 <code>daemon-reload</code>。日志这边 <code>journalctl -u</code> 加上时间和级别过滤,比去 <code>/var/log</code> 翻文件体面得多。这套流程走顺了,自己部署的服务和发行版自带的服务就是同一套管理方式。</p>]]>
</content>
<id>https://blog.codewater.top/systemctl-journalctl%E5%AE%9E%E6%88%98-%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86%E4%B8%8E%E6%97%A5%E5%BF%97%E8%BF%87%E6%BB%A4/</id>
<link href="https://blog.codewater.top/systemctl-journalctl%E5%AE%9E%E6%88%98-%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86%E4%B8%8E%E6%97%A5%E5%BF%97%E8%BF%87%E6%BB%A4/"/>
<published>2026-07-14T12:46:22.000Z</published>
<summary>systemctl 的 enable 和 start 有什么区别、unit 文件放哪、journalctl 怎么按时间和级别过滤日志。</summary>
<title>systemctl 与 journalctl 实战:enable 和 start 的区别、unit 文件与日志过滤</title>
<updated>2026-07-14T12:46:22.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="进程" scheme="https://blog.codewater.top/tags/%E8%BF%9B%E7%A8%8B/"/>
<category term="监控" scheme="https://blog.codewater.top/tags/%E7%9B%91%E6%8E%A7/"/>
<content>
<![CDATA[<p><code>top</code> 显示 load average 12.5——这个数粗略代表”平均有多少个任务在抢 CPU 或者排队等资源”,越大说明机器越忙,12.5 看着已经很吓人了;可 CPU 使用率却只有 30%,这台机器到底是忙还是闲?<code>free</code> 显示 free 列只剩几百 M,服务却跑得好好的——内存到底够不够?监控命令谁都会敲,但输出里的指标读不懂,跑了也白跑。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;<a href="/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8E%92%E6%9F%A5%E6%9C%8D%E5%8A%A1%E9%97%AE%E9%A2%98%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">服务器排查那篇</a> 给了 <code>ps</code>/<code>top</code>/<code>free</code> 的基本用法,这篇专讲输出怎么读懂。</p><h2 id="ps-aux-的-STAT-列:进程现在处于什么状态"><a href="#ps-aux-的-STAT-列:进程现在处于什么状态" class="headerlink" title="ps aux 的 STAT 列:进程现在处于什么状态"></a>ps aux 的 STAT 列:进程现在处于什么状态</h2><p><code>ps aux</code> 输出里的 STAT 列是最有信息量、也最少人看懂的一列。第一个字符是进程的主状态,就五种:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">R 运行中或在运行队列里排队等 CPU</span><br><span class="line">S 可中断睡眠——在等某个事件(网络数据、锁、定时器),绝大多数进程平时都是这个状态</span><br><span class="line">D 不可中断睡眠——通常是卡在磁盘 IO 上;这个状态的进程不响应任何信号,</span><br><span class="line"> 连 kill -9 都杀不动,只能等 IO 完成或者重启机器</span><br><span class="line">Z 僵尸进程——已经退出了,但父进程还没回收它的退出状态;</span><br><span class="line"> 杀僵尸本身没用(它已经死了),要处理的是它的父进程</span><br><span class="line">T 被暂停(收到 SIGTSTP/SIGSTOP,比如按了 Ctrl-Z)</span><br></pre></td></tr></table></figure><p>主状态后面可能还挂着几个后缀,常见的有这些:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">s 会话首进程(session leader)</span><br><span class="line">l 多线程进程</span><br><span class="line">+ 在前台进程组(占着终端)</span><br><span class="line">< 高优先级(nice 值为负,抢 CPU 更凶)</span><br><span class="line">N 低优先级(nice 值为正,会让着别的进程)</span><br><span class="line">L 有内存页被锁定在物理内存里,不允许换出(数据库进程常见)</span><br></pre></td></tr></table></figure><p>比如 <code>Ss</code> 读作”睡眠中的会话首进程”(守护进程的典型状态),<code>R+</code> 读作”正在前台运行”,<code>S<</code> 读作”睡眠中的高优先级进程”。<code><</code> 和 <code>N</code> 这两个后缀对应的 nice 优先级机制,在 <a href="/kill%E4%BF%A1%E5%8F%B7%E6%9C%BA%E5%88%B6%E4%B8%8Enice%E4%BC%98%E5%85%88%E7%BA%A7%E5%AE%9E%E6%88%98">kill 信号那篇</a> 的 nice/renice 部分讲过。</p><p>排查时有两个状态要特别敏感:<strong>一堆进程卡在 D 状态,基本可以断定磁盘 IO 出了问题</strong>(盘太慢、NFS 挂了);Z 状态零星一两个无害,大量出现说明父进程的代码有 bug(没有 wait 子进程)。<code>kill</code> 对这两种状态都无能为力,原因在 <a href="/kill%E4%BF%A1%E5%8F%B7%E6%9C%BA%E5%88%B6%E4%B8%8Enice%E4%BC%98%E5%85%88%E7%BA%A7%E5%AE%9E%E6%88%98">kill 信号那篇</a> 讲过——D 状态不响应信号,Z 已经死了没什么可杀的。</p><h2 id="VSZ-和-RSS:两个”内存占用”差在哪"><a href="#VSZ-和-RSS:两个”内存占用”差在哪" class="headerlink" title="VSZ 和 RSS:两个”内存占用”差在哪"></a>VSZ 和 RSS:两个”内存占用”差在哪</h2><p>还是看 <code>ps aux</code> 的输出——STAT 前面还有两列跟内存相关的数字,VSZ 和 RSS,多数人从来没细看过它们的区别:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">USER PID %CPU %MEM VSZ RSS ... COMMAND</span><br><span class="line">root 12345 15.2 8.4 2150432 342012 ... java -jar app.jar</span><br></pre></td></tr></table></figure><ul><li>VSZ(Virtual Set Size):进程的虚拟地址空间总大小,包括映射了但从没真正用过的内存、共享库等,通常大得吓人且没什么诊断价值</li><li>RSS(Resident Set Size):进程实际占用的物理内存</li></ul><p>两列的单位都是 KiB(1024 字节)——上面这行的 VSZ 2150432 KiB 约等于 2.05 GiB,RSS 342012 KiB 约 334 MiB。<code>ps</code> 不会帮你换算成人类可读单位,位数多的时候自己心算除两次 1024。</p><p>看一个进程”到底吃了多少内存”、判断有没有内存泄漏,盯 RSS——它持续上涨才是真的在吃内存;VSZ 大不代表任何问题。</p><h2 id="load-average:三个数字和核数一起看"><a href="#load-average:三个数字和核数一起看" class="headerlink" title="load average:三个数字和核数一起看"></a>load average:三个数字和核数一起看</h2><p><code>top</code> 第一行和 <code>uptime</code> 都会显示 load average,三个数字分别是最近 1 分钟、5 分钟、15 分钟的平均负载——粗略理解为”平均有多少个任务在使用或等待 CPU(Linux 还包括 D 状态等 IO 的进程)”。</p><p>关键在于:<strong>这个数字必须和 CPU 核数一起看</strong>。load 4.0 对单核机器是严重过载,对 16 核机器只是热身:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">nproc</span></span><br><span class="line"><span class="comment"># 查看 CPU 核数,load average 除以这个数才是"每核负载"</span></span><br><span class="line"><span class="comment"># 经验法则:每核负载持续接近或超过 1,机器就开始排队了</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">uptime</span></span><br><span class="line"><span class="comment"># load average: 12.50, 8.20, 3.10</span></span><br><span class="line"><span class="comment"># 三个数字还能看趋势:1 分钟 > 15 分钟说明负载正在上升(事情正在恶化),</span></span><br><span class="line"><span class="comment"># 反过来说明高峰已经过去,正在恢复</span></span><br></pre></td></tr></table></figure><p>开头那个问题的答案也在这:load 12.5 但 CPU 只用了 30%,多半是大量进程卡在 D 状态等 IO——load 算上了它们,CPU 却是闲的。这种”高 load 低 CPU”的组合,指向的是磁盘/网络存储瓶颈,不是 CPU 不够。</p><h2 id="top:界面输出怎么读"><a href="#top:界面输出怎么读" class="headerlink" title="top:界面输出怎么读"></a>top:界面输出怎么读</h2><p><code>top</code> 进入后是实时刷新界面,上半部分是系统摘要,下半部分是进程列表:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">top - 16:35:52 up 579 days, 22:14, 3 users, load average: 1.12, 1.23, 1.27</span><br><span class="line">Tasks: 230 total, 1 running, 229 sleeping, 0 stopped, 0 zombie</span><br><span class="line">%Cpu(s): 14.3 us, 12.2 sy, 0.0 ni, 71.0 id, 0.0 wa, 0.0 hi, 0.0 si, 2.4 st</span><br><span class="line">KiB Mem : 8008584 total, 680256 free, 5050156 used, 2278172 buff/cache</span><br><span class="line">KiB Swap: 0 total, 0 free, 0 used. 2205612 avail Mem</span><br><span class="line"></span><br><span class="line"> PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND</span><br><span class="line"> 1747 root 20 0 4044748 166192 612 S 102.0 2.1 98860:42 dockerd</span><br><span class="line"> 908 root 0 -20 11.3g 40368 4080 S 0.7 0.5 4186:36 etcd</span><br></pre></td></tr></table></figure><p>摘要区第三行 <code>%Cpu(s)</code> 是整机 CPU 时间的分布,几个字段的含义:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">us 用户态进程占的 CPU 时间(业务代码本身在跑)</span><br><span class="line">sy 内核态占的时间(系统调用、内核在干活)——sy 明显偏高说明系统调用太频繁</span><br><span class="line">ni 被调过 nice 值的进程占的时间</span><br><span class="line">id 空闲——越高越闲</span><br><span class="line">wa 等磁盘 IO 的时间——wa 高就是 IO 瓶颈的直接信号,和前面 D 状态的判断互相印证</span><br><span class="line">hi/si 硬件/软件中断占的时间,平时接近 0</span><br><span class="line">st 被宿主机偷走的时间——虚拟机/云服务器特有,st 持续偏高说明宿主机超卖了</span><br></pre></td></tr></table></figure><p>进程列表里的前几列:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PR 内核眼中的调度优先级</span><br><span class="line">NI nice 值,就是 nice/renice 设置的那个数——etcd 那行的 -20 是最高优先级</span><br><span class="line">VIRT 虚拟内存,等同 ps 里的 VSZ,看看就好</span><br><span class="line">RES 实际物理内存,等同 ps 里的 RSS,看内存占用盯这列</span><br><span class="line">SHR 和其他进程共享的内存(共享库等),RES 里有一部分是它</span><br><span class="line">S 进程状态,就是前面 ps 讲的 STAT 主状态那个字母</span><br></pre></td></tr></table></figure><p>VIRT/RES/SHR 三列默认单位也是 KiB,和 <code>ps</code> 一致;数字大到一定程度 top 会自动换算并带上单位后缀——etcd 那行 VIRT 显示的 <code>11.3g</code> 就是换算成 GiB 后的结果。摘要区的内存两行更直接,行首就标了单位(<code>KiB Mem</code>、<code>KiB Swap</code>)。</p><p>上面这份输出里有个典型困惑:摘要行 CPU 加起来才用了不到 30%(us 14.3 + sy 12.2),下面 dockerd 一个进程却显示 102.0%——两边根本对不上?因为<strong>两处的分母不一样</strong>:摘要行是把所有核的时间摊平后按整机 100% 计算的;进程列表的 %CPU 是按单核 100% 计算的,多线程进程跑满多个核就会超过 100%(102% 就是占满了一个核再多一点)。所以 8 核机器上某进程 %CPU 400%,对应到摘要行也只贡献 50%。</p><p>看的时候配几个交互键:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">P 按 CPU 占用排序(默认)</span><br><span class="line">M 按内存占用排序</span><br><span class="line">1 展开显示每个 CPU 核的占用——看负载是不是集中在单核上(单线程瓶颈的典型特征)</span><br><span class="line">k 输入 PID 发信号杀进程</span><br><span class="line">q 退出</span><br></pre></td></tr></table></figure><h2 id="free-怎么读:available-才是真正可用的"><a href="#free-怎么读:available-才是真正可用的" class="headerlink" title="free 怎么读:available 才是真正可用的"></a>free 怎么读:available 才是真正可用的</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> total used free shared buff/cache available</span><br><span class="line">Mem: 15Gi 8.2Gi 1.1Gi 512Mi 6.2Gi 6.7Gi</span><br><span class="line">Swap: 2.0Gi 0Gi 2.0Gi</span><br></pre></td></tr></table></figure><p>最容易误读的就是 free 列:它只是”完全没被碰过的内存”。Linux 会把空闲内存尽量拿去做磁盘缓存(buff/cache),提高文件读写速度——这部分在应用需要时随时可以回收。所以:</p><ul><li><strong>看 available,不看 free</strong>:available = 真正可分配给新程序的内存(含可回收的缓存),free 很小、available 充足是健康状态</li><li><strong>Swap used 持续上涨才是真缺内存</strong>:物理内存实在不够,系统开始把内存页挪到磁盘上,性能会明显劣化——这才是需要处理的信号</li></ul><h2 id="组合实战:机器变慢了怎么定位"><a href="#组合实战:机器变慢了怎么定位" class="headerlink" title="组合实战:机器变慢了怎么定位"></a>组合实战:机器变慢了怎么定位</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">uptime</span></span><br><span class="line"><span class="comment"># 第 1 步:看 load average 和核数的比值,确认是不是真的过载,趋势是升是降</span></span><br><span class="line"></span><br><span class="line">top</span><br><span class="line"><span class="comment"># 第 2 步:过载的话按 P 看谁吃 CPU;CPU 不高就怀疑 IO——</span></span><br><span class="line"><span class="comment"># 看进程列表里有没有一片 D 状态</span></span><br><span class="line"></span><br><span class="line">free -h</span><br><span class="line"><span class="comment"># 第 3 步:看 available 是否枯竭、Swap 是否被大量使用</span></span><br><span class="line"><span class="comment"># 内存不足会连锁引发 CPU 和 IO 症状(频繁回收、swap 读写)</span></span><br><span class="line"></span><br><span class="line">ps aux | awk <span class="string">'$8 ~ /^D/ {print}'</span></span><br><span class="line"><span class="comment"># 第 4 步:列出所有 D 状态的进程(第 8 列是 STAT),确认卡 IO 的是谁</span></span><br></pre></td></tr></table></figure><h2 id="命令参数速查表"><a href="#命令参数速查表" class="headerlink" title="命令参数速查表"></a>命令参数速查表</h2><p>前面重点在”输出怎么读”,命令本身的常用参数也汇总一份。</p><p><strong>ps 常用参数</strong>:</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>a</code></td><td>显示所有用户的进程,不只自己的</td></tr><tr><td><code>u</code></td><td>用户友好格式,带 %CPU/%MEM/VSZ/RSS/STAT 这些列</td></tr><tr><td><code>x</code></td><td>包含没有控制终端的进程(守护进程)</td></tr><tr><td><code>aux</code></td><td>以上三个连用,看全系统进程的标准姿势</td></tr><tr><td><code>-ef</code></td><td>System V 风格的全进程列表,带 PPID(父进程)列</td></tr><tr><td><code>--sort=-%cpu</code></td><td>按 CPU 占用降序,<code>-%mem</code> 则按内存</td></tr><tr><td><code>-p PID</code></td><td>只看指定 PID 的进程</td></tr></tbody></table><p><strong>free 常用参数</strong>:</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-h</code></td><td>人类可读单位(K/M/G 自动换算)</td></tr><tr><td><code>-m</code> / <code>-g</code></td><td>固定按 MiB / GiB 显示</td></tr><tr><td><code>-s N</code></td><td>每 N 秒刷新一次,持续输出</td></tr><tr><td><code>-t</code></td><td>末尾多一行 Mem+Swap 的合计</td></tr></tbody></table><p><strong>top 启动参数</strong>(不多,常用就这几个):</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-p PID</code></td><td>只监控指定进程</td></tr><tr><td><code>-d N</code></td><td>刷新间隔改为 N 秒,默认 3 秒</td></tr><tr><td><code>-b -n N</code></td><td>批处理模式输出 N 次后退出,适合重定向到文件留记录</td></tr></tbody></table><h2 id="指标速查表"><a href="#指标速查表" class="headerlink" title="指标速查表"></a>指标速查表</h2><p><strong>ps aux 的指标</strong>:</p><table><thead><tr><th>指标</th><th>怎么读</th></tr></thead><tbody><tr><td>STAT <code>R</code> / <code>S</code></td><td>运行中 / 正常睡眠等事件</td></tr><tr><td>STAT <code>D</code></td><td>卡在 IO,不响应信号(kill -9 也没用),成片出现 = 磁盘/存储瓶颈</td></tr><tr><td>STAT <code>Z</code></td><td>僵尸,处理它的父进程才有用</td></tr><tr><td>STAT <code>T</code></td><td>被暂停(Ctrl-Z 或 SIGSTOP)</td></tr><tr><td>后缀 <code>s</code> / <code>l</code> / <code>+</code></td><td>会话首进程 / 多线程 / 前台进程组</td></tr><tr><td>后缀 <code><</code> / <code>N</code></td><td>高优先级(nice 为负)/ 低优先级(nice 为正)</td></tr><tr><td>后缀 <code>L</code></td><td>内存页锁定不允许换出,数据库进程常见</td></tr><tr><td>VSZ</td><td>虚拟地址空间,单位 KiB,大不代表问题</td></tr><tr><td>RSS</td><td>实际物理内存,单位 KiB,查内存泄漏盯它</td></tr></tbody></table><p><strong>top 的指标</strong>:</p><table><thead><tr><th>指标/操作</th><th>怎么读</th></tr></thead><tbody><tr><td>load average</td><td>除以 <code>nproc</code> 核数看每核负载,1/5/15 分钟对比看趋势</td></tr><tr><td>高 load + 低 CPU</td><td>典型 IO 瓶颈信号,去找 D 状态进程</td></tr><tr><td>%Cpu(s) 的 us / sy</td><td>用户态 / 内核态时间,sy 偏高 = 系统调用太频繁</td></tr><tr><td>%Cpu(s) 的 ni / id</td><td>调过 nice 的进程占比 / 空闲占比</td></tr><tr><td>%Cpu(s) 的 wa</td><td>等 IO 的时间占比,偏高 = IO 瓶颈,与 D 状态互相印证</td></tr><tr><td>%Cpu(s) 的 hi / si</td><td>硬件 / 软件中断,平时接近 0</td></tr><tr><td>%Cpu(s) 的 st</td><td>被宿主机偷走的时间,云服务器上持续偏高说明宿主机超卖</td></tr><tr><td>PR / NI</td><td>内核调度优先级 / nice 值</td></tr><tr><td>VIRT / RES / SHR</td><td>对应 ps 的 VSZ / RSS / 共享内存,默认 KiB,过大自动带 g 后缀</td></tr><tr><td>S</td><td>进程状态,同 ps 的 STAT 主状态</td></tr><tr><td>摘要行 %Cpu(s) 与进程 %CPU 对不上</td><td>分母不同:摘要按整机 100%,进程列按单核 100%</td></tr><tr><td>%CPU > 100%</td><td>多线程占多核的正常现象</td></tr><tr><td>按 <code>P</code> / <code>M</code></td><td>按 CPU / 按内存排序</td></tr><tr><td>按 <code>1</code></td><td>展开每核占用,识别单线程瓶颈</td></tr><tr><td>按 <code>k</code> / <code>q</code></td><td>杀进程 / 退出</td></tr></tbody></table><p><strong>free 的指标</strong>:</p><table><thead><tr><th>指标</th><th>怎么读</th></tr></thead><tbody><tr><td>free 列</td><td>完全没被碰过的内存,很小不代表内存不够</td></tr><tr><td>buff/cache</td><td>系统拿去做磁盘缓存的内存,应用需要时可回收</td></tr><tr><td>available</td><td>真正可分配的内存(含可回收缓存),看这列下结论</td></tr><tr><td>Swap used 上涨</td><td>物理内存真不够的信号,性能开始劣化</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>这三个命令的输出各有一个最值得记的点:<code>ps</code> 看 STAT(尤其警惕成片的 D)、load average 要除以核数再下结论、<code>free</code> 看 available 不看 free。把这几个指标读对,”机器慢”就能拆成 CPU、IO、内存三条线分头去查,而不是盯着满屏数字发懵。</p>]]>
</content>
<id>https://blog.codewater.top/ps-top-free%E8%BE%93%E5%87%BA%E8%A7%A3%E8%AF%BB-STAT%E7%8A%B6%E6%80%81%E7%A0%81%E4%B8%8Eloadaverage/</id>
<link href="https://blog.codewater.top/ps-top-free%E8%BE%93%E5%87%BA%E8%A7%A3%E8%AF%BB-STAT%E7%8A%B6%E6%80%81%E7%A0%81%E4%B8%8Eloadaverage/"/>
<published>2026-07-14T12:43:57.000Z</published>
<summary>ps aux 的 STAT 列什么意思、VSZ/RSS 差在哪、load average 怎么读、free 看哪一列,一篇讲透。</summary>
<title>ps、top、free 输出怎么读:STAT 状态码、load average 与内存指标</title>
<updated>2026-07-16T08:34:49.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="网络" scheme="https://blog.codewater.top/tags/%E7%BD%91%E7%BB%9C/"/>
<content>
<![CDATA[<p><code>curl</code> 和 <code>wget</code> 都能发 HTTP 请求、都能下载文件,为什么大多数系统里两个都有?因为它们的设计目标不同:<code>curl</code> 是数据传输的瑞士军刀,调接口、看响应头、在脚本里取数据都靠它;<code>wget</code> 是专职下载器,断点续传、批量下载是它的主场。分清分工,用起来就不纠结。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;<a href="/%E7%BD%91%E7%BB%9C%E8%BF%9E%E9%80%9A%E6%80%A7%E8%AF%8A%E6%96%AD-ping-traceroute%E4%B8%8Edig%E5%AE%9E%E6%88%98">网络连通性诊断那篇</a> 的排查流程里,确认网络和 DNS 都正常后验证”服务本身有没有响应”,用的正是 <code>curl</code>。</p><h2 id="curl-的默认行为:发-GET,结果打到屏幕"><a href="#curl-的默认行为:发-GET,结果打到屏幕" class="headerlink" title="curl 的默认行为:发 GET,结果打到屏幕"></a>curl 的默认行为:发 GET,结果打到屏幕</h2><p><code>curl</code> 不带任何参数时发 GET 请求,把响应体原样打印到标准输出——不落盘。这个默认行为正是它适合调试的原因:请求发出去,响应立刻看得见,还能直接接管道处理:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">curl https://api.example.com/health</span><br><span class="line"><span class="comment"># 发 GET 请求,响应体直接打印到终端</span></span><br><span class="line"></span><br><span class="line">curl -s https://api.example.com/users | jq <span class="string">'.[0].name'</span></span><br><span class="line"><span class="comment"># -s:silent,去掉进度条等杂音输出,适合接管道</span></span><br><span class="line"><span class="comment"># 配合 jq 直接从 JSON 响应里取字段</span></span><br></pre></td></tr></table></figure><h2 id="curl-调接口:高频参数"><a href="#curl-调接口:高频参数" class="headerlink" title="curl 调接口:高频参数"></a>curl 调接口:高频参数</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">curl -I https://example.com</span><br><span class="line"><span class="comment"># -I:只请求并显示响应头(实际发的是 HEAD 请求,不下载响应体)</span></span><br><span class="line"><span class="comment"># 看状态码、Content-Type、缓存头,不关心内容时用它最快</span></span><br><span class="line"></span><br><span class="line">curl -v https://api.example.com/user</span><br><span class="line"><span class="comment"># -v:verbose,显示完整的请求和响应过程——包括 DNS 解析、TLS 握手、</span></span><br><span class="line"><span class="comment"># 发出去的请求头、收到的响应头,调试"请求到底发成什么样了"必用</span></span><br><span class="line"></span><br><span class="line">curl -X POST https://api.example.com/login \</span><br><span class="line"> -H <span class="string">"Content-Type: application/json"</span> \</span><br><span class="line"> -d <span class="string">'{"username":"test","password":"123"}'</span></span><br><span class="line"><span class="comment"># -X:指定请求方法;-H:加请求头;-d:请求体数据</span></span><br><span class="line"><span class="comment"># 其实 -d 本身就会把方法切成 POST,这里的 -X POST 可以省略,写出来更直观</span></span><br><span class="line"></span><br><span class="line">curl -L http://example.com</span><br><span class="line"><span class="comment"># -L:跟随 301/302 重定向——curl 默认不跟随,页面搬家了只会给你一个 301 响应</span></span><br><span class="line"></span><br><span class="line">curl --max-time 5 https://api.example.com/health</span><br><span class="line"><span class="comment"># --max-time:整个请求的超时上限(秒),脚本里调接口一定要加,避免卡死</span></span><br><span class="line"></span><br><span class="line">curl -s -o /dev/null -w <span class="string">"%{http_code}\n"</span> https://example.com</span><br><span class="line"><span class="comment"># -w:请求完成后按模板输出元信息,%{http_code} 是状态码</span></span><br><span class="line"><span class="comment"># -o /dev/null 丢弃响应体——组合起来就是"只要状态码",健康检查脚本的标准写法</span></span><br></pre></td></tr></table></figure><h2 id="curl-下载文件"><a href="#curl-下载文件" class="headerlink" title="curl 下载文件"></a>curl 下载文件</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">curl -o app.tar.gz https://example.com/download/app-v2.tar.gz</span><br><span class="line"><span class="comment"># -o:把响应体保存成指定文件名,代替默认的打印到终端</span></span><br><span class="line"></span><br><span class="line">curl -O https://example.com/download/app-v2.tar.gz</span><br><span class="line"><span class="comment"># -O:直接用 URL 里的文件名保存(这里存成 app-v2.tar.gz)</span></span><br></pre></td></tr></table></figure><p>偶尔下载一两个文件,<code>curl -O</code> 够用;但要断点续传、批量下载,就该换 <code>wget</code> 了。</p><h2 id="wget-的默认行为:直接存成文件"><a href="#wget-的默认行为:直接存成文件" class="headerlink" title="wget 的默认行为:直接存成文件"></a>wget 的默认行为:直接存成文件</h2><p><code>wget</code> 和 <code>curl</code> 的默认行为正相反:不带参数就把 URL 内容保存成本地文件(文件名取自 URL),从设计上就是给”下载”这个场景准备的。macOS 自带 <code>curl</code> 但不自带 <code>wget</code>,需要 <code>brew install wget</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wget https://example.com/download/app-v2.tar.gz</span><br><span class="line"><span class="comment"># 直接下载保存为 app-v2.tar.gz,带进度条和速度显示</span></span><br><span class="line"></span><br><span class="line">wget -c https://example.com/download/big-file.iso</span><br><span class="line"><span class="comment"># -c:continue,断点续传——下载中断后重跑同一条命令,从断的地方继续</span></span><br><span class="line"><span class="comment"># 下载几个 G 的大文件时,这是 wget 相对 curl 最实用的优势</span></span><br><span class="line"></span><br><span class="line">wget -P /tmp/downloads https://example.com/file.zip</span><br><span class="line"><span class="comment"># -P:指定保存目录,不加就存当前目录</span></span><br><span class="line"></span><br><span class="line">wget -r --no-parent https://example.com/docs/</span><br><span class="line"><span class="comment"># -r:递归下载,把页面里链接的资源一并抓下来</span></span><br><span class="line"><span class="comment"># --no-parent:不往上层目录爬,把范围限制在 /docs/ 之下</span></span><br><span class="line"><span class="comment"># 递归下载对目标网站压力不小,范围一定要限制好,别对着整站开抓</span></span><br></pre></td></tr></table></figure><h2 id="怎么选:一句话分工"><a href="#怎么选:一句话分工" class="headerlink" title="怎么选:一句话分工"></a>怎么选:一句话分工</h2><ul><li>调接口、看响应头、脚本里取数据、健康检查——用 <code>curl</code>,默认输出到 stdout 天生适合这些场景</li><li>下载大文件要断点续传、批量抓取——用 <code>wget</code>,<code>-c</code> 和 <code>-r</code> 是 <code>curl</code> 没有的便利</li><li>只是偶尔下载个小文件,两者都行,<code>curl -O</code> 和 <code>wget</code> 等价,用系统里现成的那个(macOS 现成的是 <code>curl</code>)</li></ul><h2 id="常用参数速查表"><a href="#常用参数速查表" class="headerlink" title="常用参数速查表"></a>常用参数速查表</h2><table><thead><tr><th>命令/参数</th><th>作用</th></tr></thead><tbody><tr><td><code>curl URL</code></td><td>发 GET,响应体打印到终端</td></tr><tr><td><code>curl -I</code></td><td>只看响应头(HEAD 请求)</td></tr><tr><td><code>curl -v</code></td><td>显示完整请求/响应过程,调试用</td></tr><tr><td><code>curl -H -d</code></td><td>加请求头、发请求体(-d 自动切 POST)</td></tr><tr><td><code>curl -L</code></td><td>跟随重定向,默认不跟随</td></tr><tr><td><code>curl --max-time N</code></td><td>整个请求的超时上限(秒)</td></tr><tr><td><code>curl -o 文件</code> / <code>-O</code></td><td>保存为指定文件名 / 用远端文件名</td></tr><tr><td><code>curl -w "%{http_code}"</code></td><td>输出状态码等元信息</td></tr><tr><td><code>wget URL</code></td><td>下载保存为文件(默认行为)</td></tr><tr><td><code>wget -c</code></td><td>断点续传</td></tr><tr><td><code>wget -P 目录</code></td><td>指定保存目录</td></tr><tr><td><code>wget -r --no-parent</code></td><td>递归下载,限制不爬上层</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>记住两个默认行为就分清了:<code>curl</code> 默认打到屏幕(所以是调试工具),<code>wget</code> 默认存成文件(所以是下载器)。调接口的高频参数就 <code>-I</code>/<code>-v</code>/<code>-H</code>/<code>-d</code>/<code>-L</code> 这几个,下载大文件认准 <code>wget -c</code>,其余参数用到时查表即可。</p>]]>
</content>
<id>https://blog.codewater.top/curl%E4%B8%8Ewget%E5%AE%9E%E6%88%98-%E8%B0%83%E6%8E%A5%E5%8F%A3%E4%B8%8B%E8%BD%BD%E4%B8%8E%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0/</id>
<link href="https://blog.codewater.top/curl%E4%B8%8Ewget%E5%AE%9E%E6%88%98-%E8%B0%83%E6%8E%A5%E5%8F%A3%E4%B8%8B%E8%BD%BD%E4%B8%8E%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0/"/>
<published>2026-07-14T06:16:45.000Z</published>
<summary>curl 调接口、wget 下载:-I/-L/-d 常用参数、断点续传与两个工具怎么分工。</summary>
<title>curl 与 wget 实战:调接口、下载文件与断点续传</title>
<updated>2026-07-14T06:16:45.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="进程" scheme="https://blog.codewater.top/tags/%E8%BF%9B%E7%A8%8B/"/>
<content>
<![CDATA[<p><code>kill -9</code> 是很多人的肌肉记忆——进程不听话,直接 <code>-9</code> 送走。但 <code>kill</code> 这个命令名其实起得有误导性:它默认发的信号根本不是”杀死”,而是”请求退出”。这两者的区别,决定了进程退出前有没有机会保存数据、释放资源。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;<a href="/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8E%92%E6%9F%A5%E6%9C%8D%E5%8A%A1%E9%97%AE%E9%A2%98%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">服务器排查那篇</a> 讲了用 <code>ps</code>/<code>top</code> 找进程,这篇讲找到之后怎么正确地”管”它。</p><h2 id="信号:内核给进程递的通知条"><a href="#信号:内核给进程递的通知条" class="headerlink" title="信号:内核给进程递的通知条"></a>信号:内核给进程递的通知条</h2><p>信号(signal)是内核发给进程的一种异步通知。大部分信号进程可以选择怎么响应——捕获它执行自定义逻辑、忽略它、或者按默认行为处理;只有极少数信号(比如 SIGKILL)是强制的,进程没有任何选择余地。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> -l</span><br><span class="line"><span class="comment"># 列出系统支持的全部信号,有几十个</span></span><br><span class="line"><span class="comment"># 日常真正需要认识的只有三四个:TERM、KILL、HUP</span></span><br></pre></td></tr></table></figure><p><code>kill</code> 命令的作用就是给指定 PID 的进程发信号——发什么信号由参数决定,不指定就发默认的。</p><h2 id="SIGTERM(15):kill-的默认信号,请求优雅退出"><a href="#SIGTERM(15):kill-的默认信号,请求优雅退出" class="headerlink" title="SIGTERM(15):kill 的默认信号,请求优雅退出"></a>SIGTERM(15):kill 的默认信号,请求优雅退出</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> 12345</span><br><span class="line"><span class="comment"># 不带任何信号参数,默认发 SIGTERM(15)</span></span><br><span class="line"><span class="comment"># 语义是"请你退出"——进程可以捕获这个信号,先做完清理再退出:</span></span><br><span class="line"><span class="comment"># 保存未写盘的数据、关闭数据库连接、写完最后的日志</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">kill</span> -15 12345</span><br><span class="line"><span class="built_in">kill</span> -TERM 12345</span><br><span class="line"><span class="comment"># 和上面完全等价,显式写出信号编号或名字</span></span><br></pre></td></tr></table></figure><p>绝大多数正经写的服务都注册了 SIGTERM 处理逻辑(Go 里的 <code>signal.Notify</code>、容器里的优雅停机钩子都是响应它),收到后会体面地收尾。这也是 <code>systemctl stop</code>、<code>docker stop</code> 停服务时先发的信号。</p><h2 id="SIGKILL(9):最后手段,不是首选"><a href="#SIGKILL(9):最后手段,不是首选" class="headerlink" title="SIGKILL(9):最后手段,不是首选"></a>SIGKILL(9):最后手段,不是首选</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> -9 12345</span><br><span class="line"><span class="comment"># SIGKILL 由内核直接终结进程,进程无法捕获、无法忽略——</span></span><br><span class="line"><span class="comment"># 也就没有任何清理机会:没保存的数据直接丢、锁文件残留、连接不释放</span></span><br></pre></td></tr></table></figure><p><code>-9</code> 的问题不在”杀不死”,恰恰在”杀得太死”:进程的清理逻辑一行都不会执行。数据库进程被 <code>-9</code> 可能留下损坏的数据文件,持锁的进程被 <code>-9</code> 可能让别的进程永远等锁。正确的顺序是:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> 12345</span><br><span class="line"><span class="comment"># 第 1 步:先发 SIGTERM,给进程清理的机会</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sleep</span> 5 && <span class="built_in">kill</span> -0 12345 2>/dev/null && <span class="built_in">kill</span> -9 12345</span><br><span class="line"><span class="comment"># 第 2 步:等几秒,确认还没退出(kill -0 只检测进程存在,不发实际信号),</span></span><br><span class="line"><span class="comment"># 这时候再上 -9 强制终结</span></span><br></pre></td></tr></table></figure><p>把 <code>-9</code> 当默认选项,等于放弃了所有进程善后的机会——它应该是 SIGTERM 无效之后的最后手段。</p><h2 id="SIGHUP(1):重载配置的约定俗成"><a href="#SIGHUP(1):重载配置的约定俗成" class="headerlink" title="SIGHUP(1):重载配置的约定俗成"></a>SIGHUP(1):重载配置的约定俗成</h2><p><code>SIGHUP</code> 历史上表示”终端挂断”(hangup,拨号上网时代的遗产),现在大量守护进程把它重新定义成”重新加载配置文件,但不重启”:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> -HUP $(<span class="built_in">cat</span> /var/run/nginx.pid)</span><br><span class="line"><span class="comment"># 给 nginx 主进程发 SIGHUP,nginx 会重读配置并平滑应用,服务不中断</span></span><br><span class="line"><span class="comment"># nginx -s reload 底层做的就是这件事</span></span><br></pre></td></tr></table></figure><p>不是所有程序都遵守这个约定,用之前查一下目标服务的文档;但见到 <code>kill -HUP</code> 时要知道,它的意图大概率是”重载配置”,不是杀进程。</p><h2 id="pkill-pgrep:按名字找和杀,跳过查-PID"><a href="#pkill-pgrep:按名字找和杀,跳过查-PID" class="headerlink" title="pkill / pgrep:按名字找和杀,跳过查 PID"></a>pkill / pgrep:按名字找和杀,跳过查 PID</h2><p><code>pgrep</code> 按进程名查 PID,<code>pkill</code> 按进程名直接发信号——省掉 <code>ps aux | grep xxx</code> 再手动复制 PID 的三步操作:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">pgrep -l nginx</span><br><span class="line"><span class="comment"># -l:连同进程名一起列出所有匹配 "nginx" 的进程 PID</span></span><br><span class="line"></span><br><span class="line">pkill nginx</span><br><span class="line"><span class="comment"># 给所有名字匹配 "nginx" 的进程发 SIGTERM(默认信号和 kill 一样)</span></span><br><span class="line"></span><br><span class="line">pkill -9 -f <span class="string">"python.*worker"</span></span><br><span class="line"><span class="comment"># -f:匹配完整命令行而不只是进程名——适合按启动参数区分同名进程</span></span><br><span class="line"><span class="comment"># 注意 -f 匹配范围大,先用 pgrep -f 确认匹配到的是哪些进程再杀,避免误伤</span></span><br></pre></td></tr></table></figure><p><code>pkill -f</code> 是双刃剑:模式写宽了会把不相干的进程一起杀掉。安全的习惯是先跑一遍同参数的 <code>pgrep -fl</code>,看清楚列表再动手。</p><h2 id="nice-renice:CPU-调度优先级"><a href="#nice-renice:CPU-调度优先级" class="headerlink" title="nice / renice:CPU 调度优先级"></a>nice / renice:CPU 调度优先级</h2><p><code>nice</code> 值决定进程在 CPU 竞争中的优先级,范围 -20 到 19——<strong>数值越低优先级越高</strong>,这个方向和直觉相反:可以理解成”nice 值越高,进程越’客气’,越愿意把 CPU 让给别人”。普通用户只能调高 nice 值(降低优先级),设负值需要 root。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">nice</span> -n 19 tar -czf backup.tar.gz /data</span><br><span class="line"><span class="comment"># -n:指定 nice 值增量,19 是最低优先级</span></span><br><span class="line"><span class="comment"># 跑大型备份、压缩这类不着急的任务时降低优先级,避免拖慢正常服务</span></span><br><span class="line"></span><br><span class="line">renice -n 10 -p 12345</span><br><span class="line"><span class="comment"># renice 调整已经在运行的进程,-p 指定 PID</span></span><br><span class="line"><span class="comment"># 发现某个后台任务吃 CPU 影响了在线服务,不用重启它,直接调低优先级</span></span><br></pre></td></tr></table></figure><h2 id="常用命令速查表"><a href="#常用命令速查表" class="headerlink" title="常用命令速查表"></a>常用命令速查表</h2><table><thead><tr><th>命令/信号</th><th>作用</th></tr></thead><tbody><tr><td><code>kill PID</code></td><td>发默认信号 SIGTERM(15),请求优雅退出</td></tr><tr><td><code>kill -9 PID</code></td><td>SIGKILL,内核强制终结,无清理机会,最后手段</td></tr><tr><td><code>kill -HUP PID</code></td><td>多数守护进程约定为”重载配置不重启”</td></tr><tr><td><code>kill -0 PID</code></td><td>不发实际信号,只检测进程是否存在</td></tr><tr><td><code>kill -l</code></td><td>列出全部信号</td></tr><tr><td><code>pgrep -l 名字</code></td><td>按进程名查 PID</td></tr><tr><td><code>pkill 名字</code></td><td>按进程名发 SIGTERM</td></tr><tr><td><code>pkill -f 模式</code></td><td>按完整命令行匹配,先用 <code>pgrep -fl</code> 确认再杀</td></tr><tr><td><code>nice -n N 命令</code></td><td>以指定优先级启动,-20 最高、19 最低,越低越优先</td></tr><tr><td><code>renice -n N -p PID</code></td><td>调整运行中进程的优先级,负值需要 root</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p><code>kill</code> 家族的正确打开方式:默认 SIGTERM 给进程体面退出的机会,几秒后还赖着不走再 <code>-9</code>;重载配置认准 SIGHUP;批量操作用 <code>pkill</code> 但先 <code>pgrep</code> 确认目标。至于 <code>nice</code>,记住”值越低越优先”这个反直觉设定,剩下的就是两条命令的事。</p>]]>
</content>
<id>https://blog.codewater.top/kill%E4%BF%A1%E5%8F%B7%E6%9C%BA%E5%88%B6%E4%B8%8Enice%E4%BC%98%E5%85%88%E7%BA%A7%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/kill%E4%BF%A1%E5%8F%B7%E6%9C%BA%E5%88%B6%E4%B8%8Enice%E4%BC%98%E5%85%88%E7%BA%A7%E5%AE%9E%E6%88%98/"/>
<published>2026-07-14T06:14:50.000Z</published>
<summary>kill 默认发的不是杀死信号:SIGTERM/SIGKILL/SIGHUP 区别、pkill 按名杀进程与 nice 优先级。</summary>
<title>kill 默认不是"杀死":进程信号与 nice 优先级实战</title>
<updated>2026-07-14T06:14:50.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="磁盘" scheme="https://blog.codewater.top/tags/%E7%A3%81%E7%9B%98/"/>
<content>
<![CDATA[<p>服务器磁盘报警 90%,挨个目录 <code>ls -la</code> 看着都不大,就是找不到空间被谁吃了——这是用错了工具:<code>ls</code> 根本不统计目录树的总占用。查文件和磁盘信息的这组命令,每个回答的问题不一样,对上号才查得动。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。</p><h2 id="ls:列目录内容,先看懂-la-的每一列"><a href="#ls:列目录内容,先看懂-la-的每一列" class="headerlink" title="ls:列目录内容,先看懂 -la 的每一列"></a>ls:列目录内容,先看懂 -la 的每一列</h2><p><code>ls</code> 列出目录内容,默认不显示 <code>.</code> 开头的隐藏文件、不显示详情,只给一排文件名。日常几乎都是配着参数用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> -la</span><br><span class="line"><span class="comment"># -l:长格式,每个文件一行,显示权限、大小、时间等详情</span></span><br><span class="line"><span class="comment"># -a:包含隐藏文件(. 开头的)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="comment"># -h:把大小从字节数转成人类可读的 K/M/G</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ls</span> -lt</span><br><span class="line"><span class="comment"># -t:按修改时间排序,最新的在最上面——找"最近改过什么"最快的办法</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ls</span> -lS</span><br><span class="line"><span class="comment"># -S:按文件大小排序,最大的在最上面</span></span><br></pre></td></tr></table></figure><p><code>ls -l</code> 输出的每一列都有含义,以这行为例:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-rw-r--r-- 1 baizhe staff 3833 Jul 7 01:20 _config.yml</span><br><span class="line"># 权限 链接数 属主 属组 大小 修改时间 文件名</span><br><span class="line"># 权限列的读法(rwx 和数字模式)见 chmod/chown 那篇文章</span><br></pre></td></tr></table></figure><p>权限列的完整规则在 <a href="/Linux%E6%9D%83%E9%99%90%E4%B8%8E%E5%B1%9E%E4%B8%BB%E7%AE%A1%E7%90%86-chmod-chown%E5%AE%9E%E6%88%98">Linux 权限与属主管理:chmod、chown</a> 里讲过,这里不重复。</p><h2 id="tree:树状展示目录层级"><a href="#tree:树状展示目录层级" class="headerlink" title="tree:树状展示目录层级"></a>tree:树状展示目录层级</h2><p><code>tree</code> 以树状缩进展示目录结构,比 <code>ls -R</code>(递归列出但没有层级视觉)直观得多。它不是系统自带命令,macOS 和很多精简系统默认没有,要先装(<code>brew install tree</code> / <code>apt install tree</code>):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">tree -L 2</span><br><span class="line"><span class="comment"># -L:限制展示深度,这里只展开两层——项目大了不加这个会刷满整个终端</span></span><br><span class="line"></span><br><span class="line">tree -d</span><br><span class="line"><span class="comment"># -d:只显示目录,不列文件,看结构骨架时更清爽</span></span><br></pre></td></tr></table></figure><h2 id="file:不看扩展名,判断文件真实类型"><a href="#file:不看扩展名,判断文件真实类型" class="headerlink" title="file:不看扩展名,判断文件真实类型"></a>file:不看扩展名,判断文件真实类型</h2><p><code>file</code> 读取文件开头的魔数(magic number)来判断真实类型,完全不依赖扩展名——文件没有后缀、或者怀疑后缀是假的(比如伪装成 .jpg 的脚本)时用它:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">file photo.jpg</span><br><span class="line"><span class="comment"># 输出:JPEG image data, ... 1920x1080 —— 连分辨率都能读出来</span></span><br><span class="line"></span><br><span class="line">file /bin/ls</span><br><span class="line"><span class="comment"># 输出:Mach-O 64-bit executable(macOS)或 ELF 64-bit(Linux)</span></span><br><span class="line"><span class="comment"># 二进制可执行文件能识别出格式和 CPU 架构</span></span><br><span class="line"></span><br><span class="line">file mystery_download</span><br><span class="line"><span class="comment"># 没有扩展名的文件也照样识别,判断"这下载下来的到底是什么"时最有用</span></span><br></pre></td></tr></table></figure><h2 id="stat:文件元信息与三个时间戳"><a href="#stat:文件元信息与三个时间戳" class="headerlink" title="stat:文件元信息与三个时间戳"></a>stat:文件元信息与三个时间戳</h2><p><code>stat</code> 显示文件的完整元信息:大小、权限、inode、属主,以及三个容易搞混的时间戳。这三个时间戳的区别值得单独记住:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">stat</span> -x _config.yml <span class="comment"># macOS/BSD 要加 -x 才输出可读格式;Linux/GNU 直接 stat 即可</span></span><br><span class="line"></span><br><span class="line"> Size: 3833 FileType: Regular File</span><br><span class="line"> Mode: (0644/-rw-r--r--) Uid: (501/baizhe) Gid: (20/staff)</span><br><span class="line">Access: Tue Jul 7 02:07:29 2026</span><br><span class="line">Modify: Tue Jul 7 01:20:52 2026</span><br><span class="line">Change: Tue Jul 7 01:20:52 2026</span><br><span class="line"><span class="comment"># Access(atime):文件内容最后一次被读取的时间</span></span><br><span class="line"><span class="comment"># Modify(mtime):文件内容最后一次被修改的时间——ls -l 显示的就是它</span></span><br><span class="line"><span class="comment"># Change(ctime):文件元信息最后一次变更的时间</span></span><br></pre></td></tr></table></figure><p>最容易讲错的是 <code>ctime</code>:它不是”创建时间”(create),是”变更时间”(change)——指的是元信息的变更。给文件 <code>chmod</code> 改个权限,内容一个字节没动,<code>mtime</code> 不变,但 <code>ctime</code> 会更新。排查”文件内容没变但权限什么时候被人改过”,看的就是 <code>ctime</code>。</p><p>注意 <code>stat</code> 是 BSD/GNU 差异比较大的命令:macOS 上直接 <code>stat file</code> 输出一行紧凑格式,要 <code>-x</code> 才有上面这种可读输出;Linux 上正相反,直接 <code>stat file</code> 就是完整格式。跨平台写脚本时别依赖它的输出格式。</p><h2 id="du-和-df:一个查”谁占的”,一个查”还剩多少”"><a href="#du-和-df:一个查”谁占的”,一个查”还剩多少”" class="headerlink" title="du 和 df:一个查”谁占的”,一个查”还剩多少”"></a>du 和 df:一个查”谁占的”,一个查”还剩多少”</h2><p>这两个命令名字长得像,回答的问题完全不同:<code>df</code>(disk free)查文件系统级别的使用率——“这块盘还剩多少”;<code>du</code>(disk usage)统计某个目录树的实际占用——“这个目录一共吃了多少”。磁盘满排查时两个要配合用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">df</span> -h</span><br><span class="line"><span class="comment"># -h:人类可读单位;输出每个挂载点的总容量、已用、剩余、使用率</span></span><br><span class="line"><span class="comment"># 先用它确认到底是哪个分区满了</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">du</span> -sh /var/log</span><br><span class="line"><span class="comment"># -s:只显示总和,不逐个列出子目录</span></span><br><span class="line"><span class="comment"># -h:人类可读单位</span></span><br><span class="line"><span class="comment"># 统计指定目录树的总占用</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">du</span> -sh /var/* | <span class="built_in">sort</span> -rh | <span class="built_in">head</span> -10</span><br><span class="line"><span class="comment"># 磁盘满排查的经典组合:列出 /var 下每个子目录的占用,</span></span><br><span class="line"><span class="comment"># 按大小逆序排列取前 10,一眼看出谁是大头</span></span><br></pre></td></tr></table></figure><p>一个偶尔会遇到的现象:<code>du</code> 把整个分区加起来的数字比 <code>df</code> 显示的已用空间小很多。常见原因是有文件已经被删除、但还有进程占着它的文件句柄——这种文件 <code>du</code> 扫不到(目录里已经没有它了),<code>df</code> 却依然把它算在已用空间里,重启对应进程空间才会真正释放。</p><h2 id="组合实战:磁盘满了怎么一步步定位"><a href="#组合实战:磁盘满了怎么一步步定位" class="headerlink" title="组合实战:磁盘满了怎么一步步定位"></a>组合实战:磁盘满了怎么一步步定位</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">df</span> -h</span><br><span class="line"><span class="comment"># 第 1 步:确认是哪个分区满了,比如看到 / 用了 92%</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">du</span> -sh /* 2>/dev/null | <span class="built_in">sort</span> -rh | <span class="built_in">head</span> -10</span><br><span class="line"><span class="comment"># 第 2 步:从根目录开始,看哪个顶层目录占用最大</span></span><br><span class="line"><span class="comment"># 2>/dev/null 忽略没权限读的目录的报错</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">du</span> -sh /var/* 2>/dev/null | <span class="built_in">sort</span> -rh | <span class="built_in">head</span> -10</span><br><span class="line"><span class="comment"># 第 3 步:进入最大的目录继续下钻,一层层缩小范围</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ls</span> -laSh /var/log | <span class="built_in">head</span></span><br><span class="line"><span class="comment"># 第 4 步:定位到具体目录后,按大小排序找出最大的文件</span></span><br></pre></td></tr></table></figure><p>从分区到目录再到具体文件,三四层下钻通常就能定位。日志文件撑满磁盘是最常见的答案,所以很多人直接从 <code>du -sh /var/log</code> 查起。</p><h2 id="常用参数速查表"><a href="#常用参数速查表" class="headerlink" title="常用参数速查表"></a>常用参数速查表</h2><table><thead><tr><th>命令/参数</th><th>作用</th></tr></thead><tbody><tr><td><code>ls -la</code></td><td>长格式列出全部文件(含隐藏)</td></tr><tr><td><code>ls -lt</code> / <code>ls -lS</code></td><td>按修改时间 / 按大小排序</td></tr><tr><td><code>tree -L N</code></td><td>树状展示目录,限制 N 层深度</td></tr><tr><td><code>tree -d</code></td><td>只显示目录</td></tr><tr><td><code>file 文件</code></td><td>按内容魔数判断文件真实类型</td></tr><tr><td><code>stat 文件</code></td><td>查看元信息;macOS 加 <code>-x</code> 才是可读格式</td></tr><tr><td>atime / mtime / ctime</td><td>最后访问 / 内容修改 / 元信息变更时间</td></tr><tr><td><code>df -h</code></td><td>各分区磁盘使用率</td></tr><tr><td><code>du -sh 目录</code></td><td>目录树总占用</td></tr><tr><td><code>du -sh /* | sort -rh</code></td><td>磁盘满排查:按占用从大到小排</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>这组命令按问题分工:文件列表和排序找 <code>ls</code>,目录结构找 <code>tree</code>,文件类型找 <code>file</code>,元信息和时间戳找 <code>stat</code>,磁盘空间 <code>df</code> 定分区、<code>du</code> 定目录。磁盘报警时别再挨个 <code>ls</code> 了——<code>df</code> 加 <code>du</code> 逐层下钻,几条命令就能揪出占空间的元凶。</p>]]>
</content>
<id>https://blog.codewater.top/ls-stat-du-df%E6%96%87%E4%BB%B6%E4%BF%A1%E6%81%AF%E4%B8%8E%E7%A3%81%E7%9B%98%E5%8D%A0%E7%94%A8%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/ls-stat-du-df%E6%96%87%E4%BB%B6%E4%BF%A1%E6%81%AF%E4%B8%8E%E7%A3%81%E7%9B%98%E5%8D%A0%E7%94%A8%E5%AE%9E%E6%88%98/"/>
<published>2026-07-14T06:12:23.000Z</published>
<summary>ls/tree/file/stat 查文件信息,du/df 查磁盘占用:三个时间戳区别与磁盘满排查实战。</summary>
<title>ls、stat、du、df 实战:文件信息与磁盘占用怎么查</title>
<updated>2026-07-14T06:12:23.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="文本处理" scheme="https://blog.codewater.top/tags/%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86/"/>
<content>
<![CDATA[<p>想知道一份访问日志里访问最多的 10 个 IP,不用现写一段 <code>awk</code> 脚本——<code>cut</code>、<code>sort</code>、<code>uniq</code>、<code>wc</code> 这几个小工具,管道接起来一行命令就能搞定,比现场想正则、调脚本快得多。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;这几个小工具经常和 <a href="/sed%E4%B8%8Eawk%E5%AE%9E%E6%88%98%E5%AF%B9%E6%AF%94-%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7%E6%80%8E%E4%B9%88%E9%80%89">sed/awk</a> 配合甚至互相替代,简单场景用它们组合,比写一段 <code>awk</code> 脚本更直接。</p><h2 id="cut:按列或按字符截取"><a href="#cut:按列或按字符截取" class="headerlink" title="cut:按列或按字符截取"></a>cut:按列或按字符截取</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">"alice,25,engineer"</span> | <span class="built_in">cut</span> -d<span class="string">','</span> -f1,3</span><br><span class="line"><span class="comment"># -d:指定分隔符,这里是逗号</span></span><br><span class="line"><span class="comment"># -f:选取第几个字段,1,3 表示第 1 和第 3 个,输出 alice,engineer</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"hello world"</span> | <span class="built_in">cut</span> -c1-5</span><br><span class="line"><span class="comment"># -c:按字符位置截取,1-5 表示第 1 到第 5 个字符,输出 hello</span></span><br></pre></td></tr></table></figure><p><code>cut</code> 的局限也很明显:分隔符必须是单个固定字符,遇到连续多个空格分隔的列(比如 <code>ps aux</code> 的输出),<code>cut -d' '</code> 会因为空格数量不固定而截错位置,这种场景交给 <code>awk</code> 的字段拆分更靠谱。</p><h2 id="sort:排序"><a href="#sort:排序" class="headerlink" title="sort:排序"></a>sort:排序</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sort</span> file.txt</span><br><span class="line"><span class="comment"># 默认按字典序(ASCII)排序——数字字符串会按"字符"比较,"10" 排在 "2" 前面,容易踩坑</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sort</span> -n file.txt</span><br><span class="line"><span class="comment"># -n:按数值大小排序,这时候 "2" 才会排在 "10" 前面</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sort</span> -r file.txt</span><br><span class="line"><span class="comment"># -r:逆序排序,配合 -n 就是数值从大到小</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sort</span> -k2,2n file.txt</span><br><span class="line"><span class="comment"># -k2,2:只按第 2 列排序(不写结束列会一直排到行尾,是很多人忽略的细节)</span></span><br><span class="line"><span class="comment"># n 后缀表示这一列按数值比较</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sort</span> -u file.txt</span><br><span class="line"><span class="comment"># -u:排序的同时去重,等价于 sort | uniq,但只需要一次调用</span></span><br></pre></td></tr></table></figure><p><code>sort</code> 默认是字典序这件事经常被忽略——处理纯数字列却忘了加 <code>-n</code>,排序结果看着”乱”,其实是没按数值比较。</p><h2 id="uniq:去重和统计重复次数"><a href="#uniq:去重和统计重复次数" class="headerlink" title="uniq:去重和统计重复次数"></a>uniq:去重和统计重复次数</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sort</span> file.txt | <span class="built_in">uniq</span></span><br><span class="line"><span class="comment"># 去除相邻的重复行——注意 uniq 只对相邻的重复行生效,输入必须先排序过</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sort</span> file.txt | <span class="built_in">uniq</span> -c</span><br><span class="line"><span class="comment"># -c:统计每一行出现的次数,输出前面带一个计数</span></span><br></pre></td></tr></table></figure><p><code>uniq</code> 最容易踩的坑:它不会去重整个文件里所有重复的行,只会去除挨在一起的重复行。原始文件里两个相同的行如果隔着别的内容,<code>uniq</code> 根本发现不了——这就是为什么几乎所有 <code>uniq</code> 的用法前面都要先接一个 <code>sort</code>,把相同的内容排到一起,<code>uniq</code> 才能真正生效。</p><h2 id="wc:统计行数、单词数、字节数"><a href="#wc:统计行数、单词数、字节数" class="headerlink" title="wc:统计行数、单词数、字节数"></a>wc:统计行数、单词数、字节数</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">wc</span> -l file.txt</span><br><span class="line"><span class="comment"># -l:统计行数,判断一个文件/输出有多少条记录时最常用</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">wc</span> -w file.txt</span><br><span class="line"><span class="comment"># -w:统计单词数(按空白字符分隔)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">wc</span> -c file.txt</span><br><span class="line"><span class="comment"># -c:统计字节数</span></span><br></pre></td></tr></table></figure><p><code>grep -c</code> 和 <code>wc -l</code> 经常配合使用:<code>grep "ERROR" file.txt | wc -l</code> 数出日志里报错的行数,比自己写循环计数快得多。</p><h2 id="jq:命令行的-JSON-处理器"><a href="#jq:命令行的-JSON-处理器" class="headerlink" title="jq:命令行的 JSON 处理器"></a>jq:命令行的 JSON 处理器</h2><p>处理 API 响应或者 JSON 格式的日志,<code>jq</code> 是命令行下最常用的工具:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">'{"name":"alice","age":25}'</span> | jq <span class="string">'.name'</span></span><br><span class="line"><span class="comment"># 取单个字段,输出 "alice"(带引号,因为 jq 输出的是 JSON 值)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">'[{"name":"alice"},{"name":"bob"}]'</span> | jq <span class="string">'.[].name'</span></span><br><span class="line"><span class="comment"># .[]:遍历数组里的每一项</span></span><br><span class="line"><span class="comment"># .name:取每一项的 name 字段,依次输出 "alice" 和 "bob"</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">'[{"name":"alice","age":25},{"name":"bob","age":17}]'</span> | jq <span class="string">'.[] | select(.age >= 18)'</span></span><br><span class="line"><span class="comment"># select:按条件过滤数组,只保留满足条件的项,这里是只保留 age >= 18 的记录</span></span><br></pre></td></tr></table></figure><p><code>jq</code> 的语法本身是一门小型查询语言,能做的远不止取字段——排序、分组、重新构造 JSON 结构都支持,日常处理 API 响应,这几个基本用法已经能覆盖大部分场景。</p><h2 id="组合实战:统计访问日志里最高频的-IP"><a href="#组合实战:统计访问日志里最高频的-IP" class="headerlink" title="组合实战:统计访问日志里最高频的 IP"></a>组合实战:统计访问日志里最高频的 IP</h2><p>访问日志每行开头是客户端 IP,统计出现次数最多的前 10 个:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'{print $1}'</span> access.log | <span class="built_in">sort</span> | <span class="built_in">uniq</span> -c | <span class="built_in">sort</span> -rn | <span class="built_in">head</span> -10</span><br><span class="line"><span class="comment"># awk '{print $1}':取每行第一列,也就是 IP 地址</span></span><br><span class="line"><span class="comment"># sort:排序,让相同的 IP 排到一起(uniq 生效的前提)</span></span><br><span class="line"><span class="comment"># uniq -c:统计每个 IP 出现的次数</span></span><br><span class="line"><span class="comment"># sort -rn:按次数数值从大到小排序</span></span><br><span class="line"><span class="comment"># head -10:只看前 10 条</span></span><br></pre></td></tr></table></figure><p>五个步骤,每一步只做一件很简单的事,串起来就完成了一个不算简单的统计任务——这正是 Unix 管道哲学的直接体现,也是<a href="/xargs%E7%AE%A1%E9%81%93%E4%B8%8ELinux%E5%91%BD%E4%BB%A4%E8%AE%BE%E8%AE%A1%E5%93%B2%E5%AD%A6">之前 xargs 那篇</a>提到的”小工具、文本接口、自由组合”的具体例子。</p><h2 id="常用参数速查表"><a href="#常用参数速查表" class="headerlink" title="常用参数速查表"></a>常用参数速查表</h2><table><thead><tr><th>命令/参数</th><th>作用</th></tr></thead><tbody><tr><td><code>cut -d -f</code></td><td>按分隔符截取指定字段</td></tr><tr><td><code>cut -c</code></td><td>按字符位置截取</td></tr><tr><td><code>sort -n</code></td><td>按数值大小排序,默认是字典序</td></tr><tr><td><code>sort -r</code></td><td>逆序排序</td></tr><tr><td><code>sort -k N,N</code></td><td>只按第 N 列排序</td></tr><tr><td><code>sort -u</code></td><td>排序同时去重</td></tr><tr><td><code>uniq -c</code></td><td>统计相邻重复行的次数,使用前必须先 sort</td></tr><tr><td><code>wc -l</code></td><td>统计行数</td></tr><tr><td><code>wc -w</code></td><td>统计单词数</td></tr><tr><td><code>wc -c</code></td><td>统计字节数</td></tr><tr><td><code>jq '.field'</code></td><td>取 JSON 字段</td></tr><tr><td><code>jq '.[]'</code></td><td>遍历 JSON 数组</td></tr><tr><td><code>jq 'select(cond)'</code></td><td>按条件过滤</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>这几个工具单独看都很简单,价值在组合——<code>sort</code> 配 <code>uniq</code> 做去重统计,<code>cut</code> 配 <code>wc</code> 做列提取加计数,<code>jq</code> 单独处理 JSON。遇到”想临时统计点什么”的需求,先想想能不能用这几个小工具接一条管道解决,很多时候比打开编辑器写脚本更快。</p>]]>
</content>
<id>https://blog.codewater.top/cut-sort-uniq-wc-jq%E6%96%87%E6%9C%AC%E5%B0%8F%E5%B7%A5%E5%85%B7%E7%BB%84%E5%90%88%E6%8B%B3%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/cut-sort-uniq-wc-jq%E6%96%87%E6%9C%AC%E5%B0%8F%E5%B7%A5%E5%85%B7%E7%BB%84%E5%90%88%E6%8B%B3%E5%AE%9E%E6%88%98/"/>
<published>2026-07-14T02:11:31.000Z</published>
<summary>cut 截取列、sort 排序、uniq 去重计数、wc 统计、jq 处理 JSON,组合替代 awk 脚本。</summary>
<title>cut、sort、uniq、wc、jq 实战:小工具组合怎么顶一个 awk 脚本</title>
<updated>2026-07-14T02:11:31.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="权限" scheme="https://blog.codewater.top/tags/%E6%9D%83%E9%99%90/"/>
<content>
<![CDATA[<p>需要用另一个身份执行操作时,<code>sudo</code>、<code>su</code>、<code>sudo su -</code> 好几种写法混着搜到的答案都不一样——都能”变成别的用户”,但具体差在哪、该用哪个,很少有人说清楚。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;跟 <a href="/Linux%E6%9D%83%E9%99%90%E4%B8%8E%E5%B1%9E%E4%B8%BB%E7%AE%A1%E7%90%86-chmod-chown%E5%AE%9E%E6%88%98">Linux 权限与属主管理:chmod、chown</a> 是互补关系——那篇讲文件权限本身,这篇讲”谁能以什么身份操作”。</p><h2 id="sudo:临时提权执行单条命令"><a href="#sudo:临时提权执行单条命令" class="headerlink" title="sudo:临时提权执行单条命令"></a>sudo:临时提权执行单条命令</h2><p><code>sudo</code>(superuser do)默认行为是以另一个用户(不指定的话是 root)的身份执行紧跟在后面的这一条命令,执行完就恢复回当前用户——不是切换整个会话。使用前必须在 <code>/etc/sudoers</code> 里被授权(通常是加入 <code>sudo</code> 或 <code>wheel</code> 组),没有授权直接报错拒绝。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl restart nginx</span><br><span class="line"><span class="comment"># 以 root 身份重启 nginx,执行完这条命令,当前 shell 还是原来的用户身份</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> -u www-data <span class="built_in">whoami</span></span><br><span class="line"><span class="comment"># -u:指定以哪个用户执行,不写默认是 root</span></span><br></pre></td></tr></table></figure><p>需要连续跑好几条 root 命令时,有两种进入”root 状态”的写法,行为不一样:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> -i</span><br><span class="line"><span class="comment"># 完整登录 shell,加载 root 自己的环境变量和 profile,工作目录切到 /root</span></span><br><span class="line"><span class="comment"># -i 是 --login 的简写</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> -s</span><br><span class="line"><span class="comment"># 保留当前用户的环境变量和 shell 配置,只是提权,不会进入 root 的家目录</span></span><br></pre></td></tr></table></figure><p><code>sudo -i</code> 更”干净”——环境完全是 root 自己的,不会被当前用户 shell 里可能存在的异常配置影响;<code>sudo -s</code> 适合只是想临时用 root 权限跑几条命令、又不想离开当前 shell 环境的场景。</p><h2 id="su:切换到另一个用户的完整会话"><a href="#su:切换到另一个用户的完整会话" class="headerlink" title="su:切换到另一个用户的完整会话"></a>su:切换到另一个用户的完整会话</h2><p><code>su</code>(switch user)是切换到另一个用户的整个 shell 会话,不像 <code>sudo</code> 那样执行完一条命令就自动恢复。同样有一个容易被忽略的区别:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">su alice</span><br><span class="line"><span class="comment"># 切换到 alice,但保留当前的环境变量和所在目录($HOME 还是原来的)</span></span><br><span class="line"></span><br><span class="line">su - alice</span><br><span class="line"><span class="comment"># 加了 -(等价 su -l alice):完整登录,环境变量、$HOME、当前工作目录</span></span><br><span class="line"><span class="comment"># 全部换成 alice 自己的,就像重新登录了一次</span></span><br></pre></td></tr></table></figure><p>不加 <code>-</code> 容易踩坑:切换过去之后 <code>$HOME</code>、<code>$PATH</code> 这些变量其实还是原来用户的,跑一些依赖环境变量的脚本可能出现莫名其妙的行为——日常切换身份,<code>su -</code> 才是更安全的默认选择。</p><h2 id="sudo-和-su-该怎么选"><a href="#sudo-和-su-该怎么选" class="headerlink" title="sudo 和 su 该怎么选"></a>sudo 和 su 该怎么选</h2><ul><li>只需要执行单条命令,用 <code>sudo</code>——每次操作都过一遍 sudoers 授权检查,操作有日志可查(记录谁在什么时间执行了什么),更适合团队协作的服务器环境</li><li>需要以另一个身份连续操作一段时间,用 <code>su -</code> 或者 <code>sudo su -</code>(先用 sudo 权限切换到 su,再完整登录)</li><li>现代实践更推荐前者:只在需要的时候临时提权,用完就退出,比长时间挂在一个 root 会话里风险更小</li></ul><h2 id="useradd-usermod:创建和修改用户"><a href="#useradd-usermod:创建和修改用户" class="headerlink" title="useradd / usermod:创建和修改用户"></a>useradd / usermod:创建和修改用户</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">useradd -m -s /bin/bash -G <span class="built_in">sudo</span> alice</span><br><span class="line"><span class="comment"># -m:创建家目录——不加这个参数,且系统的 CREATE_HOME 没打开的话,</span></span><br><span class="line"><span class="comment"># 默认不会创建家目录,这是新建用户后"登录就报错找不到家目录"的常见原因</span></span><br><span class="line"><span class="comment"># -s:指定登录 shell,这里是 bash</span></span><br><span class="line"><span class="comment"># -G:加入附加组,这里是把 alice 加进 sudo 组,让她能使用 sudo 命令</span></span><br></pre></td></tr></table></figure><p>修改已有用户的这些属性,用 <code>usermod</code>,参数含义基本和 <code>useradd</code> 一致:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">usermod -aG docker alice</span><br><span class="line"><span class="comment"># -aG:把 alice 追加(append)进 docker 组</span></span><br><span class="line"><span class="comment"># 注意一定要带 -a,只写 -G 会用新列表整个替换掉原来的附加组,把之前加入的组全部移除</span></span><br><span class="line"></span><br><span class="line">usermod -s /bin/zsh alice</span><br><span class="line"><span class="comment"># 修改 alice 的默认登录 shell</span></span><br></pre></td></tr></table></figure><h2 id="id-whoami:查看当前身份"><a href="#id-whoami:查看当前身份" class="headerlink" title="id / whoami:查看当前身份"></a>id / whoami:查看当前身份</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">id</span></span><br><span class="line"><span class="comment"># 显示当前用户的 UID、GID,以及所属的所有组</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">whoami</span></span><br><span class="line"><span class="comment"># 只输出当前用户名,不显示 UID/GID 和组信息</span></span><br></pre></td></tr></table></figure><p><code>id</code> 信息更全,排查”这个用户到底在哪些组里、有没有权限做某件事”时用它;只是想确认”我现在是谁”,<code>whoami</code> 更快。</p><h2 id="常用命令速查表"><a href="#常用命令速查表" class="headerlink" title="常用命令速查表"></a>常用命令速查表</h2><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>sudo 命令</code></td><td>以另一个用户(默认 root)身份执行单条命令,执行完恢复原身份</td></tr><tr><td><code>sudo -i</code></td><td>完整登录 shell,加载 root 自己的环境变量,进入 /root</td></tr><tr><td><code>sudo -s</code></td><td>保留当前用户的环境变量,只提权</td></tr><tr><td><code>su 用户名</code></td><td>切换用户但保留当前环境变量和目录</td></tr><tr><td><code>su - 用户名</code></td><td>完整登录,环境变量、$HOME、当前目录全部换成目标用户的</td></tr><tr><td><code>useradd -m</code></td><td>创建用户并生成家目录</td></tr><tr><td><code>useradd -s</code></td><td>指定登录 shell</td></tr><tr><td><code>useradd -G</code></td><td>加入附加组</td></tr><tr><td><code>usermod -aG</code></td><td>追加加入某个组,不会移除已有的附加组</td></tr><tr><td><code>id</code></td><td>查看当前用户的 UID/GID 和所属组</td></tr><tr><td><code>whoami</code></td><td>只查看当前用户名</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p><code>sudo</code> 和 <code>su</code> 的核心区别是”临时提权一条命令”还是”切换整个会话”;<code>su</code>/<code>su -</code> 的核心区别是环境变量要不要一起换。记住这两组对比,剩下的 <code>useradd</code>/<code>usermod</code>/<code>id</code>/<code>whoami</code> 都是围绕”这个用户是谁、能做什么”展开的具体操作,日常运维基本够用。</p>]]>
</content>
<id>https://blog.codewater.top/Linux%E7%94%A8%E6%88%B7%E4%B8%8E%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86-sudo-su%E4%B8%8Euseradd%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/Linux%E7%94%A8%E6%88%B7%E4%B8%8E%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86-sudo-su%E4%B8%8Euseradd%E5%AE%9E%E6%88%98/"/>
<published>2026-07-14T02:09:44.000Z</published>
<summary>sudo 和 su 区别在哪,useradd 建用户为什么没有家目录:常见权限操作实战。</summary>
<title>Linux 用户与权限管理:sudo、su 与 useradd 实战</title>
<updated>2026-07-14T02:09:44.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="包管理" scheme="https://blog.codewater.top/tags/%E5%8C%85%E7%AE%A1%E7%90%86/"/>
<content>
<![CDATA[<p>本地 Mac 用 <code>brew</code>,公司服务器是 Ubuntu 用 <code>apt</code>,偶尔还要连一台 CentOS 用 <code>yum</code>——同样是”装个包”,三套命令语法完全不一样,来回切换很容易记混,装个包先愣一下”这里到底该敲哪个”。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。</p><h2 id="三者分别是谁"><a href="#三者分别是谁" class="headerlink" title="三者分别是谁"></a>三者分别是谁</h2><p><code>apt</code>(Advanced Package Tool)是 Debian、Ubuntu 系发行版的包管理器,2016 年 Ubuntu 16.04 引入,把之前 <code>apt-get</code>/<code>apt-cache</code> 最常用的功能整合成一个更友好的命令,默认还带进度条和彩色输出。</p><p><code>yum</code>/<code>dnf</code> 是 RHEL、CentOS、Fedora 系发行版的包管理器。<code>dnf</code> 是 <code>yum</code> 的继任者,Fedora 从第 22 版(2015 年)起默认用 <code>dnf</code>,RHEL 8 / CentOS 8(2019 年)起也切换为默认用 <code>dnf</code>——现在的系统上敲 <code>yum</code>,背后实际调用的通常就是 <code>dnf</code>。</p><p><code>brew</code>(Homebrew)是 macOS(以及部分 Linux 发行版)的包管理器,装在用户目录下,不需要 <code>sudo</code>。</p><h2 id="常用操作对照"><a href="#常用操作对照" class="headerlink" title="常用操作对照"></a>常用操作对照</h2><p>装包、卸载、搜索这几个高频操作,三套命令放在一起看最清楚:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 更新软件源信息(不是升级软件本身,只是刷新"有哪些包、什么版本"这份清单)</span></span><br><span class="line">apt update</span><br><span class="line">dnf check-update</span><br><span class="line">brew update</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装一个包</span></span><br><span class="line">apt install nginx</span><br><span class="line">dnf install nginx</span><br><span class="line">brew install nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 卸载一个包</span></span><br><span class="line">apt remove nginx</span><br><span class="line">dnf remove nginx</span><br><span class="line">brew uninstall nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 搜索一个包</span></span><br><span class="line">apt search nginx</span><br><span class="line">dnf search nginx</span><br><span class="line">brew search nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出已安装的包</span></span><br><span class="line">apt list --installed</span><br><span class="line">dnf list installed</span><br><span class="line">brew list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看某个包的详细信息</span></span><br><span class="line">apt show nginx</span><br><span class="line">dnf info nginx</span><br><span class="line">brew info nginx</span><br></pre></td></tr></table></figure><p>三者语义基本一一对应,记住”动作”(装/卸/搜/查)而不是死记每个发行版的具体单词,切换起来会顺手很多。</p><h2 id="升级:更新源信息和升级已装软件是两回事"><a href="#升级:更新源信息和升级已装软件是两回事" class="headerlink" title="升级:更新源信息和升级已装软件是两回事"></a>升级:更新源信息和升级已装软件是两回事</h2><p><code>update</code> 类命令容易被误解成”升级软件”,实际上它只刷新本地的软件源索引(有哪些包、哪个版本),真正把已装软件升级到新版本要用另一条命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Debian/Ubuntu:先刷新索引,再升级已装的包</span></span><br><span class="line">apt update && apt upgrade</span><br><span class="line"></span><br><span class="line"><span class="comment"># RHEL/CentOS/Fedora:dnf 的 upgrade 会自动先检查更新</span></span><br><span class="line">dnf upgrade</span><br><span class="line"></span><br><span class="line"><span class="comment"># macOS:brew upgrade 升级所有已装的包(不加包名)</span></span><br><span class="line">brew update && brew upgrade</span><br></pre></td></tr></table></figure><h2 id="仓库和权限模型的本质区别"><a href="#仓库和权限模型的本质区别" class="headerlink" title="仓库和权限模型的本质区别"></a>仓库和权限模型的本质区别</h2><p><code>apt</code> 和 <code>dnf</code> 依赖系统级的仓库配置文件——<code>apt</code> 是 <code>/etc/apt/sources.list</code>(以及 <code>/etc/apt/sources.list.d/</code> 目录下的分文件配置),<code>dnf</code> 是 <code>/etc/yum.repos.d/</code> 下的一堆 <code>.repo</code> 文件。安装软件要写入系统目录(<code>/usr/bin</code>、<code>/etc</code> 等),所以需要 <code>sudo</code> 权限。</p><p><code>brew</code> 走的是完全不同的模型:软件仓库叫 tap,包和依赖全部装在用户目录下——Apple Silicon Mac 上是 <code>/opt/homebrew</code>,Intel Mac 上是 <code>/usr/local</code>(本机实测 <code>brew --prefix</code> 输出 <code>/opt/homebrew</code>)。因为不碰系统目录,<code>brew</code> 全程不需要 <code>sudo</code>,这也是它能在 macOS 这种对系统目录管得很严的系统上顺畅工作的原因。</p><h2 id="yum-还是-dnf:直接用-dnf"><a href="#yum-还是-dnf:直接用-dnf" class="headerlink" title="yum 还是 dnf:直接用 dnf"></a>yum 还是 dnf:直接用 dnf</h2><p>新装的 RHEL 8 / CentOS 8 及更新版本上,<code>yum</code> 命令依然能用,但只是指向 <code>dnf</code> 的兼容别名,背后真正执行的是 <code>dnf</code>。日常直接敲 <code>dnf</code> 就好,看到别人的脚本或者旧文档里写 <code>yum</code>,心里知道它现在等价于 <code>dnf</code> 即可,不用纠结该学哪个。</p><h2 id="三大家族命令速查表"><a href="#三大家族命令速查表" class="headerlink" title="三大家族命令速查表"></a>三大家族命令速查表</h2><table><thead><tr><th>操作</th><th>apt(Debian/Ubuntu)</th><th>dnf(RHEL/CentOS/Fedora)</th><th>brew(macOS)</th></tr></thead><tbody><tr><td>刷新源索引</td><td><code>apt update</code></td><td><code>dnf check-update</code></td><td><code>brew update</code></td></tr><tr><td>安装包</td><td><code>apt install pkg</code></td><td><code>dnf install pkg</code></td><td><code>brew install pkg</code></td></tr><tr><td>卸载包</td><td><code>apt remove pkg</code></td><td><code>dnf remove pkg</code></td><td><code>brew uninstall pkg</code></td></tr><tr><td>搜索包</td><td><code>apt search pkg</code></td><td><code>dnf search pkg</code></td><td><code>brew search pkg</code></td></tr><tr><td>列出已装</td><td><code>apt list --installed</code></td><td><code>dnf list installed</code></td><td><code>brew list</code></td></tr><tr><td>查看包信息</td><td><code>apt show pkg</code></td><td><code>dnf info pkg</code></td><td><code>brew info pkg</code></td></tr><tr><td>升级已装软件</td><td><code>apt upgrade</code></td><td><code>dnf upgrade</code></td><td><code>brew upgrade</code></td></tr><tr><td>是否需要 sudo</td><td>需要</td><td>需要</td><td>不需要</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p>三套包管理命令的核心操作就这几个:装、卸、搜、查、升级,语义上是对齐的,差的只是具体单词和权限模型。换发行版或者换到 Mac,不用重新学一遍,对照着上面这份表格套用命令名就行。</p>]]>
</content>
<id>https://blog.codewater.top/apt-yum-dnf-brew%E5%8C%85%E7%AE%A1%E7%90%86%E5%AE%9E%E6%88%98%E5%AF%B9%E6%AF%94/</id>
<link href="https://blog.codewater.top/apt-yum-dnf-brew%E5%8C%85%E7%AE%A1%E7%90%86%E5%AE%9E%E6%88%98%E5%AF%B9%E6%AF%94/"/>
<published>2026-07-14T02:07:32.000Z</published>
<summary>apt、yum/dnf、brew 常用操作怎么对应:安装、卸载、搜索、源配置一次讲清楚。</summary>
<title>apt、yum/dnf、brew 包管理实战:换发行版不用重新查命令</title>
<updated>2026-07-14T02:07:32.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="网络" scheme="https://blog.codewater.top/tags/%E7%BD%91%E7%BB%9C/"/>
<content>
<![CDATA[<p>“网站打不开”这句话背后可能是完全不同的问题:网络本身断了、中间某一跳路由出故障、域名解析错了,或者目标服务压根没在监听。不分层排查,只会对着一个打不开的页面反复刷新,猜半天猜不出所以然。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>;跟之前写的 <a href="/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8E%92%E6%9F%A5%E6%9C%8D%E5%8A%A1%E9%97%AE%E9%A2%98%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">Linux 服务器问题排查</a> 是互补关系——那篇讲的是端口、进程、日志(<code>ss</code>、<code>lsof</code>),这篇补上”网络本身通不通、DNS 解析对不对”这一层。</p><h2 id="ping:先确认最基本的连通性"><a href="#ping:先确认最基本的连通性" class="headerlink" title="ping:先确认最基本的连通性"></a>ping:先确认最基本的连通性</h2><p><code>ping</code> 基于 ICMP 协议,向目标主机发包并等待回应,用来测试连通性和延迟:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ping -c 3 baidu.com</span><br><span class="line"><span class="comment"># -c 3:只发 3 个探测包就停止,不加这个参数会一直发,要手动 Ctrl-C 停止</span></span><br><span class="line"></span><br><span class="line">64 bytes from 124.237.177.164: icmp_seq=0 ttl=54 time=124.8 ms</span><br><span class="line">64 bytes from 124.237.177.164: icmp_seq=1 ttl=54 time=125.6 ms</span><br><span class="line">64 bytes from 124.237.177.164: icmp_seq=2 ttl=54 time=127.1 ms</span><br><span class="line"><span class="comment"># time:这一个包的往返延迟,数值越大网络越慢</span></span><br><span class="line"><span class="comment"># ttl:包剩余的存活跳数,能大致反映经过了多少层网络设备(不用深究具体数值)</span></span><br></pre></td></tr></table></figure><p>有个陷阱必须提前知道:<strong>ping 不通不等于网络真的不通</strong>。很多服务器和防火墙会主动屏蔽 ICMP 协议(出于安全考虑,避免被用来扫描存活主机),这种情况下 <code>ping</code> 完全无响应,但目标服务本身其实是正常的、能正常访问。遇到 <code>ping</code> 不通,不能直接下结论”网络挂了”,还得结合后面的手段继续排查。</p><h2 id="traceroute:定位问题出在哪一跳"><a href="#traceroute:定位问题出在哪一跳" class="headerlink" title="traceroute:定位问题出在哪一跳"></a>traceroute:定位问题出在哪一跳</h2><p>如果 <code>ping</code> 确实不通,下一步是 <code>traceroute</code>——它能显示数据包从本机到目标主机,中间经过的每一跳路由,帮你定位问题出在链路上的哪一环:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">traceroute baidu.com</span><br><span class="line"></span><br><span class="line"> 1 192.168.1.1 (192.168.1.1) 2.336 ms 2.079 ms 2.250 ms</span><br><span class="line"> 2 10.169.144.154 (10.169.144.154) 31.583 ms</span><br><span class="line"> 10.169.144.158 (10.169.144.158) 13.839 ms</span><br><span class="line"> 10.169.144.186 (10.169.144.186) 7.618 ms</span><br><span class="line"> 3 10.185.174.254 (10.185.174.254) 10.458 ms 6.176 ms 6.550 ms</span><br><span class="line"><span class="comment"># 每一行是一跳,默认每跳发 3 个探测包(所以后面有 3 个时间值)</span></span><br><span class="line"><span class="comment"># 第 2 跳三次探测返回了不同的地址,说明这一跳背后是多条负载均衡的路径,属正常现象</span></span><br><span class="line"><span class="comment"># 某一跳如果显示 * * *,说明这一跳没有响应——可能是这个节点屏蔽了探测包,也可能是真的断在这里</span></span><br></pre></td></tr></table></figure><p><code>traceroute</code> 的原理并不复杂:它给数据包设置一个从 1 开始递增的 TTL(存活跳数),TTL 为 1 的包到第一跳路由器就会被丢弃,路由器同时会回一个”TTL 耗尽”的 ICMP 消息,<code>traceroute</code> 借此拿到第一跳的地址;接着发 TTL 为 2 的包获取第二跳,以此类推,直到包真正到达目标主机为止。</p><p>实战判断很直接:从某一跳开始持续超时或丢包,问题大概率就出在那一跳之后的链路上——如果那一跳是本地路由器,问题在本地网络;如果是运营商中间节点,问题可能不在你这边,得等或者换网络;如果一直跑到最后几跳才出问题,大概率是目标网络那边的事。</p><h2 id="dig-nslookup:DNS-解析诊断"><a href="#dig-nslookup:DNS-解析诊断" class="headerlink" title="dig / nslookup:DNS 解析诊断"></a>dig / nslookup:DNS 解析诊断</h2><p><code>ping</code> 能通、<code>traceroute</code> 也能跑到目标,网站却还是打不开?这时候该查的是域名解析对不对——域名解析错了,访问的可能压根不是你以为的那台服务器。<code>dig</code>(Domain Information Groper)是专门查询 DNS 记录的工具,默认查询目标域名的 A 记录(域名对应的 IPv4 地址):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">dig baidu.com</span><br><span class="line"></span><br><span class="line">;; QUESTION SECTION:</span><br><span class="line">;baidu.com.INA</span><br><span class="line"></span><br><span class="line">;; ANSWER SECTION:</span><br><span class="line">baidu.com.573INA124.237.177.164</span><br><span class="line">baidu.com.573INA111.63.65.103</span><br><span class="line"></span><br><span class="line">;; Query time: 12 msec</span><br><span class="line">;; SERVER: 100.100.100.100#53(100.100.100.100)</span><br><span class="line"><span class="comment"># QUESTION SECTION:查询了什么,这里是查 baidu.com 的 A 记录(域名对应的 IPv4 地址)</span></span><br><span class="line"><span class="comment"># ANSWER SECTION:解析结果,一个域名可能对应多个 IP(负载均衡的常见做法)</span></span><br><span class="line"><span class="comment"># Query time:这次查询耗时多久</span></span><br><span class="line"><span class="comment"># SERVER:这次查询实际使用的是哪个 DNS 服务器——如果解析结果不对,先看看是不是用错了 DNS 服务器</span></span><br></pre></td></tr></table></figure><p><code>nslookup</code> 是另一个查 DNS 的工具,功能和 <code>dig</code> 类似但输出格式更简洁:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">nslookup baidu.com</span><br><span class="line"></span><br><span class="line">Server:100.100.100.100</span><br><span class="line">Address:100.100.100.100#53</span><br><span class="line"></span><br><span class="line">Non-authoritative answer:</span><br><span class="line">Name:baidu.com</span><br><span class="line">Address: 124.237.177.164</span><br><span class="line"><span class="comment"># 一样能看出用的哪个 DNS 服务器(Server)、解析出来的地址(Address)</span></span><br><span class="line"><span class="comment"># 但信息量明显没有 dig 详细——没有 TTL、没有查询耗时</span></span><br></pre></td></tr></table></figure><p>日常排查优先用 <code>dig</code>,信息更全;<code>nslookup</code> 胜在几乎所有系统都自带,个别精简系统没装 <code>dig</code> 时可以顶上用。</p><h2 id="常用参数速查表"><a href="#常用参数速查表" class="headerlink" title="常用参数速查表"></a>常用参数速查表</h2><table><thead><tr><th>命令/参数</th><th>作用</th></tr></thead><tbody><tr><td><code>ping -c N</code></td><td>只发送 N 个探测包后停止,不加会一直发</td></tr><tr><td><code>ping</code> 输出里的 <code>time</code></td><td>单个包的往返延迟</td></tr><tr><td><code>ping</code> 输出里的 <code>ttl</code></td><td>包剩余存活跳数,大致反映经过的网络层数</td></tr><tr><td><code>traceroute -m N</code></td><td>最多探测 N 跳就停止</td></tr><tr><td><code>traceroute -w N</code></td><td>每次探测等待响应的超时时间(秒)</td></tr><tr><td><code>traceroute</code> 输出里的 <code>* * *</code></td><td>该跳没有响应,可能屏蔽了探测包或链路确实断在这里</td></tr><tr><td><code>dig 域名</code></td><td>查询域名的 A 记录,输出 ANSWER SECTION、Query time、SERVER 等信息</td></tr><tr><td><code>dig</code> 输出里的 <code>SERVER</code></td><td>这次查询实际使用的 DNS 服务器</td></tr><tr><td><code>nslookup 域名</code></td><td>查询域名解析结果,输出比 dig 简洁,没有 TTL 和查询耗时</td></tr></tbody></table><h2 id="综合排查流程"><a href="#综合排查流程" class="headerlink" title="综合排查流程"></a>综合排查流程</h2><p>网站/服务连不上,按这个顺序排查效率最高:</p><ol><li><strong><code>ping</code> 测网络层连通性</strong>——通了,跳到第 3 步;不通,先确认目标是不是屏蔽了 ICMP(比如用 <code>curl</code> 试一下能不能正常拿到响应),排除这个可能之后再进第 2 步</li><li><strong><code>traceroute</code> 定位断在哪一跳</strong>——找到持续超时/丢包开始的那一跳,判断问题出在本地网络、中间运营商还是目标网络</li><li><strong><code>dig</code> 确认域名解析对不对</strong>——解析出来的 IP 跟预期不符,问题在 DNS 这一层,检查用的 DNS 服务器或者域名解析配置</li><li><strong>解析也对、网络也通,但服务还是连不上</strong>——大概率是端口没监听或者防火墙拦截,这部分排查见 <a href="/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8E%92%E6%9F%A5%E6%9C%8D%E5%8A%A1%E9%97%AE%E9%A2%98%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">Linux 服务器问题排查</a> 里 <code>ss</code>、<code>lsof</code> 的用法</li></ol><p>这四步覆盖了从”网络本身”到”DNS”再到”目标服务”的完整链路,按顺序排查比凭感觉瞎试快得多。</p><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p><code>ping</code>、<code>traceroute</code>、<code>dig</code> 这三个工具分别对应网络排查的三个不同层次——连通性、路径、解析。记住这个分层,遇到”连不上”这类模糊的问题,就能很快把它拆解成一个个具体的、可以验证的假设,而不是对着报错信息干瞪眼。</p>]]>
</content>
<id>https://blog.codewater.top/%E7%BD%91%E7%BB%9C%E8%BF%9E%E9%80%9A%E6%80%A7%E8%AF%8A%E6%96%AD-ping-traceroute%E4%B8%8Edig%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/%E7%BD%91%E7%BB%9C%E8%BF%9E%E9%80%9A%E6%80%A7%E8%AF%8A%E6%96%AD-ping-traceroute%E4%B8%8Edig%E5%AE%9E%E6%88%98/"/>
<published>2026-07-13T12:40:49.000Z</published>
<summary>网站打不开怎么分层排查:ping 测连通、traceroute 定位断点、dig 查 DNS 解析。</summary>
<title>网络连不上怎么排查:ping、traceroute 与 dig/nslookup 实战</title>
<updated>2026-07-13T12:40:49.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="sed" scheme="https://blog.codewater.top/tags/sed/"/>
<category term="awk" scheme="https://blog.codewater.top/tags/awk/"/>
<content>
<![CDATA[<p>搜”命令行批量替换文本”,答案里有的用 <code>sed</code>,有的用 <code>awk</code>,看着好像都能解决问题,但不知道自己该学哪个、什么场景该用哪个。两者确实经常一起出现在同一批搜索结果里——但它们解决的其实是不同维度的问题:<code>sed</code> 面向”行”做替换、删除、插入;<code>awk</code> 面向”列”做结构化处理。搞清楚这个区别,选工具就不用再猜了。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。</p><h2 id="sed-基本用法:按行处理文本"><a href="#sed-基本用法:按行处理文本" class="headerlink" title="sed 基本用法:按行处理文本"></a>sed 基本用法:按行处理文本</h2><p><code>sed</code>(stream editor,流编辑器)默认逐行读取输入,对每一行执行指定的编辑命令后输出结果,不提供交互式编辑界面——写脚本自动化批量处理文本,正是它的设计目的。最核心的命令是 <code>s</code>,替换文本:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sed <span class="string">'s/old/new/'</span> file.txt</span><br><span class="line"><span class="comment"># 把每一行里第一个匹配到的 "old" 替换成 "new",只替换每行的第一处</span></span><br><span class="line"></span><br><span class="line">sed <span class="string">'s/old/new/g'</span> file.txt</span><br><span class="line"><span class="comment"># 加上 g(global),一行里所有匹配到的 "old" 都替换,不只是第一个</span></span><br></pre></td></tr></table></figure><p>默认情况下 <code>sed</code> 不会修改原文件,只是把处理结果打印到标准输出。想直接改文件本身,要加 <code>-i</code>(in-place)——这里有个必须提前知道的跨平台坑:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># macOS / BSD sed</span></span><br><span class="line">sed -i <span class="string">''</span> <span class="string">'s/old/new/'</span> file.txt</span><br><span class="line"><span class="comment"># BSD sed 要求 -i 后面必须紧跟一个备份文件后缀参数,不想要备份就传一个空字符串 ''</span></span><br><span class="line"><span class="comment"># 漏了这个空字符串参数,sed 会把下一个参数(也就是替换脚本本身)当成后缀,直接报错</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># GNU sed(Linux 发行版默认自带的 sed)</span></span><br><span class="line">sed -i <span class="string">'s/old/new/'</span> file.txt</span><br><span class="line"><span class="comment"># GNU sed 的 -i 不需要额外参数,直接原地修改,不生成备份</span></span><br><span class="line"><span class="comment"># 想要备份就不带空格直接写后缀:sed -i.bak 's/old/new/' file.txt,会生成 file.txt.bak</span></span><br></pre></td></tr></table></figure><p>同一条 <code>sed -i</code> 命令,在 macOS 上能跑通,搬到 Linux 服务器上可能报错(或者反过来)——写跨平台脚本时这是最容易被忽略的细节。</p><p>除了替换,<code>sed</code> 还能按行号删除或者操作某个范围:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sed <span class="string">'2d'</span> file.txt</span><br><span class="line"><span class="comment"># 删除第 2 行(打印到标准输出,加 -i 才会真正改文件)</span></span><br><span class="line"></span><br><span class="line">sed <span class="string">'2,4d'</span> file.txt</span><br><span class="line"><span class="comment"># 删除第 2 到第 4 行(含首尾)</span></span><br><span class="line"></span><br><span class="line">sed -e <span class="string">'s/foo/bar/'</span> -e <span class="string">'s/baz/qux/'</span> file.txt</span><br><span class="line"><span class="comment"># -e:一次执行多个替换表达式,不用为每个替换单独跑一次 sed</span></span><br></pre></td></tr></table></figure><h3 id="sed-常用参数速查表"><a href="#sed-常用参数速查表" class="headerlink" title="sed 常用参数速查表"></a>sed 常用参数速查表</h3><table><thead><tr><th>参数/命令</th><th>作用</th></tr></thead><tbody><tr><td><code>s/old/new/</code></td><td>替换命令,只替换每行第一个匹配</td></tr><tr><td><code>s/old/new/g</code></td><td>加 <code>g</code>,替换每行所有匹配</td></tr><tr><td><code>-i</code></td><td>原地修改文件;macOS/BSD 必须带空字符串参数 <code>-i ''</code>,GNU/Linux 直接 <code>-i</code> 即可</td></tr><tr><td><code>Nd</code></td><td>删除第 N 行,如 <code>2d</code></td></tr><tr><td><code>N,Md</code></td><td>删除第 N 到第 M 行</td></tr><tr><td><code>-e</code></td><td>一次执行多个表达式,不用重复调用 sed</td></tr></tbody></table><h2 id="awk-基本用法:按列处理结构化文本"><a href="#awk-基本用法:按列处理结构化文本" class="headerlink" title="awk 基本用法:按列处理结构化文本"></a>awk 基本用法:按列处理结构化文本</h2><p><code>awk</code> 是一门以”按行扫描、按字段处理”为核心的小型编程语言,处理思路跟 <code>sed</code> 不一样——它默认把每一行按空白字符拆成若干”字段”,用 <code>$1</code>、<code>$2</code> 这些变量取某一列:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">"alice 25 engineer"</span> | awk <span class="string">'{print $1}'</span></span><br><span class="line"><span class="comment"># $1 是第一个字段,输出 alice</span></span><br><span class="line"><span class="comment"># $0 是整行原始内容,$2、$3 依次是第二、第三个字段</span></span><br></pre></td></tr></table></figure><p>几个内置变量,日常最常用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'{print NF}'</span> file.txt</span><br><span class="line"><span class="comment"># NF:当前这一行有多少个字段(Number of Fields)</span></span><br><span class="line"></span><br><span class="line">awk <span class="string">'{print NR, $0}'</span> file.txt</span><br><span class="line"><span class="comment"># NR:到目前为止已经处理了多少行(Number of Records),是所有输入累计的行号</span></span><br><span class="line"><span class="comment"># 注意 NR 跨文件也是累计的——如果同时处理多个文件,"当前文件内的行号"要用 FNR,不是 NR</span></span><br></pre></td></tr></table></figure><p>按条件过滤是 <code>awk</code> 真正擅长的场景:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'$3 > 100 {print $1}'</span> data.txt</span><br><span class="line"><span class="comment"># 只有第 3 列的值大于 100 的行,才输出这一行的第 1 列</span></span><br><span class="line"><span class="comment"># 条件写在 {} 前面,满足条件的行才会执行后面 {} 里的动作</span></span><br><span class="line"></span><br><span class="line">awk <span class="string">'BEGIN {print "开始处理"} {sum += $2} END {print "总计:", sum}'</span> data.txt</span><br><span class="line"><span class="comment"># BEGIN 块:在处理任何一行之前执行一次,常用来打印表头或者初始化变量</span></span><br><span class="line"><span class="comment"># END 块:在处理完所有行之后执行一次,常用来输出汇总统计结果</span></span><br><span class="line"><span class="comment"># 中间的主体部分对每一行都执行一次,这里是把第 2 列累加进 sum</span></span><br><span class="line"></span><br><span class="line">awk -F<span class="string">','</span> <span class="string">'{print $2}'</span> data.csv</span><br><span class="line"><span class="comment"># -F:指定字段分隔符,默认是空白字符,处理 CSV 这类逗号分隔的文件要显式指定</span></span><br></pre></td></tr></table></figure><h3 id="awk-常用参数与内置变量速查表"><a href="#awk-常用参数与内置变量速查表" class="headerlink" title="awk 常用参数与内置变量速查表"></a>awk 常用参数与内置变量速查表</h3><table><thead><tr><th>参数/变量</th><th>作用</th></tr></thead><tbody><tr><td><code>$0</code></td><td>整行原始内容</td></tr><tr><td><code>$1</code>、<code>$2</code>…</td><td>第 1、第 2……个字段</td></tr><tr><td><code>NF</code></td><td>当前行的字段总数(Number of Fields)</td></tr><tr><td><code>NR</code></td><td>累计处理到第几行,跨文件累计(Number of Records)</td></tr><tr><td><code>FNR</code></td><td>当前文件内的行号,跨文件会重新计数</td></tr><tr><td><code>-F</code></td><td>指定字段分隔符,默认是空白字符</td></tr><tr><td><code>BEGIN {...}</code></td><td>处理任何一行之前执行一次,常用来初始化</td></tr><tr><td><code>END {...}</code></td><td>处理完所有行之后执行一次,常用来输出汇总</td></tr></tbody></table><h2 id="同一个任务,两种工具的差距在哪"><a href="#同一个任务,两种工具的差距在哪" class="headerlink" title="同一个任务,两种工具的差距在哪"></a>同一个任务,两种工具的差距在哪</h2><p>假设有一份访问日志,每行是”路径 响应时间(ms)”,想找出响应时间超过 500ms 的所有路径。用 <code>awk</code> 几乎是原生支持:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'$2 > 500 {print $1}'</span> access.log</span><br><span class="line"><span class="comment"># 一行搞定:按第 2 列的数值判断,满足条件就输出第 1 列</span></span><br></pre></td></tr></table></figure><p>用 <code>sed</code> 做同样的事,会很别扭——<code>sed</code> 的正则匹配本质是”文本模式匹配”,不理解”这一列的值是不是大于 500”这种数值比较,得先把数值范围拆成一堆正则模式去匹配文本形式,写出来又长又脆弱,稍微换一种数字格式就得重新调整正则。这不是 <code>sed</code> 的缺陷,是它压根不是为这类场景设计的——<code>sed</code> 面对的是”文本”,<code>awk</code> 面对的是”字段和数值”。</p><h2 id="该用哪个:判断标准"><a href="#该用哪个:判断标准" class="headerlink" title="该用哪个:判断标准"></a>该用哪个:判断标准</h2><ul><li>只是替换、删除、插入某几行文本,不涉及”按列取值”或者”数值比较”——用 <code>sed</code>,更直接</li><li>需要”取第几列””按某一列的值筛选””跨行做统计汇总”——用 <code>awk</code>,这是它的设计初衷</li><li>两者也能组合使用,比如先用 <code>awk</code> 提取出需要的列,再用 <code>sed</code> 调整输出格式,但没必要为了”只用一个工具”硬凑一个别扭的写法</li></ul><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p><code>sed</code> 和 <code>awk</code> 都是几十年历史的经典 Unix 工具,功能远不止本文列的这些(<code>sed</code> 支持完整的模式空间和保持空间操作,<code>awk</code> 本身就是一门小型编程语言,支持自定义函数、数组),但日常排查和批处理,本文这些用法已经能覆盖大部分场景。选错工具通常不是因为哪个”更强”,而是没看清任务本质是”处理行”还是”处理列”。</p>]]>
</content>
<id>https://blog.codewater.top/sed%E4%B8%8Eawk%E5%AE%9E%E6%88%98%E5%AF%B9%E6%AF%94-%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7%E6%80%8E%E4%B9%88%E9%80%89/</id>
<link href="https://blog.codewater.top/sed%E4%B8%8Eawk%E5%AE%9E%E6%88%98%E5%AF%B9%E6%AF%94-%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7%E6%80%8E%E4%B9%88%E9%80%89/"/>
<published>2026-07-13T12:38:05.000Z</published>
<summary>sed 面向行替换,awk 面向列处理:s 命令、-i 跨平台坑、NF/NR 怎么用与选型判断。</summary>
<title>sed 与 awk 实战对比:什么时候该用哪个</title>
<updated>2026-07-13T12:38:05.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="rsync" scheme="https://blog.codewater.top/tags/rsync/"/>
<content>
<![CDATA[<p>同步一个几个 G 的目录到服务器,哪怕只改了几个文件,用 <code>scp</code> 或 <code>cp</code> 还是得把整个目录重新传一遍——网络稍微差一点,等待时间就长得离谱。<code>rsync</code> 解决的正是这个问题:只传输真正变化的部分,其余的原样跳过。</p><span id="more"></span><p>这是 Linux 命令系列的一篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。</p><h2 id="rsync-为什么”增量”"><a href="#rsync-为什么”增量”" class="headerlink" title="rsync 为什么”增量”"></a>rsync 为什么”增量”</h2><p><code>rsync</code>(remote sync)是一个文件/目录同步工具,既能同步本地磁盘之间的目录,也能通过 SSH 同步到远程主机,默认行为就是只处理源和目标之间有差异的部分,没变化的文件直接跳过。它判断”变了没”,不是简单看文件名或者改动时间——它用的是一套块级别的对比算法(由 Andrew Tridgell 提出,通常直接叫 rsync algorithm):把文件切成一个个数据块,两边同时计算校验和,只有校验和对不上的块才会真正传输,没变的块直接跳过不传。</p><p>这意味着即使是同一个文件,哪怕只改了中间一小段,<code>rsync</code> 也只传那一小段变化的数据,不会把整个文件重传一遍。目录同步同理:目录里没变的文件直接跳过,只有新增、删除、内容变化的文件才会被处理。这是它比 <code>cp</code>/<code>scp</code> 快的根本原因,不是”压缩得更好”这类表面优化。</p><h2 id="最常用的参数组合:-avz"><a href="#最常用的参数组合:-avz" class="headerlink" title="最常用的参数组合:-avz"></a>最常用的参数组合:-avz</h2><p>日常用 <code>rsync</code> 基本都是这个组合起手:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz ./local_dir/ user@host:/remote/path/</span><br><span class="line"><span class="comment"># -a:archive,归档模式,等价于同时打开 -rlptgoD 这几个选项——</span></span><br><span class="line"><span class="comment"># 递归子目录、保留符号链接、保留权限、保留修改时间、保留属组属主、保留设备文件和特殊文件</span></span><br><span class="line"><span class="comment"># 简单说就是"尽量把文件的原始状态完整保留",日常同步基本都要带上</span></span><br><span class="line"><span class="comment"># -v:verbose,显示每个被传输的文件名,方便确认到底同步了什么</span></span><br><span class="line"><span class="comment"># -z:compress,传输过程中压缩数据,跨网络传输时能省带宽;本地磁盘之间同步不需要这个</span></span><br></pre></td></tr></table></figure><p><code>-a</code> 是这几个参数里最关键的一个:不加 <code>-a</code> 的话,<code>rsync</code> 默认不会递归进子目录,也不会保留权限和时间戳这些元信息,同步出来的结果跟预期会差很多。</p><h2 id="P:进度条和断点续传"><a href="#P:进度条和断点续传" class="headerlink" title="-P:进度条和断点续传"></a>-P:进度条和断点续传</h2><p>同步大文件的时候,光有 <code>-avz</code> 还不够——看不到进度,网络一断就得从头再来:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rsync -avzP bigfile.iso user@host:/remote/path/</span><br><span class="line"><span class="comment"># -P:--partial 和 --progress 两个选项的简写</span></span><br><span class="line"><span class="comment"># --progress:实时显示当前文件的传输进度(速度、百分比、预计剩余时间)</span></span><br><span class="line"><span class="comment"># --partial:传输中断时保留已经传完的部分,下次续传时接着传,不用从头开始</span></span><br></pre></td></tr></table></figure><p><code>-P</code> 对大文件、不稳定的网络环境特别有用——没有 <code>--partial</code> 的话,传到一半中断,之前传的部分会被删掉,下次又得从零开始。</p><h2 id="–delete:让目标和源完全一致"><a href="#–delete:让目标和源完全一致" class="headerlink" title="–delete:让目标和源完全一致"></a>–delete:让目标和源完全一致</h2><p>前面的同步只会”新增/更新”文件,源目录里删掉的文件不会影响目标目录——目标那边的旧文件会一直留着。想让目标和源保持完全一致(源删了,目标也跟着删),要加 <code>--delete</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz --delete ./local_dir/ user@host:/remote/path/</span><br><span class="line"><span class="comment"># --delete:目标目录里凡是源目录没有的文件,都会被删除</span></span><br><span class="line"><span class="comment"># 这是个危险参数——用反了方向(源和目标搞反),可能会把不该删的文件删掉</span></span><br></pre></td></tr></table></figure><p><code>--delete</code> 属于”威力大但容易出事”的参数,用之前一定要先确认源和目标的方向没搞反。</p><h2 id="–dry-run:先演练一遍,别直接动手"><a href="#–dry-run:先演练一遍,别直接动手" class="headerlink" title="–dry-run:先演练一遍,别直接动手"></a>–dry-run:先演练一遍,别直接动手</h2><p>尤其是带了 <code>--delete</code> 这种会删除文件的操作,实际执行前应该先跑一遍演练模式,只看会发生什么,不真的执行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz --delete --dry-run ./local_dir/ user@host:/remote/path/</span><br><span class="line"><span class="comment"># --dry-run(也可以用简写 -n):只打印会做的操作,不实际传输或删除任何文件</span></span><br><span class="line"><span class="comment"># 确认输出的文件列表符合预期之后,去掉 --dry-run 再跑一次真正执行</span></span><br></pre></td></tr></table></figure><p>只要命令里带了 <code>--delete</code>,先 <code>--dry-run</code> 看一眼输出,是能避免”手滑删错文件”最简单也最有效的办法。</p><h2 id="排除规则:跳过不想同步的文件"><a href="#排除规则:跳过不想同步的文件" class="headerlink" title="排除规则:跳过不想同步的文件"></a>排除规则:跳过不想同步的文件</h2><p>项目目录里的 <code>.git</code>、<code>node_modules</code> 这类文件,同步时通常不需要带上:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz --exclude=<span class="string">'.git'</span> --exclude=<span class="string">'node_modules'</span> ./project/ user@host:/remote/project/</span><br><span class="line"><span class="comment"># --exclude:排除匹配指定模式的文件或目录,可以写多次排除多种内容</span></span><br><span class="line"></span><br><span class="line">rsync -avz --exclude-from=<span class="string">'exclude-list.txt'</span> ./project/ user@host:/remote/project/</span><br><span class="line"><span class="comment"># --exclude-from:从文件里批量读取排除规则,规则多的时候比堆一串 --exclude 清晰</span></span><br><span class="line"><span class="comment"># exclude-list.txt 里每行写一条规则,比如:</span></span><br><span class="line"><span class="comment"># .git</span></span><br><span class="line"><span class="comment"># node_modules</span></span><br><span class="line"><span class="comment"># *.log</span></span><br></pre></td></tr></table></figure><h2 id="远程同步和一个经典的坑:源目录带不带末尾斜杠"><a href="#远程同步和一个经典的坑:源目录带不带末尾斜杠" class="headerlink" title="远程同步和一个经典的坑:源目录带不带末尾斜杠"></a>远程同步和一个经典的坑:源目录带不带末尾斜杠</h2><p><code>rsync</code> 同步远程主机走的是 SSH 通道,语法跟 <code>scp</code> 类似但功能强得多:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rsync -avz ./local_dir/ user@host:/remote/path/</span><br><span class="line"><span class="comment"># 基于 SSH 传输,需要目标主机开着 SSH 服务、当前用户有权限登录</span></span><br><span class="line"><span class="comment"># 跟 scp 语法接近,但增量传输、断点续传这些能力 scp 都没有</span></span><br></pre></td></tr></table></figure><p>源目录路径末尾带不带 <code>/</code>,行为是不一样的——这是 <code>rsync</code> 最容易踩的坑,官方手册专门用例子说明过:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">rsync -av /src/foo /dest</span><br><span class="line"><span class="comment"># 源路径不带末尾斜杠:把 foo 这个目录本身同步过去</span></span><br><span class="line"><span class="comment"># 结果是 /dest/foo/ 里存放 foo 目录的内容——多出了一层 foo 目录</span></span><br><span class="line"></span><br><span class="line">rsync -av /src/foo/ /dest/foo</span><br><span class="line"><span class="comment"># 源路径带末尾斜杠:表示"同步这个目录里的内容",不是目录本身</span></span><br><span class="line"><span class="comment"># 这条命令和上面那条命令,最终产生的结果是一样的——都是 /dest/foo/ 里存放 foo 的内容</span></span><br><span class="line"><span class="comment"># 区别在于目标路径要不要手动多写一层 foo</span></span><br></pre></td></tr></table></figure><p>简单记忆:源路径末尾的 <code>/</code> 决定”要不要在目标里多包一层目录”——同步目录内容用 <code>foo/</code>,同步目录本身(连同目录名)用 <code>foo</code>(不带斜杠),目标那边对应地要不要手动加上这层目录名。搞混这个经常导致同步出来的目录结构和预期差一层,排查起来莫名其妙。</p><h2 id="rsync-常用参数速查表"><a href="#rsync-常用参数速查表" class="headerlink" title="rsync 常用参数速查表"></a>rsync 常用参数速查表</h2><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-a</code></td><td>archive,归档模式,等价于 <code>-rlptgoD</code>:递归子目录、保留符号链接/权限/时间戳/属组属主/设备文件</td></tr><tr><td><code>-v</code></td><td>verbose,显示每个被传输的文件名</td></tr><tr><td><code>-z</code></td><td>compress,传输时压缩数据,跨网络同步能省带宽,本地同步不需要</td></tr><tr><td><code>-P</code></td><td><code>--partial --progress</code> 的简写,显示传输进度并支持断点续传</td></tr><tr><td><code>--delete</code></td><td>让目标和源完全一致,删除目标里源没有的文件——危险参数,用前确认方向没搞反</td></tr><tr><td><code>--dry-run</code> / <code>-n</code></td><td>演练模式,只打印会执行的操作,不真的传输或删除任何文件</td></tr><tr><td><code>--exclude</code></td><td>排除匹配指定模式的文件或目录,可以写多次</td></tr><tr><td><code>--exclude-from</code></td><td>从文件批量读取排除规则,规则多时比堆一串 <code>--exclude</code> 清晰</td></tr></tbody></table><h2 id="写到这里"><a href="#写到这里" class="headerlink" title="写到这里"></a>写到这里</h2><p><code>rsync</code> 的核心就是这几件事:增量传输省时间、<code>-P</code> 应付大文件和不稳定网络、<code>--delete</code> 配 <code>--dry-run</code> 保证安全、<code>--exclude</code> 跳过不需要的内容。这几个参数组合起来,日常的目录同步和轻量级备份基本够用——更复杂的定时增量备份场景,通常是在这套参数基础上配合 cron 或者专门的备份工具,但底层同步逻辑还是这些。</p>]]>
</content>
<id>https://blog.codewater.top/rsync%E5%A2%9E%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E6%88%98-%E5%B8%B8%E7%94%A8%E5%8F%82%E6%95%B0%E4%B8%8E%E8%BF%9C%E7%A8%8B%E5%90%8C%E6%AD%A5/</id>
<link href="https://blog.codewater.top/rsync%E5%A2%9E%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E6%88%98-%E5%B8%B8%E7%94%A8%E5%8F%82%E6%95%B0%E4%B8%8E%E8%BF%9C%E7%A8%8B%E5%90%8C%E6%AD%A5/"/>
<published>2026-07-13T12:35:28.000Z</published>
<summary>rsync 为什么比 scp/cp 快:增量同步原理、-avz/-P/--delete 参数与远程同步实战。</summary>
<title>rsync 增量同步实战:常用参数、排除规则与远程同步</title>
<updated>2026-07-13T12:35:28.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="Emacs" scheme="https://blog.codewater.top/tags/Emacs/"/>
<category term="readline" scheme="https://blog.codewater.top/tags/readline/"/>
<content>
<![CDATA[<p>命令敲到一半打错了个字符,很多人的反应是按住 <code>←</code> 挪半天光标,或者干脆全删了重打。其实 bash、zsh 这些 shell 默认就内置了一整套 Emacs 风格的编辑快捷键,光标跳词、整段删除、剪切粘贴全都有对应按键,不需要任何配置就能用。</p><p>这篇讲清楚这套快捷键怎么用,外加一个进阶技巧:命令太长太复杂的时候,直接把它甩进编辑器里改。</p><span id="more"></span><p>这是终端效率小系列第三篇,承接 <a href="/%E4%B8%8D%E7%86%9F%E6%82%89%E7%9A%84%E5%91%BD%E4%BB%A4%E6%80%8E%E4%B9%88%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-man%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E4%B8%8Eapropos%E9%80%9F%E6%9F%A5">不熟悉的命令怎么快速搞懂</a> 和 <a href="/vim%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-%E6%A8%A1%E5%BC%8F%E5%88%87%E6%8D%A2%E5%88%B0%E4%BF%9D%E5%AD%98%E9%80%80%E5%87%BA">vim 基本操作</a>——本篇最后一个技巧会直接用到上一篇学的 vim 保存退出操作。</p><h2 id="为什么这套快捷键几乎所有-shell-都能用"><a href="#为什么这套快捷键几乎所有-shell-都能用" class="headerlink" title="为什么这套快捷键几乎所有 shell 都能用"></a>为什么这套快捷键几乎所有 shell 都能用</h2><p>命令行的编辑功能,bash 靠的是 GNU Readline 库,zsh 靠的是自己的 ZLE(Zsh Line Editor)。两者设计上的默认编辑模式都是 Emacs 风格——但这里有个很容易踩的坑:<strong>zsh 有条隐藏规则,只要 <code>$EDITOR</code> 或 <code>$VISUAL</code> 的值里包含字符串 “vi”(<code>vim</code>、<code>nvim</code> 都算在内),zsh 启动时就会自动切换成 vi 键位,而不是 Emacs 键位</strong>。开发者把 <code>$EDITOR</code> 设成 <code>vim</code> 或 <code>nvim</code> 是很常见的习惯,这种情况下 zsh 默认给的其实是 vi 键位,本文这些 Emacs 快捷键很可能大部分不生效或者行为完全对不上。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="variable">$EDITOR</span></span><br><span class="line"><span class="comment"># 先看一眼这个变量的值,只要包含 "vi"(vim、nvim 都算),zsh 大概率已经是 vi 键位了</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">bindkey</span> -e</span><br><span class="line"><span class="comment"># 写进 ~/.zshrc,强制 zsh 使用 Emacs 键位,不再受 $EDITOR 内容影响</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -o vi</span><br><span class="line"><span class="comment"># bash 里想切换成 vi 风格的编辑模式(用 hjkl 那一套)用这个</span></span><br><span class="line"><span class="comment"># 但切过去之后,本文这些 Emacs 快捷键大部分失效,两套模式二选一</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -o emacs</span><br><span class="line"><span class="comment"># 切回 Emacs 模式,bash 默认就是这个,不受 $EDITOR 影响——这点和 zsh 不一样</span></span><br></pre></td></tr></table></figure><p>不确定自己当前是哪种键位,直接试一下 <code>Ctrl-A</code> 能不能跳到行首:能跳就是 Emacs 键位;没反应或者屏幕上蹦出一个奇怪符号,大概率是 vi 键位——vi 键位下得先按 <code>Esc</code> 进命令模式,<code>0</code> 才是跳到行首。</p><h2 id="行内光标移动:不用方向键也能秒跳"><a href="#行内光标移动:不用方向键也能秒跳" class="headerlink" title="行内光标移动:不用方向键也能秒跳"></a>行内光标移动:不用方向键也能秒跳</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Ctrl-A</span><br><span class="line"><span class="comment"># 跳到行首(A = ahead / 行的开头)</span></span><br><span class="line"></span><br><span class="line">Ctrl-E</span><br><span class="line"><span class="comment"># 跳到行尾(E = end)</span></span><br><span class="line"></span><br><span class="line">Alt-B</span><br><span class="line"><span class="comment"># 向左跳一个单词(B = back)</span></span><br><span class="line"></span><br><span class="line">Alt-F</span><br><span class="line"><span class="comment"># 向右跳一个单词(F = forward)</span></span><br></pre></td></tr></table></figure><p><code>Alt-B</code>/<code>Alt-F</code> 在部分终端里按不出效果——很多终端默认不把 <code>Alt</code> 当 Meta 键处理,需要在终端设置里打开”Use Option as Meta Key”(macOS Terminal.app 的选项,iTerm2 类似)。打不开设置的话,用 <code>Esc</code> 代替 <code>Alt</code> 也一样:先按 <code>Esc</code> 松开,再按 <code>b</code> 或 <code>f</code>,效果等价于 <code>Alt-B</code>/<code>Alt-F</code>。</p><h2 id="删除和剪切:Ctrl-W-U-K-存进-kill-ring,Ctrl-Y-取回"><a href="#删除和剪切:Ctrl-W-U-K-存进-kill-ring,Ctrl-Y-取回" class="headerlink" title="删除和剪切:Ctrl-W/U/K 存进 kill-ring,Ctrl-Y 取回"></a>删除和剪切:Ctrl-W/U/K 存进 kill-ring,Ctrl-Y 取回</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Ctrl-W</span><br><span class="line"><span class="comment"># 删除光标前面的一个单词(向后删)</span></span><br><span class="line"></span><br><span class="line">Alt-D</span><br><span class="line"><span class="comment"># 删除光标后面的一个单词(向前删)——Ctrl-W 的反方向</span></span><br><span class="line"></span><br><span class="line">Ctrl-K</span><br><span class="line"><span class="comment"># 删除从光标到行尾的所有内容</span></span><br><span class="line"></span><br><span class="line">Ctrl-Y</span><br><span class="line"><span class="comment"># 粘贴——把上一次删除的内容粘贴回来(yank)</span></span><br></pre></td></tr></table></figure><p><code>Ctrl-U</code> 单独拎出来说,因为它在 bash 和 zsh 里行为不一样,是个真实存在的坑:bash 的 <code>Ctrl-U</code>(Readline 的 <code>unix-line-discard</code>)只删从光标到行首的部分,光标后面的内容原样保留;zsh 的 <code>Ctrl-U</code>(不管 vi 键位还是 Emacs 键位)绑定的都是 <code>kill-whole-line</code>,不管光标停在哪,直接清空整行。同一个按键,bash 和 zsh 下的效果是不同的,用 zsh 的人如果照着 bash 的经验去理解 <code>Ctrl-U</code>,会觉得”怎么光标后面的内容也被删了”。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Ctrl-U</span><br><span class="line"><span class="comment"># bash:删除从光标到行首的内容,光标后面的保留</span></span><br><span class="line"><span class="comment"># zsh:不管光标在哪,直接清空整行——这是 zsh 的 kill-whole-line,跟 bash 不是一回事</span></span><br></pre></td></tr></table></figure><p>这几个删除命令背后有个共同点:删掉的内容不是直接消失,而是存进一个叫 kill-ring 的剪贴区,<code>Ctrl-Y</code> 粘贴的就是这里最新的一条。</p><h2 id="清屏和历史命令反向搜索"><a href="#清屏和历史命令反向搜索" class="headerlink" title="清屏和历史命令反向搜索"></a>清屏和历史命令反向搜索</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Ctrl-L</span><br><span class="line"><span class="comment"># 清屏,光标当前这一行会被挪到清空后的屏幕顶部,命令内容不受影响</span></span><br><span class="line"></span><br><span class="line">Ctrl-R</span><br><span class="line"><span class="comment"># 反向搜索历史命令,输入关键词实时匹配之前敲过的命令</span></span><br><span class="line"><span class="comment"># 再按一次 Ctrl-R 跳到更早的一条匹配,按 Enter 执行,按 Esc 只取用不执行</span></span><br></pre></td></tr></table></figure><p><code>Ctrl-R</code> 是历史命令搜索里最常用的一个,比按 <code>↑</code> 翻半天历史记录快得多——尤其是敲过成百上千条命令之后,翻方向键基本翻不到想要的那条。</p><h2 id="进阶技巧:把命令行甩进编辑器里改"><a href="#进阶技巧:把命令行甩进编辑器里改" class="headerlink" title="进阶技巧:把命令行甩进编辑器里改"></a>进阶技巧:把命令行甩进编辑器里改</h2><p><code>Ctrl-A</code>/<code>Ctrl-E</code> 这类单字符编辑对付一行命令够用,但遇到又长又复杂的命令——比如带了好几个换行的多行脚本片段,或者一长串 JSON——在命令行里用方向键和退格键改,效率很低。这种场景该用 <code>Ctrl-X Ctrl-E</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Ctrl-X Ctrl-E</span><br><span class="line"><span class="comment"># 把当前命令行的内容甩进编辑器,用完整的编辑器能力改</span></span><br><span class="line"><span class="comment"># 改完保存退出,编辑结果自动回填到命令行——不会直接执行,只是替换命令行内容</span></span><br></pre></td></tr></table></figure><p>用哪个编辑器由 <code>$VISUAL</code>、<code>$EDITOR</code> 这两个环境变量决定,Bash 官方文档写得很明确:依次尝试 <code>$VISUAL</code>、<code>$EDITOR</code>,两个都没设置的话,最后退回 <code>emacs</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> EDITOR=vim</span><br><span class="line"><span class="comment"># 把默认编辑器设成 vim,Ctrl-X Ctrl-E 打开的就是 vim</span></span><br><span class="line"><span class="comment"># 没设置这个变量时,bash 会依次尝试 $VISUAL、$EDITOR,都没有就用 emacs</span></span><br></pre></td></tr></table></figure><p>进了编辑器之后,改完用上一篇学的 <code>:wq</code> 或 <code>:x</code> 保存退出(这正是本文系列第二篇的用武之地),命令行会自动变成编辑后的内容,回车才会真正执行。</p><p>有个地方要注意:这个功能在 bash 里是 Readline 自带的,默认已经绑定在 <code>Ctrl-X Ctrl-E</code> 上,装好 bash 就能用;但 zsh 不一样,ZLE 默认编辑模式虽然也是 Emacs,这个”甩进编辑器”的功能却不是默认绑定的,得在 <code>~/.zshrc</code> 里手动开启:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">autoload</span> -Uz edit-command-line</span><br><span class="line"><span class="comment"># 加载 zsh 自带的 edit-command-line 功能模块</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">zle</span> -N edit-command-line</span><br><span class="line"><span class="comment"># 把它注册成一个 ZLE 可用的编辑命令</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">bindkey</span> <span class="string">'^x^e'</span> edit-command-line</span><br><span class="line"><span class="comment"># 绑定到 Ctrl-X Ctrl-E,跟 bash 保持一致的按键习惯</span></span><br></pre></td></tr></table></figure><p>写完存好 <code>.zshrc</code>,<code>source ~/.zshrc</code> 或者重开一个终端窗口生效。</p><h2 id="vim-nvim-跳转到指定行"><a href="#vim-nvim-跳转到指定行" class="headerlink" title="vim/nvim 跳转到指定行"></a>vim/nvim 跳转到指定行</h2><p>编辑长文件时,与其打开后再一步步找到目标行,不如打开的时候就直接带上跳转目标:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vim +42 access.log</span><br><span class="line"><span class="comment"># 打开文件后直接跳到第 42 行,光标停在那一行</span></span><br><span class="line"></span><br><span class="line">vim +/ERROR access.log</span><br><span class="line"><span class="comment"># 打开文件后跳到第一个匹配 "ERROR" 的位置,等价于打开后手动 /ERROR 回车</span></span><br></pre></td></tr></table></figure><p><code>+42</code> 是 <code>+:42</code> 的简写形式——vim 允许在命令行用 <code>+{Ex命令}</code> 指定打开文件后自动执行的命令,<code>42</code> 本身就是一个合法的行号跳转 Ex 命令;<code>+/pattern</code> 同理,等价于打开后立刻执行一次 <code>/pattern</code> 搜索。</p><p>如果已经在 vim 里面了,跳转用 <code>:42</code> 或者 <code>42G</code>——后者就是<a href="/vim%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-%E6%A8%A1%E5%BC%8F%E5%88%87%E6%8D%A2%E5%88%B0%E4%BF%9D%E5%AD%98%E9%80%80%E5%87%BA">上一篇 vim 文章</a>里提到的 <code>G</code> 命令,前面加数字表示跳到第几行,和 <code>gg</code>/<code>G</code> 是同一套逻辑的延伸。</p><h2 id="终端效率小系列:写到这里"><a href="#终端效率小系列:写到这里" class="headerlink" title="终端效率小系列:写到这里"></a>终端效率小系列:写到这里</h2><p>三篇写下来,从 <a href="/%E4%B8%8D%E7%86%9F%E6%82%89%E7%9A%84%E5%91%BD%E4%BB%A4%E6%80%8E%E4%B9%88%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-man%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E4%B8%8Eapropos%E9%80%9F%E6%9F%A5">不熟悉的命令怎么快速搞懂</a>,到 <a href="/vim%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-%E6%A8%A1%E5%BC%8F%E5%88%87%E6%8D%A2%E5%88%B0%E4%BF%9D%E5%AD%98%E9%80%80%E5%87%BA">vim 的基本操作</a>,再到这篇 Emacs 风格的命令行编辑——串起来就是一套完整的终端使用习惯:查文档、编辑文件、编辑命令行,三件事都能用最少的按键完成。剩下的就是练:这些快捷键只有敲进肌肉记忆之后才会真正提速,光看不练效果有限。</p>]]>
</content>
<id>https://blog.codewater.top/%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%BC%96%E8%BE%91%E8%BF%9B%E9%98%B6-Emacs%E5%BF%AB%E6%8D%B7%E9%94%AE%E4%B8%8E%E7%94%A9%E8%BF%9Bvim%E7%BC%96%E8%BE%91/</id>
<link href="https://blog.codewater.top/%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%BC%96%E8%BE%91%E8%BF%9B%E9%98%B6-Emacs%E5%BF%AB%E6%8D%B7%E9%94%AE%E4%B8%8E%E7%94%A9%E8%BF%9Bvim%E7%BC%96%E8%BE%91/"/>
<published>2026-07-12T16:30:02.000Z</published>
<summary>bash/zsh 默认是 Emacs 键位:Ctrl-A/E/W/U/K/Y 怎么用、Ctrl-X Ctrl-E 甩进编辑器改命令,附 vim 跳转指定行技巧。</summary>
<title>命令行编辑进阶:Emacs 快捷键、甩进 vim 编辑命令行与跳转到指定行</title>
<updated>2026-07-12T16:30:02.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="vim" scheme="https://blog.codewater.top/tags/vim/"/>
<content>
<![CDATA[<p>“怎么退出 vim”是程序员圈子里流传最广的梗之一——一个编辑器要是连怎么关掉都成了笑话,说明它一定有什么地方和你以为的不一样。</p><p>这个”不一样”,就是 vim 最核心的概念:模式。搞懂这一件事,剩下的操作都顺理成章。</p><span id="more"></span><p>这是终端效率小系列第二篇,承接 <a href="/%E4%B8%8D%E7%86%9F%E6%82%89%E7%9A%84%E5%91%BD%E4%BB%A4%E6%80%8E%E4%B9%88%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-man%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E4%B8%8Eapropos%E9%80%9F%E6%9F%A5">不熟悉的命令怎么快速搞懂:man 页面结构、apropos 搜索与 –help 速查</a>。</p><h2 id="vim-最核心的概念:模式"><a href="#vim-最核心的概念:模式" class="headerlink" title="vim 最核心的概念:模式"></a>vim 最核心的概念:模式</h2><p>大部分编辑器打开就能直接打字。vim 不是——它默认打开的是 <strong>Normal 模式</strong>(也叫命令模式),这时候敲键盘不是在输入文字,是在下命令。想打字,得先按 <code>i</code> 切换到 <strong>Insert 模式</strong>;打完字想回去下命令,按 <code>Esc</code> 切回 Normal 模式。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">打开 vim → 默认在 Normal 模式(敲字母是命令)</span><br><span class="line">按 i → 切到 Insert 模式(可以打字了)</span><br><span class="line">按 Esc → 切回 Normal 模式(打字模式关闭,字母重新变成命令)</span><br></pre></td></tr></table></figure><p>新手最常见的翻车场景就是这个:不知道自己在 Normal 模式,对着屏幕一通乱按,结果文件被改得面目全非——因为那些字母全被当成了命令,不是文字。记住一条:<strong>不确定自己在哪个模式,先按 <code>Esc</code>,回到 Normal 模式再说</strong>,这是最安全的复位动作。</p><h2 id="先学怎么退出"><a href="#先学怎么退出" class="headerlink" title="先学怎么退出"></a>先学怎么退出</h2><p>呼应开头的梗,这个必须先讲:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">:q</span><br><span class="line"><span class="comment"># 退出。文件没有改动过才能用,改过没保存会报错拒绝退出</span></span><br><span class="line"></span><br><span class="line">:q!</span><br><span class="line"><span class="comment"># 强制退出,不保存任何改动——改错了想放弃这次编辑,用这个</span></span><br><span class="line"></span><br><span class="line">:w</span><br><span class="line"><span class="comment"># 只保存,不退出,继续编辑</span></span><br><span class="line"></span><br><span class="line">:wq</span><br><span class="line"><span class="comment"># 保存并退出,不管文件有没有改动过,都会写入磁盘</span></span><br><span class="line"></span><br><span class="line">:x</span><br><span class="line"><span class="comment"># 也是保存并退出,但只在文件真的被改动过时才写入磁盘;</span></span><br><span class="line"><span class="comment"># 如果没有改动,:x 不会碰这个文件,文件的修改时间也不会变</span></span><br><span class="line"><span class="comment"># 日常用 :wq 和 :x 效果几乎一样,区别只在"文件没改动时要不要触碰修改时间"这种边缘场景</span></span><br></pre></td></tr></table></figure><p>所有这些命令都以 <code>:</code> 开头,输入前记得先确认自己在 Normal 模式(如果还在 Insert 模式,先按 <code>Esc</code>)。</p><h2 id="Normal-模式下移动光标"><a href="#Normal-模式下移动光标" class="headerlink" title="Normal 模式下移动光标"></a>Normal 模式下移动光标</h2><p>vim 不太依赖方向键,用几个字母键代替(键盘老式布局上这几个键离主键位更近,不用挪手,很多人这么解释这个设计,具体历史已经不可考):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">h j k l</span><br><span class="line"><span class="comment"># 依次是 左 下 上 右,代替方向键</span></span><br><span class="line"><span class="comment"># 记不住就想 j 像个往下的箭头,k 在它上面对应往上</span></span><br><span class="line"></span><br><span class="line">w</span><br><span class="line"><span class="comment"># 跳到下一个单词的开头</span></span><br><span class="line"></span><br><span class="line">b</span><br><span class="line"><span class="comment"># 跳到上一个单词的开头(back)</span></span><br><span class="line"></span><br><span class="line">e</span><br><span class="line"><span class="comment"># 跳到当前/下一个单词的结尾</span></span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"><span class="comment"># 跳到当前行的行首</span></span><br><span class="line"></span><br><span class="line">$</span><br><span class="line"><span class="comment"># 跳到当前行的行尾</span></span><br><span class="line"></span><br><span class="line">gg</span><br><span class="line"><span class="comment"># 跳到文件开头</span></span><br><span class="line"></span><br><span class="line">G</span><br><span class="line"><span class="comment"># 跳到文件结尾</span></span><br></pre></td></tr></table></figure><p>前面加数字表示重复次数:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">5j</span><br><span class="line"><span class="comment"># 往下移动 5 行,比按 5 次 j 快</span></span><br><span class="line"></span><br><span class="line">3w</span><br><span class="line"><span class="comment"># 往后跳 3 个单词</span></span><br></pre></td></tr></table></figure><h2 id="行内跳转:按字符查找与括号匹配"><a href="#行内跳转:按字符查找与括号匹配" class="headerlink" title="行内跳转:按字符查找与括号匹配"></a>行内跳转:按字符查找与括号匹配</h2><p><code>hjkl</code>/<code>w</code>/<code>b</code>/<code>e</code> 这些是逐字符或逐单词移动,遇到”跳到这一行某个具体符号上”的场景(比如跳到某个逗号、跳到配对的括号),还有更快的办法:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">f,</span><br><span class="line"><span class="comment"># 向右查找光标所在行里下一个 "," 字符,光标直接停在它上面(find)</span></span><br><span class="line"></span><br><span class="line">t,</span><br><span class="line"><span class="comment"># 向右查找下一个 "," 字符,光标停在它前面一位(till,"直到"但不包含)</span></span><br><span class="line"></span><br><span class="line">F,</span><br><span class="line"><span class="comment"># 向左查找上一个 "," 字符,光标停在它上面</span></span><br><span class="line"></span><br><span class="line">T,</span><br><span class="line"><span class="comment"># 向左查找上一个 "," 字符,光标停在它后面一位</span></span><br><span class="line"></span><br><span class="line">;</span><br><span class="line"><span class="comment"># 重复上一次 f/t/F/T 查找,跳到下一个匹配,不用重新敲一次 f 加字符</span></span><br><span class="line"></span><br><span class="line">%</span><br><span class="line"><span class="comment"># 跳到光标所在括号 ( { [ 的配对括号上,来回按 % 可以在一对括号之间横跳</span></span><br></pre></td></tr></table></figure><p>一行很长的代码里要定位到某个符号,<code>f</code> 加上那个字符,比一个字符一个字符挪快得多;改代码时经常要确认括号有没有配对齐,把光标放在括号上按 <code>%</code> 直接跳到另一半,比肉眼数括号可靠。</p><h2 id="Normal-模式下编辑文本"><a href="#Normal-模式下编辑文本" class="headerlink" title="Normal 模式下编辑文本"></a>Normal 模式下编辑文本</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">i</span><br><span class="line"><span class="comment"># 在光标当前位置之前插入,进入 Insert 模式</span></span><br><span class="line"></span><br><span class="line">a</span><br><span class="line"><span class="comment"># 在光标当前位置之后插入(append),进入 Insert 模式</span></span><br><span class="line"></span><br><span class="line">o</span><br><span class="line"><span class="comment"># 在当前行下面新起一行,进入 Insert 模式</span></span><br><span class="line"></span><br><span class="line">O</span><br><span class="line"><span class="comment"># 在当前行上面新起一行,进入 Insert 模式</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">dd</span></span><br><span class="line"><span class="comment"># 删除光标所在的整行(同时会把这行内容存到剪贴板,等于剪切)</span></span><br><span class="line"></span><br><span class="line">yy</span><br><span class="line"><span class="comment"># 复制光标所在的整行(yank,不删除)</span></span><br><span class="line"></span><br><span class="line">p</span><br><span class="line"><span class="comment"># 粘贴——把上一次 dd 或 yy 存下来的内容,粘贴到光标下面</span></span><br><span class="line"></span><br><span class="line">u</span><br><span class="line"><span class="comment"># 撤销上一步操作</span></span><br><span class="line"></span><br><span class="line">Ctrl-r</span><br><span class="line"><span class="comment"># 重做(撤销"撤销")</span></span><br><span class="line"></span><br><span class="line">x</span><br><span class="line"><span class="comment"># 删除光标所在的这一个字符——精简版的 dd,只删一个字符不是一整行</span></span><br><span class="line"></span><br><span class="line">r字符</span><br><span class="line"><span class="comment"># 把光标所在字符替换成指定字符,替换完自动回到 Normal 模式,不用再按 Esc</span></span><br><span class="line"><span class="comment"># 比如光标停在一个打错的字母上,按 rx 就把它换成 x</span></span><br><span class="line"></span><br><span class="line">cw</span><br><span class="line"><span class="comment"># 修改一个单词——删除光标到当前单词结尾的内容,直接进入 Insert 模式等着输入新内容</span></span><br><span class="line"><span class="comment"># 等于先 dw 再 i,但 cw 是更常用的固定搭配,一步到位</span></span><br><span class="line"></span><br><span class="line">.</span><br><span class="line"><span class="comment"># 重复上一次的修改操作——改完一处后,把光标移到下一个类似的地方按 .,</span></span><br><span class="line"><span class="comment"># 不用重新敲一遍命令,改多处同样的错误时特别省事</span></span><br></pre></td></tr></table></figure><p><code>i</code>/<code>a</code>/<code>o</code>/<code>O</code> 这四个进 Insert 模式的方式,区别就在光标最终停在哪:<code>i</code> 停在原地之前,<code>a</code> 停在原地之后,<code>o</code>/<code>O</code> 分别在下面/上面新开一行。</p><h2 id="Visual-模式:选中一段范围再操作"><a href="#Visual-模式:选中一段范围再操作" class="headerlink" title="Visual 模式:选中一段范围再操作"></a>Visual 模式:选中一段范围再操作</h2><p>前面的编辑命令大多是”整行”或”一个单词”级别,想自己框选一段范围再操作,要用 Visual(可视)模式。进入方式有三种,选中范围的方式不一样:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">v</span><br><span class="line"><span class="comment"># 进入字符可视模式,逐字符选中——像鼠标拖选一样,选中范围不受行/单词边界限制</span></span><br><span class="line"></span><br><span class="line">V</span><br><span class="line"><span class="comment"># 进入行可视模式,整行整行地选,光标上下移动就是按行扩大选区</span></span><br><span class="line"></span><br><span class="line">Ctrl-v</span><br><span class="line"><span class="comment"># 进入块可视模式,选出一个矩形范围,常用来同时给多行的相同列位置加字符</span></span><br></pre></td></tr></table></figure><p>选中范围之后,跟前面学过的编辑命令组合使用,效果作用在整个选区上:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">d</span><br><span class="line"><span class="comment"># 删除选中的范围</span></span><br><span class="line"></span><br><span class="line">y</span><br><span class="line"><span class="comment"># 复制选中的范围(yank)</span></span><br><span class="line"></span><br><span class="line">c</span><br><span class="line"><span class="comment"># 删除选中的范围,直接进入 Insert 模式,改成新内容</span></span><br></pre></td></tr></table></figure><p>举例:用 <code>V</code> 选中三行按 <code>d</code>,一次删掉三行;用 <code>v</code> 选中一段文字(不是整行)按 <code>y</code>,只复制这一小段。选错了想取消,按 <code>Esc</code> 退出 Visual 模式,不会做任何改动。</p><h2 id="查找和替换"><a href="#查找和替换" class="headerlink" title="查找和替换"></a>查找和替换</h2><p>搜索用 <code>/</code>,和<a href="/%E4%B8%8D%E7%86%9F%E6%82%89%E7%9A%84%E5%91%BD%E4%BB%A4%E6%80%8E%E4%B9%88%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-man%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E4%B8%8Eapropos%E9%80%9F%E6%9F%A5">上一篇讲 man 页面查找</a>里 <code>less</code> 的搜索快捷键是同一套逻辑——vim 的分页和搜索机制本来就深受这套习惯影响:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">/关键词</span><br><span class="line"><span class="comment"># 向下搜索,回车确认</span></span><br><span class="line"></span><br><span class="line">n</span><br><span class="line"><span class="comment"># 跳到下一个匹配</span></span><br><span class="line"></span><br><span class="line">N</span><br><span class="line"><span class="comment"># 跳到上一个匹配(大写,方向和 n 相反)</span></span><br></pre></td></tr></table></figure><p>全局替换用 <code>:%s</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">:%s/旧文本/新文本/g</span><br><span class="line"><span class="comment"># % 表示作用范围是整个文件(不加 % 只替换当前光标所在行)</span></span><br><span class="line"><span class="comment"># s 是 substitute,替换命令本身</span></span><br><span class="line"><span class="comment"># g 表示每一行里所有匹配都替换,不加 g 的话每行只替换第一个匹配到的</span></span><br></pre></td></tr></table></figure><p><code>%</code> 和 <code>g</code> 这两个符号经常被忽略,结果就是”明明替换了,怎么还有没改的地方”——十有八九是漏了 <code>%</code>(只在当前行生效)或者漏了 <code>g</code>(每行只替换了第一处)。</p><h2 id="终端效率小系列:下一篇"><a href="#终端效率小系列:下一篇" class="headerlink" title="终端效率小系列:下一篇"></a>终端效率小系列:下一篇</h2><p>下一篇讲 Emacs 风格快捷键——包括一个进阶技巧:在 shell 命令行敲了一半的命令,直接甩进 vim/nvim 里编辑,改完存盘退出,编辑结果自动回到命令行。用到的正是这篇学的基础操作。</p>]]>
</content>
<id>https://blog.codewater.top/vim%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-%E6%A8%A1%E5%BC%8F%E5%88%87%E6%8D%A2%E5%88%B0%E4%BF%9D%E5%AD%98%E9%80%80%E5%87%BA/</id>
<link href="https://blog.codewater.top/vim%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C-%E6%A8%A1%E5%BC%8F%E5%88%87%E6%8D%A2%E5%88%B0%E4%BF%9D%E5%AD%98%E9%80%80%E5%87%BA/"/>
<published>2026-07-12T16:23:35.000Z</published>
<summary>vim 的 Normal/Insert 模式是什么、怎么退出、hjkl 怎么移动、dd/yy/p 怎么编辑、%s 怎么全局替换,一篇学会基础操作。</summary>
<title>vim 基本操作:从模式切换到保存退出</title>
<updated>2026-07-12T16:23:35.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="man" scheme="https://blog.codewater.top/tags/man/"/>
<content>
<![CDATA[<p>写脚本、敲命令的时候,冒出一个没见过的命令,或者记得某个命令但想不起某个参数怎么写——这种场景几乎每天都会遇到。很多人第一反应是打开浏览器搜索,但终端里其实自带一整套比搜索引擎更快、更准的查法:<code>man</code>、<code>apropos</code>、<code>--help</code>,选对工具往往比记住参数本身更重要。</p><span id="more"></span><p>这是终端效率小系列的第一篇,后续还会写 vim 基本操作、Emacs 风格快捷键与进阶技巧。</p><h2 id="打开一个命令的-man-页面,看到的是什么"><a href="#打开一个命令的-man-页面,看到的是什么" class="headerlink" title="打开一个命令的 man 页面,看到的是什么"></a>打开一个命令的 man 页面,看到的是什么</h2><p><code>man</code> 后面跟命令名,比如 <code>man ls</code>,打开的页面几乎总是这几个固定部分组成的,认清这个结构,比死记某一个命令的参数更有用:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">NAME</span><br><span class="line"> 一句话描述这个命令是干什么的,最快判断"是不是我要找的东西"就看这里</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line"> 用法概要,一行或几行,用方括号/尖括号/省略号表示参数是否必填、能不能重复</span><br><span class="line"> (这套符号约定和 --help 输出的用法概要是同一套规则,后文详细展开)</span><br><span class="line"></span><br><span class="line">DESCRIPTION</span><br><span class="line"> 详细说明,参数多的命令这部分会很长,是真正讲清楚"每个参数具体做什么"的地方</span><br><span class="line"></span><br><span class="line">OPTIONS</span><br><span class="line"> 参数列表,部分命令会把这部分和 DESCRIPTION 合并,不单独分节</span><br><span class="line"></span><br><span class="line">EXAMPLES</span><br><span class="line"> 实战示例,不是所有命令都有这部分,有的话是最值得先看的地方</span><br><span class="line"></span><br><span class="line">SEE ALSO</span><br><span class="line"> 相关命令,遇到一个命令做不到想要的效果,这里经常能带出更合适的那个</span><br><span class="line"></span><br><span class="line">AUTHOR</span><br><span class="line"> 作者信息,日常查阅基本用不上</span><br></pre></td></tr></table></figure><p>拿到一个陌生命令,不用从头到尾通读,思路应该是:先看 <code>NAME</code> 确认找对了地方,直接跳到 <code>EXAMPLES</code>(如果有)抄一个改改能用最好,改不出想要的效果再回头看 <code>OPTIONS</code>/<code>DESCRIPTION</code> 找具体参数。</p><h2 id="为什么会查到同名但不是你想要的条目"><a href="#为什么会查到同名但不是你想要的条目" class="headerlink" title="为什么会查到同名但不是你想要的条目"></a>为什么会查到同名但不是你想要的条目</h2><p>有时候 <code>man</code> 打开的页面内容明显不对——比如查 <code>printf</code> 却看到一堆看不懂的返回值类型和参数说明。这不是找错了命令,是 <code>man</code> 手册本身按内容类型分成好几个”层”(section),同一个名字在不同层里可能是完全不同的东西:</p><table><thead><tr><th>章节</th><th>内容</th></tr></thead><tbody><tr><td>1</td><td>用户可以直接执行的命令或 shell 命令</td></tr><tr><td>2</td><td>系统调用——内核提供的功能</td></tr><tr><td>3</td><td>库函数——程序库里的函数(大部分 libc 函数在这里)</td></tr><tr><td>4</td><td>特殊文件,通常是 <code>/dev</code> 下的设备文件</td></tr><tr><td>5</td><td>文件格式和约定,比如配置文件的写法</td></tr><tr><td>6</td><td>游戏</td></tr><tr><td>7</td><td>杂项——协议、字符集标准、文件系统布局这类概览性内容</td></tr><tr><td>8</td><td>系统管理命令,通常只有 root 能执行</td></tr><tr><td>9</td><td>内核例程,<strong>非标准章节</strong>,不是所有系统都有</td></tr></tbody></table><p><code>printf</code> 在 man 手册里至少有两个条目:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">man 1 <span class="built_in">printf</span></span><br><span class="line"><span class="comment"># 章节 1:shell/coreutils 提供的 printf 命令,格式化输出文本用</span></span><br><span class="line"></span><br><span class="line">man 3 <span class="built_in">printf</span></span><br><span class="line"><span class="comment"># 章节 3:C 标准库里的 printf() 函数,写 C 代码时才会用到</span></span><br><span class="line"></span><br><span class="line">man <span class="built_in">printf</span></span><br><span class="line"><span class="comment"># 不指定章节,man 按顺序查找,通常先找到章节 1 的版本</span></span><br><span class="line"><span class="comment"># 但这个"通常"不是保证——取决于系统配置,遇到内容明显不对时,</span></span><br><span class="line"><span class="comment"># 加上章节号重新查是最快的解法</span></span><br></pre></td></tr></table></figure><p><code>printf</code> 只是最经典的例子,很多名字在用户命令和库函数/系统调用里都同时存在。查手册时如果内容感觉不对(比如冒出一堆看不懂的 C 类型),第一反应应该是”这是不是查到别的章节去了”,加上章节号重新查。</p><h2 id="man-内部导航:其实你在用-less"><a href="#man-内部导航:其实你在用-less" class="headerlink" title="man 内部导航:其实你在用 less"></a>man 内部导航:其实你在用 less</h2><p><code>man</code> 本身不负责分页显示,它把内容交给一个分页器(pager)处理,默认通常是 <code>less</code>。这意味着 <code>less</code> 的快捷键在 <code>man</code> 里全部适用,这是查手册最实用的一套技能:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">man tar</span><br><span class="line"><span class="comment"># 打开后是 less 在分页显示内容,以下操作都在这个界面里进行</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 翻页</span></span><br><span class="line">空格 或 f <span class="comment"># 向下翻一页</span></span><br><span class="line">b <span class="comment"># 向上翻一页</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 搜索</span></span><br><span class="line">/关键词 <span class="comment"># 向下搜索关键词,比如 /-f 找 -f 参数的说明</span></span><br><span class="line">?关键词 <span class="comment"># 向上搜索关键词</span></span><br><span class="line">n <span class="comment"># 跳到下一个匹配</span></span><br><span class="line">N <span class="comment"># 跳到上一个匹配(大写,方向和 n 相反)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 退出</span></span><br><span class="line">q <span class="comment"># 退出 man,回到命令行</span></span><br></pre></td></tr></table></figure><p>一个几百行的手册页,靠肉眼从头翻到尾找一个参数是最低效的做法——<code>/参数名</code> 直接跳过去,才是正确用法。</p><h2 id="忘了具体命令名,只记得想干什么,怎么搜"><a href="#忘了具体命令名,只记得想干什么,怎么搜" class="headerlink" title="忘了具体命令名,只记得想干什么,怎么搜"></a>忘了具体命令名,只记得想干什么,怎么搜</h2><p>前面几节都假设你已经知道命令名,只是想搞懂它怎么用。但更常见的场景是:只知道自己想干什么(比如”压缩文件”),完全不确定该用哪个命令。这时候 <code>apropos</code> 或 <code>man -k</code> 才是该用的工具——这两个是同一件事:搜索所有手册页的一句话描述(也就是前面提到的 <code>NAME</code> 那一行)里包含指定关键词的条目:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">apropos compress</span><br><span class="line"><span class="comment"># 搜索所有命令里,一句话描述包含 "compress" 的条目</span></span><br><span class="line"><span class="comment"># 结果会列出 gzip、tar、zip 等等一堆候选,帮你先找到该用哪个命令</span></span><br><span class="line"></span><br><span class="line">man -k compress</span><br><span class="line"><span class="comment"># 和上面等价——man -k 内置在 man 命令里,apropos 是独立命令,</span></span><br><span class="line"><span class="comment"># 两者功能相同,apropos 在个别精简系统上可能需要单独装,man -k 只要装了 man 就有</span></span><br></pre></td></tr></table></figure><p>结合前面讲的定位技巧,实战流程通常是这样:<code>apropos</code> 先找到候选命令名 → <code>man</code> 打开确认 <code>NAME</code> 对不对 → <code>/参数名</code> 跳到具体参数说明。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">man tar</span><br><span class="line"><span class="comment"># 打开后输入:</span></span><br><span class="line">/-f</span><br><span class="line"><span class="comment"># 直接跳到 -f 参数的说明段落,不用从头翻</span></span><br></pre></td></tr></table></figure><h2 id="help:比-man-快,但要先看懂它的格式"><a href="#help:比-man-快,但要先看懂它的格式" class="headerlink" title="--help:比 man 快,但要先看懂它的格式"></a><code>--help</code>:比 man 快,但要先看懂它的格式</h2><p><code>man</code> 详细但长,很多时候你只是想快速确认”这个命令到底有没有我要的参数”,这时候 <code>--help</code> 比翻 <code>man</code> 快得多:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tar --<span class="built_in">help</span></span><br><span class="line"><span class="comment"># 大多数命令都支持这个参数,输出比 man 手册短得多,只列参数不讲原理</span></span><br></pre></td></tr></table></figure><p><code>--help</code> 的输出通常分两部分:开头一行 <strong>用法概要(usage synopsis)</strong>,后面跟着<strong>参数列表</strong>。用法概要这一行的符号是有约定含义的,看懂它比记住某个具体命令的用法更重要——这套约定跟前面 <code>man</code> 页面 <code>SYNOPSIS</code> 里的符号是同一套规则:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Usage: tar [OPTION...] [FILE]...</span><br><span class="line"># OPTION...:方括号 [ ] 表示可选,... 表示可以出现多次(可以传多个 OPTION)</span><br><span class="line"># FILE...:同样可选、可重复,表示可以传多个文件</span><br><span class="line"># 没有方括号、直接写出来的部分(比如命令名本身)是必需的</span><br></pre></td></tr></table></figure><p>常见约定:方括号 <code>[ ]</code> 包起来的是可选项;尖括号 <code>< ></code> 或者不带括号直接写的词是必填项;省略号 <code>...</code> 表示前面那一项可以重复出现;竖线 <code>|</code> 表示几选一。这套约定不是 <code>tar</code> 专属的,几乎所有遵循 GNU/POSIX 惯例的命令都这么写,学会看一次,后面每个命令的用法说明都能看懂。</p><p>参数列表部分,每个参数通常是”短选项, 长选项 说明文字”这样的格式排开。输出长的话,直接用 <code>--help</code> 配合搜索工具:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">tar --<span class="built_in">help</span> | grep -A1 <span class="string">"exclude"</span></span><br><span class="line"><span class="comment"># grep 从 --help 的输出里过滤出包含 "exclude" 的行,-A1 多带一行看清楚说明</span></span><br><span class="line"><span class="comment"># 比整段输出里肉眼找快得多,尤其是参数很多的命令(比如 tar、find)</span></span><br><span class="line"></span><br><span class="line">curl --<span class="built_in">help</span> | less</span><br><span class="line"><span class="comment"># curl 的 --help 输出本身就很长,直接接 less 分页,用前面讲过的 / 搜索快捷键定位</span></span><br></pre></td></tr></table></figure><p><code>--help</code> 和 <code>man</code> 不是二选一:先用 <code>--help</code> 快速扫一眼有没有你要的参数,确认存在了但不确定具体行为,再去 <code>man</code> 里查那一条的详细说明。</p><h2 id="man-之外的其他辅助手段"><a href="#man-之外的其他辅助手段" class="headerlink" title="man 之外的其他辅助手段"></a>man 之外的其他辅助手段</h2><ul><li><a href="/tldr">tldr</a>:直接给实战例子,不讲原理只给”抄了就能用”的命令,想直接抄一条命令用时最合适</li><li><code>info</code>:GNU 项目常用的另一套文档系统,比 <code>man</code> 更结构化(支持章节跳转、超链接式导航),但普及度不如 <code>man</code>,主要是 GNU 核心工具用得多,比如 <code>coreutils</code> 这个包</li></ul><p>遇到不认识的命令,<code>apropos</code> 先定位候选命令;确定命令了,<code>--help</code> 快速确认有没有要的参数;想深入理解某个参数为什么这么设计,才去查 <code>man</code> 的 <code>DESCRIPTION</code>;懒得看文档直接要例子,<code>tldr</code>。几种手段配合用,不是非此即彼。</p><h2 id="终端效率小系列:下一篇"><a href="#终端效率小系列:下一篇" class="headerlink" title="终端效率小系列:下一篇"></a>终端效率小系列:下一篇</h2><p>下一篇讲 vim 基本操作——毕竟查完命令用法,回头改配置文件、写代码,还是得会用编辑器。</p>]]>
</content>
<id>https://blog.codewater.top/%E4%B8%8D%E7%86%9F%E6%82%89%E7%9A%84%E5%91%BD%E4%BB%A4%E6%80%8E%E4%B9%88%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-man%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E4%B8%8Eapropos%E9%80%9F%E6%9F%A5/</id>
<link href="https://blog.codewater.top/%E4%B8%8D%E7%86%9F%E6%82%89%E7%9A%84%E5%91%BD%E4%BB%A4%E6%80%8E%E4%B9%88%E5%BF%AB%E9%80%9F%E6%90%9E%E6%87%82-man%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E4%B8%8Eapropos%E9%80%9F%E6%9F%A5/"/>
<published>2026-07-12T16:18:38.000Z</published>
<summary>遇到不认识的命令或忘了参数怎么办:man 页面固定结构怎么读、apropos 怎么按关键词搜命令、--help 怎么用。</summary>
<title>不熟悉的命令怎么快速搞懂:man 页面结构、apropos 搜索与 --help 速查</title>
<updated>2026-07-12T16:18:38.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="xargs" scheme="https://blog.codewater.top/tags/xargs/"/>
<content>
<![CDATA[<p><code>find . -name "*.log" | rm</code> 这条命令,直觉上应该是”找到所有 .log 文件,然后删掉”。实际跑一下:什么都不会发生,一个文件都删不掉,<code>rm</code> 也不会报任何错。</p><p>管道明明把 <code>find</code> 的结果传给了 <code>rm</code>,为什么 <code>rm</code> 完全不理会?这篇讲清楚背后的原因,以及支撑整个 Linux 命令行生态的设计哲学。</p><span id="more"></span><p>这是 Linux 命令系列的第六篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>、<a href="/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98">grep</a>、<a href="/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98">find</a>、<a href="/tar-zip-unzip%E5%BD%92%E6%A1%A3%E5%8E%8B%E7%BC%A9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91%E5%AE%9E%E6%88%98">tar/zip/unzip</a>,以及姐妹篇 <a href="/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E9%80%89%E9%A1%B9%E8%AE%BE%E8%AE%A1%E7%BA%A6%E5%AE%9A">Linux 命令行选项设计约定</a>——上一篇讲的是怎么给单个命令传参数,这篇讲怎么把多个命令组合起来干活。</p><h2 id="管道的本质:把一个命令的输出接成另一个命令的输入"><a href="#管道的本质:把一个命令的输出接成另一个命令的输入" class="headerlink" title="管道的本质:把一个命令的输出接成另一个命令的输入"></a>管道的本质:把一个命令的输出接成另一个命令的输入</h2><p>管道符 <code>|</code> 做的事情很纯粹:把左边命令的标准输出(stdout)直接接到右边命令的标准输入(stdin)。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> | grep <span class="string">".md"</span></span><br><span class="line"><span class="comment"># ls 把当前目录的文件列表写到 stdout</span></span><br><span class="line"><span class="comment"># | 把这份输出原样接到 grep 的 stdin</span></span><br><span class="line"><span class="comment"># grep 从 stdin 读入这份列表,从中过滤出包含 .md 的行</span></span><br></pre></td></tr></table></figure><p>两个命令互相不知道对方的存在——<code>ls</code> 不知道自己的输出被谁读走了,<code>grep</code> 也不关心这些文本行是 <code>ls</code> 生成的还是别的什么命令生成的。它们唯一的约定是:数据以纯文本的形式流动。这个”互不知情、只认文本”的设计,正是管道能随意串联任意命令的原因。</p><h2 id="关键区分:谁读-stdin,谁只认命令行参数"><a href="#关键区分:谁读-stdin,谁只认命令行参数" class="headerlink" title="关键区分:谁读 stdin,谁只认命令行参数"></a>关键区分:谁读 stdin,谁只认命令行参数</h2><p>回到开头的问题:<code>find . -name "*.log" | rm</code> 为什么删不掉文件?</p><p>答案是:<code>rm</code> 根本不读 stdin。管道确实把 <code>find</code> 的结果传了过去,但 <code>rm</code> 压根没有”从标准输入读取要删除的文件列表”这个功能——它只认命令行参数,比如 <code>rm file1.log file2.log</code>。管道传过去的内容,<code>rm</code> 收到了也不会看,它只会检查自己的命令行参数里有没有文件名,没有就什么都不干、正常退出。</p><p>Linux 命令大致能分两类:</p><ul><li><strong>过滤器类命令</strong>:默认从 stdin 读取数据,处理后写到 stdout,天生适合接在管道里。<code>cat</code>、<code>grep</code>、<code>sed</code>、<code>wc</code>、<code>sort</code> 都是这一类</li><li><strong>操作型命令</strong>:只认命令行参数,不读 stdin,因为它们的设计目的是”对指定的东西做一次性操作”,不是”处理一批流式数据”。<code>rm</code>、<code>chmod</code>、<code>mkdir</code>、<code>cp</code> 都是这一类</li></ul><p>这个区分不是随意的——<code>rm</code> 删文件、<code>chmod</code> 改权限,这类操作要的是明确的目标列表,命令行参数比一份不确定长度的流式数据更适合表达”目标是谁”。</p><h2 id="xargs-的意义:把-stdin-转换成命令行参数"><a href="#xargs-的意义:把-stdin-转换成命令行参数" class="headerlink" title="xargs 的意义:把 stdin 转换成命令行参数"></a>xargs 的意义:把 stdin 转换成命令行参数</h2><p><code>xargs</code> 存在的目的就是补上这个断层——把 stdin 里的内容转换成命令行参数,喂给那些只认参数的命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.log"</span> | xargs <span class="built_in">rm</span></span><br><span class="line"><span class="comment"># find 把匹配到的文件路径列表写到 stdout</span></span><br><span class="line"><span class="comment"># xargs 从 stdin 读进这份列表,转换成 rm 的命令行参数</span></span><br><span class="line"><span class="comment"># 实际执行的是 rm file1.log file2.log file3.log ...</span></span><br></pre></td></tr></table></figure><p>同样的管道,中间插一个 <code>xargs</code>,语义就通了:<code>xargs</code> 把”过滤器类命令的输出”转换成”操作型命令能理解的参数”,架起两类命令之间的桥。</p><h2 id="xargs-核心参数"><a href="#xargs-核心参数" class="headerlink" title="xargs 核心参数"></a>xargs 核心参数</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.tmp"</span> | xargs -n 5 <span class="built_in">rm</span></span><br><span class="line"><span class="comment"># -n:每次调用命令时最多传几个参数,这里是每 5 个文件调一次 rm</span></span><br><span class="line"><span class="comment"># 不写 -n 的话,xargs 会尽量把所有参数塞进一次调用(受系统 ARG_MAX 限制)</span></span><br><span class="line"></span><br><span class="line">find . -name <span class="string">"*.log"</span> | xargs -I {} <span class="built_in">mv</span> {} /backup/</span><br><span class="line"><span class="comment"># -I:自定义占位符,{} 代表 xargs 传进来的每一项</span></span><br><span class="line"><span class="comment"># 用 -I 之后参数位置更灵活,能把参数插到命令中间而不是只能接在最后</span></span><br><span class="line"></span><br><span class="line">find . -name <span class="string">"*.log"</span> -print0 | xargs -0 <span class="built_in">rm</span></span><br><span class="line"><span class="comment"># -0:配合 find 的 -print0 使用,用 null 字符分隔文件名而不是空格/换行</span></span><br><span class="line"><span class="comment"># 这个组合专门解决文件名里带空格的问题,前两篇 grep 和 find 文章里都提到过它</span></span><br><span class="line"></span><br><span class="line">lsof -ti:4000 | xargs -r <span class="built_in">kill</span></span><br><span class="line"><span class="comment"># -r:stdin 完全没有内容时,不执行后面的命令</span></span><br><span class="line"><span class="comment"># 没有这个参数,GNU xargs 默认哪怕 stdin 是空的也会执行一次命令——</span></span><br><span class="line"><span class="comment"># 这里如果端口 4000 没有进程占用,lsof 不会输出任何内容,</span></span><br><span class="line"><span class="comment"># 不加 -r 的话 xargs 还是会尝试跑一次不带参数的 kill,报一句用法错误;加了 -r 就直接跳过,什么都不做</span></span><br></pre></td></tr></table></figure><h2 id="xargs-P:容易被忽略的并行执行"><a href="#xargs-P:容易被忽略的并行执行" class="headerlink" title="xargs -P:容易被忽略的并行执行"></a>xargs -P:容易被忽略的并行执行</h2><p><code>xargs</code> 默认是串行的——一个一个调用命令,等上一个执行完再执行下一个。<code>-P</code> 可以指定并行度:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.jpg"</span> | xargs -P 4 -n 1 convert -resize 50%</span><br><span class="line"><span class="comment"># -P 4:最多同时跑 4 个进程</span></span><br><span class="line"><span class="comment"># -n 1:每个进程只处理 1 个文件,这样才有 4 个独立任务可以并行</span></span><br></pre></td></tr></table></figure><p><code>-P</code> 有个容易踩的坑:它必须配合 <code>-n</code> 一起用,否则很可能不起作用——<code>xargs</code> 默认会把所有参数尽量塞进一次调用(一次 <code>rm file1 file2 file3 ...</code>),只有一次调用,<code>-P</code> 再高也没有第二个进程可以并行。想让并行真正生效,得先用 <code>-n</code> 把参数拆成多次独立调用,<code>-P</code> 才有事可做。</p><h2 id="xargs-常用参数速查表"><a href="#xargs-常用参数速查表" class="headerlink" title="xargs 常用参数速查表"></a>xargs 常用参数速查表</h2><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-n NUM</code></td><td>每次调用命令时最多传 NUM 个参数,不写则尽量一次塞满(受 <code>ARG_MAX</code> 限制)</td></tr><tr><td><code>-I REPLACE</code></td><td>自定义占位符,可以把参数插到命令中间而不是只能接在最后</td></tr><tr><td><code>-0</code></td><td>用 null 字符分隔输入项,配合 <code>find -print0</code> 处理带空格的文件名</td></tr><tr><td><code>-r</code></td><td>stdin 为空时不执行命令,避免 GNU xargs 默认”哪怕没输入也跑一次”的行为</td></tr><tr><td><code>-P NUM</code></td><td>最多同时跑 NUM 个进程,默认是 1(串行);必须配合 <code>-n</code> 才能真正并行</td></tr><tr><td><code>-t</code></td><td>执行前先把实际要跑的命令打印出来,调试危险操作时先加这个看一眼</td></tr><tr><td><code>-p</code></td><td>每次执行前都询问确认,比 <code>-t</code> 更保守,确认了才真正执行</td></tr></tbody></table><h2 id="xargs-和-find-exec-的对比"><a href="#xargs-和-find-exec-的对比" class="headerlink" title="xargs 和 find -exec 的对比"></a>xargs 和 find -exec 的对比</h2><p><a href="/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98">上一篇 find 文章</a>里讲过 <code>-exec</code> 的两种写法(<code>\;</code> 和 <code>+</code>),做的事情和 <code>xargs</code> 很像——都是”把找到的东西喂给下一个命令”。两者的区别:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.log"</span> -<span class="built_in">exec</span> <span class="built_in">rm</span> {} +</span><br><span class="line"><span class="comment"># -exec 内建在 find 里,只服务 find 自己找到的结果</span></span><br><span class="line"></span><br><span class="line">find . -name <span class="string">"*.log"</span> | xargs <span class="built_in">rm</span></span><br><span class="line"><span class="comment"># xargs 是独立命令,管道前面可以是 find,也可以是 grep -l、ls,或者任何产生一行行文本的命令</span></span><br></pre></td></tr></table></figure><p><code>-exec</code> 是 <code>find</code> 自带的能力,用起来更直接,但只能配合 <code>find</code>。<code>xargs</code> 是通用工具,任何命令的输出只要能通过管道传过来,都能配合 <code>xargs</code> 转换成参数——适用范围更广。日常写 <code>find ... -exec</code> 就够用的场景不用特意换成 <code>xargs</code>,但如果数据来源不是 <code>find</code>(比如 <code>grep -l ... | xargs</code>),就只能用 <code>xargs</code>。</p><h2 id="Unix-设计哲学落地:小工具、文本接口、自由组合"><a href="#Unix-设计哲学落地:小工具、文本接口、自由组合" class="headerlink" title="Unix 设计哲学落地:小工具、文本接口、自由组合"></a>Unix 设计哲学落地:小工具、文本接口、自由组合</h2><p>前面这些具体机制,背后有一套统一的设计思想。1978 年,Unix 管道的发明者 Doug McIlroy 在 <em>Bell System Technical Journal</em> 上把这套思想总结成三句话:</p><blockquote><p>Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.</p><p>写只做一件事、并且做好这一件事的程序;写能相互协作的程序;写能处理文本流的程序,因为文本流是通用接口。</p></blockquote><p>这三句话直接对应本文讲的内容:</p><ul><li><strong>只做一件事</strong>:<code>grep</code> 只管过滤文本,<code>rm</code> 只管删文件,没有哪个命令试图把所有功能都塞进自己</li><li><strong>相互协作</strong>:靠管道把小工具串起来,而不是靠一个大程序内置所有功能</li><li><strong>文本作为通用接口</strong>:不管是 <code>ls</code> 的输出还是 <code>grep</code> 的输出,都是纯文本,任何命令都能读懂任何命令的输出——这正是管道能随意组合任意命令的前提</li></ul><p>这和图形界面软件的设计思路是相反的:图形应用倾向于把功能都装进一个软件里,命令行工具则倾向于拆成几十个只做一件事的小程序,需要什么功能现场用管道拼起来。前者上手快,后者组合起来的灵活度是图形界面很难达到的——今天学的这几个命令,明天可以用完全没预料到的方式重新拼装解决新问题。</p><h2 id="Linux-命令系列:走到这里"><a href="#Linux-命令系列:走到这里" class="headerlink" title="Linux 命令系列:走到这里"></a>Linux 命令系列:走到这里</h2><p>这个系列从具体命令讲到了设计哲学:<a href="/Linux%E6%9D%83%E9%99%90%E4%B8%8E%E5%B1%9E%E4%B8%BB%E7%AE%A1%E7%90%86-chmod-chown%E5%AE%9E%E6%88%98">chmod/chown</a> 管权限,<a href="/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98">grep</a> 管文本搜索,<a href="/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98">find</a> 管文件查找,<a href="/tar-zip-unzip%E5%BD%92%E6%A1%A3%E5%8E%8B%E7%BC%A9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91%E5%AE%9E%E6%88%98">tar/zip/unzip</a> 管归档压缩,<a href="/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E9%80%89%E9%A1%B9%E8%AE%BE%E8%AE%A1%E7%BA%A6%E5%AE%9A">命令行选项设计约定</a> 讲怎么给命令传参数,这篇讲怎么把命令组合起来。后续候选话题还有 <code>rsync</code> 的增量同步和包管理命令,写完再回来更新 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。</p>]]>
</content>
<id>https://blog.codewater.top/xargs%E7%AE%A1%E9%81%93%E4%B8%8ELinux%E5%91%BD%E4%BB%A4%E8%AE%BE%E8%AE%A1%E5%93%B2%E5%AD%A6/</id>
<link href="https://blog.codewater.top/xargs%E7%AE%A1%E9%81%93%E4%B8%8ELinux%E5%91%BD%E4%BB%A4%E8%AE%BE%E8%AE%A1%E5%93%B2%E5%AD%A6/"/>
<published>2026-07-12T08:52:33.000Z</published>
<summary>管道的本质是什么、为什么有些命令吃 stdin 有些只认参数、xargs 怎么架桥,以及背后支撑整个 Linux 命令行的 Unix 设计哲学。</summary>
<title>xargs、管道与 Linux 命令设计哲学:为什么 find | rm 删不掉任何文件</title>
<updated>2026-07-13T16:35:18.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="CLI" scheme="https://blog.codewater.top/tags/CLI/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<content>
<![CDATA[<p>写完前几篇 Linux 命令教程,你可能已经发现一个规律,紧接着又发现一堆反例:<code>grep -v</code> 是反向匹配,<code>curl -v</code> 是显示详细过程,换成 <code>node -v</code> 却变成了查版本号。同一个字母,三个命令三种含义。</p><p>到底是谁定的这些规则?答案可能有点意外:<strong>没有人统一定</strong>。这篇讲清楚这套”有历史惯例但没有强制标准”的设计约定。</p><span id="more"></span><p>这是 Linux 常见命令系列第五篇,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>、<a href="/Linux%E6%9D%83%E9%99%90%E4%B8%8E%E5%B1%9E%E4%B8%BB%E7%AE%A1%E7%90%86-chmod-chown%E5%AE%9E%E6%88%98">chmod/chown</a>、<a href="/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98">grep</a>、<a href="/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98">find</a>、<a href="/tar-zip-unzip%E5%BD%92%E6%A1%A3%E5%8E%8B%E7%BC%A9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91%E5%AE%9E%E6%88%98">tar/zip/unzip</a>。</p><h2 id="两种选项风格:POSIX-短选项与-GNU-长选项"><a href="#两种选项风格:POSIX-短选项与-GNU-长选项" class="headerlink" title="两种选项风格:POSIX 短选项与 GNU 长选项"></a>两种选项风格:POSIX 短选项与 GNU 长选项</h2><p>命令行选项有两套并存的写法:</p><ul><li><strong>短选项</strong>:单个字符前面加一个短横线,比如 <code>-v</code>、<code>-f</code>。这是 POSIX 定义的标准写法,对应的解析函数 <code>getopt</code> 本身就是 POSIX 规范的一部分——历史上敲命令都是手动输入,单字符选项打字快</li><li><strong>长选项</strong>:完整单词前面加两个短横线,比如 <code>--verbose</code>、<code>--file</code>。这是 GNU 项目后来引入的扩展——对应的 <code>getopt_long</code> 函数不属于原始 POSIX 规范,是 GNU 对 POSIX 的补充。GNU 官方编码规范给出的理由不只是”更好读”:目标是让同一个概念在不同程序之间保持一致的拼写——任何 GNU 程序如果有”详细输出”这个选项,都应该精确地写成 <code>--verbose</code>,用户不用每个程序重新猜一遍这个功能叫什么</li></ul><p>这不是”新标准取代旧标准”的关系,是互补——大多数 GNU 命令行工具两套写法都支持,短的敲起来快,长的写脚本时更清楚意图,用哪个看场景。</p><h2 id="短选项能挤在一起写,长选项不能"><a href="#短选项能挤在一起写,长选项不能" class="headerlink" title="短选项能挤在一起写,长选项不能"></a>短选项能挤在一起写,长选项不能</h2><p>短选项有一个长选项做不到的能力:可以把几个不需要参数的选项拼在同一个短横线后面。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">tar -cvf archive.tar.gz files/</span><br><span class="line"><span class="comment"># -c -v -f 三个短选项挤在一起,等价于 tar -c -v -f archive.tar.gz</span></span><br><span class="line"><span class="comment"># 只有最后一个(-f)需要带参数,前面的 c、v 不需要,所以能挤</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ls</span> -la</span><br><span class="line"><span class="comment"># 等价于 ls -l -a</span></span><br><span class="line"></span><br><span class="line">tar --create --verbose --file=archive.tar.gz files/</span><br><span class="line"><span class="comment"># 长选项没法挤成 tar --createverbosefile=archive.tar.gz,只能一个个分开写</span></span><br></pre></td></tr></table></figure><p>短选项能挤的前提是:挤在一起的这些选项里,只有排在最后的那个可以带参数——这也是<a href="/tar-zip-unzip%E5%BD%92%E6%A1%A3%E5%8E%8B%E7%BC%A9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91%E5%AE%9E%E6%88%98">上一篇 tar 文章</a>里”<code>-f</code> 必须放最后一位”这条规则的根源。</p><h2 id="没有中央标准:同一个字母在不同命令里可能完全不是一回事"><a href="#没有中央标准:同一个字母在不同命令里可能完全不是一回事" class="headerlink" title="没有中央标准:同一个字母在不同命令里可能完全不是一回事"></a>没有中央标准:同一个字母在不同命令里可能完全不是一回事</h2><p>这是全文最重要的一点。<code>-v</code> 在不同命令里的真实含义对比:</p><table><thead><tr><th>命令</th><th><code>-v</code> 的含义</th></tr></thead><tbody><tr><td><code>grep -v</code></td><td>invert,反向匹配</td></tr><tr><td><code>curl -v</code></td><td>verbose,显示详细请求过程</td></tr><tr><td><code>node -v</code></td><td>version,查看版本号</td></tr></tbody></table><p>没有任何组织规定”<code>-v</code> 必须表示什么”。<code>grep</code>、<code>curl</code>、<code>node</code> 是完全独立的项目,各自的作者按自己的直觉选了这个字母——<code>curl</code> 的作者觉得 <code>-v</code> 该是 verbose,<code>node</code> 的作者觉得 <code>-v</code> 该是 version,两边都没错,只是没有对上。</p><p>这不是巧合或者哪边”用错了”,纯粹是因为命令行选项的字母分配从来没有一个中央注册机构去协调。唯一可靠的做法是:<strong>遇到不确定的选项,查这个命令自己的文档</strong>(<code>--help</code> 或 <code>man</code>),别指望”我在别的命令里见过 <code>-v</code>“这种经验能直接套用。</p><h2 id="长短选项不是一一对应关系"><a href="#长短选项不是一一对应关系" class="headerlink" title="长短选项不是一一对应关系"></a>长短选项不是一一对应关系</h2><p>容易踩的另一个误区是假设”每个短选项都有对应的长选项,反过来也一样”——实际上两边是各自独立设计的集合:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">find . --exclude=<span class="string">"*.log"</span></span><br><span class="line"><span class="comment"># find 的 --exclude 只有长选项写法,没有对应的短选项</span></span><br><span class="line"></span><br><span class="line">grep -P <span class="string">'\d+'</span> file.txt</span><br><span class="line"><span class="comment"># -P 有对应的长选项 --perl-regexp,两种写法都行</span></span><br><span class="line"></span><br><span class="line">curl -o output.txt https://example.com</span><br><span class="line"><span class="comment"># -o 有对应的长选项 --output</span></span><br></pre></td></tr></table></figure><p>具体某个选项有没有短/长两种写法,完全取决于这个命令的作者当初怎么设计,没有规律可循,用之前查一下这个命令自己的文档最稳妥。</p><h2 id=":告诉命令”后面全部当普通参数,别再解析成选项”"><a href="#:告诉命令”后面全部当普通参数,别再解析成选项”" class="headerlink" title="--:告诉命令”后面全部当普通参数,别再解析成选项”"></a><code>--</code>:告诉命令”后面全部当普通参数,别再解析成选项”</h2><p><code>--</code> 是 POSIX 明确定义的一个特殊标记:出现在命令行里之后,后面所有内容都当成普通参数处理,即使它们长得像选项(以 <code>-</code> 开头)也不再被解析。</p><p>最经典的应用场景:删除一个文件名本身以 <code>-</code> 开头的文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -filename</span><br><span class="line"><span class="comment"># 报错:rm 会把 "-filename" 当成一个未知选项去解析,而不是文件名</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">rm</span> -- -filename</span><br><span class="line"><span class="comment"># 正确写法:-- 之后的 -filename 被当成普通参数(文件名),不再解析成选项</span></span><br></pre></td></tr></table></figure><p>只要命令是通过标准的 <code>getopt</code> 系列函数解析参数的,都支持 <code>--</code>,这是 POSIX Utility Conventions 里定义的通用行为,不是某个命令单独发明的。</p><p><strong>但这条规则不是无条件成立的</strong>:bash 内建的 <code>echo</code> 就是个例外——它不走 <code>getopt</code> 那套解析逻辑,<code>--</code> 传给它不会被当成”选项结束”标记,而是原样当成要输出的文本打印出来。真正通用可靠的做法是遇到不确定的命令先查一下它的文档,别假设”所有命令都认 <code>--</code>“。</p><h2 id="选项带参数的几种等价写法"><a href="#选项带参数的几种等价写法" class="headerlink" title="选项带参数的几种等价写法"></a>选项带参数的几种等价写法</h2><p>短选项和长选项各自还有细分的写法:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">tar -f archive.tar.gz files/</span><br><span class="line"><span class="comment"># 短选项 -f 后面留空格接参数</span></span><br><span class="line"></span><br><span class="line">tar -farchive.tar.gz files/</span><br><span class="line"><span class="comment"># 短选项也可以不留空格,直接把参数粘在字母后面,效果一样</span></span><br><span class="line"></span><br><span class="line">tar --file archive.tar.gz files/</span><br><span class="line"><span class="comment"># 长选项后面留空格接参数</span></span><br><span class="line"></span><br><span class="line">tar --file=archive.tar.gz files/</span><br><span class="line"><span class="comment"># 长选项也可以用等号连接参数,两种写法都行</span></span><br></pre></td></tr></table></figure><p>日常写惯了哪种就用哪种,四种写法在大多数 GNU 工具里都能正常解析,不用纠结选哪个更”正确”。</p><h2 id="回看前四篇:这套规律怎么解释你已经踩过的坑"><a href="#回看前四篇:这套规律怎么解释你已经踩过的坑" class="headerlink" title="回看前四篇:这套规律怎么解释你已经踩过的坑"></a>回看前四篇:这套规律怎么解释你已经踩过的坑</h2><p>前四篇里出现过的几个选项设计细节,用这篇的框架回头看就都说得通了:</p><ul><li><strong>tar 的 <code>-f</code> 必须放 clumped 短选项最后一位</strong>:因为短选项挤在一起时只有最后一个能带参数,<code>-f</code> 恰好是需要带参数的那个,位置不能乱放</li><li><strong>grep 的 <code>-E</code> 和 <code>--extended-regexp</code> 是同一个选项</strong>:短/长两种写法,效果完全一样,选哪个纯粹是个人习惯</li><li><strong>find 的 <code>--exclude</code> 没有对应短选项</strong>:印证了”长短选项不是一一对应”这条规律,<code>find</code> 的作者就没给它设计短选项</li></ul><p>这些当初看起来是”这个命令特有的坑”,其实都是同一套通用设计约定的具体体现。</p><h2 id="命令行选项之外"><a href="#命令行选项之外" class="headerlink" title="命令行选项之外"></a>命令行选项之外</h2><p>选项讲的是”怎么给一个命令传参数”,下一篇要讲的是”怎么把多个命令组合起来干活”——<code>xargs</code>、管道,以及背后那套”每个工具只做一件事、靠文本流互相拼接”的 Linux 命令设计哲学。</p>]]>
</content>
<id>https://blog.codewater.top/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E9%80%89%E9%A1%B9%E8%AE%BE%E8%AE%A1%E7%BA%A6%E5%AE%9A/</id>
<link href="https://blog.codewater.top/Linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E9%80%89%E9%A1%B9%E8%AE%BE%E8%AE%A1%E7%BA%A6%E5%AE%9A/"/>
<published>2026-07-12T08:45:34.000Z</published>
<summary>短选项和长选项是什么关系、为什么 grep -v 和 node -v 意思完全不同、-- 怎么用,讲清楚 Linux 命令行选项背后没有强制标准的设计约定。</summary>
<title>Linux 命令行选项设计:-v 和 --verbose 的区别,以及谁在决定这些约定</title>
<updated>2026-07-12T08:45:34.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="tar" scheme="https://blog.codewater.top/tags/tar/"/>
<category term="zip" scheme="https://blog.codewater.top/tags/zip/"/>
<content>
<![CDATA[<p>部署脚本里要把一堆文件打包传到服务器,敲命令时永远在纠结:<code>tar -czf</code> 和 <code>tar -xzf</code> 哪个是打包哪个是解包?参数顺序换一下会不会报错?打包完发现体积比预期大一倍,是哪里出了问题?</p><p>这些问题的根源都是同一件事:<code>tar</code> 到底在干什么,很多人从来没搞清楚过。</p><span id="more"></span><p>这是 Linux 常见命令系列第四篇详细教程,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>、<a href="/Linux%E6%9D%83%E9%99%90%E4%B8%8E%E5%B1%9E%E4%B8%BB%E7%AE%A1%E7%90%86-chmod-chown%E5%AE%9E%E6%88%98">chmod/chown</a>、<a href="/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98">grep</a>、<a href="/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98">find</a>。</p><h2 id="tar-是什么:打包和压缩是两件事"><a href="#tar-是什么:打包和压缩是两件事" class="headerlink" title="tar 是什么:打包和压缩是两件事"></a>tar 是什么:打包和压缩是两件事</h2><p><code>tar</code> 全称 tape archive(磁带归档),这个名字暴露了它的历史——最早是为了把多个文件写进磁带这种只能顺序读写的设备设计的。它做的事情只有一件:<strong>把多个文件和目录归档成一个文件流</strong>,文件之间的边界、权限、目录结构都保留下来,但内容本身<strong>不压缩</strong>。</p><p>压缩是另外一回事,靠 <code>tar</code> 调用外部的压缩程序(gzip、bzip2、xz)来完成。这就是为什么打包压缩的命令永远长这样:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">tar -czf archive.tar.gz files/</span><br><span class="line"><span class="comment"># -c:创建归档(打包)</span></span><br><span class="line"><span class="comment"># -z:调用 gzip 压缩</span></span><br><span class="line"><span class="comment"># -f:指定输出文件名</span></span><br><span class="line"><span class="comment"># 三个动作一步做完,但概念上是两件事:先打包,再压缩</span></span><br></pre></td></tr></table></figure><p>只打包不压缩,得到的是 <code>.tar</code> 文件;打包之后再压缩,才是 <code>.tar.gz</code>、<code>.tar.bz2</code> 这些格式——文件名里的双重后缀不是巧合,就是在诚实地告诉你这个文件经过了两道工序。</p><h2 id="核心操作模式:-c、-x、-t、-r"><a href="#核心操作模式:-c、-x、-t、-r" class="headerlink" title="核心操作模式:-c、-x、-t、-r"></a>核心操作模式:-c、-x、-t、-r</h2><p>四个字母决定 tar 这次要做什么:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">tar -czf archive.tar.gz files/</span><br><span class="line"><span class="comment"># -c(create):创建一个新归档</span></span><br><span class="line"></span><br><span class="line">tar -xzf archive.tar.gz</span><br><span class="line"><span class="comment"># -x(extract):把归档解出来</span></span><br><span class="line"></span><br><span class="line">tar -tzf archive.tar.gz</span><br><span class="line"><span class="comment"># -t(list):只列出归档里有什么文件,不真的解压</span></span><br><span class="line"><span class="comment"># 解压一个来路不明的压缩包之前,先用这个看看里面到底装了什么,避免解出一堆文件把当前目录搞乱</span></span><br><span class="line"></span><br><span class="line">tar -rf archive.tar files/new_file.txt</span><br><span class="line"><span class="comment"># -r(append):往已有的归档里追加文件</span></span><br><span class="line"><span class="comment"># 注意 -r 不能配合压缩格式使用(不能对已压缩的归档直接追加),只在处理未压缩的 .tar 文件时有效,不算高频用法</span></span><br></pre></td></tr></table></figure><p><code>-c</code>、<code>-x</code>、<code>-t</code>、<code>-r</code> 这四个是互斥的,一条命令只会用其中一个——决定这次操作”创建””解压”还是”查看”。</p><h2 id="压缩格式选择:-z、-j、-J"><a href="#压缩格式选择:-z、-j、-J" class="headerlink" title="压缩格式选择:-z、-j、-J"></a>压缩格式选择:-z、-j、-J</h2><p>打包之后要不要压缩、用哪种压缩算法,是三个独立的选项:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">tar -czf archive.tar.gz files/</span><br><span class="line"><span class="comment"># -z:gzip 压缩,速度快,压缩率一般,日常打包首选</span></span><br><span class="line"></span><br><span class="line">tar -cjf archive.tar.bz2 files/</span><br><span class="line"><span class="comment"># -j:bzip2 压缩,压缩率比 gzip 更高,但压缩速度更慢</span></span><br><span class="line"></span><br><span class="line">tar -cJf archive.tar.xz files/</span><br><span class="line"><span class="comment"># -J:xz 压缩,压缩率最高,速度也最慢,适合追求体积、不在乎时间的场景(比如长期归档存储)</span></span><br></pre></td></tr></table></figure><p>选型建议很简单:不确定就用 <code>-z</code>,这是使用最广泛、兼容性最好的选择;只有明确要求”体积越小越好、时间不是问题”时才考虑 <code>-J</code>;<code>-j</code> 处在中间地带,除非有历史包袱(老脚本已经在用 bzip2),日常新写的命令不太有必要专门选它。</p><p>这三个选项本质上是 tar 去调用系统里对应的外部压缩程序(gzip、bzip2、xz),不是 tar 自己内置的压缩实现——精简过的容器镜像如果没装对应的二进制,加了 <code>-z</code> 也会报错,排查”tar 报错找不到命令”时先确认这个压缩程序本身装了没有。</p><h2 id="f-参数:几乎总是需要"><a href="#f-参数:几乎总是需要" class="headerlink" title="-f 参数:几乎总是需要"></a><code>-f</code> 参数:几乎总是需要</h2><p><code>-f</code> 用来指定归档文件名,几乎每条 tar 命令都会带上——不写 <code>-f</code>,tar 默认会尝试读写标准输入输出而不是一个文件,这在日常场景下基本不是你想要的行为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">tar -czf backup.tar.gz /var/www/site</span><br><span class="line"><span class="comment"># -f 后面紧跟的 backup.tar.gz 就是输出文件名</span></span><br><span class="line"></span><br><span class="line">tar -zcf backup.tar.gz /var/www/site</span><br><span class="line"><span class="comment"># -czf 和 -zcf 效果一样——c、z 谁前谁后无所谓,因为它俩都不需要额外参数</span></span><br><span class="line"></span><br><span class="line">tar -fcz backup.tar.gz /var/www/site</span><br><span class="line"><span class="comment"># 错误示范:-f 一旦不在最后一位,紧跟在它后面的 cz 会被当成 -f 的参数(文件名变成字面的 "cz"),</span></span><br><span class="line"><span class="comment"># 真正的文件名 backup.tar.gz 反而落空,GNU 官方文档明确警告:重排选项顺序时不把对应参数一起挪走,</span></span><br><span class="line"><span class="comment"># 可能导致覆盖文件——把 -f 放最后是唯一安全的写法</span></span><br></pre></td></tr></table></figure><h2 id="解压到指定目录:-C"><a href="#解压到指定目录:-C" class="headerlink" title="解压到指定目录:-C"></a>解压到指定目录:-C</h2><p>不需要先 <code>cd</code> 到目标目录再解压,<code>-C</code> 直接指定解压目的地:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tar -xzf archive.tar.gz -C /opt/deploy/</span><br><span class="line"><span class="comment"># -C:解压到这个目录,不影响当前工作目录</span></span><br><span class="line"><span class="comment"># 等价于 cd /opt/deploy/ && tar -xzf /path/to/archive.tar.gz,但不用你手动切目录、也不用担心相对路径算错</span></span><br></pre></td></tr></table></figure><h2 id="排除文件:–exclude"><a href="#排除文件:–exclude" class="headerlink" title="排除文件:–exclude"></a>排除文件:–exclude</h2><p>打包项目目录时,通常不想把 <code>.git</code>、<code>node_modules</code> 这类无关内容也打进去:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tar -czf project.tar.gz --exclude=<span class="string">".git"</span> --exclude=<span class="string">"node_modules"</span> project/</span><br><span class="line"><span class="comment"># --exclude:排除匹配的文件或目录,可以写多个</span></span><br><span class="line"><span class="comment"># 打包前排除比打包后再删省事得多,尤其是 node_modules 这种体积大的目录</span></span><br></pre></td></tr></table></figure><h2 id="只解压部分内容"><a href="#只解压部分内容" class="headerlink" title="只解压部分内容"></a>只解压部分内容</h2><p>归档很大、只需要其中一两个文件时,不用全解出来:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tar -xzf archive.tar.gz path/to/specific-file.txt</span><br><span class="line"><span class="comment"># 在 -f 后面直接跟归档内的具体路径,只解出这一个文件</span></span><br><span class="line"><span class="comment"># 具体路径要和 tar -tzf 列出来的路径完全一致,包括开头有没有 ./ 这种细节</span></span><br></pre></td></tr></table></figure><h2 id="两个常见坑"><a href="#两个常见坑" class="headerlink" title="两个常见坑"></a>两个常见坑</h2><p><strong>打包时用了绝对路径,解压到别的机器上跑到意料之外的地方</strong>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">tar -czf backup.tar.gz /home/user/project</span><br><span class="line"><span class="comment"># 打包进去的路径信息是 home/user/project(tar 默认会去掉开头的 /)</span></span><br><span class="line"><span class="comment"># 解压时会在当前目录下创建 home/user/project 这一整串目录结构,而不是直接解出 project 内容</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> /home/user/project && tar -czf backup.tar.gz .</span><br><span class="line"><span class="comment"># 正确做法:先 cd 进目标目录,再用相对路径(. 表示当前目录)打包</span></span><br><span class="line"><span class="comment"># 解压出来就是干净的文件内容,不会带着一层多余的目录结构</span></span><br></pre></td></tr></table></figure><p><strong>压缩包体积比预期大,排查是不是重复压缩了</strong>:图片、视频、已经是 <code>.zip</code>/<code>.gz</code> 格式的文件本身已经压缩过,信息熵接近上限,再用 <code>-z</code> 压一遍收益很小,有时因为压缩算法的元数据开销,体积甚至会比不压缩略大。如果打包对象里混了大量这类文件,可以考虑不加压缩参数、只用纯 <code>-c</code> 打包(<code>.tar</code> 而不是 <code>.tar.gz</code>),省下压缩耗时也不会有实质损失。</p><h2 id="zip、unzip:跨平台场景的另一个选择"><a href="#zip、unzip:跨平台场景的另一个选择" class="headerlink" title="zip、unzip:跨平台场景的另一个选择"></a>zip、unzip:跨平台场景的另一个选择</h2><p>tar 打包出来的格式在 Linux/macOS 生态里很自然,但要把文件发给用 Windows 的同事,对方系统原生就认识 <code>.zip</code>,不用额外装解压软件——这是 <code>zip</code>/<code>unzip</code> 的核心价值:跨平台兼容性比 tar 更好。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">zip -r archive.zip project/</span><br><span class="line"><span class="comment"># -r:递归打包整个目录,跟 tar 的行为类似</span></span><br><span class="line"><span class="comment"># zip 和 tar 不同:打包和压缩是同一步完成的,没有"先打包再压缩"这道概念区分</span></span><br><span class="line"></span><br><span class="line">zip -r archive.zip project/ -x <span class="string">"*.log"</span></span><br><span class="line"><span class="comment"># -x:排除匹配的文件,这里排除所有 .log 文件</span></span><br><span class="line"></span><br><span class="line">unzip archive.zip -d /target/dir</span><br><span class="line"><span class="comment"># -d:解压到指定目录,效果类似 tar 的 -C</span></span><br><span class="line"></span><br><span class="line">unzip -l archive.zip</span><br><span class="line"><span class="comment"># -l:只列出压缩包里有什么文件,不真的解压,类似 tar -t</span></span><br></pre></td></tr></table></figure><p>zip 还有一个 tar 没有的功能:直接加密。<code>zip -e archive.zip file1 file2</code> 会在打包时提示输入密码,之后解压需要密码才能打开。但这里有个安全上的坑要提醒:<strong>标准 zip 加密的强度很弱</strong>(用的是老旧的算法,容易被破解),只适合防”顺手翻一下”这种低要求场景,真正需要保密用 <code>gpg</code> 或 <code>age</code> 这类专门的加密工具,别指望 zip 密码扛得住认真的破解尝试。</p><h2 id="tar-常用参数速查表"><a href="#tar-常用参数速查表" class="headerlink" title="tar 常用参数速查表"></a>tar 常用参数速查表</h2><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-c</code></td><td>创建归档</td></tr><tr><td><code>-x</code></td><td>解压归档</td></tr><tr><td><code>-t</code></td><td>只列出归档内容,不解压</td></tr><tr><td><code>-r</code></td><td>追加文件到已有归档(不能用于已压缩的归档)</td></tr><tr><td><code>-z</code></td><td>调用 gzip 压缩/解压,速度快</td></tr><tr><td><code>-j</code></td><td>调用 bzip2 压缩/解压,压缩率更高但更慢</td></tr><tr><td><code>-J</code></td><td>调用 xz 压缩/解压,压缩率最高,最慢</td></tr><tr><td><code>-f FILE</code></td><td>指定归档文件名</td></tr><tr><td><code>-v</code></td><td>显示处理过程中的文件列表(verbose)</td></tr><tr><td><code>-C DIR</code></td><td>解压到指定目录</td></tr><tr><td><code>--exclude=PATTERN</code></td><td>排除匹配的文件或目录</td></tr></tbody></table><p><code>zip</code>/<code>unzip</code> 常用的几个参数:</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-r</code></td><td>递归打包整个目录</td></tr><tr><td><code>-x PATTERN</code></td><td>排除匹配的文件</td></tr><tr><td><code>-e</code></td><td>加密压缩包(强度弱,别指望防真正的攻击者)</td></tr><tr><td><code>-d DIR</code></td><td>(unzip)解压到指定目录</td></tr><tr><td><code>-l</code></td><td>(unzip)只列出内容,不解压</td></tr></tbody></table><h2 id="tar-之外"><a href="#tar-之外" class="headerlink" title="tar 之外"></a>tar 之外</h2><p>系列下一篇计划写 <code>rsync</code>——增量同步、排除规则、远程传输这些参数量级比 tar 还大,够写满一整篇。</p>]]>
</content>
<id>https://blog.codewater.top/tar-zip-unzip%E5%BD%92%E6%A1%A3%E5%8E%8B%E7%BC%A9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/tar-zip-unzip%E5%BD%92%E6%A1%A3%E5%8E%8B%E7%BC%A9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91%E5%AE%9E%E6%88%98/"/>
<published>2026-07-11T09:00:00.000Z</published>
<summary>tar 为什么不等于压缩、-z/-j/-J 怎么选、zip/unzip 的跨平台场景,配合真实踩坑讲清楚 Linux 归档压缩命令的实战用法。</summary>
<title>tar、zip/unzip:归档压缩命令与常见坑实战</title>
<updated>2026-07-11T09:00:00.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="find" scheme="https://blog.codewater.top/tags/find/"/>
<content>
<![CDATA[<p>知道 <code>fd</code> 语法更直觉、速度更快,但 SSH 到一台没装第三方工具的服务器上,还是得翻出记忆里生锈的 <code>find</code> 语法。条件和动作到底谁写前面、<code>-mtime</code> 和 <code>-atime</code> 哪个是修改时间、<code>-exec</code> 后面跟的是 <code>\;</code> 还是 <code>+</code>——每次都要重新查一遍。</p><p><code>find</code> 是所有 Linux 发行版都自带的工具,这篇把它自己的用法讲清楚。</p><span id="more"></span><p>这是 Linux 常见命令系列第三篇详细教程,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>、<a href="/Linux%E6%9D%83%E9%99%90%E4%B8%8E%E5%B1%9E%E4%B8%BB%E7%AE%A1%E7%90%86-chmod-chown%E5%AE%9E%E6%88%98">chmod/chown</a>、<a href="/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98">grep</a>。如果你的环境能装第三方工具,<a href="/%E7%BB%88%E7%AB%AF%E6%90%9C%E7%B4%A2%E4%B8%89%E4%BB%B6%E5%A5%97-fzf-ripgrep-fd">fd 语法比 find 直觉得多</a>——但装不上的场合,还得靠这篇。</p><h2 id="基本语法:起始路径、条件、动作三段式"><a href="#基本语法:起始路径、条件、动作三段式" class="headerlink" title="基本语法:起始路径、条件、动作三段式"></a>基本语法:起始路径、条件、动作三段式</h2><p><code>find</code> 命令的结构是固定的三段:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find 起始路径 条件 动作</span><br></pre></td></tr></table></figure><p>比如 <code>find . -name "*.log" -print</code>:<code>.</code> 是起始路径(从当前目录开始找),<code>-name "*.log"</code> 是条件(文件名匹配这个模式),<code>-print</code> 是动作(把匹配到的路径打印出来)。</p><p>新手最容易搞混的地方是把条件和动作当成一回事。<code>-name</code>、<code>-type</code>、<code>-mtime</code> 这些是<strong>条件</strong>,负责”筛选出哪些文件符合要求”;<code>-print</code>、<code>-delete</code>、<code>-exec</code> 这些是<strong>动作</strong>,负责”对筛出来的文件做什么”。不写动作时 find 默认执行 <code>-print</code>,所以 <code>find . -name "*.log"</code> 和 <code>find . -name "*.log" -print</code> 效果一样——但显式写上 <code>-print</code>,尤其是命令里还有别的动作时,意图更清楚,不容易和条件搞混。</p><h2 id="按名字和类型找:-name、-iname、-type"><a href="#按名字和类型找:-name、-iname、-type" class="headerlink" title="按名字和类型找:-name、-iname、-type"></a>按名字和类型找:-name、-iname、-type</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.log"</span></span><br><span class="line"><span class="comment"># -name:按文件名匹配,区分大小写,*.log 和 *.LOG 是两回事</span></span><br><span class="line"></span><br><span class="line">find . -iname <span class="string">"*.log"</span></span><br><span class="line"><span class="comment"># -iname:和 -name 一样,但不区分大小写,access.log、ACCESS.LOG 都能匹配到</span></span><br><span class="line"></span><br><span class="line">find . -<span class="built_in">type</span> f -name <span class="string">"*.go"</span></span><br><span class="line"><span class="comment"># -type f:只要普通文件,不要目录</span></span><br><span class="line"><span class="comment"># -type d:只要目录</span></span><br><span class="line"><span class="comment"># -type l:只要符号链接</span></span><br><span class="line"></span><br><span class="line">find . -maxdepth 2 -name <span class="string">"*.md"</span></span><br><span class="line"><span class="comment"># -maxdepth:限制递归深度,2 表示最多进两层子目录,不会一路挖到底</span></span><br><span class="line"><span class="comment"># 忘加这个参数是 find 跑得慢、结果里混进一堆不相关文件的常见原因</span></span><br><span class="line"></span><br><span class="line">find . -mindepth 1 -maxdepth 1 -<span class="built_in">type</span> d</span><br><span class="line"><span class="comment"># -mindepth 1:不包括起始路径本身,只要子级</span></span><br><span class="line"><span class="comment"># 配合 -maxdepth 1,效果就是"只列出当前目录下的一级子目录",不递归</span></span><br></pre></td></tr></table></figure><h2 id="按时间找:-mtime、-atime、-ctime-的区别"><a href="#按时间找:-mtime、-atime、-ctime-的区别" class="headerlink" title="按时间找:-mtime、-atime、-ctime 的区别"></a>按时间找:-mtime、-atime、-ctime 的区别</h2><p>三个时间条件容易搞混,先说清楚各自对应什么:</p><table><thead><tr><th>参数</th><th>对应的时间戳</th><th>什么情况下会变</th></tr></thead><tbody><tr><td><code>-mtime</code>(modify time)</td><td>内容修改时间</td><td>文件内容被写入/修改</td></tr><tr><td><code>-atime</code>(access time)</td><td>访问时间</td><td>文件被读取(有些文件系统挂载时禁用了 atime 更新,出于性能考虑)</td></tr><tr><td><code>-ctime</code>(change time)</td><td>状态变更时间</td><td>文件的元数据变了——权限、属主、链接数,不一定是内容变了</td></tr></tbody></table><p>日常排查用得最多的是 <code>-mtime</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">find . -mtime -7</span><br><span class="line"><span class="comment"># -7:前面带减号,表示"7 天以内"(比 7 天新)</span></span><br><span class="line"><span class="comment"># 符号方向:- 是"比这个数小",+ 是"比这个数大",不带符号是"精确等于这个数"</span></span><br><span class="line"></span><br><span class="line">find /var/log -mtime +30 -name <span class="string">"*.log"</span></span><br><span class="line"><span class="comment"># +30:30 天以前修改过的日志文件,排查该清理哪些旧日志时常用</span></span><br><span class="line"></span><br><span class="line">find . -newer reference.txt -name <span class="string">"*.go"</span></span><br><span class="line"><span class="comment"># -newer:找修改时间比 reference.txt 更新的文件,不用自己算具体天数</span></span><br></pre></td></tr></table></figure><h2 id="按大小找:-size-的符号前缀"><a href="#按大小找:-size-的符号前缀" class="headerlink" title="按大小找:-size 的符号前缀"></a>按大小找:-size 的符号前缀</h2><p><code>-size</code> 的符号方向和 <code>-mtime</code> 是同一套逻辑,但因为单位是大小不是时间,容易理解反:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">find . -size +100M</span><br><span class="line"><span class="comment"># +100M:大于 100MB 的文件,排查磁盘占用时常用</span></span><br><span class="line"></span><br><span class="line">find . -size -1k</span><br><span class="line"><span class="comment"># -1k:小于 1KB 的文件,找空文件或者几乎是空的配置文件</span></span><br><span class="line"></span><br><span class="line">find . -size 0</span><br><span class="line"><span class="comment"># 不带符号:精确匹配大小为 0 的文件,找真正的空文件</span></span><br></pre></td></tr></table></figure><p>单位后缀:<code>c</code> 是字节,<code>k</code> 是 KB,<code>M</code> 是 MB,<code>G</code> 是 GB,不写单位默认是 512 字节的块数(历史遗留,容易踩坑,写 size 条件时最好总是带上单位)。</p><h2 id="逻辑组合:-and、-or、-not-与括号分组"><a href="#逻辑组合:-and、-or、-not-与括号分组" class="headerlink" title="逻辑组合:-and、-or、-not 与括号分组"></a>逻辑组合:-and、-or、-not 与括号分组</h2><p>多个条件之间默认是”且”的关系,<code>-and</code> 可以省略:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">find . -<span class="built_in">type</span> f -name <span class="string">"*.go"</span></span><br><span class="line"><span class="comment"># 等价于 find . -type f -and -name "*.go",-and 默认存在,通常不用写</span></span><br><span class="line"></span><br><span class="line">find . -name <span class="string">"*.go"</span> -or -name <span class="string">"*.md"</span></span><br><span class="line"><span class="comment"># -or:找 .go 或 .md 文件,这个必须显式写,不写就是 -and 的默认关系</span></span><br><span class="line"></span><br><span class="line">find . -not -path <span class="string">"*/.git/*"</span> -name <span class="string">"*.go"</span></span><br><span class="line"><span class="comment"># -not:排除条件,这里是"路径不包含 .git 目录"且"文件名匹配 .go"</span></span><br><span class="line"></span><br><span class="line">find . \( -name <span class="string">"*.go"</span> -or -name <span class="string">"*.md"</span> \) -not -path <span class="string">"*/.git/*"</span></span><br><span class="line"><span class="comment"># 括号分组控制优先级,圆括号在 shell 里有特殊含义,必须转义成 \( \)</span></span><br><span class="line"><span class="comment"># 这条的意思是:(.go 或 .md 文件) 并且 不在 .git 目录下</span></span><br></pre></td></tr></table></figure><h2 id="exec-的两种写法:-和-,性能差在哪"><a href="#exec-的两种写法:-和-,性能差在哪" class="headerlink" title="-exec 的两种写法:\; 和 +,性能差在哪"></a><code>-exec</code> 的两种写法:<code>\;</code> 和 <code>+</code>,性能差在哪</h2><p>这是 find 里最容易被忽略、但影响最大的细节。两种写法看起来只差一个符号,行为和性能却完全不同:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.log"</span> -<span class="built_in">exec</span> gzip {} \;</span><br><span class="line"><span class="comment"># \; 结尾:每找到一个文件,就单独起一次 gzip 命令处理这一个文件</span></span><br><span class="line"><span class="comment"># {} 是占位符,代表当前这个匹配到的文件路径</span></span><br><span class="line"></span><br><span class="line">find . -name <span class="string">"*.log"</span> -<span class="built_in">exec</span> gzip {} +</span><br><span class="line"><span class="comment"># + 结尾:尽量把多个文件路径打包,一次性作为参数传给 gzip</span></span><br><span class="line"><span class="comment"># 行为类似 xargs——攒够一批(受命令行长度上限约束)就调用一次</span></span><br></pre></td></tr></table></figure><p><strong>为什么性能差这么多</strong>:<code>\;</code> 结尾时,找到 1000 个文件,就要 fork 1000 次子进程、启动 1000 次 gzip——每次进程创建都有实打实的系统开销(内存分配、内核调度),文件越多,这个开销累加得越明显。<code>+</code> 结尾时,find 会把匹配到的文件路径尽量攒在一起,打包成尽可能少的调用次数,具体能攒多少受系统的 <code>ARG_MAX</code>(单次命令调用能传递的参数总长度上限)约束——Linux 上典型值是 2097152 字节(约 2MB,<code>getconf ARG_MAX</code> 能查到本机的实际值)。假设文件路径平均 50 字节,2MB 够装下四万多个路径,1000 个文件的路径总长度远小于这个上限,实际可能一次调用就打包完了,进程创建次数从”文件数”级别降到”个位数”,量级差出去很多。</p><p>日常写脚本的经验规则:只要命令支持一次接收多个文件路径(大多数命令都支持,比如 <code>rm</code>、<code>gzip</code>、<code>chmod</code>),优先用 <code>+</code>;只有命令一次只能处理一个文件、或者每个文件需要单独判断结果时,才用 <code>\;</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.tmp"</span> -<span class="built_in">exec</span> <span class="built_in">rm</span> {} +</span><br><span class="line"><span class="comment"># 批量删除 .tmp 文件,用 + 一次调用处理完,比 \; 快得多</span></span><br></pre></td></tr></table></figure><h2 id="delete:比-exec-rm-更安全高效"><a href="#delete:比-exec-rm-更安全高效" class="headerlink" title="-delete:比 -exec rm 更安全高效"></a><code>-delete</code>:比 <code>-exec rm</code> 更安全高效</h2><p>需要删除匹配到的文件时,<code>find</code> 自带的 <code>-delete</code> 动作比 <code>-exec rm {} +</code> 更直接——不用再额外 fork 一个 <code>rm</code> 进程:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.tmp"</span> -delete</span><br><span class="line"><span class="comment"># -delete:直接删除匹配到的文件,不需要再调用外部 rm 命令</span></span><br></pre></td></tr></table></figure><p><strong>用之前一定要先确认范围</strong>:把 <code>-delete</code> 换成 <code>-print</code>,跑一遍看看到底会命中哪些文件,确认没问题了再换回 <code>-delete</code>——<code>find</code> 的条件写错很容易命中比预期更大的范围,删除操作又无法撤销:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.tmp"</span> -<span class="built_in">print</span></span><br><span class="line"><span class="comment"># 先跑这条确认命中范围,看着输出结果没问题</span></span><br><span class="line">find . -name <span class="string">"*.tmp"</span> -delete</span><br><span class="line"><span class="comment"># 确认没问题后再换成 -delete 真正执行</span></span><br></pre></td></tr></table></figure><h2 id="两个常见坑:文件名带空格、通配符被-shell-提前展开"><a href="#两个常见坑:文件名带空格、通配符被-shell-提前展开" class="headerlink" title="两个常见坑:文件名带空格、通配符被 shell 提前展开"></a>两个常见坑:文件名带空格、通配符被 shell 提前展开</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">"*.txt"</span> -print0 | xargs -0 <span class="built_in">rm</span></span><br><span class="line"><span class="comment"># -print0:文件名之间用 null 字符(\0)分隔,而不是换行或空格</span></span><br><span class="line"><span class="comment"># xargs -0:配合读取 null 分隔的输入</span></span><br><span class="line"><span class="comment"># 文件名里带空格(比如 "my file.txt")时,普通的换行分隔会被 xargs 误判成两个参数,</span></span><br><span class="line"><span class="comment"># null 分隔是唯一不会出现在合法文件名里的字符,能保证安全</span></span><br><span class="line"></span><br><span class="line">find . -name *.txt</span><br><span class="line"><span class="comment"># 危险写法:*.txt 没加引号,如果当前目录下已经有匹配 *.txt 的文件,</span></span><br><span class="line"><span class="comment"># shell 会在 find 执行前就把通配符展开成具体文件名,find 实际收到的</span></span><br><span class="line"><span class="comment"># 是一堆文件名而不是模式字符串,只会在这些文件里精确匹配,行为完全不对</span></span><br><span class="line"></span><br><span class="line">find . -name <span class="string">"*.txt"</span></span><br><span class="line"><span class="comment"># 正确写法:加引号,让 *.txt 原样传给 find,由 find 自己去做模式匹配</span></span><br></pre></td></tr></table></figure><h2 id="find-常用参数速查表"><a href="#find-常用参数速查表" class="headerlink" title="find 常用参数速查表"></a>find 常用参数速查表</h2><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-name PATTERN</code></td><td>按文件名匹配,区分大小写</td></tr><tr><td><code>-iname PATTERN</code></td><td>按文件名匹配,不区分大小写</td></tr><tr><td><code>-type f</code> / <code>d</code> / <code>l</code></td><td>只要普通文件 / 目录 / 符号链接</td></tr><tr><td><code>-maxdepth N</code></td><td>最多递归 N 层</td></tr><tr><td><code>-mindepth N</code></td><td>至少从第 N 层开始算</td></tr><tr><td><code>-mtime ±N</code></td><td>内容修改时间在 N 天以内(<code>-</code>)/ 以前(<code>+</code>)/ 精确 N 天(不带符号)</td></tr><tr><td><code>-atime ±N</code></td><td>访问时间,符号规则同 <code>-mtime</code></td></tr><tr><td><code>-ctime ±N</code></td><td>元数据(权限/属主)变更时间,符号规则同 <code>-mtime</code></td></tr><tr><td><code>-newer FILE</code></td><td>修改时间比 FILE 更新</td></tr><tr><td><code>-size ±N[ckMG]</code></td><td>文件大小,符号规则同 <code>-mtime</code>,单位 c/k/M/G</td></tr><tr><td><code>-and</code></td><td>且,默认关系,通常省略</td></tr><tr><td><code>-or</code></td><td>或,必须显式写</td></tr><tr><td><code>-not</code></td><td>非,排除条件</td></tr><tr><td><code>\( ... \)</code></td><td>括号分组,圆括号需转义</td></tr><tr><td><code>-exec CMD {} \;</code></td><td>对每个匹配文件单独执行一次命令</td></tr><tr><td><code>-exec CMD {} +</code></td><td>尽量批量打包,减少命令调用次数</td></tr><tr><td><code>-delete</code></td><td>直接删除匹配到的文件</td></tr><tr><td><code>-print0</code></td><td>用 null 字符分隔输出,配合 <code>xargs -0</code> 处理带空格的文件名</td></tr></tbody></table><h2 id="find-之外"><a href="#find-之外" class="headerlink" title="find 之外"></a>find 之外</h2><p>装得上第三方工具的话,<a href="/%E7%BB%88%E7%AB%AF%E6%90%9C%E7%B4%A2%E4%B8%89%E4%BB%B6%E5%A5%97-fzf-ripgrep-fd">fd</a> 默认行为更符合直觉(自动跳过 <code>.gitignore</code>、语法更接近自然语言),日常开发优先用它,<code>find</code> 留给装不上工具的场合。Linux 命令系列下一篇计划写 <code>rsync</code> 或 <code>tar</code>——都是比 <code>find</code> 参数量级还大的命令,增量同步、归档压缩各有一堆细节值得展开。</p>]]>
</content>
<id>https://blog.codewater.top/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/find%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88%E4%B8%8Eexec%E6%80%A7%E8%83%BD%E5%AE%9E%E6%88%98/"/>
<published>2026-07-11T07:00:00.000Z</published>
<summary>find 的条件与动作怎么分、-mtime/-size 符号方向、-exec 用 \; 和 + 性能差在哪,一篇讲清楚原生 find 的实战用法。</summary>
<title>find:条件组合、-exec 两种写法与性能实战</title>
<updated>2026-07-11T07:00:00.000Z</updated>
</entry>
<entry>
<author>
<name>Code Water</name>
</author>
<category term="tools" scheme="https://blog.codewater.top/categories/tools/"/>
<category term="Linux" scheme="https://blog.codewater.top/tags/Linux/"/>
<category term="命令行" scheme="https://blog.codewater.top/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
<category term="grep" scheme="https://blog.codewater.top/tags/grep/"/>
<category term="正则表达式" scheme="https://blog.codewater.top/tags/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
<content>
<![CDATA[<p>本地终端习惯了用 ripgrep,SSH 到一台精简过的服务器或者容器镜像上,才发现根本没装 <code>rg</code>,只能用回系统自带的 grep。这时候才发现自己其实记不住 grep 的参数——<code>-E</code> 和 <code>-P</code> 到底有什么区别、括号要不要转义、上下文参数是哪三个字母。</p><p>grep 是所有 Linux 发行版都自带的工具,不需要额外安装,这篇把它自己的用法讲清楚。</p><span id="more"></span><p>这是 Linux 常见命令系列第二篇详细教程,承接 <a href="/Linux%E5%B8%B8%E8%A7%81%E5%91%BD%E4%BB%A4%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%9C%B0%E5%9B%BE">Linux 常见命令与工具地图</a>。如果你的环境能装第三方工具,<a href="/%E7%BB%88%E7%AB%AF%E6%90%9C%E7%B4%A2%E4%B8%89%E4%BB%B6%E5%A5%97-fzf-ripgrep-fd">ripgrep 通常是更好的选择</a>——但装不上的场合,还得靠这篇。</p><h2 id="三种正则模式:BRE、-E、-P-到底有什么区别"><a href="#三种正则模式:BRE、-E、-P-到底有什么区别" class="headerlink" title="三种正则模式:BRE、-E、-P 到底有什么区别"></a>三种正则模式:BRE、-E、-P 到底有什么区别</h2><p>这是 grep 里最容易搞混的地方,因为同一个符号在不同模式下含义不一样。</p><p><strong>默认模式是 BRE(Basic Regular Expression,基础正则)</strong>:<code>+</code>、<code>?</code>、<code>|</code>、<code>(</code>、<code>)</code>、<code>{</code>、<code>}</code> 这几个符号<strong>不是</strong>特殊字符,是普通文本本身;要让它们起特殊作用,得加反斜杠转义成 <code>\+</code>、<code>\?</code>、<code>\|</code>。这一点非常反直觉——很多人写 <code>grep "a+b"</code> 以为在匹配”一个或多个 a 后面跟 b”,实际在 BRE 下匹配的是字面的字符串 <code>a+b</code>。</p><p><strong><code>-E</code> 启用 ERE(Extended Regular Expression,扩展正则,等价于老命令 <code>egrep</code>)</strong>:上面那几个符号反过来变成特殊字符,不用转义就能用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">grep <span class="string">'a\+b'</span> file.txt</span><br><span class="line"><span class="comment"># BRE 默认模式:\+ 转义后才是"一个或多个 a",匹配 ab、aab、aaab...</span></span><br><span class="line"></span><br><span class="line">grep -E <span class="string">'a+b'</span> file.txt</span><br><span class="line"><span class="comment"># -E 扩展正则:+ 天生就是特殊字符,效果和上面那条一样</span></span><br><span class="line"><span class="comment"># -E 等价于直接用 egrep 命令(egrep 只是 grep -E 的别名,新脚本建议用 -E)</span></span><br></pre></td></tr></table></figure><p><strong><code>-P</code> 启用 PCRE(Perl 兼容正则)</strong>:语法最丰富,<code>\d</code>(数字)、<code>\s</code>(空白)、<code>\w</code>(单词字符)、<code>\b</code>(单词边界)、非贪婪匹配 <code>.*?</code> 这些 BRE 和 -E 都不支持的写法,只有 -P 能用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">grep -P <span class="string">'\d{3}-\d{4}'</span> contacts.txt</span><br><span class="line"><span class="comment"># -P:Perl 正则,\d{3} 表示连续 3 个数字,匹配形如 123-4567 的号码</span></span><br><span class="line"><span class="comment"># BRE/-E 下要写成等价的 [0-9]\{3\} 或 [0-9]{3},没有 \d 这种简写</span></span><br></pre></td></tr></table></figure><p>同一个”匹配一个或多个数字”的需求,在三种模式下写法完全不同:</p><table><thead><tr><th>需求</th><th>BRE(默认)</th><th><code>-E</code></th><th><code>-P</code></th></tr></thead><tbody><tr><td>一个或多个数字</td><td><code>[0-9]\+</code></td><td><code>[0-9]+</code></td><td><code>\d+</code></td></tr><tr><td>括号分组</td><td><code>\(abc\)</code></td><td><code>(abc)</code></td><td><code>(abc)</code></td></tr><tr><td>或(多选一)</td><td><code>a|b</code>(GNU 扩展,非 POSIX 标准)</td><td><code>a|b</code></td><td><code>a|b</code></td></tr></tbody></table><p><strong>一个容易踩的坑</strong>:<code>-P</code> 依赖 grep 编译时链接了 PCRE 库,绝大多数主流发行版的 grep 都带这个支持,但个别精简过的容器镜像可能没有,报 <code>grep: this platform does not support -P</code> 时就只能退回 -E 或 BRE 手动转义。日常写脚本,如果不确定目标机器的 grep 支持情况,优先用 <code>-E</code>——兼容性最好,语法也不用记转义规则。</p><h2 id="基础用法:递归、忽略大小写、反向匹配"><a href="#基础用法:递归、忽略大小写、反向匹配" class="headerlink" title="基础用法:递归、忽略大小写、反向匹配"></a>基础用法:递归、忽略大小写、反向匹配</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">grep -r <span class="string">"TODO"</span> src/</span><br><span class="line"><span class="comment"># -r:递归搜索目录下所有文件</span></span><br><span class="line"></span><br><span class="line">grep -R <span class="string">"TODO"</span> src/</span><br><span class="line"><span class="comment"># -R:和 -r 的唯一区别是符号链接的处理,见下面的说明</span></span><br><span class="line"></span><br><span class="line">grep -i <span class="string">"error"</span> app.log</span><br><span class="line"><span class="comment"># -i:忽略大小写,ERROR、Error、error 都能匹配到</span></span><br></pre></td></tr></table></figure><p><strong>先说清楚一个前提</strong>:不加 <code>-r</code>/<code>-R</code>,grep 根本不会进目录递归——不管这个参数是真实目录还是指向目录的符号链接,直接报错 <code>grep: xxx: Is a directory</code>,跳过不搜。要进目录搜索,<code>-r</code> 是必须的,”跟不跟随符号链接”是加了 <code>-r</code> 之后才谈得上的下一层问题:</p><ul><li><code>-r</code>:命令行里直接传的符号链接(比如 <code>grep -r "TODO" src/shared-link</code>,<code>shared-link</code> 指向另一个目录)会被跟随,当成真目录进去搜;但递归过程中、在 <code>src/</code> 内部某个子目录里才碰到的符号链接,不会跟随,直接跳过</li><li><code>-R</code>:不区分是不是命令行直接给的,只要递归时遇到符号链接指向目录就都跟随进去搜</li></ul><p>如果符号链接指向的是<strong>文件</strong>而不是目录,这个规则完全不适用——不需要 <code>-r</code>,直接 <code>grep "pattern" 符号链接文件</code> 就能读到内容,因为这只是普通的文件读取,操作系统打开文件时本来就会跟随符号链接,跟目录递归是两回事。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">grep -v <span class="string">"DEBUG"</span> app.log</span><br><span class="line"><span class="comment"># -v:反向匹配,输出不包含 DEBUG 的行——排查日志时过滤掉噪音行常用</span></span><br></pre></td></tr></table></figure><h2 id="只要结果的一部分:-l、-L、-c、-o、-n"><a href="#只要结果的一部分:-l、-L、-c、-o、-n" class="headerlink" title="只要结果的一部分:-l、-L、-c、-o、-n"></a>只要结果的一部分:-l、-L、-c、-o、-n</h2><p>默认 grep 输出的是匹配到的整行,但很多时候你只想要文件名、数量,或者匹配的那一小段:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">grep -l <span class="string">"TODO"</span> src/**/*.go</span><br><span class="line"><span class="comment"># -l:只列出包含匹配内容的文件名,不显示具体是哪一行</span></span><br><span class="line"></span><br><span class="line">grep -L <span class="string">"TODO"</span> src/**/*.go</span><br><span class="line"><span class="comment"># -L:反过来,列出不包含匹配内容的文件名</span></span><br><span class="line"></span><br><span class="line">grep -c <span class="string">"ERROR"</span> app.log</span><br><span class="line"><span class="comment"># -c:只输出匹配的行数,不显示内容本身,快速确认量级用</span></span><br><span class="line"></span><br><span class="line">grep -o -E <span class="string">'[0-9]+\.[0-9]+\.[0-9]+'</span> package.json</span><br><span class="line"><span class="comment"># -o:只输出匹配到的那部分文本,不是整行;这里配合 -E 提取版本号</span></span><br><span class="line"></span><br><span class="line">grep -n <span class="string">"func main"</span> main.go</span><br><span class="line"><span class="comment"># -n:匹配的每一行前面带上行号,定位到编辑器里直接跳转用</span></span><br></pre></td></tr></table></figure><h2 id="精确匹配:-w-整词、-x-整行"><a href="#精确匹配:-w-整词、-x-整行" class="headerlink" title="精确匹配:-w 整词、-x 整行"></a>精确匹配:-w 整词、-x 整行</h2><p>不加限制的话,<code>grep "cat"</code> 会把 <code>category</code>、<code>concatenate</code> 这类包含 “cat” 子串的词也匹配上:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">grep -w <span class="string">"cat"</span> animals.txt</span><br><span class="line"><span class="comment"># -w:整词匹配,只匹配独立的单词 cat,不会命中 category 里的 cat</span></span><br><span class="line"></span><br><span class="line">grep -x <span class="string">"done"</span> status.txt</span><br><span class="line"><span class="comment"># -x:整行匹配。status.txt 里某一行如果内容恰好就是 "done" 这四个字符,能匹配上;</span></span><br><span class="line"><span class="comment"># 如果那一行是 "task done" 或者 "done!",虽然包含 "done" 这个子串,但整行内容不完全等于 "done",匹配不上</span></span><br></pre></td></tr></table></figure><h2 id="上下文控制:-A、-B、-C"><a href="#上下文控制:-A、-B、-C" class="headerlink" title="上下文控制:-A、-B、-C"></a>上下文控制:-A、-B、-C</h2><p>排查报错时,只看到匹配的那一行往往不够,前后几行的上下文才能定位问题:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">grep -A 3 <span class="string">"Exception"</span> app.log</span><br><span class="line"><span class="comment"># -A(after):匹配行之后再多输出 3 行,看报错发生后系统还做了什么</span></span><br><span class="line"></span><br><span class="line">grep -B 3 <span class="string">"Exception"</span> app.log</span><br><span class="line"><span class="comment"># -B(before):匹配行之前再多输出 3 行,看报错发生前的调用链</span></span><br><span class="line"></span><br><span class="line">grep -C 3 <span class="string">"Exception"</span> app.log</span><br><span class="line"><span class="comment"># -C(context):等价于同时加 -A 3 -B 3,前后各多输出 3 行</span></span><br></pre></td></tr></table></figure><p>多个不连续的匹配段之间,grep 会用 <code>--</code> 分隔线隔开,方便肉眼区分不同的匹配区块。</p><h2 id="按文件类型过滤:–include、–exclude、–exclude-dir"><a href="#按文件类型过滤:–include、–exclude、–exclude-dir" class="headerlink" title="按文件类型过滤:–include、–exclude、–exclude-dir"></a>按文件类型过滤:–include、–exclude、–exclude-dir</h2><p>递归搜索整个项目目录时,通常不想搜到 <code>node_modules</code>、构建产物这些无关内容:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">grep -r <span class="string">"TODO"</span> . --include=<span class="string">"*.go"</span></span><br><span class="line"><span class="comment"># --include:只在匹配这个 glob 模式的文件里搜,这里只搜 .go 文件</span></span><br><span class="line"></span><br><span class="line">grep -r <span class="string">"TODO"</span> . --exclude=<span class="string">"*.min.js"</span></span><br><span class="line"><span class="comment"># --exclude:排除匹配这个模式的文件,压缩后的 JS 文件通常没必要搜</span></span><br><span class="line"></span><br><span class="line">grep -r <span class="string">"TODO"</span> . --exclude-dir=<span class="string">"node_modules"</span> --exclude-dir=<span class="string">".git"</span></span><br><span class="line"><span class="comment"># --exclude-dir:整个目录都跳过,node_modules 和 .git 是最常见的两个</span></span><br></pre></td></tr></table></figure><p>这几个选项只在配合 <code>-r</code>/<code>-R</code> 递归搜索时才有意义,搜单个文件用不上。</p><h2 id="和管道、xargs-组合的实战"><a href="#和管道、xargs-组合的实战" class="headerlink" title="和管道、xargs 组合的实战"></a>和管道、xargs 组合的实战</h2><p>grep 真正的威力在于能接进管道,和其他命令组合:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">grep -l <span class="string">"deprecated_func"</span> src/**/*.go | xargs grep -n <span class="string">"deprecated_func"</span></span><br><span class="line"><span class="comment"># 先用 -l 找出所有包含 deprecated_func 的文件(只要文件名)</span></span><br><span class="line"><span class="comment"># 再用 xargs 把这些文件名传给第二个 grep,这次带上 -n 看具体在哪一行</span></span><br><span class="line"><span class="comment"># 两步拆开的好处:第一步先确认"影响范围有多大",再决定要不要继续深入</span></span><br><span class="line"></span><br><span class="line">grep -r <span class="string">"TODO"</span> . --include=<span class="string">"*.go"</span> | grep -v <span class="string">"TODO(done)"</span></span><br><span class="line"><span class="comment"># 链式过滤:第一个 grep 找出所有 TODO,第二个 grep 用 -v 排除已经标记完成的</span></span><br><span class="line"><span class="comment"># 两个 grep 串联,比写一条复杂的正则更好读</span></span><br><span class="line"></span><br><span class="line">grep -l <span class="string">"old_config_key"</span> config/*.yaml | xargs sed -i <span class="string">'s/old_config_key/new_config_key/g'</span></span><br><span class="line"><span class="comment"># 场景:配置项改名了(old_config_key 要统一换成 new_config_key),但不确定散落在多少个 yaml 文件里</span></span><br><span class="line"><span class="comment"># grep -l 先扫一遍,只列出真正包含 old_config_key 的文件名(没提到的文件完全不动)</span></span><br><span class="line"><span class="comment"># xargs 把这些文件名接力传给 sed,对每个文件执行替换,一条命令完成"找出范围 + 批量改名"两件事</span></span><br><span class="line"><span class="comment"># -i:sed 的原地修改参数,不加的话 sed 默认只输出到 stdout,不会真的改文件内容</span></span><br></pre></td></tr></table></figure><p><code>grep -l | xargs</code> 这个组合的价值在于分两步走:第一步只是”侦察”,看看改动会影响哪些文件,确认范围没问题了再执行第二步的实际操作,比直接一条命令梭哈更安全。</p><h2 id="grep-常用参数速查表"><a href="#grep-常用参数速查表" class="headerlink" title="grep 常用参数速查表"></a>grep 常用参数速查表</h2><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>-r</code>, <code>--recursive</code></td><td>递归搜索目录,只跟随命令行直接给出的符号链接</td></tr><tr><td><code>-R</code>, <code>--dereference-recursive</code></td><td>递归搜索目录,跟随所有遇到的符号链接</td></tr><tr><td><code>-i</code>, <code>--ignore-case</code></td><td>忽略大小写</td></tr><tr><td><code>-v</code>, <code>--invert-match</code></td><td>反向匹配,输出不匹配的行</td></tr><tr><td><code>-l</code>, <code>--files-with-matches</code></td><td>只列出有匹配的文件名</td></tr><tr><td><code>-L</code>, <code>--files-without-match</code></td><td>只列出没有匹配的文件名</td></tr><tr><td><code>-c</code>, <code>--count</code></td><td>只输出匹配的行数</td></tr><tr><td><code>-o</code>, <code>--only-matching</code></td><td>只输出匹配到的部分,不是整行</td></tr><tr><td><code>-n</code>, <code>--line-number</code></td><td>显示匹配行的行号</td></tr><tr><td><code>-w</code>, <code>--word-regexp</code></td><td>整词匹配</td></tr><tr><td><code>-x</code>, <code>--line-regexp</code></td><td>整行匹配</td></tr><tr><td><code>-A NUM</code>, <code>--after-context=NUM</code></td><td>额外输出匹配行之后 NUM 行</td></tr><tr><td><code>-B NUM</code>, <code>--before-context=NUM</code></td><td>额外输出匹配行之前 NUM 行</td></tr><tr><td><code>-C NUM</code>, <code>--context=NUM</code></td><td>等价于 <code>-A NUM -B NUM</code></td></tr><tr><td><code>-E</code>, <code>--extended-regexp</code></td><td>启用扩展正则(等价于 <code>egrep</code>)</td></tr><tr><td><code>-P</code>, <code>--perl-regexp</code></td><td>启用 Perl 兼容正则</td></tr><tr><td><code>--include=GLOB</code></td><td>只在匹配 GLOB 的文件里搜(配合 <code>-r</code>/<code>-R</code>)</td></tr><tr><td><code>--exclude=GLOB</code></td><td>排除匹配 GLOB 的文件(配合 <code>-r</code>/<code>-R</code>)</td></tr><tr><td><code>--exclude-dir=GLOB</code></td><td>排除匹配 GLOB 的目录(配合 <code>-r</code>/<code>-R</code>)</td></tr></tbody></table><h2 id="grep-之外"><a href="#grep-之外" class="headerlink" title="grep 之外"></a>grep 之外</h2><p>以上这些参数,ripgrep 全部支持而且默认行为更省心(自动递归、自动跳过 <code>.gitignore</code>),装得上就优先用 <a href="/%E7%BB%88%E7%AB%AF%E6%90%9C%E7%B4%A2%E4%B8%89%E4%BB%B6%E5%A5%97-fzf-ripgrep-fd">ripgrep</a>。Linux 命令系列下一篇计划写 <code>find</code>——参数量级比 grep 还大,文件查找的条件组合、<code>-exec</code> 的两种写法够写满一整篇。</p>]]>
</content>
<id>https://blog.codewater.top/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98/</id>
<link href="https://blog.codewater.top/grep%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8E%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8E%A7%E5%88%B6%E5%AE%9E%E6%88%98/"/>
<published>2026-07-11T02:00:00.000Z</published>
<summary>grep 的 BRE/-E/-P 三种正则模式区别在哪、-A/-B/-C 上下文控制怎么用、配合 xargs 批量处理匹配文件,一篇讲清楚原生 grep。</summary>
<title>grep:正则匹配、上下文控制与 -E/-P 实战</title>
<updated>2026-07-11T02:00:00.000Z</updated>
</entry>
</feed>