Cara membaca dari dua file input menggunakan while

27

Saya ingin tahu apakah ada cara membaca dari dua file input dalam sebuah nested loop satu baris sekaligus. Sebagai contoh, katakanlah saya punya dua file FileAdan FileB.

FileA:

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

FileB:

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

Skrip Contoh Saat Ini:

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

Eksekusi:

[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

Masalah dan output yang diinginkan:

Ini loop atas FileB sepenuhnya untuk setiap baris di FileA. Saya mencoba menggunakan continue, break, exit tetapi tidak satupun dari mereka yang dimaksudkan untuk mencapai output yang saya cari. Saya ingin skrip untuk membaca hanya satu baris dari File A dan kemudian satu baris dari FileB dan keluar dari loop dan lanjutkan dengan baris kedua dari File A dan baris kedua dari File B. Sesuatu yang mirip dengan skrip berikut -

[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

Apakah ini mungkin dicapai dengan while?

jaypal singh
sumber
Sebuah solusi hebat oleh @codaddict ada di sini: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Jawaban:

32

Jika Anda tahu pasti bahwa beberapa karakter tidak akan pernah muncul di file pertama maka Anda dapat menggunakan tempel.

Contoh tempel menggunakan tab pembatas default:

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

Contoh tempel menggunakan @:

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

Perhatikan bahwa itu sudah cukup jika karakter dijamin tidak terjadi pada file pertama. Ini karena readakan diabaikan IFSketika mengisi variabel terakhir. Jadi bahkan jika @terjadi pada file kedua itu tidak akan terpecah.

Contoh tempel menggunakan beberapa fitur bash untuk kode yang lebih bersih:

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

Fitur Bash yang digunakan: ansi c string ( $'\t') dan proses substitusi ( <(...)) untuk menghindari loop sementara dalam masalah subkulit .

Jika Anda tidak dapat memastikan bahwa karakter apa pun tidak akan pernah muncul di kedua file, maka Anda dapat menggunakan deskriptor file .

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

Tidak banyak diuji. Mungkin istirahat di garis kosong.

File deskriptor nomor 0, 1, dan 2 sudah digunakan untuk stdin, stdout, dan stderr. Deskriptor file dari 3 ke atas (biasanya) gratis. Manual bash memperingatkan dari menggunakan deskriptor file yang lebih besar dari 9, karena mereka "digunakan secara internal".

Perhatikan bahwa deskriptor file terbuka diwarisi untuk fungsi shell dan program eksternal. Fungsi dan program yang mewarisi deskriptor file terbuka dapat membaca dari (dan menulis ke) deskriptor file. Anda harus berhati-hati untuk menutup semua deskriptor file yang tidak diperlukan sebelum memanggil suatu fungsi atau program eksternal.

Berikut adalah program yang sama seperti di atas dengan pekerjaan aktual (pencetakan) dipisahkan dari meta-work (membaca baris demi baris dari dua file secara paralel).

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

Sekarang kita berpura-pura bahwa kita tidak memiliki kendali atas kode kerja dan kode itu, untuk alasan apa pun, mencoba membaca dari deskriptor file 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

Ini adalah contoh output. Perhatikan bahwa baris kedua dari file pertama "dicuri" dari loop.

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

Di sini adalah bagaimana Anda harus menutup deskriptor file sebelum memanggil kode eksternal (atau kode apa pun dalam hal ini).

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
sumber
17

Buka dua file pada deskriptor file yang berbeda . Alihkan input dari readbuilt-in ke deskriptor bahwa file yang Anda inginkan terhubung. Di bash / ksh / zsh, Anda bisa menulis read -u 3alih-alih read <&3.

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

Cuplikan ini berhenti ketika file terpendek telah diproses. Lihat Membaca dua file menjadi IFS sementara loop - Apakah ada cara untuk mendapatkan hasil nol diff dalam kasus ini? jika Anda ingin terus memproses hingga akhir kedua file.

Lihat juga Kapan Anda akan menggunakan deskriptor file tambahan? untuk informasi tambahan tentang deskriptor file, dan Mengapa `sementara IFS = read` sering digunakan, alih-alih` IFS =; saat membaca..`? untuk penjelasan IFS= read -r.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terima kasih @Gilles untuk tautan tambahan pada deskriptor file.
jaypal singh
@Gilles mungkin saya salah paham dengan Anda, tapi saya tidak bisa membuat proses loop sepenuhnya file (yang selalu $ fileA dalam kasus saya), jadi saya membuatnya menjadi pertanyaan yang terpisah, menjadi: apakah ada cara untuk menulis loop jadi diff yang tidak memperhatikan perbedaan antara input dan output? unix.stackexchange.com/questions/26780/... yang paling dekat yang bisa saya dapatkan adalah beda hanya dengan menemukan satu baris perbedaan.
ixtmixilix
3

Saya tahu Anda ingin skrip shell, tetapi Anda mungkin ingin melihat pasteperintahnya.

Lutzky
sumber
Terima kasih @ lutzky. pastejuga keren.
jaypal singh
2

Coba perintah di bawah ini:

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

Atau, saya kira Anda bisa menyeruput file ke dalam variabel array yang mengikat setiap baris file ke dalam array [line_of_file_index] menggunakan perintah mapfile bash. Namun, saya tidak yakin apakah itu hanya untuk Bash3 lebih tinggi atau Bash4.

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

Nikhil Mulley
sumber