文章目录
  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. 删除所有符号
  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。第二条命令的思路则完全不同,是先在首尾都加上双引号,然后再把所有的,都替换成","。中间的竖线|用反斜杠转义后就是正则表达式中的“或”的意思。

全局替换

把所有的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]

查看特定行

查(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. 删除所有符号
  4. 4.
    1. 4.1. 查看特定行
    2. 4.2. 查看行范围
    3. 4.3. 查看奇/偶数行
    4. 4.4. 从单行中查找
  5. 5. 参考资料