Jak czytać z dwóch plików wejściowych za pomocą pętli while


27

Chciałem wiedzieć, czy jest jakiś sposób odczytu z dwóch plików wejściowych w zagnieżdżonej pętli while po jednej linii na raz. Załóżmy na przykład, że mam dwa pliki FileAi FileB.

Plik a:

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

Plik B:

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

Aktualny przykładowy skrypt:

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

Wykonanie:

[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 i pożądane wyjście:

Powoduje to całkowite zapętlenie pliku FileB dla każdej linii w FileA. Próbowałem użyć kontynuacji, przerwania, wyjścia, ale żaden z nich nie jest przeznaczony do uzyskania oczekiwanego rezultatu. Chciałbym, aby skrypt odczytał tylko jedną linię z pliku A, a następnie jedną linię z pliku B i opuścił pętlę i kontynuował z drugą linią pliku A i drugą linią pliku B. Coś podobnego do następującego skryptu -

[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

Czy można to osiągnąć za pomocą pętli while?


Świetne rozwiązanie autorstwa @codaddict jest tutaj: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Odpowiedzi:


32

Jeśli wiesz na pewno, że jakiś znak nigdy nie pojawi się w pierwszym pliku, możesz użyć wklejania.

Przykład wklejania przy użyciu domyślnej karty ogranicznika:

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

Przykład wklejania przy użyciu @:

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

Zauważ, że wystarczy, jeśli znak nie pojawi się w pierwszym pliku. Jest tak, ponieważ readzignoruje IFSpodczas wypełniania ostatniej zmiennej. Więc nawet jeśli @występuje w drugim pliku, nie zostanie podzielony.

Przykład wklejania przy użyciu niektórych funkcji bash dla prawdopodobnie czystszego kodu:

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

Zastosowane funkcje Bash: ansi c string ( $'\t') i proces substytucji ( <(...)), aby uniknąć pętli while w problemie podpowłoki .

Jeśli nie możesz być pewien, że w obu plikach nigdy nie wystąpi żaden znak, możesz użyć deskryptorów plików .

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

Niewiele testowane. Może się zerwać na pustych liniach.

Deskryptory plików o numerach 0, 1 i 2 są już używane odpowiednio dla stdin, stdout i stderr. Deskryptory plików od 3 i więcej są (zwykle) darmowe. Podręcznik bash ostrzega przed użyciem deskryptorów plików większych niż 9, ponieważ są one „używane wewnętrznie”.

Zauważ, że otwarte deskryptory plików są dziedziczone po funkcjach powłoki i programach zewnętrznych. Funkcje i programy dziedziczące otwarty deskryptor pliku mogą odczytywać deskryptor pliku (i zapisywać do niego). Przed wywołaniem funkcji lub programu zewnętrznego należy zamknąć wszystkie deskryptory plików, które nie są wymagane.

Oto ten sam program jak powyżej z faktyczną pracą (druk) oddzieloną od meta-pracy (czytanie linii po linii z dwóch plików równolegle).

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

Teraz udajemy, że nie mamy kontroli nad kodem pracy i ten kod z jakiegokolwiek powodu próbuje odczytać z deskryptora pliku 3.

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

Oto przykładowy wynik. Zauważ, że druga linia z pierwszego pliku jest „skradziona” z pętli.

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

Oto, w jaki sposób należy zamknąć deskryptory plików przed wywołaniem zewnętrznego kodu (lub dowolnego innego kodu w tym zakresie).

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

17

Otwórz dwa pliki na różnych deskryptorach plików . Przekieruj wejście readwbudowanego do deskryptora, do którego podłączony jest żądany plik. W bash / ksh / zsh możesz pisać read -u 3zamiast read <&3.

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

Ten fragment kodu zatrzymuje się po przetworzeniu najkrótszego pliku. Zobacz Odczytywanie dwóch plików w pętli IFS while - Czy w takim przypadku można uzyskać wynik zerowej różnicy? jeśli chcesz kontynuować przetwarzanie do końca obu plików.

Zobacz także Kiedy użyjesz dodatkowego deskryptora pliku? w celu uzyskania dodatkowych informacji o deskryptorach plików i dlaczego tak często używa się opcji „while IFS = read” zamiast „IFS =; podczas czytania ... dla wyjaśnienia IFS= read -r.


Dzięki @Gilles za dodatkowe linki w deskryptorze pliku.
jaypal singh

@Gilles być może źle cię zrozumiałem, ale nie mogłem sprawić, że pętla przetwarza całkowicie najdłuższy plik (w moim przypadku zawsze jest to plik $ fileA), więc sformułowałem to w osobne pytanie, ponieważ: czy istnieje sposób na napisanie pętli, więc czy diff nie zauważa żadnej różnicy między wejściem a wyjściem? unix.stackexchange.com/questions/26780/… najbliżej mogłem dostać diff tylko znaleźć jedną linię różnicy.
ixtmixilix,

3

Wiem, że chcesz skrypt powłoki, ale możesz rzucić okiem na pastepolecenie.


Dzięki @lutzky. pasteteż jest fajny.
jaypal singh,

2

Spróbuj wykonać poniższe polecenie:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.