文章目录
  1. 1.
    1. 1.1. 在文件头/尾增加一行
    2. 1.2. 在匹配的地方增加一行
  2. 2.
    1. 2.1. 删除特定行
    2. 2.2. 删除关联行
  3. 3.
    1. 3.1. 所有行头/尾增加项目
    2. 3.2. 所有行修改项目
    3. 3.3. 正则替换
    4. 3.4. 全局替换
    5. 3.5. 大小写替换
    6. 3.6. 修改指定行
    7. 3.7. 删除所有符号
    8. 3.8. 使用变量
  4. 4.
    1. 4.1. 查看特定行
    2. 4.2. 查看行范围
    3. 4.3. 查看奇/偶数行
    4. 4.4. 从单行中查找
  5. 5. 参考资料

这次重温一下Linux/Unix下一个很老(反正比我老)很有用的流编辑器:sed(stream editor)。要是不经常使用,很容易忘记。可以把本文当成一个例子库,有用的时候来查一下。后来还写了篇看例子学awk

假设我们有一个csv文件如下:

1
2
3
4
5
6
cat << EOF >staff.csv
Gavo,35
Jane,21
Bill,25
Jimmy,42
EOF

在文件头/尾增加一行

1
2
sed -i '1i Name,Age' staff.csv
cat staff.csv

如果没有-i,第一行命令会输出新的文件内容但不会改变staff.csv1i中的1是指第1行,i是指在读取文件此行前增加(include)记录。如果把i换成a,指的是读取文件此行后增加(append)记录。是不是有点vi的感觉?下面这条命令的结果就会把新行插入到第三行:

1
sed '2a Hetty,29' staff.csv

要是想在文件尾增加一行的话,用:

1
2
sed '$a Hetty,29' staff.csv # 指定行内容
sed $'$a \\\n' staff.csv # 增加空行

其中的$指最后一行。如果要在倒数第二行增加一行呢?把a换成i吧。倒数第三行呢?你确认你真的有这么奇葩的需求么…

在匹配的地方增加一行

如果我们要在Jane上面增加一行,这么做:

1
sed '/Jane/i Hetty,29' staff.csv

意思是当匹配到Jane的时候,便做后面的操作。接下来的i不用说了吧,也能替换成a。这里的匹配指的是部分匹配,也能匹配多行。如果需要插入的行以空格开头,就用反斜杠\来转义这个空格。试试下列命令:

1
2
3
sed '/Jane/i \ \ Hetty,29' staff.csv # 行头插入两个空格
sed '/21/i Hetty,29' staff.csv # 部分匹配
sed '/J/i Hetty,29' staff.csv # 匹配了两行

下面这个命令可以在Gavo后面增加两行:

1
sed '/Gavo/a Hetty,29\nEmma,45' staff.csv

删除特定行

删(delete)和很相似,区别是把ia换成d即可:

1
2
3
4
5
sed '1d' staff.csv # 删除第一行
sed '$d' staff.csv # 删除最后一行
sed '/Jane/d' staff.csv # 删除包含Jane的一行
sed '/21/d' staff.csv # 删除包含21的一行
sed '/J/d' staff.csv # 删除包含J的两行

删除关联行

下面这个命令把Gavo这一行和下一行都删掉:

1
sed '/Gavo/{N;d;}' staff.csv

其中的N就是下一行(next line)的意思。如果不想删除Gavo这行,用这个命令:

1
sed '/Gavo/{N;s/\n.*//;}' staff.csv

相当于匹配了两行也就是Gavo,35\nJane,21之后,再把\n之后的所有文本替换成空白,即删除。替换的命令在下面的中会详细介绍。

所有行头/尾增加项目

1
2
sed -i 's/^/China,/' staff.csv
cat staff.csv

其中的s表示替换(substitute),^表示开头,相对应的$表示结尾。

所有行修改项目

1
2
sed -i 's/China/US/' staff.csv
cat staff.csv

这样就能把所有的China换成US。如果想把所有的名字后面都加上一个-dev呢?运行:

1
2
sed 's/,[A-Z][a-z]*/&-dev/' staff.csv
sed -r 's/(,.*),/\1-dev,/' staff.csv

其中第一条命令的正则表达式,[A-Z][a-z]*匹配逗号和名字,&表示匹配上的内容,比如对于第二行来说,是,Gavo。第二条命令略微麻烦点,-r表示扩展的正则表达式(extended regular expressions),圆括号表示分组,第一个圆括号中间是第一组,替换的时候用\1表示匹配上的内容。所以\1就是,之前的文本。文件的每一行都有两个逗号,sed会匹配最远的那一个。比如对于第二行来说,匹配到了第二个逗号,所以\1的值就是US,Gavo。加完-dev之后要再补上逗号。所以sed是非常灵活的,可以用多种办法来实现一个功能。

正则替换

给所有的项目都加上引号:

1
2
sed -r 's/[^,]+/"&"/g' staff.csv
sed -e 's/^\|$/"/g' -e 's/,/","/g' staff.csv

上面的命令是实现的两种方式。第一条命令的意思是除了逗号以外的所有匹配文本都加双引号。对于第二行来说,匹配到了三个文本:USGavo35。第二条命令的思路则完全不同,是先在首尾都加上双引号,然后再把所有的,都替换成","。中间的竖线|用反斜杠转义后就是正则表达式中的“或”的意思。如果不想用正则的方式,只想做完全匹配的字符串替换,可以用perl,例如:

1
2
3
4
5
6
7
8
9
sed -r 's/[name]/ggg/g' << EOF
hello [name]
EOF
# hgggllo [gggggggggggg]
perl -pe 's/\Q[name]/ggg/g' << EOF
hello [name]
EOF
# hello ggg

全局替换

把所有的l改成L

1
sed 's/l/L/g' staff.csv

后面的/g代表整行范围内的所有匹配全部替换,不加g的话就会被替换成BiLl。可以换成2只替换第二个匹配项。还可以选择i来忽略大小写,也可以一起用。它们都是正则表达式的范畴。

同时替换lm,以下两种方式都可以:

1
2
sed 's/l/L/g; s/m/M/g' staff.csv
sed -e 's/l/L/g' -e 's/m/M/g' staff.csv

大小写替换

1
2
sed 's/.*/\L&/' staff.csv
sed 's/.*/\U&/' staff.csv

\L就是全部小写(lowercase),\U就是全部大写(uppercase)。&在上文有提到,表示匹配上的内容。

修改指定行

现在表头的第一列也成了US,把它改成Country:

1
2
sed -i '1s/US/Country/' staff.csv
cat staff.csv

把第2行到4行的US替换成China:

1
sed '2,4s/US/China/' staff.csv

把第3行整行替换掉:

1
sed '3s/.*/China,Hetty,29/' staff.csv

删除所有符号

1
sed 's/[[:punct:]]//g' staff.csv

[[:punct:]]是正则表达式中预先定义的子字符类(character classes),代表所有的标点符号。sed支持的子字符类如下:

  • [:alnum:]:[0-9A-Za-z]
  • [:alpha:]:[A-Za-z]
  • [:blank:]:空格和TAB
  • [:cntrl:]:控制字符(Control characters),ASCII码为000~037和177 (DEL)
  • [:digit:]:[0-9]
  • [:graph:]:[:alnum:]和[:punct:]
  • [:lower:]:[a-z]
  • [:print:]:[:alnum:]、[:punct:]和空格
  • [:punct:]:符号 ! “ # $ % & ‘ ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
  • [:space:]:[:blank:]和回车、换行等
  • [:upper:]:[A-Z]
  • [:xdigit:]:16进制 [0-9A-Fa-f]

使用变量

如果想用变量里的值来代替,可以这么写(注意是双引号而不是单引号了):

1
2
3
4
5
6
7
8
name=ggg
echo $name
sed -r "s/name/${name}/g" << EOF
hello name
EOF
unset name

查看特定行

查(print)也和很类似,区别是把ia换成p即可:

1
sed '1p' staff.csv

但是…只是把匹配的行多打一遍而已。如果想要达到grep般的效果,加上-n就可以了:

1
2
3
4
5
6
7
sed -n '1p' staff.csv # 查看第一行
sed -n '$p' staff.csv # 查看最后一行
sed -n '/Jane/p' staff.csv # 查看包含Jane的一行
sed -n '/21/p' staff.csv # 查看包含21的一行
sed -n '/J/p' staff.csv # 查看包含J的两行
sed -n '/21$/p' staff.csv # 查看以21结尾的一行
sed -n '/21/!p' staff.csv # 查看包含21以外的其它行

倒数第二个命令中的21$表示以21结尾。如果要以21开头,用^21。最后一个命令中的!是取反的意思,所以21的记录就反而被隐藏了,而其他的记录倒都显示出来了。

查看行范围

如果想要查看直到匹配某条记录,用下面这条命令:

1
sed '/Jane/q' staff.csv

其中的q代表查到后退出(quit)。还有几种方式:

1
2
3
sed -n '1,/Jane/p' staff.csv # 从第一行开始到匹配Jane的记录为止
sed -n '/Gavo/,/Jane/p' staff.csv # 从匹配Gavo的记录开始到匹配Jane的记录为止
sed -n '/Jane/,$p' staff.csv # 从匹配Jane的记录开始到最后一行为止

查看奇/偶数行

1
2
sed 'n;d' staff.csv # 奇数行
sed '1d;n;d' staff.csv # 偶数行

第一条命令中的n;表示输出当前行并立即读取下一行。第二条命令先把第一行记录删除,于是再输出的奇数行就自然变成原来的偶数行了。

从单行中查找

比如想从I am 18 years old里查找18这个年龄,可以这么做:

1
echo "I am 18 years old" | sed -n "s/I am \(.*\) years old/\1/p"

这里\1代表第一个被匹配上的内容也就是\(.*\)。发挥想象力:

1
echo "I am 18 years old" | sed -n "s/I am \(.*\) \(.*\) old/\2: \1/p"

参考资料

The UNIX School 里的awk and sed tutorials含有大量的例子和解释,非常容易上手,本文就是以其为基础整理而成。
酷壳的sed 简明教程很适合入门。
当然还有最全面的官方文档

文章目录
  1. 1.
    1. 1.1. 在文件头/尾增加一行
    2. 1.2. 在匹配的地方增加一行
  2. 2.
    1. 2.1. 删除特定行
    2. 2.2. 删除关联行
  3. 3.
    1. 3.1. 所有行头/尾增加项目
    2. 3.2. 所有行修改项目
    3. 3.3. 正则替换
    4. 3.4. 全局替换
    5. 3.5. 大小写替换
    6. 3.6. 修改指定行
    7. 3.7. 删除所有符号
    8. 3.8. 使用变量
  4. 4.
    1. 4.1. 查看特定行
    2. 4.2. 查看行范围
    3. 4.3. 查看奇/偶数行
    4. 4.4. 从单行中查找
  5. 5. 参考资料