如何对三个文件执行“分组合并”,同时省略每个文件的前几行?

如何对三个文件执行“分组合并”,同时省略每个文件的前几行?

这是我的可重现的例子

  • file01.txt

    line to skip
    line to skip
    line to skip
    line to keep file 01
    heading 1 in the form: 2017243 01 2017243 01
    data 1 file 01
    heading 2 in the form: 2017243 02 2017243 02
    data 2 file 01
    heading 3 in the form: 2017243 03 2017243 03
    data 3 file 01
    
  • file02.txt

    line to skip
    line to skip
    line to skip
    line to keep file 02
    heading 1 in the form: 2017243 01 2017243 01
    data 1 file 02
    heading 2 in the form: 2017243 02 2017243 02
    data 2 file 02
    heading 3 in the form: 2017243 03 2017243 03
    data 3 file 02
    
  • file03.txt

    line to skip
    line to skip
    line to skip
    line to keep file 03
    heading 1 in the form: 2017243 01 2017243 01
    data 1 file 03
    heading 2 in the form: 2017243 02 2017243 02
    data 2 file 03
    heading 3 in the form: 2017243 03 2017243 03
    data 3 file 03
    
  • 期望的输出

    line to keep file 01
    line to keep file 02
    line to keep file 03
    heading 1 in the form: 2017243 01 2017243 01
    data 1 file 01
    data 1 file 02
    data 1 file 03
    heading 2 in the form: 2017243 02 2017243 02
    data 2 file 01
    data 2 file 02
    data 2 file 03
    heading 3 in the form: 2017243 03 2017243 03
    data 3 file 01
    data 3 file 02
    data 3 file 03
    

到目前为止,我已经完成了非常简单的任务,通过以下方式从每个输入文件中提取第四行:

awk 'FNR == 4' *.txt >> out_row4

但后来我陷入了文件处理的其余部分,无法构想出可行的最终解决方案......

我需要保持解决方案非常通用,因为文件数量和要处理的行非常大(每个文件超过 5900 行)

可供参考的一般模式:

  • 始终跳过每个文件的前 3 行
  • 保留每个文件的第 4 行
  • 标题 1、2、3(...等等)在不同文件中完全相同(因此只需在所需的输出文件中报告一次)
  • 所有文件包含相同的行数
  • 文件没有已知的结构化格式,它们是纯文本文件

要提取和重新排列的常见模式是:

heading n in the form: 2017243 n 2017243 n
data n file ...

有什么提示吗?

答案1

应用DSU 习语,使用任何版本的强制 POSIX 工具 awk、sort 和 cut:

$ cat tst.sh
#!/usr/bin/env bash

awk -v OFS='\t' '
    FNR == 1 { fileNr++ }
    FNR >= 4 { print FNR-3, fileNr, $0 }
' "${@:--}" |
sort -n -k1,1 -k2,2 |
awk '($1 % 2) || ($2 == 1)' |
cut -f 3-

$  ./tst.sh file01.txt file02.txt file03.txt
line to keep file 01
line to keep file 02
line to keep file 03
heading 1 in the form: 2017243 01 2017243 01
data 1 file 01
data 1 file 02
data 1 file 03
heading 2 in the form: 2017243 02 2017243 02
data 2 file 01
data 2 file 02
data 2 file 03
heading 3 in the form: 2017243 03 2017243 03
data 3 file 01
data 3 file 02
data 3 file 03

上面唯一必须立即处理所有输入的工具是sort设计用于通过使用需求分页等来处理大量输入的,因此无论您有多少个输入文件(只要它们当然不要超过 ARG_MAX)或它们有多大。

或者,使用任何 awk 并假设输入文件的数量不足以产生“打开文件过多”错误:

$ cat tst.awk
BEGIN {
    while ( ! eof ) {
        for ( fileNr=1; fileNr<ARGC; fileNr++ ) {
            if ( (getline vals[fileNr] < ARGV[fileNr]) <= 0 ) {
                eof = 1
            }
        }
        if ( !eof && (++lineNr >= 4) ) {
            if ( lineNr % 2 ) {
                print vals[1]
            }
            else {
                for ( fileNr=1; fileNr<ARGC; fileNr++ ) {
                    print vals[fileNr]
                }
            }
        }
    }
    exit
}

$ awk -f tst.awk file01.txt file02.txt file03.txt
line to keep file 01
line to keep file 02
line to keep file 03
heading 1 in the form: 2017243 01 2017243 01
data 1 file 01
data 1 file 02
data 1 file 03
heading 2 in the form: 2017243 02 2017243 02
data 2 file 01
data 2 file 02
data 2 file 03
heading 3 in the form: 2017243 03 2017243 03
data 3 file 01
data 3 file 02
data 3 file 03

getline在上面谨慎使用以避免一次将大部分输入文件读入内存,请参阅http://awk.freeshell.org/AllAboutGetline有关何时/如何使用它的更多信息。

答案2

我确实将您上面给出的模式保存在三个文件中。我通过这种方式得到了完成 awk 过滤所需的输出:

for i in {4..15}; do awk "FNR == $i" *.txt | sort -u; done

答案3

如果您不介意使用 awk 以外的其他工具:

for f in $(ls *.txt) ; do awk 'FNR >=4' $f | egrep "." -n ; done | sort -n | uniq | cut -d: -f2-

会成功的

解释:

  • for 循环将从每个文件中删除前 3 行(使用 awk)并对它们进行计数(使用 egrep -n 和任何 char 作为 grep 的条件)
  • 然后将按行号对输出进行排序
  • 然后将删除重复的标题行
  • 最后将删除行号

更新

我删除了egrep的使用,因为awk已经遍历了整个文件,它还可以将行号添加到输出中(避免读取文件两次)。

for f in $(ls *.txt) ; do awk 'FNR >=4 {printf("%s#%s\n", FNR-3, $0)}' $f ; done | sort -n | uniq | cut -d# -f2-

相关内容