这是我的可重现的例子
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-