2017-03-20 3 views
0

Meine Dateien aussehenWie Dateien Zeile für Zeile in bash

file0  file1  file2 
a   1   ## 
a   1   ## 

b   2   @@ 
b   2   @@ 

und ich möchte verschmelzen diese Dateien Linien durch Linien zu verbinden, so dass es ich meine, wie

merged file 
a 
a  
1 
1 
## 
## 

b  
b 
2 
2 
@@ 
@@   

aussehen soll, wählen einige Zeilen für jede Datei und füge sie in eine Datei zusammen. Ich versuchte unter Bash-Skript.

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 

Es funktioniert gut, aber dauert zu viel Zeit. Gibt es einen effizienten Weg?

+0

siehe https://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice zur Diskussion auf Shell-Schleifen verwenden, um Text zu verarbeiten – Sundeep

+0

@Sundeep wahrscheinlich nicht verwandt. – Socowi

+0

Haben alle Eingabedateien dieselbe Anzahl von Zeilen? – anubhava

Antwort

0

limitierende Faktoren

Ihr Skript hatte zwei Fehler, die es langsam gemacht:

  • Viele Dateien wurden erstellt und kopiert. Vor allem die ... > tmp; cat ini.dat tmp > tmpp; cp tmpp ini.dat könnte als ... >> ini.dat geschrieben worden sein.

  • Um die i -te Zeile einer Datei zu lesen, muss das Skript die Datei von Anfang an scannen, bis die i -te Zeile erreicht ist. Wenn wiederholt getan i = 1, 2, 3, ..., n wird es dauern O (n). Lesen der gesamten Datei einmal (O ( n)) in einem Array und accesing die Zeilen durch Indizes ( O (1)), nur nimmt O (n).

Reiner Bash Lösung

Der folgende Bash-Skript macht den Job ein bisschen schneller. linesPerBlock entspricht dem Parameter n aus Ihrem Skript. Das Skript druckt so viele Blöcke wie möglich. Das ist:

  • Sobald die kürzeste Eingabedatei gedruckt wurde, wird das Skript beendet. Folgende Zeilen von längeren Dateien werden nicht gedruckt.
  • Wenn die kürzeste Eingabedatei die Anzahl der Zeilen von n nicht teilbar ist, die letzten Zeilen (weniger als n) wird verzichtet.
#! /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 
0

Diese awk sollte die Arbeit tun und wird viel schneller, dass Ihre Shell-Skript sein:

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 
@@ 
@@ 

In einer einzigen Zeile:

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} 
0

hier ist eine andere awk, nicht alle Inhalte Cachen

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()}' 

Wenn die Anzahl der Zeilen nicht durch teilbar ist n, es wird sich mit leeren Zeilen füllen.

+0

Gute Idee, 'Paste' zu ​​verwenden. Aber muss man nicht sicherstellen, dass man '\ t' in keiner der Dateien benutzt? – Socowi

+0

In diesem Fall sollten Sie Ihr eigenes Trennzeichen auswählen und FS damit festlegen. – karakfa

Verwandte Themen