Come leggere da due file di input usando il ciclo while


27

Volevo sapere se esiste un modo per leggere da due file di input in un ciclo nidificato mentre una riga alla volta. Ad esempio, supponiamo che io abbia due file FileAe 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

Script di esempio corrente:

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

Esecuzione:

[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

Problema e output desiderato:

Questo passa su FileB completamente per ogni riga in FileA. Ho provato a usare continue, break, exit ma nessuno di questi è pensato per ottenere l'output che sto cercando. Vorrei che lo script leggesse solo una riga dal file A e quindi una riga dal file B e uscisse dal ciclo e continuasse con la seconda riga del file A e la seconda riga del file B. Qualcosa di simile al seguente script:

[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

È possibile farlo con ciclo while?


Una grande soluzione per @codaddict è qui: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Risposte:


32

Se sai con certezza che nel primo file non compariranno mai alcuni caratteri, puoi usare incolla.

Esempio di incolla utilizzando la scheda delimitatore predefinita:

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

Esempio di incolla usando @ :

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

Si noti che è garantito che il carattere non sia presente nel primo file. Questo è perchéread ignorerà IFSquando si riempie l'ultima variabile. Quindi anche se@ verifica nel secondo file, non verrà suddiviso.

Esempio di incolla usando alcune funzionalità di bash per un codice probabilmente più pulito:

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

Funzionalità di Bash utilizzate: ansi c string ( $'\t') e process substitution ( <(...)) a evitare il ciclo while in un problema di subshell .

Se non si è certi che non si verificheranno mai caratteri in entrambi i file, è possibile utilizzare i descrittori di 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

Non testato molto. Potrebbe rompersi su linee vuote.

I descrittori di file numero 0, 1 e 2 sono già utilizzati rispettivamente per stdin, stdout e stderr. I descrittori di file da 3 in poi sono (solitamente) gratuiti. Il manuale di bash avverte di usare descrittori di file maggiori di 9, perché sono "usati internamente".

Si noti che i descrittori di file aperti sono ereditati da funzioni shell e programmi esterni. Funzioni e programmi che ereditano un descrittore di file aperto possono leggere (e scrivere su) il descrittore di file. Si consiglia di chiudere tutti i descrittori di file che non sono richiesti prima di chiamare una funzione o un programma esterno.

Ecco lo stesso programma di cui sopra con il lavoro effettivo (la stampa) separato dal meta-lavoro (lettura riga per riga da due file in parallelo).

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

Ora facciamo finta di non avere alcun controllo sul codice di lavoro e quel codice, per qualsiasi motivo, cerca di leggere dal descrittore di 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

Ecco un esempio di output. Si noti che la seconda riga del primo file viene "rubata" dal loop.

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

Ecco come chiudere i descrittori di file prima di chiamare un codice esterno (o qualsiasi altro codice).

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

Apri i due file su descrittori di file diversi . Reindirizzare l'input del readbuilt-in al descrittore a cui è collegato il file desiderato. In bash / ksh / zsh, puoi scrivere read -u 3invece di read <&3.

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

Questo frammento si interrompe quando viene elaborato il file più corto. Vedere Lettura di due file in un ciclo IFS mentre - Esiste un modo per ottenere un risultato di differenza zero in questo caso?se si desidera continuare l'elaborazione fino alla fine di entrambi i file.

Vedi anche Quando useresti un descrittore di file aggiuntivo? per ulteriori informazioni sui descrittori di file e Why perché `while IFS = read` viene usato così spesso, invece di` IFS =; mentre leggi..`? per una spiegazione di IFS= read -r.


Grazie @Gilles per i collegamenti aggiuntivi sul descrittore di file.
jaypal singh,

@Gilles forse ti ho frainteso, ma non sono riuscito a fare in modo che il ciclo elabori completamente il file più lungo (che è sempre $ fileA nel mio caso), quindi l'ho trasformato in una domanda separata, essendo: c'è un modo per scrivere il ciclo così che diff non nota alcuna differenza tra input e output? unix.stackexchange.com/questions/26780/… il più vicino che ho potuto ottenere era diff solo trovare una linea di differenza.
ixtmixilix,

3

So che vuoi uno script di shell, ma potresti voler dare un'occhiata al pastecomando.


Grazie @lutzky. pasteè anche bello.
jaypal singh

2

Prova il comando seguente:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.