【问题标题】:gnu sed - delete lines between first X and last Y linesgnu sed - 删除前 X 行和最后 Y 行之间的行
【发布时间】:2014-03-22 16:32:45
【问题描述】:

目标是缩短大文本:
删除前 X 行和最后 Y 行之间的所有内容
并且可能在中间插入一行“文件被截断为 XY 行...”。
我玩弄并通过奇怪的重定向(Pipe output to two different commands)、子shell、 tee 和多个 sed 调用,我想知道是否

sed -e '10q'

sed -e :a -e '$q;N;11,$D;ba'

可以通过将两者合并到一个 sed 调用中来简化。

提前致谢

【问题讨论】:

    标签: bash sed


    【解决方案1】:

    您还可以使用sed -u 5q(使用GNU sed)作为head -n5 的无缓冲替代品:

    $ seq 99|(sed -u 5q;echo ...;tail -n5)
    1
    2
    3
    4
    5
    ...
    95
    96
    97
    98
    99
    

    【讨论】:

      【解决方案2】:

      这是一个不需要知道文件长度的 sed 替代方案。

      您可以将修改后的“头”表达式插入“尾”表达式的滑动循环中。例如:

      sed ':a; 10s/$/\n...File truncated.../p; $q; N; 11,$D; ba'
      

      请注意,如果范围重叠,输出中会出现重复行。

      例子:

      seq 30 | sed ':a; 10s/$/\n...File truncated.../p; $q; N; 11,$D; ba'
      

      输出:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      ...File truncated...
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      

      这是一个注释的多行版本来解释发生了什么:

      :a                                   # loop label
      10s/$/\n...File truncated.../p       # on line 10, replace end of pattern space
      $q                                   # quit here when on the last line
      N                                    # read next line into pattern space
      11,$D                                # from line 11 to end, delete the first line of pattern space
      ba                                   # goto :a
      

      【讨论】:

      • 非常感谢,像魅力一样工作,重叠只是一个表面问题,将调查并尝试理解这种 sed 语法,从未使用过 ":a , N;"等之前使用 sed。
      • @layer23:不客气,我添加了一个注释版本作为解释。
      【解决方案3】:

      这可能对你有用(GNU sed):

      sed '1,5b;:a;N;s/\n/&/8;Ta;$!D;s/[^\n]*\n//;i\*** truncated file ***' file
      

      这里是x=5Y=8

      注意这样可以使短文件保持原样。

      【讨论】:

      • 非常感谢,这是雷神回答的增强版。一个小要求:如果 x 的计数在第一个空行之后开始,会有什么修改。这对于切割类似 rfc822 的纯文本电子邮件文件同时保持其标题完好无损非常方便。提前致谢!
      【解决方案4】:

      您可以通过 tee、进程替换和 stdio 重定向的神奇咒语来做到这一点:

      x=5 y=8
      seq 20 | { 
          tee >(tail -n $y >&2) \
              >({ head -n $x; echo "..."; } >&2) >/dev/null 
      } 2>&1
      
      1
      2
      3
      4
      5
      ...
      13
      14
      15
      16
      17
      18
      19
      20
      

      这个版本更顺序,输出要一致:

      x=5 y=8
      seq 20 | {
          { 
              # read and print the first X lines to stderr
              while ((x-- > 0)); do 
                  IFS= read -r line 
                  echo "$line" 
              done >&2
              echo "..." >&2  
              # send the rest of the stream on stdout
              cat - 
          } |
          # print the last Y lines to stderr, other lines will be discarded
          tail -n $y >&2
      } 2>&1
      

      【讨论】:

      • Glenn:我的第三个答案正朝着这个方向前进,但不确定如何保证“头”和“尾”的输出顺序。您似乎已经合并了 stdout 和 stderr 来实现这一点,但是我缺少什么微妙之处来保证顺序?
      • 不知道能不能保证。也许>(sleep 1; tail -n $y) 会足够长,以至于“头”分支肯定会先完成。最安全的方法是写入文件。或者也许一个先进先出会更好......
      • 酷 - 我知道我使用文件是有原因的 :-)
      【解决方案5】:

      使用headtail

      (head -$X infile; echo Truncated; tail -$Y infile) > outfile
      

      或者awk

      awk -v x=$x -v y=$y '{a[++i]=$0}END{for(j=1;j<=x;j++)print a[j];print "Truncated"; for(j=i-y;j<=i;j++)print a[j]}' yourfile
      

      或者,如果如您所说,输入来自管道,则可以像这样使用 tee 进行进程替换:

      yourcommand | tee >(head -$x > p1) | tail -$y > p2 ; cat p[12]
      

      【讨论】:

      • 感谢您的回答,不幸的是,第一种方法中的“head”在使用 stdin(需要管道)时吃掉了上游,sed 和 awk 方法显示第 1-x 行和第 y-$ 行;不是问题中提到的最后 y 行。
      • 抱歉,我从描述中没有意识到它正在接收来自管道的输入。
      • 还有 tee 部分。
      【解决方案6】:

      如果你知道文件的长度

      EndStart=$(( ${FileLen} - ${Y} + 1))
      sed -n "1,${X} p
      ${X} a\\
       --- Truncated part ---
      ${EndStart},$ p" YourFile
      

      【讨论】:

        猜你喜欢
        • 2019-02-25
        • 1970-01-01
        • 2019-05-18
        • 1970-01-01
        • 2020-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-11
        相关资源
        最近更新 更多