【问题标题】:Process a line based on lines before and after in bash根据bash中的前后行处理一行
【发布时间】:2017-02-03 04:39:25
【问题描述】:

我试图弄清楚如何编写一个 bash 脚本,它使用一行之前和之后的行作为条件。我将给出一个对我有意义的类似 python 的伪代码示例。

基本上:

for line in FILE:
    if line_minus_1 == line_plus_one:
        line = line_minus_1

最好的方法是什么?

所以如果我有一个输入文件如下:

3
1
1
1
2
2
1
2
1
1
1
2
2
1
2

我的输出是:

3
1
1
1
2
2
2
2
1
1
1
2
2
2
2

请注意,它从第一行开始到最后一行,并尊重之前行中所做的更改,所以如果我有:

2
1
2
1
2
2

我会得到:

2
2
2
2
2
2

而不是:

2
1
1
1
2
2

【问题讨论】:

    标签: bash readlines


    【解决方案1】:
    $ awk 'minus2==$0{minus1=$0} NR>1{print minus1} {minus2=minus1; minus1=$0} END{print minus1}' file
    3
    1
    1
    1
    2
    2
    2
    2
    1
    1
    1
    2
    2
    2
    2
    

    工作原理

    • minus2==$0{minus1=$0}

      如果前 2 行的行与当前行相同,则设置前 1 行的行等于当前行。

    • NR>1{print minus1}

      如果我们超过了第一行,则打印 1 行之前的行。

    • minus2=minus1; minus1=$0

      更新变量。

    • END{print minus1}

      读完文件后,打印最后一行。

    多行版本

    对于那些喜欢多行代码的人:

    awk '
        minus2==$0{
            minus1=$0
        }
    
        NR>1{
            print minus1
        }
    
        {
            minus2=minus1
            minus1=$0
        }
    
        END{
            print minus1
        }
        ' file
    

    【讨论】:

    • 没错! awk 非常棒
    【解决方案2】:

    这是一个 (GNU) sed 解决方案:

    $ sed -r '1N;N;/^(.*)\n.*\n\1$/s/^(.*\n).*\n/\1\1/;P;D' infile
    3
    1
    1
    1
    2
    2
    2
    2
    1
    1
    1
    2
    2
    2
    2
    

    这适用于移动的三行窗口。更具可读性:

    sed -r '        # -r for extended regular expressions: () instead of \(\)
        1N          # On first line, append second line to pattern space
        N           # On all lines, append third line to pattern space
        /^(.*)\n.*\n\1$/s/^(.*\n).*\n/\1\1/    # See below
        P           # Print first line of pattern space
        D           # Delete first line of pattern space
    ' infile
    

    N;P;D 是获取移动两行窗口的惯用方式:追加一行、打印第一行、删除模式空间的第一行。为了获得一个移动的三行窗口,我们读取了另外一行,但只读取了一次,即在处理第一行时 (1N)。

    复杂的一点是检查模式空间的第一行和第三行是否相同,如果相同,则将第二行替换为第一行。为了检查我们是否必须进行替换,我们使用地址

    /^(.*)\n.*\n\1$/
    

    锚点^$ 并不是真正需要的,因为我们在模式空间中总是有精确的换行符,但它更清楚地表明我们想要匹配完整的模式空间。我们将第一行放入一个捕获组,并使用反向引用查看它是否在第三行重复。

    然后,如果是这种情况,我们执行替换

    s/^(.*\n).*\n/\1\1/
    

    这会捕获包含换行符的第一行,匹配包含换行符的第二行,并用第一行的两倍替换。 PD 然后打印并删除第一行。

    当到达结尾时,整个模式空间都会被打印出来,所以我们不会吞下任何行。

    这也适用于第二个输入示例:

    $ sed -r '1N;N;/^(.*)\n.*\n\1$/s/^(.*\n).*\n/\1\1/;P;D' infile2
    2
    2
    2
    2
    2
    2
    

    要使用 BSD sed(在 OS X 中),您要么必须使用 -E 而不是 -r 选项,要么不使用选项,即基本正则表达式并转义所有括号 ( \(\)) 在捕获组中。换行符匹配应该可以,但我没有测试它。如有疑问,请检查this great answer 列出所有差异。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-25
      • 2013-03-11
      • 1970-01-01
      相关资源
      最近更新 更多