Wie man mit while-Schleife aus zwei Eingabedateien liest

27

Ich wollte wissen, ob es eine Möglichkeit gibt, aus zwei Eingabedateien in einer verschachtelten while-Schleife jeweils eine Zeile zu lesen. Nehmen wir zum Beispiel an, ich habe zwei Dateien FileAund FileB.

Datei A:

[jaypal:~/Temp] cat filea
this is File A line1
this is File A line2
this is File A line3

Datei B:

[jaypal:~/Temp] cat fileb
this is File B line1
this is File B line2
this is File B line3

Aktuelles Beispielskript:

[jaypal:~/Temp] cat read.sh 
#!/bin/bash
while read lineA
    do echo $lineA 
    while read lineB
        do echo $lineB 
        done < fileb
done < filea

Ausführung:

[jaypal:~/Temp] ./read.sh 
this is File A line1
this is File B line1
this is File B line2
this is File B line3
this is File A line2
this is File B line1
this is File B line2
this is File B line3
this is File A line3
this is File B line1
this is File B line2
this is File B line3

Problem und gewünschte Ausgabe:

Dadurch wird FileB für jede Zeile in FileA vollständig durchlaufen. Ich habe versucht, continue, break, exit zu verwenden, aber keines von ihnen ist für das Erreichen der gewünschten Ausgabe gedacht. Ich möchte, dass das Skript nur eine Zeile aus Datei A und dann eine Zeile aus Datei B liest, die Schleife verlässt und mit der zweiten Zeile von Datei A und der zweiten Zeile von Datei B fortfährt.

[jaypal:~/Temp] cat read1.sh 
#!/bin/bash
count=1
while read lineA
    do echo $lineA 
        lineB=`sed -n "$count"p fileb`
        echo $lineB
        count=`expr $count + 1`
done < filea

[jaypal:~/Temp] ./read1.sh 
this is File A line1
this is File B line1
this is File A line2
this is File B line2
this is File A line3
this is File B line3

Ist das mit while-Schleife möglich?

Jaypal Singh
quelle
Eine großartige Lösung von @codaddict ist hier: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Antworten:

32

Wenn Sie sicher sind, dass ein Zeichen niemals in der ersten Datei vorkommt, können Sie Einfügen verwenden.

Beispiel für das Einfügen unter Verwendung der Standardbegrenzerregisterkarte:

paste file1 file2 | while IFS="$(printf '\t')" read -r f1 f2
do
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done

Beispiel für eine Paste mit @:

paste -d@ file1 file2 | while IFS="@" read -r f1 f2
do
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done

Beachten Sie, dass es ausreicht, wenn das Zeichen garantiert nicht in der ersten Datei vorkommt. Dies readwird ignoriert, IFSwenn die letzte Variable gefüllt wird. Selbst wenn @es in der zweiten Datei vorkommt, wird es nicht aufgeteilt.

Beispiel für das Einfügen mit einigen Bash-Funktionen für wohl saubereren Code:

while IFS=$'\t' read -r f1 f2
do
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done < <(paste file1 file2)

Verwendete Bash-Funktionen: ansi c string ( $'\t') und process substitution ( <(...)), um die while-Schleife in einem Subshell-Problem zu vermeiden .

Wenn Sie nicht sicher sind, dass in beiden Dateien niemals ein Zeichen vorkommt, können Sie Dateideskriptoren verwenden .

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  printf 'f1: %s\n' "$f1"
  printf 'f2: %s\n' "$f2"
done 3<file1 4<file2

Nicht viel getestet. Könnte in leeren Zeilen brechen.

Die Dateideskriptoren 0, 1 und 2 werden bereits für stdin, stdout und stderr verwendet. Dateideskriptoren ab 3 Jahren sind (in der Regel) kostenlos. Das Bash-Handbuch warnt davor, Dateideskriptoren zu verwenden, die größer als 9 sind, da sie "intern verwendet" werden.

Beachten Sie, dass offene Dateideskriptoren an Shell-Funktionen und externe Programme vererbt werden. Funktionen und Programme, die einen geöffneten Dateideskriptor erben, können aus dem Dateideskriptor lesen (und in ihn schreiben). Sie sollten darauf achten, alle nicht benötigten Dateideskriptoren zu schließen, bevor Sie eine Funktion oder ein externes Programm aufrufen.

Hier ist dasselbe Programm wie oben, wobei die eigentliche Arbeit (das Drucken) von der Metaarbeit getrennt ist (zeilenweise aus zwei parallelen Dateien lesen).

work() {
  printf 'f1: %s\n' "$1"
  printf 'f2: %s\n' "$2"
}

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  work "$f1" "$f2"
done 3<file1 4<file2

Jetzt tun wir so, als hätten wir keine Kontrolle über den Arbeitscode und dieser Code versucht, aus welchem ​​Grund auch immer, aus Dateideskriptor 3 zu lesen.

unknowncode() {
  printf 'f1: %s\n' "$1"
  printf 'f2: %s\n' "$2"
  read -r yoink <&3 && printf 'yoink: %s\n' "$yoink"
}

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  unknowncode "$f1" "$f2"
done 3<file1 4<file2

Hier ist eine Beispielausgabe. Beachten Sie, dass die zweite Zeile aus der ersten Datei aus der Schleife "gestohlen" wird.

f1: file1 line1
f2: file2 line1
yoink: file1 line2
f1: file1 line3
f2: file2 line2

So schließen Sie die Dateideskriptoren, bevor Sie externen Code (oder einen anderen Code) aufrufen.

while true
do
  read -r f1 <&3 || break
  read -r f2 <&4 || break
  # this will close fd3 and fd4 before executing anycode
  anycode "$f1" "$f2" 3<&- 4<&-
  # note that fd3 and fd4 are still open in the loop
done 3<file1 4<file2
Lesmana
quelle
17

Öffnen Sie die beiden Dateien in unterschiedlichen Dateideskriptoren . Leiten Sie die Eingabe des readeingebauten in den Deskriptor um, mit dem die gewünschte Datei verbunden ist. In bash / ksh / zsh können Sie read -u 3anstelle von schreiben read <&3.

while IFS= read -r lineA && IFS= read -r lineB <&3; do
  echo "$lineA"; echo "$lineB"
done <fileA 3<fileB

Dieses Snippet stoppt, wenn die kürzeste Datei verarbeitet wurde. Siehe Lesen von zwei Dateien in eine IFS while-Schleife - Gibt es in diesem Fall eine Möglichkeit, ein Null-Diff-Ergebnis zu erhalten? Wenn Sie die Verarbeitung bis zum Ende beider Dateien fortsetzen möchten.

Siehe auch Wann würden Sie einen zusätzlichen Dateideskriptor verwenden? für zusätzliche Informationen zu Dateideskriptoren, und warum wird `while IFS = read` so oft verwendet, anstatt` IFS =; während gelesen..`? für eine Erklärung von IFS= read -r.

Gilles 'SO - hör auf böse zu sein'
quelle
Vielen Dank an @Gilles für die zusätzlichen Links zum Dateideskriptor.
Jaypal Singh
@ Gilles vielleicht habe ich dich missverstanden, aber ich konnte den Schleifenprozess nicht zur längsten Datei machen (in meinem Fall immer $ fileA), also habe ich das zu einer separaten Frage gemacht: Gibt es eine Möglichkeit, die Schleife so zu schreiben? das diff merkt keinen unterschied zwischen eingang und ausgang? unix.stackexchange.com/questions/26780/… das engste, was ich bekommen konnte, war diff nur eine Zeile Unterschied zu finden.
ixtmixilix
3

Ich weiß, Sie möchten ein Shell-Skript, aber vielleicht möchten Sie sich den pasteBefehl ansehen .

Lutzky
quelle
Danke @lutzky. pasteist auch cool.
Jaypal Singh
2

Versuchen Sie den folgenden Befehl:

paste -d '\n' inp1.txt inp2.txt > outfile.txt
Shree
quelle
0

Alternativ können Sie die Datei auch in eine Array-Variable schlürfen, indem Sie jede Zeile der Datei mit dem Befehl mapfile von bash in das Array [line_of_file_index] einbinden. Ich bin mir jedoch nicht sicher, ob es nur für Bash3 höher oder Bash4 ist.

http://wiki.bash-hackers.org/commands/builtin/mapfile

Nikhil Mulley
quelle