UP | HOME

definitive-guide-to-sed

Table of Contents

Chapter 01: Introduction to sed

sed is a 'Stream Editor'

  • sed 是一种stream editor,用来编辑多行text file. sed会从input 读取文件,一行一 行的分析,然后输出结果到output
  • 相比于享有盛名的vi和emacs, sed是另外一种维度的编辑器,他只在命令行起作用,并没 有自己的界面,但是这也是一个优点:
    • 它可以作为bash pipe的一个部分
    • 处理大文件的时候速度超快
  • sed和vi,Emacs各有各的生存哲学,来源于两者实现的不同:
    • vi,emacs是在disk上面编辑文件,而sed是在'stream'上面编辑
    • vi,emacs是从keyboard上面获取指令,而sed自动运行,运行的规则写在了'sed script' 里面
    • vi,emacs可以看做是"random read"的例子,可以到文章的任何位置,但是sed不可以, sed只能从第一行开始,运行到最后一行
  • sed非常适合Unix philosphy:也就是使用shell和小的Unix utilities,通过pipe和中间 文件结合起来完成工作,而不是使用一整个大的文件
    Unix philosophy: uses the shell and small Unix utilities, connected
    with pipes or intermediate files, as opposed to using large
    monolithic programs.
    
  • sed处理的是stream,所以可以从pipe上面接受到stream,处理完之后,抛给后面的pipe成员
    hfeng@ sed $ cut -c 9 file.txt
    s
    s
    u
    c
    o
    o
    s
    u
    hfeng@ sed $ cut -c 9 file.txt | sed s/o/0/g | sort
    0
    0
    c
    s
    s
    s
    u
    u
    

PatSpace and HoldSpace

  • awk和perl,也能做'stream editing'的事情,也能起到和sed一样的作用.但是各有特点:
    • awk在输出方面更有研究,而sed在输入输出方面都不错,也会有把sed的输入给awk让awk 更加优美的输出的事情
    • perl其实是一门语言了,而且特别的难以理解,和sed这种小工具就不是一个维度的了.
  • sed是有Lee McMahon在bell实验室发明的,其名字的来历是ed编辑器(Ken Thompson发明), 可能你会惊讶,ed这种编辑器每次只能编辑一行,是上古时代计算机计算性能低下导致的, 但是ed还是引入了很多新的强大的command,以及正则表达式,从而影响了如下振聋发聩 的名字:awk,ex,grep,sed, perl,vi
  • sed拥有自己的scripting语言,但是这门语言不是我们通常理解的那种,它没有那么复杂, 比如它就没有变量和数组的概念
  • 但是sed是拥有其两个特殊的概念:
    • buffer: 我们叫做PatSpace
    • workspace: 我们叫做HoldSpace
  • PatSpace(pattern space)是sed主要的工作区域,大部分的sed其实就是读取并且更改 PatSpace: sed把input text一行接一行(line by line)的读取到patspace,每当读取完 一行以后,它就开始一次'Cycle',来处理读取到的这一行,这个Cycle通常是:
    • sed保存着一个line counter从1开始,每当一行读取完毕,Cycle开始之前,都会增加这个 counter
    • 对每一行,sed都会执行sed script给予的命令(这个script命令一般都很短,但是其实 很长也可以的,只不过很长的话,我们可能会单独写一段python或者perl了)
    • 当sed script都指向完了以后,sed从通常会自动打印PatSpace里面的内容,清空 PatSpace,然后读取下一行到PatSpace,开始新的一轮Cycle
  • 我们看到PatSpace就是为一次的Cycle准备的,下一次Cycle开始的时候,上一次的内容就 已经从PatSpace里面清空了,为了能够有更好的扩展性,sed引入了另外一个概念HoldSpace 这个space在Cycle之间是不clear的.
  • 普通用户是用不到HotSpace的,高端用户可以用到HotSpace,但是HotSpace确实是有很多 令人费解的地方,需要小心.本书会介绍HotSpace的使用,即便你用不到,至少可以理解概念

Introducing the s Command

  • s(substitute)是sed里面最最常用的命令,如果你只需要理解一个sed命令,那么这个命令 肯定是s
  • s命令其实就是find and replace.假设我们的old.txt里面只有一个文字old,运行下面 的命令就可以把old转换成new,并打印到standard output(注意old.txt文件不变)
    hfeng@ sed $ cat old.txt
    old
    hfeng@ sed $ sed 's/old/new/' old.txt
    new
    hfeng@ sed $ cat old.txt
    old
    
  • 从PatSpace的角度我们来分析一下这个过程:
    • sed把'old'读取到PatSpace
    • Trailing newline被移走
    • s命令运行,把'old'转化成'new'
    • sed把trailing newline加回来
    • sed 'AutoPrints(print and clear)' PatSpace('new')
    • 没有更多的input了, sed退出
  • s命令真正的script syntax如下
    's/RegEx/SubEx/'
    
  • 我们很自然的注意到s命令find的肯定不仅仅是exact match的字符串,同样要包括正则 表达式,这个范围就广泛的多了
  • s命令replace的也可以不仅仅是exact match的字符,SubEx代表的是'substitution expression',这是一种比较灵活的字符串,后面会介绍
  • 如果我们的RegEx没有被sed匹配到,那么s命令什么也不会做,输入到PatSpace里面的字符 会原封不到的打印出去
    hfeng@ sed $ cat old.txt
    old
    hfeng@ sed $ sed 's/red/blue/' old.txt
    old
    

Quoting Command-Line Scripts

  • 在上面的例子中's/old/new'就是我们的sed script,这个script有多重的quote方法:
    • 使用单引号:
      sed 's/old/new' old.txt
      
    • 使用双引号
      sed "s/old/new" old.txt
      
    • 不要各种引号
      sed s/old/new old.txt
      

Chapter 02: sed s Command(substitue)

Delimiter for s Command

  • 前面说到过这段script 's/RegEx/SubEx/', 这里面的'/'就是分隔符(delimiter),对于 分隔符有一个偏见就是一定要使用'/',其实不是这样子的,任何可见的char都可以作为分 隔符,只不过'/'是常用手段罢了
    hfeng@ sed $ echo old | sed 's/old/new/'
    new
    hfeng@ sed $ echo old | sed 's:old:new:'
    new
    hfeng@ sed $ echo old | sed 's|old|new|'
    new
    hfeng@ sed $ echo old | sed 's_old_new_'
    new
    
  • 既然什么char都可以作为分隔符,那万一我们的input stream里面就包含了分隔符怎么办 呢?办法自然是使用escape字符串
    hfeng@ sed $ echo /A/ | sed 's/\/A\//\/B\//'
    /B/
    
  • 这个代码看着就晕,所以我们可以使用另外的字符作为分隔符
    hfeng@ sed $ echo /A/ | sed 's:/A/:/B/:'
    /B/
    
  • 最后,需要记住的是分隔符永远是三个,缺少最后一个是常见错误
    hfeng@ sed $ echo /A/ | sed 's:/A/:/B/'
    sed: 1: "s:/A/:/B/": unterminated substitute in regular expression
    

sed Input from File or stdin

  • sed经常和file来配合,一个配合的例子如下,文件名是放在最后面的.
    hfeng@ sed (master) $ cat rgb
    low(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    hfeng@ sed (master) $ sed 's/RED/xxx/' rgb
    low(#1): "red green blue"
    UPPER(#2): "xxx GREEN BLUE"
    
  • sed当然也可以从stdin里面读取,stdin的来源很多:
    • 从keyboard:竟然可以从keyboard!
      sed s/old/new/
      old2                            # input by hand
      new2
      # ctrl + D return
      
    • 通过"|"从另外的程序获取stdin
      $ echo old | sed 's/old/new/'
      new
      
  • 我们还可以使用'<'来强制从某个文件获取stdin,但是对于sed意义不大
    $ sed 's/RED/xxx/' < rgb
    low(#1): "red green blue"
    UPPER(#2): "xxx GREEN BLUE"
    

sed Command Line Options

  • Unix命令都需要command line options,sed也不例外,sed有12个option,其中5个非常常 用.后面会一一介绍:
    1. -e: add scrip tot end of over sed script
    2. -f: add lines in script-file to end of overall script)
    3. -i: edit input file in place
    4. -n: suppress AutoPrint of PatSpace
    5. -r: use extended regular expression

-e sed Command Line Option

  • sed script是sed用来"对付"input lines的,我们可以使用-e来指定sed script:
    • 指定一次的情况下,和不使用-e区别不大
      hfeng@ sed (master) $ echo old | sed 's/old/new/'
      new
      hfeng@ sed (master) $ echo old | sed -e 's/old/new/'
      new
      
    • 指定多于一次的情况下,-e用处就大了.比如你想把'o'换成'=', 把'U'换成'-',那么你 可以两次使用-e,append两段sed script
      hfeng@ sed (master) $ cat rgb
      low(#1): "red green blue"
      UPPER(#2): "RED GREEN BLUE"
      hfeng@ sed (master) $ sed -e s/o/=/ -e s/U/-/ rgb
      l=w(#1): "red green blue"
      -PPER(#2): "RED GREEN BLUE"
      
    • 现代的sed可以使用';'来append sed script,但是一般只能用在s命令上面,其他命令 会发生误会
      $ sed 's/o/=/; s/U/-/' rgb
      l=w(#1): "red green blue"
      -PPER(#2): "RED GREEN BLUE"
      

-f sed Command Line Option

  • sed script当然可以放到另外一个文件里面,这个时候使用-f来指定script file文件的 位置,这样script file里面可以使用回车分割多种的规则
    hfeng@ sed (master) $ cat s1.sed
    s/red/333/
    s/GREEN/55555/
    hfeng@ sed (master) $ cat rgb
    low(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    hfeng@ sed (master) $ sed -f s1.sed rgb
    low(#1): "333 green blue"
    UPPER(#2): "RED 55555 BLUE"
    
  • script file里面不要谁用任何的引号,否则会出错

sed Output to File or stdout

  • 如果想把sed的结果写入到其他文件,使用'>'就好了
    hfeng@ sed (master) $ sed 's/old/new/' old.txt  > temp
    hfeng@ sed (master) $ cat temp
    new
    
  • 我们的sed可以和for循环配合,更改一系列文件里面的内容,复杂的sed script还可以写 在另外的文件里面
    list='one.txt two.txt'
    for file in $list; do
        sed -f rename.sed $file > temp.x
        mv temp.x $file
    done
    

-i sed Command Line Option

  • 前面我们说过,sed的最后跟的是一个文件,这个文件再sed过后不会改变.如果我们想改变 这个文件的内容,需要重定向,和另外一个中间文件
    $ sed 's/A/B/' in.txt > temp
    $ mv temp in.txt
    
  • 这样做一来是比较麻烦,二来呢容易受到权限问题的困扰,所以我们使用一个option -i 来做到"最终把结果写回到文件"的作用.注意,在mac上,这个参数可能使用范围有限
    test@openstest:~/tmp$ cat demo.txt
    old
    test@openstest:~/tmp$ sed -i 's/old/new/' demo.txt
    test@openstest:~/tmp$ cat demo.txt
    new
    
  • 我们前面使用了一段script来把一系列文件里面的内容更改(还使用了for循环),如果使 用-i参数的话,就可以一行解决问题了
    test@openstest:~/tmp/sed-playground$ cat one.txt
    HELLO world
    test@openstest:~/tmp/sed-playground$ cat two.txt
    world HELLO
    test@openstest:~/tmp/sed-playground$ sed -i 's/world/WORLD/' one.txt two.txt
    test@openstest:~/tmp/sed-playground$ cat one.txt
    HELLO WORLD
    test@openstest:~/tmp/sed-playground$ cat two.txt
    WORLD HELLO
    

Chapter 03: Flags for s(substitute) Command

  • 我们来看一个简单的例子,更改old为new
    test@openstest:~/sed-playground$ echo old old | sed s/old/new/
    new old
    
  • 我们发现,只有第一个old被更改了,这是因为如果不加特殊说明,s只更改第一次出现的匹 配,如果需要全部匹配,那么就需要给s命令增加g flag
    $ echo old old | sed s/old/new/g
    new new
    
  • 我们的s命令的syntax就更新为
    s/RegEx/SubEx/[flags]
    
  • 我们下面来看看几个常见的flag

i(ignore case) Flag

  • 如果不加flag的话,我们的匹配是大小写区分的,old不能匹配Old,因为RegEx就是区分 大小写的
    ~/github/sed-playground $ echo old | sed s/Old/xxx/
    old
    
  • 我们可以在s命令的flag里面加上ignore大小写
    ~/github/sed-playground $ echo old | sed s/Old/xxx/i
    xxx
    
  • 需要说明的是,除了i flag,我们还可以在正则表达式那里设置大小写不敏感
    ~/github/sed-playground $ echo ab | sed s/AB/=/i
    =
    ~/github/sed-playground $ echo ab | sed s/[aA][bB]/=/i
    =
    

g(global) and n(number) Flags

  • g和n是两个关于替换的属性:
    • 设置g表示全部替换
      ~/github/sed-playground $ echo oldold | sed s/old/new/
      newold
      ~/github/sed-playground $ echo oldold | sed s/old/new/g
      newnew
      
    • 设置n表示替换第几次匹配
      ~/github/sed-playground $ echo oldold | sed s/old/new/
      newold
      ~/github/sed-playground $ echo oldold | sed s/old/new/2
      oldnew
      
  • 对于不设置n来说,就是默认为1,而且如果n很大的话(没匹配到),那么什么都不会发生
    ~/github/sed-playground $ echo oldold | sed s/old/new/9
    oldold
    

p(print) and w(write) Flags

  • 我们前面讲过sed的AutoPrint,在s命令的flag里面,我们还可以加上一个s命令的打印 要求'p',这个打印和AutoPrint是两个完全不同的命令,它比AutoPrints要求要严格 一点,它要求一定要匹配了,才打印.所以如果我们使用p,又恰巧匹配的话,会有双重效果
    ~/github/sed-playground $ echo ABC | sed s/ABC/xxx/p
    xxx
    xxx
    
  • 为了让这个命令的语义更纯粹,我们通常把这个命令和sed的一个command line option 一起使用(注意,是command line option,而不是flag),这个option就是n,它的作用是 关掉AutoPrint,这样的话flag p的作用就会很明显:能够匹配的才打印,否则就不打印
    ~/github/sed-playground $ cat rgb
    low(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    ~/github/sed-playground $ sed -n 's/RED/xxx/p' rgb
    UPPER(#2): "xxx GREEN BLUE"
    
  • 和p相似的一个flag是w,它也是在replace发生的时候才起作用!起的作用是把结果写 到一个文件里面
    ~/github/sed-playground $ cat rgb
    low(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    ~/github/sed-playground $ cat a.txt
    =======had some text========
    ~/github/sed-playground $ sed 's/red/xxx/w a.txt' rgb
    low(#1): "xxx green blue"
    UPPER(#2): "RED GREEN BLUE"
    ~/github/sed-playground $ cat a.txt
    low(#1): "xxx green blue"
    
  • 上面的例子有以下几点需要说明:
    • 只有真正replace的才会写入到文件
    • 文件原来存在的话,内容会被clear
    • sed原来的内容不会影响到w写入,sed的结果会显示带stdout,真正replace的结果会 写入到文件里面

e(execute) and m(multi) Flags

  • 这个flag很有bash特色,是把匹配成功的字符串作为一个命令来运行,注意,一定是匹配 成功的字符串
    test@openstest:~/sed-playground$ expr 2 + 4
    6
    test@openstest:~/sed-playground$ echo 2 | sed 's/./expr & + 4 /e'
    6
    
  • multi-line command flag会更常用一点:这个flag通常会和'N;'一起起作用,所以我们 要先看看'N;',这个命令会把多行通过'\n'联系起来
    $ seq 2 | sed 'N;s/1\n2/=/'
    =
    
  • 这种情况下的最大问题,是我们原来用来'区别'不同行的Anchor MetaChars信息,在'N;' 的处理下,全部消失了
    $ seq 2 | sed 'N;s/^2/=/'
    1
    2
    
  • 我们使用multi-line mode+'N;'的话,等于在'1$'和'^2'之间加了'\n'
    $ seq 2 | sed 'N;s/^2/=/m'
    1
    =
    

Combining s Command Flags

  • 所有s命令的flag都可以结合起来使用,但是有一些规则需要规避:
    • w flag如果有,必须在最后,后面跟文件名(最好加一个空格)
    • 不要重复使用flag
    • n和g两个flag结合起来就是从n开始替换n, n+1, n+2等等
  • 例子如下:
    • 大小写不敏感的全部替换
      ~/github/sed-playground $ sed 's/r/+/ig' rgb
      low(#1): "+ed g+een blue"
      UPPE+(#2): "+ED G+EEN BLUE"
      
    • 全部替换,但成功的才打印
      ~/github/sed-playground $ cat rgb
      low(#1): "red green blue"
      UPPER(#2): "RED GREEN BLUE"
      ~/github/sed-playground $ sed -n 's/e/+/gp' rgb
      low(#1): "r+d gr++n blu+"
      
    • 更改从第3次match开始的元音
      ~/github/sed-playground $ sed 's/[aeiou]/+/3g' rgb
      low(#1): "red gr++n bl++"
      UPPER(#2): "RED GREEN BLUE"
      

Chapter 04: Single Character MetaChars

  • s命令的第一个参数是RegEx,也就是一个regular expression,正则表达式一个字母所能 表示的范围非常的广,可能是一个char,也可能是一系列的char(比如[A-Z])

Literal Character in RegEx

  • RegEx即便是最简单的Literal 字符比如'am',但是也是正则表达式,只不过能够匹配 的有限罢了
  • 在escape字符串的帮助下,正则表达式能表示很多很多的literal字符,比如
    \a Alert(ASCII 7)
    \n NewLine
    \f Form Feed
    \r Carriage Return
    \t Horizontal Tab
    \v Vertical Tab
    

.(Wildcard Character)

  • literal总体上来说,是比较守规矩的,一个对一个.另外一种正则的成员MetaChar (metacharacter)就没那么简单了
  • 最常见的MetaChar是'.',它能够对应任何单个字符(any single character)甚至是换 行符.我们也可以理解为'.'可以对应任何的literal
  • 使用'.'要非常小心,因为它会极大的提高你要对应的字符串数量:
    • 比如hat可以对应hat, hatch, shatter
    • h.t就可以匹配hat,hit,hot,hatch等等更多的"合理的"字符串
    • h.t还可以匹配h5t,h=t,hjt等可能你并不需要的字符串
  • 一个使用'.'的例子
    ~/github/sed-playground $ cat rgb
    low(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    ~/github/sed-playground $ sed 's/.E/++/g' rgb
    low(#1): "red green blue"
    UP++R(#2): "++D G++EN BL++"
    

\ (Specify Literal Character)

  • '.'本身其实也是一个literal,但是如果不加任何修饰的话,就成了MetaChar,如果我们 需要'.'的literal的话,需要使用escape char '\'
    ~/github/sed-playground $ echo 103 | sed 's/1.3/1.4/'
    1.4
    ~/github/sed-playground $ echo 103 | sed 's/1\.3/1.4/'
    103
    
  • 所有的MetaChar想使用自己的literal的时候都要用到escape char
    MetaChar Escaped literal
    * \*
    ^ \^
    $ \$
    [ \[
    . \.
    \ \\

[] (Character Set)

  • 单个的literal的能力太局限,MetaChar的能力又太广泛,能力在这两者之间的就是 character set:不至于只能匹配一个,但是也不至于所有都匹配,可以在一个[]里面限 定好自己需要的数量,比如[aeiou]就可以用来只匹配原因
    ~/github/sed-playground $ echo abet | sed s/[aeiou]/=/g
    =b=t
    
  • character set最朴素的写法当然是所有的罗列,比如[aeiou],,但是也 可以使用'-'来作为through的意思:
    • [0-9]所有的数字
    • [a-z]所有的小写字母
    • [A-M], A到M之间所有的大写字母
  • 'x-y'可以相互的join起来,变成更强大range的character set
    • [a-zA-Z] alphabetic
    • [a-zA-Z_] alphabetic or _
    • [a-zA-Z0-9] alphanumeric
    • [a-zA-Z0-9_] alphanumeric or _
  • 如果希望把']'也作为character set的一部分的话,它必须在最左边(不能再右边,以 防被误判成右边的character set边界)
    ~/github/sed-playground $ echo '()[]{}' | sed 's/[])}]/+/g'
    (+[+{+
    
  • 如果希望'-'也作为character set的一部分的话,它必须在最左边或最右边(不能在中 间,以防止被误判成range)
  • '^'的用法很特别,总体上来说,'^'必须在character set的最前面,表示对它后面的 character set进行取反,但是实际用处有两种:
    • 整个正则表达式只有character set,那么就是'取反某些character set的作用'
      ~/github/sed-playground $ echo '()[]{}' | sed 's/[^])}]/+/g'
      +)+]+}
      
    • 整个character set是其他的正则表示式的一部分的情况下,就是用来缩小匹配区间 的(因为匹配是贪婪的),比如下面的例子,'e.*e'的匹配如果中间不加上[^e]的话,就 会从"第一个e开始,匹配到最后一个e",而我们希望从"第一个e开始,匹配到第二个e"
      ~/github/sed-playground $ cat rgb
      lower(#1): "red green blue"
      UPPER(#2): "RED GREEN BLUE"
      ~/github/sed-playground $ sed 's/e.*e/++++/' rgb
      low++++"
      UPPER(#2): "RED GREEN BLUE"
      ~/github/sed-playground $ sed 's/e[^e]*e/++++/' rgb
      low++++d green blue"
      UPPER(#2): "RED GREEN BLUE"
      

\w \W (Word and Non-Word)

  • \w和\W看起来像是literal,其实是character set:
    • \w代表所有的'Word',等于[a-zA-Z0-9_]
    • \W代表所有的非'Word',等于[^a-zA-Z0-9_]
  • 这里又有一个新的概念叫做sed word,如果一个空格分割的字符串,其每个成员都是 [a-zA-Z0-9_],那么它就是sed word.否则不是
    • bit_flag就是sed word
    • bit-flag就不是sed word
  • 看两个例子:
    • 're'能够匹配'r\w',注意这里分隔符使用的是'!'
      ~/github/sed-playground $ cat rgb
      lower(#1): "red green blue"
      UPPER(#2): "RED GREEN BLUE"
      ~/github/sed-playground $ sed 's!r\w!++!' rgb
      lower(#1): "++d green blue"
      UPPER(#2): "RED GREEN BLUE"
      
    • 'ue"'能够匹配'u\w\W'
      ~/github/sed-playground $ cat rgb
      lower(#1): "red green blue"
      UPPER(#2): "RED GREEN BLUE"
      ~/github/sed-playground $ sed 's!u\w\W!+++!' rgb
      lower(#1): "red green bl+++
      UPPER(#2): "RED GREEN BLUE"
      

[::] (Posix Character Class)

  • character set还有一种更加"语义化"的写法,就是使用一段英文来替代[:xxx:]里面 的xxx,用来代表某一个character set.注意,posix character class需要两层的中括 号,[:black:]等同于\t,所以:black:等同于[\t]
  • 这种做法更加的容易理解,并且有时候会在不同的locale里面引入其他字符串,比如在 西班牙语的locale里面[:alpha:]就等于aeiou加上它们认为是元音的其他字符(比如v)
  • 下面是常见的posix character和character set的对应
    Posix Character Character Set
    [:alnum:] a-zA-Z
    [:blank:] \t
    [:digit:] 0-9
    [:lower:] a-z
    [:space:] \t\r\n\v\f
    [:upper:] A-Z
    [:xdigit:] A-Fa-f0-9
  • posix 也是可以和其他character set组合的:
    • [[:digit:]ab]等同于[0-9ab]
    • [[:lower:]12]等同于[a-z12]

Chapter 05: Anchor MetaChars

  • 所谓的'Anchor MetaChar'是这样一种char,它们既不占用空间,也不匹配任何字符串, 它起到的是"边界"判断的作用,比如判断是不是在PatSpace的开头

^(Start of PatSpace)

  • 在vi里面我们也见到了,^是表示一行的开头.
  • 首先^能够自己起作用,这个时候因为^不占用空间,所以sed的replace会转化为insert
    ~/github/org/notes/misc $ echo XYZ | sed 's/^/=/g'
    =XYZ
    
  • ^和其他literal或者Metachar配合就能有更准确的匹配,insert也就会回归成replace 了
    ~/github/org/notes/misc $ echo XYZ | sed 's/^X/=/g'
    =YZ
    
  • 如果^不在最前面,那么它就是自己literal的作用
    ~/github/org/notes/misc $ echo 2^3=8 | sed 's/2^3/8/'
    8=8
    
  • 如果第一个字符恰好是^,那么使用^^来匹配
    ~/github/org/notes/misc $ echo ^HDR | sed 's/^^HDR/=/'
    =
    

$(End of PatSpace)

  • ^的counterpart就是$,$表示PatSpace的最后,除了表示的位置相反,其所有的用法和^ 是一样的
  • '^$'联合起来可以表示空行

\< \> \b (Word Boundaries)

  • 有时候你要匹配一个word,比如cow改成ox,如果如下书写,在大部分情况下是没问题的
    ~/github/org/notes/misc $ echo cow | sed s/cow/ox/
    ox
    
  • 但是如果某些word里面包含cow,那么上面的字符串就会"错误匹配"某些字符串,比如 scow明显不应该改成sox
    ~/github/org/notes/misc $ echo scow | sed s/cow/ox/
    sox
    
  • 有些人还希望使用' cow '这种两边带空格的正则来匹配,但是这种情况下遇到如下的 字符串就匹配不对了:有句号
    Please yoke the cow.
    
  • 正确的处理办法是使用对word有效的标明其"边界"的Anchor MetaData:
    • '\<':代表sed word的start
    • '\>':代表sed word的stop
  • 前面已经讲过sed word的概念了,sed word就是:一系列[a-zA-Z0-9_]char组成的sequence
    sed word is a sequence of [a-zA-Z0-9_] characters.
    
  • '\<'和'\>'写起来太麻烦,还有一个便捷的用法就是'\b','\b'可以根据情景,自动匹配 成word的start或者stop
  • 一下子问题就解决了
    ~/github/org/notes/misc $ echo 'cow scow' | sed 's/\bcow/ox/'
    ox scow
    

\B (Not a Word Boundary)

  • \B就是\b的反面,匹配任何不是word boundar的位置
    ~/github/org/notes/misc $ echo 'cow scow' | sed 's/\Bcow/++/'
    cow s++
    

Chapter 06: Simple Repetition MetaChars

  • Repetition MetaChar顾名思义,就是能够确认其前面的char(或者 character set)能够 重复多少次的字符

(0 or More of Previouse)

  • 我们使用'*'来表示重复0次或多次重复,换句话说,就是任意次.比如'x*'就表示'0 or more x'
  • 这是一个非常强大的MetaChar,如果再结合'.'(任意char),那么就总能匹配PatSpace, 也就是说'.*'能够匹配任何PatSpace
  • 看起来更有意义的肯定是其他字符加上*,比如'b*'.说到'b*',这种字符串是非常容易 用错的RegExp,因为这个字符串能够匹配的是两种情况:
    • 以b开头的"长度大于1"的字符串:这个是很明显的匹配,至于为什么会有"长度小于1" 的字符串,那是因为我们有Anchor MetaChars,它们的长度其实是0
    • 任何Anchro MetaChar:因为'b*'也可以匹配任何长度为0的字符串, Anchor MetaChar 真是长度为0的字符串.所以我们会看到非常奇怪的匹配,比如下面的'0123'字符串, 每两个字符串之间都会有一个Anchor MetaChar,而且首尾也会有Anchor MetaChar 所以会导致匹配到很多结果
      $ echo 0123 | sed 's/b*/=/g'
      =0=1=2=3=
      
  • 所以'看起来有意义的b*'其实还是没啥意义,真正想匹配b开头的字符串,应该写'b.*'
    $ echo abc | sed 's/b.*/=/g'
    a=
    
  • 而去除所有的至少有一个b的字符串的行动,似乎更常见
    $ echo abbbbcb | sed 's/bb*/=/g'
    a=c=
    
  • 好了,这里出现了一个问题,当sed匹配bbbb的时候,它可以匹配一个,两个,三个,但为 什么最后匹配了四个?原因是因为:所有的repetition MetaChar都是贪婪的
    All repetition MetaChars are greedy.
    
  • 这种贪婪,导致'.*'通常都不是一个好的RegExp,因为它匹配了太长太长的字符串
    $ echo 'a:b:c:d:e:f:g:h' | sed 's/:.*:/:=:/'
    a:=:h
    
  • 所以我们一般不使用'.*',而应该加一个比'.'能力低一点的char,哪怕低一点效果就 准确很多,比如,我们这里吧'.'换成'[^:]',从'所有字符'变成了'非:字符',看似只减 少了一个字符,但是效果达到了,因为遇到':'就会退出了
    $ echo 'a:b:c:d:e:f:g:h' | sed 's/:[^:]*:/:=:/'
    a:=:c:d:e:f:g:h
    
  • 这种[^something]的办法非常的有效,比如我们想把rgb里面的被""包裹的字符串给替 换掉,那么""被包裹的是什么呢?只可能是非引号!
    test@openstest:~/sed-playground$ cat rgb
    low(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed 's/"[^"]*"/"="/g' rgb
    low(#1): "="
    UPPER(#2): "="
    

\+ (1 or More of Previous)

  • 相比于*, '\+'会清晰的多,因为它会至少要求有一个,就避免了0,这个灰色地带,比如 'b\+'就代表一个或者多个b
    test@openstest:~/sed-playground$ echo abc | sed 's/b\+/=/'
    a=c
    test@openstest:~/sed-playground$ echo abbbc | sed 's/b\+/=/'
    a=c
    

-r sed Command Line Option

  • '\+'的问题在于需要写一个'\',这是为了和真正的'+'进行区别.但是真实的情况下'+' 出现的概率并不高,所以正则表达式发明了一种叫做regexp extend的情景,在这种情景 下,你可以使用'+'来替代'\+'
  • 在sed里面,是使用-r(–regexp-extended)来代表我们的正则表达式是"扩展的":
    test@openstest:~/sed-playground$ echo abbv | sed 's/b\+/=/'
    a=v
    test@openstest:~/sed-playground$ echo abbv | sed 's/b+/=/'
    abbv
    test@openstest:~/sed-playground$ echo abbv | sed -r 's/b+/=/'
    a=v
    
  • 那么我们在"扩展的"正则表达式里面想匹配'+'怎么办呢?答案是使用'\+',是不是很有 哲学感:"扩展的"正则表达式设计的初衷就是,'+'出现的概率非常低,而'一次或者多次' 的使用概率高很多.我们让'一次或者多次'的使用更加便捷
    $ echo abb+ | sed -r 's/b\+/=/'
    ab=
    
  • 从上面的例子我可以看出,扩展的正则表达式其实完全等价于普通正则表达式,只是基 于"概率"来做了一些改良,所以,如果可能的话,我们尽量要使用"扩展的"正则表达式
  • 需要注意的是,我们的"扩展的"正则表达式并不是要取消'\'的使用,而是要减少'\'的 使用,有些情况下也是不可避免的要使用'\':
    • 前面说了,匹配'+'的时候,就必须要要使用'\+'了
    • boundary metachar的'\'不可省略:'\<', '\>', '\b', '\B'

\? (0 or 1 Previous)

  • \? 是一种"存在与否"的判断,要么有一个,要么没有.所以它还是和'*'一样,涉及到了 0这个灰色地带要非常小心(后面,我们都会使用扩展的正则)
    $ echo abc | sed -r 's/x?/=/g'
    =a=b=c=
    
  • 常见的用法是匹配'HA',但是前后可以有'T'
    test@openstest:~/sed-playground$ echo THAT | sed -r 's/T?HAT?/++/'
    ++
    test@openstest:~/sed-playground$ echo HA | sed -r 's/T?HAT?/++/'
    ++
    

Compare * \+ \?

  • 我们再来总结下如下三个重要的repetition metachars
    • '*': 0 or more of previous
    • '\+': 1 or more of previous
    • '\?': 0 or 1 of previous
  • 匹配'zero occurrence'看起来有些滑稽,有些人认为'\+'(1 or more)会比'*'(0 or more) 更加的常用,但是其实是相反的,'*'的使用频率会多一点
  • 对于'*'和'\?',因为会涉及到nothing,所以要特别的小心,因为他们总会匹配到开头, 在不设置g flag的时候,往往只会更改PatSpace的开头. '\+'就不会有这个烦恼
    test@openstest:~/sed-playground$ echo abbc | sed 's/b*/=/'
    =abbc
    test@openstest:~/sed-playground$ echo abbc | sed 's/b\?/=/'
    =abbc
    test@openstest:~/sed-playground$ echo abbc | sed 's/b\+/=/'
    a=c
    

Chapter 07: General Repetion MetaChars

  • 前面介绍了一些简单的repetion metachar,我们这里介绍一下更general的设置,这种 general的设置是可以完全覆盖前面的simple设置的

\{N\} (Exact N of Previous)

  • 顾名思义,就是可以精确匹配多次出现,比如'x\{3\}'就是意味着'xxx'
  • 当然了,你其实是可以手动写数目的,麻烦一点而已.如果N是100的话,想你也不会不用这个设置
    test@openstest:~/sed-playground$ echo abbbbbbbbbbc | sed -r 's/b{10}/=/'
    a=c
    test@openstest:~/sed-playground$ echo abbbbbbbbbbc | sed -r 's/bbbbbbbbbb/=/'
    a=c
    

\{L,\} (Low, Higher of Previous)

  • '\{L, }'就是通常意义上的"至少有L个重复"
    test@openstest:~/sed-playground$ echo abc | sed -r 's/b{2,}/#/'
    abc
    test@openstest:~/sed-playground$ echo abbc | sed -r 's/b{2,}/#/'
    a#c
    test@openstest:~/sed-playground$ echo abbbc | sed -r 's/b{2,}/#/'
    a#c
    
  • 所以'*'其实就是'\{0, \}'的simple写法
  • '\+'是'\{1,\}'的simple写法

\{L, H\} (Low, High of Previous)

  • 这个也很"顾名思义"了,看一个例子'b\{2,3\}'匹配bb或者bbb
    test@openstest:~/sed-playground$ echo abc | sed 's!b\{2,3\}!=!'
    abc
    test@openstest:~/sed-playground$ echo abbc | sed 's!b\{2,3\}!=!'
    a=c
    test@openstest:~/sed-playground$ echo abbbc | sed 's!b\{2,3\}!=!'
    a=c
    test@openstest:~/sed-playground$ echo abbbbc | sed 's!b\{2,3\}!=!'
    a=bc
    
  • '\?'其实也就是'\{0, 1\}'

Chapter 08: Other RegEx MetaChars

\| (Alternative Patterns)

  • '\|' 允许我们的regexp有多个候选人,比如我们想把red或者BLUE都替换成xxx,那么就 可以如下
    test@openstest:~/sed-playground$ sed "s/red\|BLUE/xxx/g" rgb
    low(#1): "xxx green blue"
    UPPER(#2): "RED GREEN xxx"
    test@openstest:~/sed-playground$ sed -r "s/red|BLUE/xxx/g" rgb
    low(#1): "xxx green blue"
    UPPER(#2): "RED GREEN xxx"
    

\( \) (Grouping and Saving)

  • \(\)可以定义一个'group',定义的这个group可以在后面被重复使用(通过\1 \2),因为 涉及到了position,这个的使用有一些巧妙效果,比如reverse两个char的字符串
    $ echo xy | sed -r 's/(.)(.)/\2\1/'
    yx
    
  • \(\)还可以跟repetition metachar一起使用,比如去除偶数个的'A'
    test@openstest:~/sed-playground$ echo AA | sed -r 's/(AA)+/=/'
    =
    test@openstest:~/sed-playground$ echo AAA | sed -r 's/(AA)+/=/'
    =A
    test@openstest:~/sed-playground$ echo AAAA | sed -r 's/(AA)+/=/'
    =
    

\`(Always Start of PatSpace)

  • 高端特性,强制版本的'^'.
  • 为什么说是强制版本的'^'呢,因为'^'的意义是会改变的:
    • 在普通模式下,'^'会去匹配PatSpace里面最开始的empty string
      # 在普通模式下, ^功能单一
      test@openstest:~/sed-playground$ seq 2 | sed 's/^2/=/'
      1
      =
      test@openstest:~/sed-playground$ seq 2 | sed 'N;s/^2/=/'
      1
      2
      
    • 在multi-line模式下面, '^'变成了匹配newline+empty string
      test@openstest:~/sed-playground$ seq 2 | sed 'N;s/^2/=/'
      1
      2
      test@openstest:~/sed-playground$ seq 2 | sed 'N;s/^2/=/m'
      1
      =
      
  • '\`'就永远不改变自己的节操,永远匹配最开始的empty string
    test@openstest:~/sed-playground$ seq 2 | sed 'N;s/\`2/=/'
    1
    2
    test@openstest:~/sed-playground$ seq 2 | sed 'N;s/\`2/=/m'
    1
    2
    

\'(Always End of PatSpace)

  • 高端特性,强制版本的'$',非常不常用,主要配合multi-line mode

Chapter 09: SubEx MetaChars

  • SubEx = 'Substitution Expression'. SubEx指的是被替代的部分,也就是s命令的第二 个'//'里面的内容

& (Entire Matched Portion)

  • 在第一个'//'里面成功匹配的部分,会被保存在'&'里面,如果在第二个'//'里面有&的 话,就等于我们要把匹配结果都再显示出来
    $ seq 3 | sed 's/./Line &/'
    Line 1
    Line 2
    Line 3
    
  • 当然了,如果你像使用literal &的话,需要在前面加一个'\'
    test@openstest:~/sed-playground$ sed 's/...../[&]/' rgb
    [lower](#1): "red green blue"
    [UPPER](#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed 's/...../[\&]/' rgb
    [&](#1): "red green blue"
    [&](#2): "RED GREEN BLUE"
    

\N BackRef(Play Saved Group)

  • 每次把整个匹配的结果都打印出来,在很多时候并没有意义,很多时候,我们只希望使用 匹配结果的一部分.sed为了能够实现这个需求,从匹配的部分入手,让我们的匹配成功 的部分分成了好几个group:使用()来区分
  • 然后我们在SubEx里面,就是使用'\N'来打印第N次匹配成功的结果啦,最经典的例子就是 reverse xy
    $ echo xy | sed -r 's/(.)(.)/\2\1/'
    yx
    
  • 当然不仅仅是reverse,很多时候,我们只需要一部分的匹配结果,比如第一部分
    $ echo xy | sed -r 's/(.)(.)/a\1/'
    ax
    
  • 需要注意的是BackRef也可以是RegEx的一部分!
    $ echo xx | sed -r 's/(.)\1/\1/'
    x
    

\l \u (Case for Next Character)

  • '\l'把下一个char变成小写,'\u'char变成大写,看例子
    test@openstest:~/sed-playground$ sed 's/./\u&/g' rgb
    LOWER(#1): "RED GREEN BLUE"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed 's/./\l&/g' rgb
    lower(#1): "red green blue"
    upper(#2): "red green blue"
    

\L \U \E (Case for Next Span)

  • '\L' 把剩余的SubEx变成lowercase,知道遇到'\E'(或者'\U')
  • '\U' 把剩余的SubEx变成uppercase,知道遇到'\E'(或者'\L')

Chapter 10: Command Addresses

  • 每个sed comamand在其前面有个可选的参数,叫做Address.Address要紧贴着在command 之前
  • 如果你不设置Address,那么command"一直"会运行,但是你设置了Address,那么command 就会有选择的运行啦.
  • 为了让我们的例子更加生动,我们先采取下11章才会介绍的delete命令,如下删除能够匹 配的那一整行(PatSpace)
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed '/\<GREEN\>/d' rgb
    lower(#1): "red green blue"
    

Address Omitted

  • 先看一个没有address的例子,command总是运行
    $ seq 4 | sed 'd'
    

N Format Address

  • 在看一个只有一个integer作为Address的例子:只删除第一个
    $ seq 4 | sed '1d'
    2
    3
    4
    
  • '$'是比较特殊的一个N,它可用来表示让command运行于最后一个input
    $ seq 4 | sed '$d'
    1
    2
    3
    

L,H Format Address

  • 这种address就是设置一个运行command的区间
    $ seq 8 | sed '1,6d'
    7
    8
    
  • 一般来说L要小于H,如果不是的话,只有L会执行
    $ seq 4 | sed '3,1d'
    1
    2
    4
    

/RegEx/ Format Address

  • A '/RegEx/'作为address就是说,如果能够match到这个reg,那么我们就指向command
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed '/red/ d' rgb
    UPPER(#2): "RED GREEN BLUE"
    

/RegEx/,/RegEx/ Address

  • 就是reg也可以做个range
    test@openstest:~/sed-playground$ cat a-i.txt
    a
    b
    c
    d
    e
    f
    g
    h
    i
    test@openstest:~/sed-playground$ sed '/a/,/g/ d' a-i.txt
    h
    i
    

L,/RegEx/ Format Address

  • 就是从L行开始,遇到/RegEx/就结束
    test@openstest:~/sed-playground$ cat a-i.txt
    a
    b
    c
    d
    e
    f
    g
    h
    i
    test@openstest:~/sed-playground$ sed '1, /e/ d' a-i.txt
    f
    g
    h
    i
    

/RegEx/,+N Format Address

  • 就是遇到/RegEx/开始,再运行N次结束(注意这个再)
    test@openstest:~/sed-playground$ cat a-i.txt
    a
    b
    c
    d
    e
    f
    g
    h
    i
    test@openstest:~/sed-playground$ sed '/b/, +2 d' a-i.txt
    a
    e
    f
    g
    h
    i
    

/RegEx/,~N Format Address

  • 遇到RegEx开始,到第Nth行结束
    test@openstest:~/sed-playground$ cat a-i.txt
    a
    b
    c
    d
    e
    f
    g
    h
    i
    test@openstest:~/sed-playground$ sed '/b/,~3 d' a-i.txt
    a
    d
    e
    f
    g
    h
    i
    

First~Step Address

  • 就是从第First行开始运行,然后First+Step运行,First+Step+Step运行等等
    $ seq 5 | sed '1~2d'
    2
    4
    

! (Inverts Address Match)

  • 就是符合某个,就不运行
    $ seq 4 | sed '$! d'
    4
    

Chapter 11: Delete PatSpace Cntent - dD

sed d Command(delete)

  • delete是和substitute地位一样的删除命令,删除的是真个PatSpace
  • 一般来说,只使用d命令是没有意义的,只会全部删除
    $ seq 2 | sed 'd'
    $
    
  • 所以d命令一般都是和其他前面的Address紧密联系,上一章我们已经看到了
    $ seq 4 | sed '$d'
    1
    2
    3
    

sed D Command

  • 如果没有'\n'的话,那么D和d命令是一致的,但是因为引入了'\n'的话.就没有restart 一说了,这个时候需要一个新的命令,那就是D
  • D不是删除这个PatSpace,而删除第一行(#1)外加一个newline

Chapter 12: Append, Insert, Change - aic

sed a Command(append)

  • a命令是往PatSpace的打印结果里面append,不是向PatSpace里面append
  • 往结果最后加字符串
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed '$ a zzz' rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    zzz
    
  • 匹配到red以后,在下一行加文字
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed '/red/ a zzz' rgb
    lower(#1): "red green blue"
    zzz
    UPPER(#2): "RED GREEN BLUE"
    

sed i Command(insert)

  • 和a对应,a是匹配到了,"下一行"加文字,i命令是匹配到了"上一行"加文字
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed '/red/ i zzz' rgb
    zzz
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    

sed c Command(change)

  • change顾名思义,就是更改啦
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed '1 c xxx' rgb
    xxx
    UPPER(#2): "RED GREEN BLUE"
    

Chapter 13: Print PatSpace -pPl

sed p Command(print)

  • p命令是用来打印PatSpace的,我们知道PatSpace是有AutoPrint的,但是是在sed script 完毕的时候才会调用.如果我们想在其他时间打印PatSpace的情况,那么p命令是不二之选
  • 比如我们在替换之前先看我们要替换的是sha
    $ cat rgb | sed 'p; s/red/xxx/'
    lower(#1): "red green blue"
    lower(#1): "xxx green blue"
    UPPER(#2): "RED GREEN BLUE"
    UPPER(#2): "RED GREEN BLUE"
    
  • p命令当然可以选择打印其中某一行,一般这种情况下都要调用sed command option的-n 停止AutoPrint
    test@openstest:~/sed-playground$ sed '/2/ p' rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ sed -n '/2/ p' rgb
    UPPER(#2): "RED GREEN BLUE"
    

sed P Command(Print)

  • 在没有'\n'的情况下P和p命令完全相等
  • 在有'\n'的情况下,P命令会打印#1直到第一个'\n'

sed l Command(display line)

  • 对于'\n'困扰多时的人来说,多么希望能够明确的知道当前的PatSpace里面有没有'\n' 这个时候l命令出现了,它能够打印出'\n',在debug的时候非常有用
    test@openstest:~/sed-playground$ seq 3 | sed -n 'l'
    1$
    2$
    3$
    test@openstest:~/sed-playground$ seq 3 | sed -n 'N;l'
    1\n2$
    

Chapter 14: Read/Write File - rR wW

sed r Command(read RFile)

  • 就是在Address配置的位置,进行读取文件的操作
    test@openstest:~/sed-playground$ cat rgb
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    test@openstest:~/sed-playground$ seq 2 | sed '1 r rgb'
    1
    lower(#1): "red green blue"
    UPPER(#2): "RED GREEN BLUE"
    2
    

sed R Command (Read Rfile)

  • 每次读取一行,首先读取#1,然后是#2,以此类推

sed w Command (write Wfile)

  • 把结果写入到文件,注意,只有Address部分会写入文件,AutoPrint只会打印到stdout
    test@openstest:~/sed-playground$ seq 2 | sed '1 w a.txt'
    1
    2
    test@openstest:~/sed-playground$ cat a.txt
    1
    test@openstest:~/sed-playground$ seq 2 | sed '2 w a.txt'
    1
    2
    test@openstest:~/sed-playground$ cat a.txt
    2
    

sed W Command(Write Wfile)

  • 每次把一行(从#1开始)数据写入到Wfile

Chapter 15: Read Line into PatSpace -nN

sed n Command (next line)

  • n命令就是把next line读取到PatSpace

sed N Command (Next Line)

  • 相比于n命令,N命令更加重要,它在next line读取到PatSpace之前,先加了一个newline 不过这个newline得靠l读取出来
    test@openstest:~/sed-playground$ seq 3 | sed -n 'l'
    1$
    2$
    3$
    test@openstest:~/sed-playground$ seq 3 | sed -n 'N;l'
    1\n2$
    
  • 值得注意的是,我们的命令在N读取完2就结束了.如果想继续,可能需要重启script的命 令,比如D
    $ seq 3 | sed -n 'N;l;D'
    1\n2$
    2\n3$
    

Chapter 16: Access HoldSpace -hH gG x