【问题标题】:Extract between two line numbers in same order以相同的顺序在两个行号之间提取
【发布时间】:2016-01-20 14:31:53
【问题描述】:

我有一个包含一列的文件 1:

File 1
apple
pineapple
banana
cherry
kiwi
orange
mango
grape
watermelon

我需要在两个行号之间以相同的顺序提取行的内容,用制表符分隔。例如第 3 行到第 8 行的输出应该是:

Output (Forward)    
banana cherry kiwi orange mango grape

对于第 7 行到第 2 行,输出应为:

Output (reverse)    
mango orange kiwi cherry banana pineapple

我知道用 以正向顺序在行之间提取,但反向顺序有问题。

sed '3,8!d'  

【问题讨论】:

    标签: sed awk sed


    【解决方案1】:

    我会使用 awk:

    awk -v from="7" -v to="2" 'BEGIN{rev=from>to;s=rev?to:from;e=rev?from:to}
    NR>=s && NR<=e{r[NR]=$0}
    NR>e{
        while(from!=to){
            printf "%s\t",r[from]
            rev?--from:++from
        }
    print r[from]
    exit}' file
    
    • 使用这个 awk 脚本,您只需提供 fromto 变量。如果您提供了颠倒的数字,它会反向打印该范围内的行。将它嵌入到您的 shell 脚本中也很容易,以便从您的 shell 变量中接收 from, to
    • 脚本将在处理max(from,to) 行后中断。例如,如果你的文件有 500 万行,你给 from:2, to:7 脚本只会处理到第 7 行。

    对您的输入进行一些测试:

    kent$  cat f
    apple
    pineapple
    banana
    cherry
    kiwi
    orange
    mango
    grape
    watermelon
    
    kent$  awk -v from="2" -v to="7" 'BEGIN{rev=from>to;s=rev?to:from;e=rev?from:to}
    NR>=s && NR<=e{r[NR]=$0}
    NR>e{
            while(from!=to){
                    printf "%s\t",r[from]
                    rev?--from:++from
            }
    print r[from]
    exit}' f
    pineapple       banana  cherry  kiwi    orange  mango
    
    kent$  awk -v from="7" -v to="2" 'BEGIN{rev=from>to;s=rev?to:from;e=rev?from:to}          
    NR>=s && NR<=e{r[NR]=$0}
    NR>e{
            while(from!=to){
                    printf "%s\t",r[from]
                    rev?--from:++from
            }
    print r[from]
    exit}' f
    mango   orange  kiwi    cherry  banana  pineapple
    

    【讨论】:

      【解决方案2】:
      $ cat tst.awk
      BEGIN {
          OFS="\t"
          if (beg < end) { min=beg; max=end; delta=+1 }
          else           { min=end; max=beg; delta=-1 }
      }
      NR >= min { a[NR] = $0 }
      NR == max {
          for (i=beg; i!=end; i+=delta) {
              printf "%s%s", a[i], OFS
          }
          print a[end]
          exit
      }
      
      $ awk -v beg=3 -v end=8 -f tst.awk file
      banana  cherry  kiwi    orange  mango   grape
      
      $ awk -v beg=7 -v end=2 -f tst.awk file
      mango   orange  kiwi    cherry  banana  pineapple
      

      【讨论】:

        【解决方案3】:

        我会使用

        sed '2,7!d' file1 | tac
        

        tac 只是反向(按行)重复给出的内容。

        至于制表符分隔的部分,使用 sed 有很多方法可以做到这一点。其中之一是

        sed '2,7!d' | tac | sed '1h; 1!H; $!d; x; s/\n/\t/g'
        

        这会在保持缓冲区中组装完整的输入,然后将其交换到模式空间中并用制表符替换其中的所有换行符:

        1h          # first line: save to hold buffer
        1!H         # subsequent lines: append to hold buffer
        $!d         # if more input is to read, stop here (don't print anything)
        x           # otherwise: swap in assembled lines
        s/\n/\t/g   # replace newlines with tabs.
        

        您也可以考虑在此步骤中使用 tr,但尾随的换行符使这不像最初想象的那么简单。

        或者,您可以使用 sed 一次性完成所有操作:

        sed '2,7 { G; x; }; $!d; x; s/\n$//; s/\n/\t/g' file1
        

        这有点棘手:

        2,7 {                  # In lines 2 to 7:
          G                    # Append the hold buffer to the pattern space
                               # this is originally a blank line and later the reverse
                               # of the lines already read
          x                    # then swap it back into the hold buffer
        }
        $!d                    # If the input has not ended, stop here (print nothing)
        x                      # When the whole input is consumed, swap the assembled
                               # reverse lines back in
        s/\n$//                # remove the trailing newline
        s/\n/\t/g              # then replace the newlines with tabs
        

        这有点折腾哪种方法更好。后者在使用 sed 方面仍然有些理智,但更复杂的 sed 脚本的蝙蝠侠解码器环属性已经显示出来。坦率地说,我很伤心,因为我对 sed 情有独钟,在这种情况下考虑放弃 sed 以换取更长但更易读的替代方案(例如awk)并不是一个坏主意:

        awk 'NR == 2, NR == 7 { result = $0 sep result; sep = "\t" } END { print result }' file1
        

        【讨论】:

        • 我在第一次阅读问题时忽略了 reverse order 要求。 tac 是个好办法!
        【解决方案4】:

        以相反的顺序处理行是sed 不适合的任务。由于它作为 处理器的特性,它被设计为按正序处理行。

        我强烈建议使用awk。虽然基本上即使awk 不提供以相反顺序处理输入文件的功能,但它提供了编程语言功能来缓冲感兴趣的行并在到达停止线后以相反的顺序打印它们:

        script.awk:

        BEGIN {
            reverse = 0
            if(start>stop) {
                reverse = 1
                start_ = start
                start = stop
                stop = start_
            }
        }
        
        NR>=start && NR<=stop {
            buf[NR]=$0
        }
        
        NR==stop{
            if(!reverse) {
                for(i=start;i<=stop;i++) {
                    printf "%s ",buf[i]
                }
            } else {
                for(i=stop;i>=start;i--) {
                    printf "%s\t",buf[i]
                }
            }
            printf "\n"
            exit(0)
        }
        

        这样称呼它:

        awk -vstart=4 -vstop=9 -f script.awk input.file
        

        awk -vstart=3 -vstop=8 -f script.awk input.file
        

        除了awk,您可以使用任何其他您想要的编程语言。

        【讨论】:

        • 最好考虑在NR==stop 时退出,但是我认为您应该将END{} 块中的代码移动到NR==stop。或者除非用户给出max(start,stop) == lastLine,否则没有输出。 END{...} 实际上没有必要。
        • 我认为你的sed | tac 行不行。你在tac 得到它之前组装了这条线,所以tac 只有一条线要反转。 tacsed 之前会遇到在不知道会有多少行输入的情况下选择行的问题,所以如果要使用tac,我认为没有办法绕过sed | tac | sed
        • @Kent awk 将处理END即使在显式调用exit() 之后,程序仍将运行。但是,你是对的,我可以重构它并合并 NR==stopEND 块。 (编辑)
        • 我看到的是空格而不是制表符,这与问题不同,但足够接近/易于修改。 tr 也会用制表符替换尾随的换行符,这可能是也可能不是问题。无论如何,这就是为什么我在那里放了一个 sed 的东西。
        • @Wintermute sed 解决方案将添加一个附加选项卡,这是真的。但是,一般的答案应该是:“使用 awk”,因为 sed 本质上根本无法解决此类问题:它是一个 stream 编辑器。也许我应该在我的回答中更加强调这一点。
        猜你喜欢
        • 1970-01-01
        • 2014-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多