Comment lire à partir de deux fichiers d'entrée en utilisant la boucle while

27

Je voulais savoir s'il existe un moyen de lire à partir de deux fichiers d'entrée dans une boucle imbriquée while une ligne à la fois. Par exemple, disons que j'ai deux fichiers FileAet FileB.

Déposer un:

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

FichierB:

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

Exemple de script actuel:

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

Exécution:

[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

Problème et sortie souhaitée:

Cela boucle complètement sur FileB pour chaque ligne de FileA. J'ai essayé d'utiliser continue, break, exit mais aucun d'eux n'est destiné à atteindre le résultat que je recherche. Je voudrais que le script lise une seule ligne du fichier A, puis une ligne du fichier B et quitte la boucle et continue avec la deuxième ligne du fichier A et la deuxième ligne du fichier B. Quelque chose de similaire au script suivant -

[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

Est-ce possible de le faire avec la boucle while?

jaypal singh
la source
Une excellente solution par @codaddict est ici: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Réponses:

32

Si vous savez avec certitude qu'aucun caractère n'apparaîtra jamais dans le premier fichier, vous pouvez utiliser coller.

Exemple de collage à l'aide de l'onglet de délimitation par défaut:

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

Exemple de collage utilisant @:

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

Notez que cela suffit si le caractère est garanti de ne pas apparaître dans le premier fichier. En effet read, ignorera IFSlors du remplissage de la dernière variable. Donc, même si cela @se produit dans le deuxième fichier, il ne sera pas divisé.

Exemple de collage utilisant certaines fonctionnalités bash pour un code sans doute plus propre:

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

Fonctionnalités bash utilisées: chaîne ansi c ( $'\t') et substitution de processus ( <(...)) pour éviter la boucle while dans un problème de sous-shell .

Si vous ne pouvez pas être certain qu'aucun caractère n'apparaîtra jamais dans les deux fichiers, vous pouvez utiliser des descripteurs de fichiers .

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

Pas beaucoup testé. Peut se casser sur des lignes vides.

Les descripteurs de fichiers numéro 0, 1 et 2 sont déjà utilisés pour stdin, stdout et stderr, respectivement. Les descripteurs de fichiers à partir de 3 et plus sont (généralement) gratuits. Le manuel bash met en garde contre l'utilisation de descripteurs de fichiers supérieurs à 9, car ils sont "utilisés en interne".

Notez que les descripteurs de fichiers ouverts sont hérités des fonctions shell et des programmes externes. Les fonctions et programmes héritant d'un descripteur de fichier ouvert peuvent lire (et écrire dans) le descripteur de fichier. Vous devez prendre soin de fermer tous les descripteurs de fichiers qui ne sont pas requis avant d'appeler une fonction ou un programme externe.

Voici le même programme que ci-dessus avec le travail réel (l'impression) séparé du méta-travail (lecture ligne par ligne de deux fichiers en parallèle).

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

Maintenant, nous prétendons que nous n'avons aucun contrôle sur le code de travail et que ce code, pour une raison quelconque, essaie de lire à partir du descripteur de fichier 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

Voici un exemple de sortie. Notez que la deuxième ligne du premier fichier est "volée" dans la boucle.

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

Voici comment fermer les descripteurs de fichiers avant d'appeler du code externe (ou tout autre code d'ailleurs).

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
la source
17

Ouvrez les deux fichiers sur des descripteurs de fichiers différents . Redirigez l'entrée du readintégré vers le descripteur auquel le fichier que vous souhaitez est connecté. Dans bash / ksh / zsh, vous pouvez écrire à la read -u 3place de read <&3.

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

Cet extrait s'arrête lorsque le fichier le plus court a été traité. Voir Lecture de deux fichiers dans une boucle IFS while - Existe-t-il un moyen d'obtenir un résultat de différence nulle dans ce cas?si vous souhaitez continuer le traitement jusqu'à la fin des deux fichiers.

Voir aussi Quand utiliseriez-vous un descripteur de fichier supplémentaire? pour plus d'informations sur les descripteurs de fichiers, et Pourquoi est-ce que `alors que IFS = lire` est utilisé si souvent, au lieu de` IFS =; pendant la lecture .. »? pour une explication de IFS= read -r.

Gilles 'SO- arrête d'être méchant'
la source
Merci @Gilles pour les liens supplémentaires sur le descripteur de fichier.
jaypal singh
@ Gilles peut-être que je vous ai mal compris, mais je ne pouvais pas faire du processus de boucle le fichier le plus long entièrement (qui est toujours $ fileA dans mon cas), alors j'ai fait cela dans une question distincte, étant: y a-t-il un moyen d'écrire la boucle afin ce diff ne remarque aucune différence entre l'entrée et la sortie? unix.stackexchange.com/questions/26780/… le plus proche que j'ai pu obtenir était de trouver une seule différence.
ixtmixilix
3

Je sais que vous voulez un script shell, mais vous voudrez peut-être jeter un œil à la pastecommande.

Lutzky
la source
Merci @lutzky. pastec'est cool aussi.
jaypal singh
2

Essayez la commande ci-dessous:

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

Alternativement, je suppose que vous pouvez extraire le fichier dans une variable de tableau liant chaque ligne du fichier dans le tableau [line_of_file_index] en utilisant la commande mapfile de bash. Cependant, je ne sais pas si c'est uniquement pour Bash3 supérieur ou Bash4.

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

Nikhil Mulley
la source