如何在bash中逐行合并文件

时间:2017-03-20 14:07:53

标签: bash

我的文件看起来像

file0      file1      file2  
a           1          ##
a           1          ##

b           2          @@
b           2          @@

我想逐行合并这些文件,所以看起来应该是

merged file  
a   
a       
1
1
##
##

b    
b
2
2
@@
@@        

我的意思是,为每个文件选择一些行并将它们合并到一个文件中。 我试过下面的bash脚本。

touch ini.dat
n=2
linenum=$(wc -l < file0)
iter=$((linenum/n))

for i in $(seq 0 1 $iter)
do
    for j in $(seq 0 1 2)
    do
            awk 'NR > '$(($i*$n))' && NR <= '$((($i+1)*$n))'' file"$j" > tmp
            cat ini.dat tmp > tmpp
            cp tmpp ini.dat
            rm tmpp
    done
done

它工作正常,但需要太多时间。有没有有效的方法?

3 个答案:

答案 0 :(得分:0)

这个awk应该可以胜任你的shell脚本:

awk 'fn != FILENAME {
   fn = FILENAME
   n = 1
}
NF {
   a[FILENAME,n++] = $0
}
END {
   for(i=0; i<(n-1)/2; i++) {
      for(j=1; j<ARGC; j++)
         printf "%s\n%s\n", a[ARGV[j],i*2+1], a[ARGV[j],i*2+2];
      print ""
   }
}' file{0..2}

a
a
1
1
##
##

b
b
2
2
@@
@@

单行:

awk 'fn != FILENAME{fn=FILENAME; n=1} NF{a[FILENAME,n++]=$0} END{for(i=0; i<(n-1)/2; i++) { for(j=1; j<ARGC; j++) printf "%s\n%s\n", a[ARGV[j],i*2+1], a[ARGV[j],i*2+2]; print "" } }' file{0..2}

答案 1 :(得分:0)

这是另一个awk,而不是缓存所有内容

paste file{0..2} | awk -v n=2 '
                   function pr() {for(j=1;j<=NF;j++)
                                    for(i=0;i<n;i++) print a[i,j]}
                       {for(j=1;j<=NF;j++) a[c+0,j]=$j; c++}
               !(NR%n) {pr(); delete a; c=0}
                   END {pr()}'

如果行数不能被n整除,则会填充空行。

答案 2 :(得分:0)

限制因素

你的脚本有两个缺陷导致它变​​慢:

  • 创建并复制了很多文件。特别是... > tmp; cat ini.dat tmp > tmpp; cp tmpp ini.dat可以写成... >> ini.dat

  • 要读取文件的 i 行,脚本必须从头开始扫描该文件,直到到达 i 行。如果重复完成 i = 1,2,3,..., n ,则需要 O n 2 )。将整个文件( O n ))读入数组并通过索引( O (1))访问行只需要< EM> 0 的(名词的)。

Pure Bash Solution

以下bash脚本可以更快地完成工作。 linesPerBlock对应于脚本中的参数n。脚本将打印尽可能多的块。那就是:

  • 打印出最短的输入文件后,脚本将终止。来自较长文件的以下行将不会被打印。
  • 如果最短输入文件的行数不能被 n 整除,则最后一行(少于 n )将被省略。
#! /bin/bash

files=(file{0..2})
linesPerBlock=2

starts=(0)
maxLines=9223372036854775807 # bash's max. number
for i in "${!files[@]}"; do
    lineCount="$(wc -l < "${files[i]}")"
    (( lineCount < maxLines )) && (( maxLines = lineCount ))
    (( starts[i+1] = starts[i] + maxLines ))
    mapfile -t -O "${starts[i]}" -n "$maxLines" lines < "${files[i]}"
done

for (( b = 0; b < maxLines / linesPerBlock; ++b )); do
    for f in "${!files[@]}"; do
        start="${starts[f]}"
        for (( i = 0; i < linesPerBlock; ++i )); do
            echo "${lines[start + b*linesPerBlock + i]}"
        done
    done
done > outputFile