sed 's/old/new/' file每行只替换第一次出现的 old 为 new。sed 默认行为:逐行、只换首个。
old old old foo old bar old
new old old foo new bar old
sed 's/cat/dog/' pets.txt
echo 'hello world' | sed 's/world/sed/'
awk + sed 命令速查,80+ 条文本处理一行命令,真实例子和常见坑。
sed 's/old/new/' file每行只替换第一次出现的 old 为 new。sed 默认行为:逐行、只换首个。
old old old foo old bar old
new old old foo new bar old
sed 's/cat/dog/' pets.txt
echo 'hello world' | sed 's/world/sed/'
sed 's/old/new/g' file全局替换,每行的所有出现都换,不止首个。最常用的 g flag。
old old old
new new new
⚠ 常见坑: 忘 g 是 sed 最常见的踩坑。在 CSV 行上跑 `sed s/,/|/` 只换第一个逗号,后面全留着。
sed 's/ /_/g' file.txt
sed -i 's/http:/https:/g' index.html
sed 's/old/new/2' file只替换每行的第 N 次匹配(这里第 2 次)。数字 flag。
a a a a
a new a a
sed 's/,/|/3' csv-row.txt # change only the 3rd comma
sed 's/old/new/3g' fileN 和 g 组合:从第 N 次匹配起一直替换到行末。
x x x x x
x x new new new
sed 's/ /_/2g' file # keep first space, underscore the rest
sed -n '5p' file只打第 5 行。-n 关掉默认打印,p 打地址匹配。
a b c d e f
e
sed -n '5p' /etc/passwd
sed -n '100,200p' big.log # range
sed -n '10,20p' file打第 10 到第 20 行(含两端)。比 head + tail 干脆。
(file with 30 lines)
(lines 10 through 20)
sed -n '10,20p' big.log
sed -n '/START/,/END/p' file # regex range
sed '5d' file删第 5 行,其他原样打出来。
a b c d e
a b c d
sed '1d' file.csv # strip header row
sed '$d' file # drop last line
sed '/^$/d' file删空行(^$ 之间无字符)。
a b c
a b c
⚠ 常见坑: 只命中真的空行。带空格/制表符的"看着空"的行要用 `/^[[:space:]]*$/d`。
sed '/^$/d' notes.md
sed '/^[[:space:]]*$/d' file # also blanks
sed '/pattern/d' file删每一行匹配正则的行。
ok 1 err 2 ok 3 err 4
ok 1 ok 3
sed '/^#/d' config # drop comment lines
sed '/DEBUG/d' app.log
sed '5i\
NEW LINE' file在第 5 行前插一行新内容。i\\ 后面的文字就是要插的行。
a b c d e f
a b c d NEW LINE e f
sed '1i\ #!/bin/bash' script.sh # add shebang at top
sed '5a\
appended text' file在第 5 行后追加一行新内容。
1 2 3 4 5 6
1 2 3 4 5 appended text 6
sed '$a\ EOF marker' file # append at end
sed '5c\
replacement' file把匹配的整行整个替换成新文本。
a b old line d
a b replacement d
sed '/^TODO/c\ DONE' tasks.md
sed 'y/abc/xyz/' file字符转换:a→x、b→y、c→z。像 sed 内置的 tr。
cab cab
zxy zxy
sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' file # upper-case
sed -E 's/[0-9]+/N/g' file用 -E 开扩展正则:把任意一串数字换成 N。-E 后 +、?、| 不用加反斜杠。
order 42 then 1337
order N then N
⚠ 常见坑: GNU sed 接受 -r 作为 -E 的别名,BSD 只认 -E。可移植脚本一律写 -E。
sed -E 's/(\w+)@(\w+)/REDACTED/g' file
sed -n '1,5p' file打前 5 行,相当于 head -5。-n 关默认打印,范围地址挑第 1 到第 5 行。
a b c d e f g
a b c d e
sed -n '1,5p' file
sed '5q' file # quit after line 5, even faster
sed '5q' file打前 5 行后退出。比范围地址快,大文件读到第 5 行就停,不会扫到底。
(file with 1M lines)
(first 5 lines only)
sed '10q' big.log
sed '/READY/q' boot.log # quit at first READY
sed -n '2~3p' fileGNU 步进地址:打第 2 行,之后每隔 3 行打一次(2、5、8 …)。`first~step` 是 GNU 扩展。
l1 l2 l3 l4 l5 l6 l7 l8
l2 l5 l8
⚠ 常见坑: `~` 步进地址只 GNU 有。BSD/macOS sed 会报错,要可移植就用 awk `NR%3==2`。
sed -n '1~2p' file # odd lines
sed -n '0~2p' file # even lines (GNU)
sed '2,4d' file删一段行(第 2 到第 4),其余原样打出。
a b c d e
a e
sed '1,10d' file # drop first 10 lines
sed '2,$d' file # keep only line 1
sed -n 'p' file每行打一次。-n 关了默认打印,p 再补回来,等于 cat。
a b
a b
⚠ 常见坑: 去掉 -n 后 `sed p` 每行打两遍:默认打印加显式 p。很常见的手滑。
sed -n 'p' file # = cat
sed 'p' file # every line doubled
sed 's/old/new/I' file用 I flag 做大小写不敏感替换(GNU)。Old、OLD、old 都命中。
Old OLD old
new new new
⚠ 常见坑: I flag 是 GNU sed 的。BSD/macOS 不支持,退而用字符类 `s/[Oo]ld/new/g`。
sed 's/error/ERR/Ig' app.log
sed 's/.*/[&]/' file把每整行用方括号包起来。替换里的 & 代表"整段匹配到的文本"。
hello world
[hello] [world]
sed 's/[0-9]*/<&>/' file # bracket the leading number
sed 's/\(.*\)/\1\1/' file用反向引用把每行内容复制一遍。`\(.*\)` 捕获整行,`\1\1` 打两遍。
ab
abab
sed -E 's/(.*)/\1 \1/' file # space between copies, ERE
sed -n '/foo/,+2p' file打匹配 /foo/ 的行加后面 2 行。`+N` 相对范围是 GNU 扩展。
a foo b c d
foo b c
⚠ 常见坑: `addr,+N` 只 GNU 有。BSD 上用 `sed -n "/foo/{N;N;p;}"` 抓固定条数的后续行。
grep -A2 foo file # the grep equivalent
sed '/foo/!d' file删掉不匹配 /foo/ 的行,只留匹配行。`!` 对地址取反,不匹配的被删。
foo 1 bar 2 foo 3
foo 1 foo 3
sed '/^#/!d' config # keep only comment lines
sed 's/^[ \t]*//' file去掉每行开头的空白(左 trim)。和行尾去空白配套用。
hi bye
hi bye
sed -E 's/^[[:space:]]+//' file # POSIX class, portable
sed -n '/foo/I p' file大小写不敏感地打匹配 /foo/ 的行(GNU 的 I 地址修饰符)。
Foo bar FOO
Foo FOO
sed -n '/error/Ip' app.log
sed -i 's/old/new/g' file原地修改文件,直接覆盖原文件。GNU sed 写法。
⚠ 常见坑: macOS / BSD 上这条会挂。BSD sed 要扩展名参数:`sed -i "" "s/a/b/" file`(空串)或 `sed -i.bak`。最稳:`sed -i.bak`,两边都跑得起来。
sed -i 's/foo/bar/g' file.txt
sed -i.bak 's/foo/bar/g' file.txt # portable
sed -i '' 's/old/new/g' fileBSD / macOS 的原地修改。那个空字符串 `""` 是备份扩展名,在 macOS 上必须有。
hello old world
(file rewritten to: hello new world)
sed -i '' 's/http:/https:/g' index.html
sed -n '/start/,/end/p' file正则地址范围,从首次 /start/ 到下次 /end/ 之间的行全打出来。
a start b c end d
start b c end
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' chain.pem
sed -n '/pattern/{N;p;}' file行匹配 /pattern/ 时,用 N 把下一行也拉进 pattern space,再 p 把两行一起打。
sed -n '/ERROR/{N;p;}' app.log # show ERROR plus the next linesed 'N;s/\n/ /' file把每两行拼成一行,空格连接。N 把两行读进 pattern space,把里面那个换行换成空格。
name Lei role dev
name Lei role dev
sed 'N;s/\n/ /' two-line-records.txt
sed '/start/,/end/{//!d}' file删除块的"主体",保留首尾标记。`//!d` 表示"不匹配最近正则的行都删"。
sed '/^BEGIN/,/^END/{//!d}' filesed '1!G;h;$!d' file经典 sed 一行:用 hold space(h、G)把文件按行反转。
1 2 3
3 2 1
sed '1!G;h;$!d' file # like `tac`
sed -e 's/a/b/' -e 's/c/d/' file一次跑多条替换,顺序敏感。-e 可以叠任意条。
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file
sed -f script.sed file从文件里读 sed 命令(每行一条)。复杂处理放文件好维护、好 git。
sed -f rewrites.sed input.txt > output.txt
sed 'H;1h;$!d;x;s/\n/, /g' file把整个文件折成一行用逗号连接(hold space 累加器写法)。
a b c
a, b, c
sed 'H;1h;$!d;x;s/\n/, /g' names.txt
sed -E 's|http://([^/]+)/|https://\1/|g' file把 | 当分隔符,URL 里的斜杠不用转义。\1 引用第一组捕获(主机名)。
http://example.com/page
https://example.com/page
⚠ 常见坑: sed 把 s 后面跟的第一个字符当分隔符。挑数据里没有的字符:|、#、!、@ 都常用。
sed 's|/usr/local|/opt|g' paths.conf
sed -i.bak 's/old/new/g' *.md原地改当前目录所有 .md,留 .md.bak 备份。GNU 和 BSD 都能跑。
⚠ 常见坑: glob 展到很多文件时备份也跟着爆。改完确认无误后 `rm *.md.bak` 清干净。
sed -i.bak 's/2025/2026/g' docs/*.md && rm docs/*.md.bak
sed ':a;N;$!ba;s/\n/ /g' file把整个文件读进 pattern space(标签 :a + N + 分支 ba 循环到最后一行),再把所有换行换成空格,折成一行。
a b c
a b c
sed ':a;N;$!ba;s/\n/,/g' file # comma-join the whole file
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' file把 ISO 日期(YYYY-MM-DD)重排成 DD/MM/YYYY,靠三个捕获组加反向引用。
2026-05-30
30/05/2026
sed -E 's/(....)-(..)-(..)/\2\/\3\/\1/' dates.txt # to MM/DD/YYYY
sed -n 'l' file以无歧义形式打印:tab 显示成 \t,行尾显示成 $,不可打印字节显示成八进制转义。查隐藏字符神器。
(line with a tab)
col1\tcol2$
sed -n 'l' suspicious.txt # reveal tabs and CR
cat -A file # similar idea
sed 's/\r$//' file去掉行尾回车符,把 Windows 的 CRLF 换行转成 Unix 的 LF。
line\r
line
⚠ 常见坑: BSD sed 不认 `\r`。macOS 上用字面回车:`sed $'s/\r$//'`,或直接 `tr -d '\r'`。
sed -i 's/\r$//' file.txt # dos2unix, GNU
sed 's/$/\r/' file # unix2dos
sed -n '/foo/{=;p;}' file每个匹配 /foo/ 的行,先打行号(=)再打内容(p),相当于带行号的 grep,一次命中两行输出。
a foo b foo
2 foo 4 foo
sed -n '/ERROR/{=;p;}' app.logsed '/^$/{N;/^\n$/D}' file把连续空行压成一个空行。遇空行读下一行,两行都空就删头一行,D 循环处理。
a b
a b
cat -s file # the simpler equivalent on GNU coreutils
sed 'n;d' file隔行删,保留奇数行。n 打当前行并读下一行,d 把下一行删掉。
1 2 3 4 5
1 3 5
sed 'n;d' file # keep odd lines
sed '1d;n;d' file # keep even lines
sed -E 's/([a-z])([A-Z])/\1_\2/g' file在小写字母和大写字母之间插下划线,把 camelCase 拆向 snake_case(大小写再单独处理)。
fooBarBaz
foo_Bar_Baz
sed -E 's/([a-z])([A-Z])/\1_\2/g' file | sed 's/.*/\L&/' # full snake_case, GNU \L
sed 's/\(.\)/\1\n/g' file把每个字符拆到单独一行,在每个字符后插换行(GNU sed 替换里认 \n)。
abc
a b c
⚠ 常见坑: 替换里的字面 `\n` 是 GNU 扩展。BSD 上要在脚本里用反斜杠加真换行插入。
sed 's/\(.\)/\1\n/g' file # one char per line, GNU
sed '0~2d' file删每个偶数行(留奇数行),用 GNU 的 `first~step` 地址,从 0 起步长 2。
1 2 3 4
1 3
⚠ 常见坑: 只 GNU 有的地址。BSD/macOS 改用 awk `NR%2`。
sed '1~2d' file # delete odd lines, keep even
sed -E 's/(.)\1+/\1/g' file把连续重复的同一字符压成一个。`(.)` 捕一个字符,`\1+` 匹配它的重复,只留一个。
aaabbbcccd
abcd
echo 'heelllooo' | sed -E 's/(.)\1+/\1/g' # helo
sed -e '1{h;d;}' -e '2{H;x;}' file用 hold space 交换前两行:第 1 行先存起来(h;d),第 2 行追加(H)再交换(x)。
one two three
two one three
sed -e '1{h;d;}' -e '2{H;x;}' filesed '$d' file删最后一行。$ 是地址"最后一行"。
a b c d
a b c
sed '$d' file.log
sed -n '$=' file数文件行数。= 打当前行号,$ 限制到最后一行。
a b c d
4
sed -n '$=' file
sed = file | sed 'N;s/\n/\t/'给文件加行号(穷人版 nl)。第一个 sed 插数字行,第二个把两行合一。
a b c
1 a 2 b 3 c
sed = file | sed 'N;s/\n/\t/'
sed -n '/pattern/p' file相当于 grep pattern file,只打匹配行。pipeline 里已经有 sed 时这条好用。
sed -n '/ERROR/p' app.log
sed -n '/^From: /,/^$/p' mbox打每封邮件的 header 块,从 From: 行到下一空行。
sed -n '/^From: /,/^$/p' inbox.mbox
sed -n '/foo/=' file打匹配行的行号(不打内容)。
a foo b foo bar c
2 4
sed -n '/TODO/=' notes.md
sed 's/^/ /' file每行前补 4 个空格(贴 Markdown 代码块时常用)。
a b
a
bsed 's/^/ /' snippet.txt
sed 's/[ \t]*$//' file去掉每行末尾的空白,干掉那些看不见的行尾空格。
sed -i 's/[ \t]*$//' src/**/*.py
sed -n '1!G;h;$p' file把文件按行倒序,和 tac 一样,在没装 tac 的 BSD 上能用。
sed -n '1!G;h;$p' file
sed 'G' file给文件加双倍行距,每行后加一行空。
a b
a b
sed 'G' notes.txt
sed -n '/^$/!p' file只打非空行,跳空行的短写法。
sed -n '/^$/!p' file
sed -e :a -e '$!N;s/\n/ /;ta' file把全文所有行折成一行用空格连(用标签 :a + 分支 ta 做循环)。
sed -e :a -e '$!N;s/\n/ /;ta' lines.txt
sed -n '2p' file只打第 2 行。换数字就能取任意单行。
a b c
b
sed -n '1p' file # first line
sed -n '3p;7p' file # lines 3 and 7
sed -n '$p' file打文件最后一行(sed 版 tail -1)。$ 是"最后一行"地址。
a b c
c
sed -n '$p' file
sed 's/^/> /' file每行前加 "> ",像邮件引用一样把文本引起来。
hi there
> hi > there
sed 's/^/> /' quote.txt
sed 's/$/;/' file每行末尾加分号。这里的 $ 是行尾锚点。
a b
a; b;
sed 's/$/,/' col.txt # turn a column into CSV-ish
sed 's/ */ /g' file把多个空格压成一个,快速规整空白。
a b c
a b c
sed -E 's/ +/ /g' file # same with ERE
sed -n '1p;$p' file一遍打出文件的首行和末行。
a b c d
a d
sed -n '1p;$p' big.log
sed 's/\t/,/g' file把制表符换成逗号,字段不含逗号时的快速 TSV 转 CSV。
a b c
a,b,c
⚠ 常见坑: BSD sed 不展开 `\t`,macOS 上敲真 Tab 或用 `sed $'s/\t/,/g'`。
sed $'s/\t/,/g' in.tsv > out.csv # portable
sed -n '/^$/=' file打所有空行的行号,方便找段落分隔位置。
a b c
2 4 5
sed -n '/^$/=' essay.txt
sed '/foo/r insert.txt' file在每个匹配 /foo/ 的行之后,读入并插入 insert.txt 整个内容。`r` 命令追加一个文件。
sed '/<!-- INCLUDE -->/r snippet.html' page.html
sed -n '/foo/,/bar/{/foo/d;/bar/d;p;}' file打 /foo/ 和 /bar/ 之间的行,去掉这两条边界行本身。
x foo A B bar y
A B
sed -n '/BEGIN/,/END/{/BEGIN/d;/END/d;p;}' block.txtsed '$!N;/^\(.*\)\n\1$/!P;D' file删相邻的重复行(sed 版 uniq)。用反向引用把每行和下一行比较。
a a b b b c
a b c
sed '$!N;/^\(.*\)\n\1$/!P;D' file # = uniq for adjacent dups
sed 's/\(.*\):.*/\1/' file保留每行最后一个冒号之前的内容(贪婪 `.*` 吃到最后一个冒号)。
a:b:c
a:b
sed 's/:[^:]*$//' file # same: drop after last colon
GNU sed -i vs BSD sed -iGNU `sed -i "…"` 直接跑;BSD/macOS `sed -i "…"` 必须在 -i 紧后给扩展名参数(不要备份就写 `""`)。
⚠ 常见坑: 不可移植的 sed -i 是跨平台脚本最常翻的车。统一写 sed -i.bak,GNU 和 BSD 都吃,跑完 rm *.bak 收尾。
sed -i.bak 's/old/new/g' file # portable
case $(sed --version 2>&1) in *GNU*) SED='sed -i' ;; *) SED="sed -i ''" ;; esac
Regex dialect: BRE vs EREsed 默认 BRE(基础正则):+、?、|、()、{} 都是字面字符,要加反斜杠才变元字符。-E 切 ERE 才是平时熟的那个。
⚠ 常见坑: `sed s/foo+/bar/` 不会匹配 foooo,它在找字面串 `foo+`。要么 `\+`(GNU),要么 `sed -E s/foo+/bar/`。
sed -E 's/(foo)+/bar/' file # works on both GNU and BSD
Slash collision in URLs模式或替换里有 / 时换分隔符。sed 让你用 s 后面紧跟的任意字符当分隔符。
⚠ 常见坑: `sed s/http:\/\/a\.com/b.com/g` 没法读。`sed s|http://a.com|b.com|g` 一模一样的效果,看着舒服。
sed 's|http://|https://|g' file
sed 's#/var/log#/opt/log#g' config
Greedy match swallows too muchsed 正则是贪婪的,BRE/ERE 里没有 ? 懒惰修饰。`.*` 拿的是最长串,不是最短。
⚠ 常见坑: `sed "s/<.*>//" "<a><b>"` 把整段 <a><b> 都删了,不是只删 <a>。用 [^>]* 才对:`s/<[^>]*>//`。
sed 's/<[^>]*>//g' page.html # strip tags, no greediness trap
Backreferences need escaped parens in BREBRE 默认下捕获组写 `\(...\)`,反向引用 `\1`。ERE(-E)下直接 `(…)`。
sed 's/\(foo\)\(bar\)/\2\1/' file # BRE
sed -E 's/(foo)(bar)/\2\1/' file # ERE, clearer
Anchors inside a multiline pattern spaceN 把第二行拉进 pattern space 后,`^` 和 `$` 仍只匹配最开头和最末尾,不是每个内嵌行。要定位内部行界得用 `\n`。
⚠ 常见坑: 很多人以为 N 之后 `s/^/> /g` 给两行都加前缀。它只给第一行加。要够到第二行得匹配 `\n`:`s/\n/\n> /`。
sed 'N;s/^/> /;s/\n/\n> /' file
Last line has no trailing newline输入文件结尾没有换行时,sed 原样保留,输出也缺末尾换行,可能搞坏后面期待换行的工具。
⚠ 常见坑: 结尾缺换行的文件喂给循环时,最后一条记录可能被悄悄丢掉。用 `sed -e '$a\' file`(GNU)或 `printf '\n' >> file` 规整。
[ -n "$(tail -c1 file)" ] && echo >> file # add newline if missing
Single-quote escaping in sed scripts单引号里 shell 原样传 sed 脚本,但里面不能直接放单引号。得收尾引号、转义、再开:`'\''`。
⚠ 常见坑: 要替换一个单引号得写一串四字符:`sed 's/x/'\''/g'`。或者整段改双引号,转义 `$` 和反斜杠。
sed "s/x/'/g" file # double-quote the whole script
sed is line-oriented, not whole-filesed 默认逐行处理,正则跨不了换行,除非先用 N 或循环把更多行拉进 pattern space。
⚠ 常见坑: `sed "s/foo\nbar/X/"` 默认永远不匹配,因为 foo 和 bar 在不同行。先把文件读进来:`sed ":a;N;$!ba;s/foo\nbar/X/"`。
sed ':a;N;$!ba;s/foo\nbar/X/g' file # cross-line match
Special chars in the REPLACEMENT side替换部分里 `&`、`\` 和分隔符都是特殊字符。要输出字面 & 得写 `\&`,否则 & 会把整段匹配重新塞回去。
⚠ 常见坑: `sed "s/price/price & tax/"` 会变成 "price price tax",裸 & 展开了。转义它:`s/price/price \& tax/`。
sed 's/x/A \& B/' file # literal ampersand
awk '{ print $1 }' file每行打第一个字段。默认按空白分字段。
alice 30 dev bob 28 ops
alice bob
awk '{ print $1 }' /etc/passwd # actually need -F:ls -l | awk '{ print $9 }'awk '{ print $NF }' file每行打最后一个字段。NF 是字段总数。
a b c 1 2 3 4
c 4
ls -l | awk '{ print $NF }' # filenamesawk 'NR==5' file只打第 5 行。NR 是记录计数(默认 RS 时等于行号)。
a b c d e f
e
awk 'NR==5' file
awk 'NR>=10 && NR<=20' file # range
awk 'END { print NR }' file数行数(等于 wc -l),在 END 块里打 NR 的最终值。
a b c
3
awk 'END { print NR }' fileawk -F, '{ print $2 }' file.csv把逗号当字段分隔符(CSV),打第二列。-F 在启动时设 FS。
alice,30,dev bob,28,ops
30 28
⚠ 常见坑: -F 默认按字面字符切;-F"\t" 要加引号。要正则分隔符用 -F"[,;]"(awk 内置 ERE)。
awk -F, '{ print $1, $3 }' data.csvawk -F: '{ print $1 }' /etc/passwdawk 'BEGIN { print "hi" }'在读输入之前跑一段。常用来打表头、设常量,或者当无输入的计算器。
hi
awk 'BEGIN { print 2+2 }' # 4awk 'BEGIN { for(i=1;i<=5;i++) print i }'awk '/pattern/ { print }' file打匹配正则的行(基本等同 grep)。默认动作是 print,所以 `awk /pattern/` 也行。
awk '/ERROR/' app.log
awk '/^200/ { print $7 }' access.logawk '/start/,/end/' file范围模式,从首次 /start/ 到下次 /end/ 之间的行(含)全打。
awk '/BEGIN/,/END/' file
awk '{ printf "%-10s %5d\n", $1, $2 }' file用 printf 格式化:第一列左对齐宽 10,第二列右对齐 5 位整数。
alice 30 bob 285
alice 30 bob 285
awk '{ printf "%s=%d\n", $1, $2 }' kv.txtawk 'BEGIN { FS=","; OFS="|" } { $1=$1; print }' file把 CSV 改成 | 分隔。诀窍:OFS 只在你"重新赋值字段"时才生效,所以写 $1=$1 触发一下。
a,b,c 1,2,3
a|b|c 1|2|3
⚠ 常见坑: 不写 $1=$1 输出就是原行没变,这个坑第一次踩能耗 30 分钟。
awk 'BEGIN{FS=",";OFS="\t"} {$1=$1;print}' a.csv > a.tsvawk -v name=lei '{ print name, $0 }' file用 -v 把 shell 变量传进 awk,比在 awk 程序里硬塞 shell 引号干净。
awk -v user="$USER" '{ print user, $1 }' fileawk -v d=$(date +%F) 'BEGIN{ print d }'awk 'NF' file打有字段的行,即跳空行。NF > 0 时为真。
a b c
a b c
awk 'NF' messy.txt > clean.txt
awk '{ print NR, $0 }' file给每行前面加行号,等效 cat -n 的简写。
a b c
1 a 2 b 3 c
awk '{ print NR, $0 }' notes.mdawk '{ print $2, $1 }' file调换前两列。print 里的逗号会在两者间插 OFS(默认空格)。
alice 30 bob 28
30 alice 28 bob
awk '{print $2, $1}' names.txtawk 'NF > 3' file打字段数超过 3 的行,用来挑出比预期还宽的畸形行。
a b c a b c d e
a b c d e
awk -F, 'NF != 5' data.csv # rows that are not exactly 5 columns
awk '$2 == "dev"' file打第二字段等于字符串 "dev" 的行。字符串比较要把值放双引号里。
alice dev bob ops cara dev
alice dev cara dev
awk '$3 == "200"' access.log # exact status match
awk '$1 ~ /^a/' file打第一字段匹配正则的行(这里:以 a 开头)。`~` 是匹配运算符,`!~` 是不匹配。
alice 1 bob 2 amy 3
alice 1 amy 3
awk '$0 !~ /DEBUG/' app.log # drop DEBUG lines
awk 'BEGIN { print 2**10 }'awk 当计算器:`**`(以及 `^`)是乘方,打出 1024。
1024
awk 'BEGIN { print sqrt(2), 7%3, int(3.9) }' # 1.41421 1 3awk '{ print FNR, FILENAME, $0 }' a.txt b.txt打每行时带上该文件内的行号(FNR)和来源文件名(FILENAME)。多文件输入时好用。
awk '{print FILENAME":"FNR": "$0}' *.log # grep -Hn styleawk 'NR >= 10 && NR <= 20' file用 NR 数值条件打第 10 到第 20 行,等同 `sed -n "10,20p"`。
(30-line file)
(lines 10 through 20)
awk 'NR>=10 && NR<=20' big.log
awk 'END { print $0 }' file打文件最后一行。END 里 `$0` 仍保存着 awk 读到的最后一条记录。
a b c
c
awk 'END{print $0}' file # like tail -1awk -F'\t' '{ print $1 }' file.tsv按制表符分列并打第 1 列,处理 TSV 的正确分隔符。
a b c
a
awk -F'\t' '{print $2}' data.tsvawk '$3 > 100' file打第三字段数值大于 100 的行。两边都像数字时 awk 按数值比较。
a 1 50 b 2 150 c 3 99
b 2 150
awk '$NF > 1000' sizes.txt
awk '{ $2=""; print }' file把第 2 字段清空。给字段赋值会用 OFS 重建 $0,原字段位置留下一个双分隔符。
a b c
a c
⚠ 常见坑: 这会留下一个双空格,不是真删一列。要真正去掉一列得把后面的字段前移或手动重建记录。
awk '{$2=$3; $3=""; print}' file # crude column dropawk 'BEGIN { print ENVIRON["HOME"] }'通过内置 ENVIRON 数组读环境变量,不用 -v 中转。
awk 'BEGIN { print ENVIRON["USER"], ENVIRON["PWD"] }'awk '{ c[$1]++ } END { for (k in c) print k, c[k] }' file统计第一列出现次数,典型 group by。关联数组以值为 key。
apple banana apple cherry banana apple
apple 3 banana 2 cherry 1
awk '{ c[$7]++ } END { for (k in c) print k, c[k] }' access.log # URL frequencyawk 'NR==FNR { a[$1]=$2; next } { print $0, a[$1] }' map.txt data.txt双文件 join:先把 map.txt 读进数组 a,再给 data.txt 每行带上对应值。FNR==NR 只在读第一个文件时成立。
map.txt: alice dev\nbob ops\ndata.txt: alice 30\nbob 28
alice 30 dev bob 28 ops
awk 'NR==FNR{a[$1]=$2;next} {print $0, a[$1]}' users.tsv logins.tsvawk '!seen[$0]++' file保序去重(不像 sort -u 会乱序)。`seen[$0]++` 首次返回 0(假,取反为真,打)。
a b a c b d
a b c d
awk '!seen[$0]++' urls.txt
awk '{ s+=$3 } END { print s/NR }' file算第三列均值,累加除以 NR。只算非空行用 `NF { s+=$3; n++ } END { print s/n }`。
a 1 10 b 2 20 c 3 30
20
awk '{ s+=$3 } END { print s/NR }' scores.txtawk 'BEGIN { max=-1 } $2>max { max=$2; line=$0 } END { print line }' file找第二列最大值所在行。流式 max,单遍 O(1) 内存。
a 10 b 50 c 30
b 50
awk 'BEGIN{m=-1} $2>m{m=$2;line=$0} END{print line}' scores.txtawk '{ n=split($0, parts, "/"); print parts[n] }' filesplit() 把字符串切到数组里。这里取每个 / 分隔的路径的最后一段(basename)。
/a/b/c.txt /x/y
c.txt y
awk '{ n=split($0,p,"/"); print p[n] }' paths.txtawk '{ gsub(/[0-9]+/, "N") } 1' filegsub() = 全局替换。把所有数字串换成 N。末尾的 1 触发默认打印。
order 42 then 1337
order N then N
awk '{ gsub(/secret-[a-z0-9]+/, "[REDACTED]") } 1' fileawk '{ print substr($0, 1, 80) }' file把每行截到 80 字符。substr(字符串, 起始, 长度),起始从 1 开始。
awk '{ print substr($0, 1, 80) }' wide.logawk '{ print toupper($0) }' file整行转大写。配套有 tolower()。
hello world
HELLO WORLD
awk '{ print toupper($1), tolower($2) }' data.txtawk -F'\t' 'BEGIN { OFS="," } { $1=$1; print }' in.tsv一行把 TSV 转成 CSV。Tab 作输入 FS,逗号作输出 OFS,$1=$1 触发重写。
awk -F'\t' 'BEGIN{OFS=","} {$1=$1;print}' in.tsv > out.csvawk 'length > 80' file只打长度超过 80 的行。length 不带参数返回 $0 的长度。
awk 'length > 120' src/**/*.py # find long lines
awk 'BEGIN { RS=""; FS="\n" } { print $1 }' file段落模式:RS 设空串后,awk 按空行分隔的块当一条记录。$1 是每段第一行。
awk 'BEGIN { RS=""; FS="\n" } { print $1 }' essay.txtawk -F: '$3 >= 1000 { print $1 }' /etc/passwd只列真人用户,UID 列 ≥ 1000。冒号分隔,条件写在 { 动作 } 前。
awk -F: '$3 >= 1000 && $7 != "/usr/sbin/nologin" { print $1 }' /etc/passwdawk '{ sum[$1] += $2 } END { for (k in sum) print k, sum[k] }' file分组求和:按第一字段分组,对第二字段求和。聚合数据时关联数组的主力写法。
a 10 b 5 a 20 b 7
a 30 b 12
awk '{rev[$1]+=$3} END{for(k in rev)print k,rev[k]}' sales.txtawk 'BEGIN{min=1e9;max=-1e9} {if($1<min)min=$1; if($1>max)max=$1; s+=$1; n++} END{print "min",min,"max",max,"avg",s/n}' file一遍算出第一列的最小值、最大值、平均值。min 设很大、max 设很小,逐个折进去。
5 2 9 4
min 2 max 9 avg 5
awk '{s+=$1;n++} END{print s/n}' nums.txt # just the meanawk 'seen[$2]++ == 0' file每个第二列不同值只留首次出现的那行(按 key 列保序去重)。
a x b x c y d y
a x c y
awk '!seen[$1]++' file # dedupe by first column
awk 'END { print NR, NF }' file打总行数和最后一行的字段数,快速看一张表的形状。
a b c d e f
2 3
awk -F, 'END{print NR" rows, "NF" cols"}' data.csvawk 'p { print p } { p=$0 }' file靠缓存前一行,打除最后一行外的所有行。`p` 存上一条记录,最后一条永远没轮到打。
a b c d
a b c
awk 'NR>1{print prev} {prev=$0}' file # same idea, named varawk '{ a[NR]=$0 } END { for (i=NR;i>=1;i--) print a[i] }' file把每行按 NR 存进数组,再从后往前打,实现整文件反转。awk 版 tac。
1 2 3
3 2 1
awk '{a[NR]=$0} END{for(i=NR;i;i--)print a[i]}' fileawk 'BEGIN{FS=OFS=","} {$2=toupper($2)}1' file.csv原地改一列 CSV:把第 2 字段转大写并重打整行。FS=OFS 一起设保持逗号格式。
a,b,c d,e,f
a,B,c d,E,f
awk 'BEGIN{FS=OFS=","}{$3=$3*1.1}1' prices.csv # bump column 3 by 10%awk 'match($0, /[0-9]+/) { print substr($0, RSTART, RLENGTH) }' file抽出每行第一个数字。match() 设置 RSTART 和 RLENGTH,再用 substr() 切出来。
order 42 total item 7
42 7
awk 'match($0,/[A-Z]{3,}/){print substr($0,RSTART,RLENGTH)}' fileawk 'NF { $1=$1; print } !NF { print }' file把非空行的内部空白规整(用 $1=$1 重建压掉多余 FS),空行保持原样。
a b c x y
a b c x y
awk '{$1=$1}1' file # squeeze whitespace everywhereawk '{ for(i=NF;i>=1;i--) printf "%s%s", $i, (i>1?OFS:ORS) }' file把每行的字段顺序反转,从第 NF 个打到第 1 个,用 OFS 连接。
a b c
c b a
awk '{for(i=NF;i>=1;i--)printf "%s%s",$i,(i>1?" ":"\n")}' fileawk '/start/{f=1} f; /end/{f=0}' file用标志变量打标记之间的块,比范围地址灵活,你能控制标记本身是否打印。
x start A end y
start A end
awk '/BEGIN/{f=1;next} /END/{f=0} f' file # exclude both markersawk '{ print > ("part_" int((NR-1)/100) ".txt") }' file把文件按每 100 行切块,给每条记录算出文件名并重定向 print(awk 版 split)。
awk '{print > ("chunk_" int(NR/1000) ".txt")}' big.logawk '{ s += $1 } END { print s }' file第一列求和。awk 处理数字最经典的一条。
10 20 30
60
awk '{s+=$1} END {print s}' nums.txtawk -F, 'NR>1 { s += $3 } END { print s }' data.csv对 CSV 第三列求和,跳过表头那一行。
awk -F, 'NR>1 {s+=$3} END {print s}' sales.csvawk '{ print $3, $2, $1 }' file调列序:打第 3、第 2、第 1 列。
a b c 1 2 3
c b a 3 2 1
awk '{print $NF, $1}' file # last column, then firstawk '{ print $1, $3 }' file挑指定列(第 1 和第 3)。配 -F 就是支持正则的 cut。
awk -F, '{ print $1, $3 }' data.csvps aux | awk '$3 > 50 { print $2, $11 }'找 CPU 占用 >50% 的进程,打 PID 和命令。真实管道用法。
ps aux | awk '$3 > 50 {print $2, $11}'df -h | awk '$5+0 > 80 {print $0}' # disks >80% fullawk '{ print length, $0 }' file | sort -n按长度排行(从短到长)。awk 配 sort 的常见组合。
awk '{print length, $0}' words.txt | sort -n | headawk '{ c[$1]++ } END { for (k in c) print c[k], k }' file | sort -rn分组计数 + 按次数倒排,一行就出 top-N。
awk '{c[$1]++} END {for(k in c)print c[k],k}' access.log | sort -rn | headawk 'NR % 2 == 0' file打偶数行(2、4、6 …)。`% 2 == 1` 打奇数行。
a b c d
b d
awk 'NR%2==1' interleaved.txt # odd lines
awk '{ print } /pattern/ { c++ } END { print c " matches" }' file一边打文件,一边数 /pattern/ 命中,结尾报总数。两个动作走一遍。
awk '{print} /ERROR/{c++} END{print c, "errors"}' app.logawk 'BEGIN { srand(); for(i=0;i<5;i++) print int(rand()*100) }'在 [0, 100) 之间生成 5 个随机整数。BEGIN 里一定要 srand(),不然 awk 用固定种子。
awk 'BEGIN{srand();for(i=0;i<10;i++)print int(rand()*1000)}'awk -F: '{ print NR": "$1 }' /etc/passwd列用户名带行号,冒号分隔文件的快速 nl 替代。
awk -F: '{print NR": "$1}' /etc/passwdawk -F, 'END { print NF }' file.csv读最后一行的 NF 数 CSV 列数。快速看"这个 CSV 多宽"。
awk -F, 'END {print NF}' wide.csvawk 'END { print NR }' file数行数,像 wc -l,但没有某些 wc 会加的前导空格。
a b c d
4
awk 'END{print NR}' fileawk '{ w += NF } END { print w }' file把每行的 NF 加起来,数全文单词总数,awk 版 wc -w。
one two three
3
awk '{w+=NF} END{print w}' essay.txtawk '{ print $NF }' file只打每行最后一个字段,挑文件名、状态码或末尾值很方便。
GET /a 200 POST /b 404
200 404
awk '{print $(NF-1)}' file # second-to-last fieldawk 'NR==1 { print; exit }' file只打第一行就立刻 exit,高效的 head -1,读到第一行就停。
a b c
a
awk 'NR==1{print;exit}' huge.csv # grab the header fastawk -F, '{ print NF }' file.csv | sort -u列出 CSV 各行的不同列数,快速发现列数不齐的行。
awk -F, '{print NF}' data.csv | sort | uniq -cawk '$0 ~ /foo/ { c++ } END { print c+0 }' file数匹配 /foo/ 的行(grep -c)。`+0` 在没匹配时强制输出数字 0 而不是空串。
foo bar foo
2
awk '/ERROR/{c++} END{print c+0}' app.logawk '{ print rand(), $0 }' file | sort -n | cut -d' ' -f2-给每行贴随机数、排序、再去掉标记来打乱行序,可移植的 shuf 替代。
awk 'BEGIN{srand()}{print rand(),$0}' file | sort -n | cut -d' ' -f2-awk 'NR==FNR{n++;next} FNR==int(n*0.5)' file file读两遍文件打中位行:第一遍数行数,第二遍打中间那行。
awk 'NR==FNR{n++;next} FNR==int((n+1)/2)' file fileawk '{ gsub(/^ +| +$/, "") } 1' file一个 gsub 去掉每行首尾空格(`^ +` 和 ` +$` 取或),再默认打印。
hi bye
hi bye
awk '{gsub(/^[ \t]+|[ \t]+$/,"")}1' file # also tabsawk -F, '{ print $1 }' file | sort | uniq -c | sort -rn按频次排 CSV 第一列的高频值:抽列、计数、倒排。经典日志分析管道。
awk '{print $1}' access.log | sort | uniq -c | sort -rn | headawk 'BEGIN { for(i=1;i<=10;i++) print i }'不要输入文件,生成 1 到 10 的数字,awk 版 seq。
1 2 3 4 5 6 7 8 9 10
awk 'BEGIN{for(i=0;i<=100;i+=5)print i}' # 0,5,10,...,100awk '{ t = $1; $1 = $NF; $NF = t; print }' file用临时变量交换每行的首字段和末字段,再默认重建打印。
a b c 1 2 3
c b a 3 2 1
awk '{t=$1;$1=$NF;$NF=t;print}' fileFloating-point precision (IEEE 754)awk 数字是 C double。`awk "BEGIN{print 0.1+0.2}"` 打出 0.3 是被默认 OFMT 圆掉了,`printf "%.17f\n", 0.1+0.2` 才看见真相 0.30000000000000004。
⚠ 常见坑: 浮点别用 == 比较。求和后用容差比:`if (s > target - 0.001 && s < target + 0.001)`。算钱用整数(分),最后再除回元。
awk 'BEGIN { printf "%.17f\n", 0.1+0.2 }' # 0.30000000000000004awk 'BEGIN { OFMT="%.10g"; print 0.1+0.2 }'OFS does not auto-apply光设 OFS 没用,至少改一个字段才会生效。不动字段输出原样不变。
⚠ 常见坑: 标准修复:`$1=$1` 空操作触发 OFS 重写。`awk "BEGIN{OFS=\",\"}{$1=$1;print}"`。
awk 'BEGIN{OFS=","}{$1=$1;print}' fileNR vs FNR with multiple filesNR 跨文件累加,FNR 每个文件重置。`NR==FNR` 等价"还在读第一个文件",双文件 join 的经典写法。
awk 'NR==FNR{a[$1]=$2;next} {print $0, a[$1]}' map.txt data.txtNumeric strings sort lexically without +0awk 按上下文决定字段是字符串还是数字。要强制按数字比较(尤其排序场景),加个 0:`$3+0 > 100`。
⚠ 常见坑: `awk "$3 > 100" data.txt` 在第三列像数字时没问题,但行里有 "42MB" 这样的会悄悄走字符串比较,结果误导你。
df -h | awk '$5+0 > 80' # force numeric on "80%"
gawk vs awk vs mawk常见三种 awk:gawk(GNU)功能最全(真正则、--csv、网络),mawk 最快,macOS 自带 BSD awk 最弱。
⚠ 常见坑: gawk 上能跑的 `gsub(/regex/, "x", arr[i])`,BSD awk 不行;用 `length(array)` 的脚本需要 gawk。`awk --version` 确认手里是哪一只。
awk --version 2>&1 | head -1
brew install gawk # macOS users wanting gawk
Uninitialized variables are 0 and emptyawk 没初始化的变量既是数字 0 又是空串,按上下文决定。计数器很方便,但变量名打错也会被悄悄当成 0。
⚠ 常见坑: 把计数器名打成 `coutn[$1]++`,awk 照样新建一个数组,不报错,只给你错答案。除非 `gawk --lint`,否则没有"变量未定义"警告。
gawk --lint '{c[$1]++} END{for(k in c)print k,c[k]}' fileprint vs printf newline`print` 会补 ORS(默认换行);`printf` 什么都不补,得自己写 `\n`,否则输出会连成一团。
⚠ 常见坑: `awk "{printf \$1}"` 把每个第一字段无分隔地粘成一长行。补换行:`printf "%s\n", $1`。
awk '{printf "%s\n", $1}' file # remember the \n-F with a regex vs a literal单字符 -F 按字面;多字符 -F 值当正则。所以 `-F.` 是按任意字符切,不是按字面点号。
⚠ 常见坑: `awk -F. "{print \$1}"` 处理 `a.b.c` 打出空字段,因为 `.` 是正则"任意字符"。转义它:`-F"\\."` 或 `-F"[.]"`。
awk -F'[.]' '{print $1}' host.example.com # split on literal dotLeading-zero numbers and octal像 "010" 这样的字段值在算术里当十进制 10,但前导零和进制相关的字符串与数字转换会让你意外,尤其用 strtonum() 时。
⚠ 常见坑: gawk `strtonum("010")` 返回 8(八进制),而 `"010"+0` 返回 10。在同一份数据上混用结果不一致。选定一种转换方式并贯彻到底。
awk 'BEGIN{print "010"+0}' # 10gawk 'BEGIN{print strtonum("010")}' # 8Modifying $0 resplits the fields给 $0 赋值会用当前 FS 重新分字段,给任一字段赋值会用 OFS 重建 $0。顺序没留意时两者会互相覆盖。
⚠ 常见坑: 若写 `$0 = $0 "x"; print $3`,字段会从新的 $0 重新计算,之前对字段的修改可能丢失。整体改 $0 之前先把字段编辑做完。
awk '{$0=$0" extra"; print NF}' file # NF recomputed可搜索的 awk + sed 速查,80+ 条一行命令,覆盖你周二早上真正 会在 shell 里敲的文本处理任务。每个工具分四类。sed 基础: s/// 替换、全局 g、第 N 次匹配的数字 flag、地址范围、d 删除、 p 打印(配 -n)、i 插入、a 追加、c 替换、y 字符转换。sed 高级: hold space(h H g G x)、pattern space 技巧、分支跳转、多文件 改、GNU sed 与 BSD sed 的 -i 差异、macOS 上 -i 后那个空 '' 的坑、in-place 备份、BRE vs ERE 正则。awk 基础:NF NR $0 $1 $2、BEGIN 和 END 块、printf 格式串、FS / OFS / RS / ORS、 pattern { action } 流程、/regex/、范围模式、-F 字段分隔、-v。 awk 进阶:关联数组、split()、substr()、gsub()、length()、 toupper/tolower()、累加模式(求和/均值/max/min)、用数组去 重、FNR==NR 双文件 join、输出格式化、配合 sort / uniq / grep 接管道。日常一行命令:删空行、删第 N 行或末行、只打第 10 到 20 行、只在匹配行里替换、列求和、列求均值、统计去 重、交换两列、从 CSV 取第三列、保序去重。再加"常见坑"一节: GNU sed 和 BSD sed 的 -i 不一样,macOS 上要 sed -i '' 加个空 字符串;awk 浮点是 double,0.1+0.2 出来是 0.30000000000000004; 正则方言(BRE vs ERE)把 perl 来的人坑哭;sed URL 里斜杠撞, 要换分隔符(s|http://||);awk 的 OFS 不重新赋值字段就不 生效。每条都给中英文双语说明、真实输入输出、对应坑、至少 一条能直接拷贝的例子。搜索框跨命令/说明/坑/输入输出/例子 五个字段一起过滤,工具胶囊切 awk / sed,分类胶囊切 基础 / 高级 / 一行命令 / 常见坑,每条一键复制。完全浏览器里跑, 不上传不追踪。
把内容粘贴或拖入工具面板。
点击按钮,在浏览器内本地处理,文件不上传。
一键复制结果或下载到本地。
适合穿插在写代码、查问题、做 Review、上线前的小任务里。
这些入口会把当前任务接到更完整的工具链里。
团队把 `getUser` 改名成 `fetchUser`,CI 在 312 个源文件上飘红。你先用 `rg -l 'getUser' | xargs sed 's/getUser/fetchUser/g'` 不带 -i 跑一遍配 diff 预览,确认无误再原地替换。速查里的多文件 sed 那条加 macOS `-i ''` 的坑,帮你躲掉那个会把半个改名悄悄漏掉的静默空操作。
线上一直冒 500,你只有一台离网机器的 shell,没 Python 没外网。拷一条 分组命令 `awk '{ c[$9]++ } END { for (k in c) print k, c[k] }' access.log`, 4 秒钟就看出 503 从 12 次飙到 8400 次。关联数组那条恰好是你凭记忆 写不出来的模式。
供应商甩来一份 5 万行的 CSV,季度结账要把第三列求和。你抓 `awk -F, '{ s += $3 } END { print s }' data.csv`,撞上 FAQ 提醒的 带引号逗号那个坑,换成 `gawk --csv`。速查在有人签字之前就把你从 一个错误总数边上绕开了。
两台 nginx 机器漂移,配置只差空白和注释。你用 `awk 'NF && $1 !~ /^#/' a.conf` 和 `... b.conf` 把两边都归一化再 diff。四个字符的 `awk 'NF'` 写法把一份 400 行的吵闹 diff 收成真正改了的那三行。
macOS 上忘了那对空引号。`sed -i 's/a/b/' f` 在 BSD sed 上啥也不干,要写 `sed -i '' 's/a/b/' f` 或可移植的 `sed -i.bak '...'`。
拿 `awk -F,` 硬怼真实 CSV。`"Smith, John"` 这种字段会被里面的逗号切开。引号里可能含逗号时,换 `gawk --csv`(gawk 5.3+)或 `mlr`。
以为 OFS 会自己重排输出。`awk '{ OFS="," } 1'` 还是原来的空格,得碰一下字段,比如 `awk '{ $1=$1 } 1'`,才会触发重建。
每条一行命令、搜索框、awk/sed 与分类胶囊、复制按钮全在你浏览器里跑,对着 一个内存里的数组。你敲的命令、搜的词、拷的内容都不会发到服务器,也不会写 进分享链接的 URL。打开 DevTools 的 Network 一边输入一边看,零出站请求。 公司代理后面、飞机上、离网跳板机上都照跑,而这些场景恰恰最需要 awk 和 sed。
做你这行的人, 还会一起用这些。