【问题标题】:Apply an gawk script to multiple files in a folder将 gawk 脚本应用于文件夹中的多个文件
【发布时间】:2017-02-09 18:04:40
【问题描述】:

我想使用以下 awk 行来删除文本文件中的每个偶数行(并保留奇数行)。

awk 'NR%2==1' filename.txt > output

问题是我很难在 awk 中正确循环或构建一个 shell 脚本以将其应用于文件夹中的所有 *.txt 文件。我试着用这个单线

gawk 'FNR==1{if(o)close(o);o=FILENAME;
sub(/\.txt/,"_oddlines.txt",o)}{NR%2==1; print>o}'  

但这并没有删除偶数行。而且我对 shell 脚本更不熟悉。我在win7cygwin 下使用gawkbash。非常感谢任何想法。

【问题讨论】:

    标签: bash awk


    【解决方案1】:

    您现有的 gawk one-liner 非常接近。这里它被格式化为更易读的脚本:

    FNR == 1 {
        if (o)
            close(o)
        o = FILENAME
        sub(/\.txt/, "_oddlines.txt", o)
    }
    {
        NR % 2 == 1
        print > o
    }
    

    这应该使错误显而易见1。所以现在我们删除了那个错误:

    FNR == 1 {
        if (o)
            close(o)
        o = FILENAME
        sub(/\.txt/, "_oddlines.txt", o)
    }
    NR % 2 == 1 {
        print > o
    }
    
    $ awk -f foo.awk *.txt
    

    它可以工作(当然你可以重新单行化它)。

    (通常我会像其他答案一样使用for 来执行此操作,但我想向您展示您有多接近!)


    1每条评论,可能不是很明显?

    Awk 的基本语言结构是“模式-动作”语句。 awk program 只是这些语句的列表。 “模式”之所以如此命名,是因为最初它们大多是类似 grep 的正则表达式模式:

    $ awk '/^be.*st$/' < /usr/share/dict/web2
    beanfeast
    beast
    [snip]
    

    (除了斜线,这基本上只是运行grep,因为它使用默认操作print。)

    模式实际上可以包含两个地址,但更典型的是使用一个,例如在这些情况下。不包含在斜线内的模式允许像 FNR == 1File-specific Nnumber of this Record 等于 1)或 NR % 2 == 1Nnumber of this Record—cumulative跨所有文件!-mod 2 等于 1)。

    不过,一旦您打开大括号,您就进入了“动作”部分。现在NR % 2 == 1 只需计算结果(真或假),然后将其丢弃。如果您完全省略“模式”部分,则“动作”部分将在 每个 输入行上运行。所以这会打印每一行。

    请注意,测试NR % 2 == 1 正在测试累积 记录号。因此,如果某个文件的行数为奇数(“记录”),则下一个文件将打印出每一行 偶数 行(这将持续到您遇到另一个行数为奇数的文件)。

    例如,假设两个输入文件是A.txtB.txt。 awk 开始读取A.txt,并将第一行的FNRNR 都设置为1,例如file A, line 1。由于FNR == 1 第一个“动作”已经完成,设置o。然后 awk 测试第二个模式。 NR 是 1,所以 NR % 2 是 1,所以第二个“动作”完成,将该行打印到 A_oddlines.txt

    现在假设文件A.txt 只包含那一行。 awk 现在继续归档B.txt,重置FNR,但留下NR 累积。 B 的第一行可能是 file B, line 1。 awk 尝试第一个“模式”,实际上是FNR == 1,所以这会关闭旧的o 并设置新的。

    但是NR2,因为NR所有 输入文件中是累积的。所以第二个模式(NR % 2 == 1)计算2 % 2(即0)并比较== 1,这是错误的,因此awk跳过文件B.txt的第1行的第二个“动作”。第 2 行,如果存在,将有 FNR == 2NR == 3,因此该行将被复制出来。

    (我最初认为,由于您的脚本接近工作,您打算这样做并且只是在语法上卡住了一点。)

    【讨论】:

    • 感谢它有效,我看到了错误,但我并不真正理解它。你介意举个例子吗。谢谢。
    • 您好 torek,感谢您的出色解释。我为下一个 awk 问题学到了一些东西!
    【解决方案2】:

    使用GNU awk,您可以这样做:

    $ awk 'FNR%2{print > (FILENAME".odd")}' *.txt
    

    这将为当前目录中仅包含奇数行的每个 .txt 文件创建一个 .odd 文件。


    但是sed 在简洁性方面占了上风。以下GNU sed 命令将删除所有偶数行并将扩展名为.bck 的旧文件存储在当前目录中的所有.txt 文件中:

    $ sed -ni.bck '1~2p' *txt
    

    演示:

    $ ls
    f1.txt  f2.txt
    
    $ cat f1.txt
    1
    2
    3
    4
    5
    
    $ cat f2.txt
    6
    7
    8
    9
    10
    
    $ sed -ni.bck '1~2p' *txt
    
    $ ls
    f1.txt  f1.txt.bck  f2.txt  f2.txt.bck
    
    $ cat f1.txt
    1
    3
    5
    
    $ cat f1.txt.bck
    1
    2
    3
    4
    5
    
    $ cat f2.txt
    6
    8
    10
    
    $ cat f2.txt.bck
    6
    7
    8
    9
    10
    

    如果您不备份文件,那么只需:

    $ sed -ni '1~2p' *txt
    

    【讨论】:

    • 感谢 sed 的解决方案。我用 awk 解决了这个问题,这更简单,因为我已经使用了 awk。
    • 发布的 awk 脚本不会从每个文件中选择奇数行,它会在所有文件中选择奇数行。如果前 2 个文件都有 3 行,那么它将从 file1 中选择第 1 行和第 3 行,但从 file2 中选择第 2 行。您需要在测试中使用 FNR,而不是 NR。此外,ENDFILE 是 gawk 特定的,但如果您使用的是 gawk,则无需在更改文件时关闭文件。最后,你不需要变量 f 因为你可以只做 print &gt; (FILENAME".odd") 但为了提高效率你可以保留它但只在 FNR==1 时设置它,我会做 'FNR==1{if(f)close(f); f=FILENAME".odd"} FNR%2{print &gt; f}'
    • @EdMorton 我最初发布FNR 不确定我更改了什么。我确实使用GNU awkgawk 声明仍然限制打开文件的数量,对吗?您不需要变量,但这是一种很好的做法,尽管只在 BEGINFILE 中初始化是有意义的。
    • 不,gawk 不限制打开文件的数量,它会在内部神奇地处理它。我知道你说的是 GNU awk,我只是说因为它是 GNU awk,所以你不需要使用 ENDFILE 来关闭文件。
    • @EdMorton 在这种情况下从头开始处理文件。
    【解决方案3】:

    就个人而言,我会使用

    for filename in *.txt; do
        awk 'NR%2==1' "$filename" > "oddlines-$filename"
    done
    

    编辑:引用文件名

    【讨论】:

    • 感谢它有效,但我已经用 awk 脚本解决了这个问题。你的也试过了,以后会用的。
    • 不会选择每个文件中的奇数行,并且会因各种文件名而神秘地失败。
    • 更新了“各种文件名”问题,但 Ed Morton 在第一个问题上是错误的
    【解决方案4】:

    你可以试试 for 循环:

    #!/bin/bash
    
    for file in dir/*.txt
    do    
       oddfile=$(echo "$file" | sed -e 's|\.txt|_odd\.txt|g')  #This will create file_odd.txt
       awk 'NR%2==1' "$file" > "$oddfile"  # This will output it in the same dir.
    done
    

    【讨论】:

    • 你不需要逃避。在替换字符串中,-e 和 g 是多余的 sed 's/[.]txt$/_odd.txt/'
    【解决方案5】:

    您的问题是NR%2==1{NR%2==1; print&gt;o} '动作块' 内并且没有作为'条件'启动。改用这个:

    gawk 'FNR==1{if(o)close(o);o=FILENAME;sub(/\.txt/,"_oddlines.txt",o)};
         FNR%2==1{print > o}' *.txt
    

    【讨论】:

    • 非常感谢您的回答。它完美地完成了这项工作。但是,如果它对您来说很明显,我不完全理解为什么 NR%2==1 必须在大括号之外。我认为 awk 始终具有可能的 BEGIN 和 END 的主要部分。它工作得很好,但不会将NR$2==1 留在其中一个部分之外吗?抱歉,如果这看起来是一个无聊的问题。谢谢。
    • @greta,好问题。这是basic introduction awk 的工作原理,这应该可以让您了解基本概念。
    • 差不多,把NR%2改成FNR%2就行了。
    猜你喜欢
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 2013-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多