Linux的sed命令

环境

  • Ubuntu 22.04

概述

sed 是“stream editor”的意思,用来处理文本,比如做文本查找、替换。

处理单行文本

用法1

语法: sed 's/aaaa/bbbb/'

  • aaaa :用正则表达式来匹配字符串,并用圆括号括起来需要获取的内容,如 xx\(yy\)zz
  • bbbb :获取第几个圆括号里的内容,如 \1

比如,有一个由3个冒号“ : ”所分隔的字符串,比如 a:b:c:d 。想要获取第一个冒号之前的内容。

从正则表达式角度,该字符串可以看做:

.*:.*:.*:.*

分别用圆括号 () 把两个冒号之间的内容括起来,即:

(.*):(.*):(.*):(.*)

注意,圆括号需要转义,所以是:

\(.*\):\(.*\):\(.*\):\(.*\)

然后指定获取第几部分,比如第一部分就是 \1 (注意需要转义)。

完整的例子如下:

➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\):\(.*\):\(.*\)/\1/'
a
➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\):\(.*\):\(.*\)/\2/'
b
➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\):\(.*\):\(.*\)/\3/'
c
➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\):\(.*\):\(.*\)/\4/'
d

贪婪匹配

如果我们把 a:b:c:d.*:.* 来通配,即分成两部分,那么sed会怎么来划分呢?

答案:第一部分是 a:b:c ,第二部分是 d

➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\)/\1/'
a:b:c
➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\)/\2/'
d

这是因为,在正则表达式里, .* 是“贪婪式”的,会尽可能多的向右匹配。

所以,如果我们只想获取最后一个冒号右边的内容,并不需要把整个字符串按每个冒号划分为多个部分,而只需用 .*:.* 来通配,参见上面的例子。

sed貌似无法指定非贪婪式匹配。

如果我们想要获取第一个冒号前面的内容,可以像前面所介绍的,按照每个冒号分隔为多个部分,然后取第一部分。但是这种做法其实不太方便,比如如果字符串发生变化,多了一个冒号,那么正则表达式也得随之变化,否则就会取错内容。

一个变通的方法是:从左到右,把所有非冒号的字符( [^:]* )作为第一部分( ^ 表示“非”),后面就是 :.* ,即冒号以及随后的所有内容。

➜  ~ echo "a:b:c:d" | sed 's/\([^:]*\):.*/\1/'
a

用法2

sed还有一种用法,获取正则表达式匹配之外的内容。

语法: sed 's/aaaa//'

  • aaaa :是一个正则表达式

例如:

➜  ~ echo "abcdefg" | sed 's/cde//'
abfg

正则表达式匹配了 cde ,则获取的是前面和后面没有匹配上的内容,即 abfg

按此方法,对于 a:b:c:d ,要获取最后一个冒号右边的内容,可以简化如下:

➜  ~ echo "a:b:c:d" | sed 's/\(.*\)://'
d

若正则没有匹配成功则获取整个字符串

注意:sed的这两种用法,有一个共同的行为特点:如果正则表达式没有匹配成功,则会获取整个字符串。

比如:

➜  ~ echo "abcd" | sed 's/ab\(c\)d/\1/'
c

这是正确的匹配。如果正则没有匹配上:

➜  ~ echo "abcd" | sed 's/zzzab\(c\)d/\1/'
abcd

则获取了整个字符串。

第二种用法也同理:

➜  ~ echo "abcd" | sed 's/abc//'
d

这是正确的匹配。如果正则没有匹配上:

➜  ~ echo "abcd" | sed 's/zzzabc//'
abcd

则获取了整个字符串。

处理多行文本(比如文件)

查找内容

假设在 ~/.zshrc 里包含了 JAVA_HOME 设置:

➜  ~ grep JAVA_HOME ~/.zshrc
export JAVA_HOME=/home/ding/Downloads/jdk-17.0.3.1
export PATH=${JAVA_HOME}/bin:$PATH

现在需要通过sed获取 JAVA_HOME 的设置,即 /home/ding/Downloads/jdk-17.0.3.1

一种方法是,先通过grep命令找到那一行(假设只有一行满足条件),然后就只需处理单行文本:

➜  ~ cat ~/.zshrc | grep JAVA_HOME= | sed 's/.*JAVA_HOME=\(.*\)/\1/' 
/home/ding/Downloads/jdk-17.0.3.1

修改文件内容

比如,要把 JAVA_HOME 设置为 myPath

语法: sed -i '/aaaa/s/bbbb/cccc/'

  • aaaa :正则表达式,用来匹配行
  • s :表示替换
  • bbbb :正则表达式,表示旧内容
  • cccc :新内容

其中, -i 表示修改文件,否则只会把结果输出。

注:可以在最后加一个 g ,表示对本行做全局替换,否则只替换第一处。本例中只有一处,所以无需加 g

sed -i '/JAVA_HOME=/s/=.*/=myPath/' ~/.zshrc

本例中:

  • aaaa :为 JAVA_HOME= ,即查找符合条件的行
  • bbbb :为 =.* ,即等号以及后面所有内容
  • cccc :为目标字符串

运行后,文件内容被修改为:

......
export JAVA_HOME=myPath
......

注意:这里的 bbbb 无需匹配全部内容,而只是需被替换的内容,而前面介绍的,从字符串获取文本时,正则表达式必须要匹配全部内容。

如果目标字符串需要包含 bbbb 的内容,则可用 & 来代表。

比如,要把 export JAVA_HOME=xxxxx 改为 export JAVA_HOME=xxxxx/jdk

sed -i '/JAVA_HOME=/s/=.*/=&\/jdk/' ~/.zshrc

运行后,文件内容被修改为:

......
export JAVA_HOME==/home/ding/Downloads/jdk-17.0.3.1/jdk
......

获取一部分内容

语法: sed -n '/xxxx/,/yyyy/p'

表示输出从 xxxxyyyy 的行。其中 yyyy 可以为空,表示输出到最末。

比如,创建文件 test1.txt 如下:

abcdefg
hijklmn
opq
rst
uvw
xyz

则:

➜  ~ sed -n "/jk/,/st/p" test1.txt 
hijklmn
opq
rst
➜  ~ sed -n "/cde/,/uv/p" test1.txt
abcdefg
hijklmn
opq
rst
uvw
➜  ~ sed -n "/w/,//p" test1.txt
uvw
xyz

在一部分内容里做替换

语法: sed -i '/xxxx/,/yyyy/s/aaaa/bbbb/'

  • xxxxyyyy :开始行和结束行,也就是指定的部分内容
  • aaaa :旧内容
  • bbbb :新内容

也就是说,在 xxxxyyyy 所指定的范围内,把 aaaa 替换为 bbbb

比如, test1.txt 内容如下:

rst
abcdefg
hijklmn
opq
rst
uvw
xyz

要把所有的 rst 里的 rs 替换为 123

sed -i '/.*/s/rs/123/' test1.txt

而如果只把下方的 rst 里的 rs 替换为 123

sed -i '/pq/,/u/s/rs/123/' test1.txt

其中, pqu 划定了处理内容的范围。

分隔符

如果字符串里本身含有 / ,则匹配时,需要用 \ 来转义。

比如,要想获取字符串 /etc/ansible/hosts 里的 ansible

➜  ~ echo "/etc/ansible/hosts" | sed 's/\/etc\/\(.*\)\/hosts/\1/'
ansible

这里的正则表达式为 \/etc\/\(.*\)\/hosts ,较为复杂。

为了方便,我们可以换一个分隔符,比如换成 |

➜  ~ echo "/etc/ansible/hosts" | sed 's|/etc/\(.*\)/hosts|\1|'
ansible

这里的正则表达式为 /etc/\(.*\)/hosts ,相对简单一些。

考一考

1

求输出结果:

echo "a:b:c:d" | sed 's/\(.*\):\(.*\):\(.*\)/\1/'

答:贪婪式匹配

➜  ~ echo "a:b:c:d" | sed 's/\(.*\):\(.*\):\(.*\)/\1/'
a:b

2

求输出结果:

echo "a:b:c" | sed 's/\(.*\):\(.*\):\(.*\):\(.*\)/\1/'

答:没有匹配成功,则获取整个字符串

➜  ~ echo "a:b:c" | sed 's/\(.*\):\(.*\):\(.*\):\(.*\)/\1/'
a:b:c

3

已知字符串 {"key1":"<value1>","key2":"<value2>",......} ,从中获取 <value2> 的值( key2 是已知字符串,且 <value2> 中不含引号 " )。

答:

➜  ~ echo '{"key1":"value1","key2":"value2","key3":"value3"}' | sed 's/.*"key2":"\([^"]*\)".*/\1/'
value2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值