Parcourez les lignes de deux fichiers en parallèle [fermé]

18

Le but du script que je fais est de comparer deux séries de fichiers. Les noms de fichiers sont eux-mêmes stockés dans deux fichiers distincts, un chemin par ligne. Mon idée est d'avoir deux while readboucles, une pour chaque liste de noms de fichiers, mais comment mélanger les deux boucles ensemble?

while read compareFile <&3; do     
 if [[ ! $server =~ [^[:space:]] ]] ; then  #empty line exception
    continue
 fi   
    echo "Comparing file - $compareFile"
 if diff "$compareFile" _(other file from loop?_) >/dev/null ; then
     echo Same
 else
      echo Different
 fi 
done 3</infanass/dev/admin/filestoCompare.txt

J'ai besoin de pouvoir comparer des fichiers de deux listes différentes en même temps à travers deux boucles de lecture ... Est-ce même possible?

mkrouse
la source
Avez-vous l'intention de faire de ce script un roi du défi? Sinon, des outils puissants existent déjà pour comparer des fichiers, comme diff.
lgeorget
" genre de défi", désolé
lgeorget
@lgeorget que l'OP utilise diff.
terdon
ah, des fichiers de deux listes. Désolé pour les commentaires inutiles ...
lgeorget
Veuillez éviter la publication
croisée

Réponses:

20

Vous n'avez pas besoin de deux boucles; il vous suffit de lire à partir de deux fichiers dans la même boucle.

while read compareFile1 <&3 && read compareFile2 <&4; do     
 if [[ ! $server =~ [^[:space:]] ]] ; then  #empty line exception
    continue
 fi   
    echo "Comparing file - $compareFile"
 if diff "$compareFile1" "$compareFile2" >/dev/null ; then
     echo Same
 else
      echo Different
 fi 
done 3</infanass/dev/admin/filestoCompare.txt 4<other_file
psusi
la source
c'est beaucoup moins de code merci! comment gérer une exception de ligne vide en même temps que pour les deux boucles?
mkrouse
@mkrouse, je ne sais pas ce que vous faisiez avec cette variable $ server auparavant, mais quelle que soit la façon dont vous testez une ligne vide sur une variable, vous faites juste la même chose sur l'autre ...
psusi
7

Méthode 1: utilisez ce que vous savez

Comme vous savez déjà comment boucler sur un fichier, vous pouvez combiner les fichiers, puis traiter les fichiers combinés. La commande pastejoint deux fichiers ligne par ligne. Il met un onglet entre les lignes provenant des deux fichiers, donc cette solution suppose qu'il n'y a pas d'onglets dans vos noms de fichiers. (Vous pouvez changer le séparateur mais vous devez trouver un caractère qui n'est pas présent dans un nom de fichier.)

paste -- "$list1.txt" "list2.txt" |
while IFS=$'\t' read -r file1 file2 rest; do
  diff -q -- "$file1" "$file2"
  case $? in
    0) status='same';;
    1) status='different';;
    *) status='ERROR';;
  esac
  echo "$status $file1 $file2"
done

Si vous souhaitez ignorer les lignes vides, vous devez le faire dans chaque fichier séparément, car il pastepeut correspondre à une ligne vierge d'un fichier avec une ligne non vide d'un autre fichier. Vous pouvez utiliser greppour filtrer les lignes non vides.

paste -- <(grep '[^[:space:]]' "$list1.txt") <(grep '[^[:space:]]' "list2.txt") |
while IFS=$'\t' read -r file1 file2 rest; do
  

Notez que si les deux fichiers ont des longueurs différentes, vous obtiendrez un vide $file2(quelle que soit la liste terminée en premier).

Méthode 2: boucle sur deux fichiers

Vous pouvez mettre une commande aussi complexe que vous le souhaitez dans la condition de la boucle while. Si vous le mettez, read file1 <&3 && read file2 <&4la boucle s'exécutera tant que les deux fichiers auront une ligne à lire, c'est-à-dire jusqu'à ce qu'un fichier soit épuisé.

while read -u 3 -r file1 && read -u 4 -r file2; do
  
done 3<list1..txt 4<list2.txt

Si vous voulez sauter des lignes vides, c'est un peu plus compliqué, car vous devez faire le saut dans les deux fichiers indépendamment. Le moyen le plus simple consiste à diviser le problème en deux parties: ignorer les lignes vierges d'un fichier et traiter les lignes non vides. Une méthode pour sauter les lignes vides consiste à procéder grepcomme ci-dessus. Méfiez-vous de l'espace nécessaire entre l' <opérateur de redirection et celui <(qui démarre une substitution de commande.

while read -u 3 -r file1 && read -u 4 -r file2; do
  
done 3< <(grep '[^[:space:]]' "$list1.txt") 4< <(grep '[^[:space:]]' "list2.txt")

Une autre méthode consiste à écrire une fonction qui se comporte comme readmais qui saute les lignes vides. Cette fonction peut fonctionner en appelant readen boucle. Il n'est pas nécessaire que ce soit une fonction, mais une fonction est la meilleure approche, à la fois pour organiser votre code et parce que ce morceau de code doit être appelé deux fois. Dans la fonction, ${!#}est une instance de la construction bash ${!VARIABLE}qui évalue la valeur de la variable dont le nom est la valeur de VARIABLE; ici, la variable est la variable spéciale #qui contient le nombre de paramètres positionnels, tout ${!#}comme le dernier paramètre positionnel.

function read_nonblank {
  while read "$@" &&
        [[ ${!#} !~ [^[:space:]] ]]
  do :; done
}
while read_nonblank -u 3 -r file1 && read_nonblank -u 4 -r file2; do
  
done 3<list1..txt 4<list2.txt
Gilles 'SO- arrête d'être méchant'
la source
J'aime l'utilisation de l' -uoption de lecture
Felipe Alvarez
1

Une approche serait d'utiliser read -raau lieu de simplement read. En supposant que les filestoCompare.txtcontenus 2 colonnes avec les noms de fichiers dans chacun, le read -ralirait les deux colonnes en même temps et les affecter dans un tableau, compareFile. Ce tableau pourrait alors être accessible de sorte que l'index 0 serait le premier fichier et l'index 1 serait le deuxième fichier à chaque fois dans la whileboucle.

Exemple

Disons que j'ai ce fichier:, filestoCompare.txtet il contient les éléments suivants:

file1 file2
file3 file4
file5 file6

La commande pour parcourir ce fichier serait la suivante:

$ while read -ra a ; do printf "%s\t%s\n" ${a[0]} ${a[1]}; done < filestoCompare.txt
file1   file2
file3   file4
file5   file6

Si les 2 fichiers sont en effet des fichiers distincts tels que:

#list1
file1
file2
file3

#list2
file4
file5
file6

Ils peuvent être associés à la pastecommande comme suit:

$ paste list1 list2 > list1and2

Voici le contenu de list1and2:

$ cat list1and2
file1   file4
file2   file5
file3   file6
slm
la source
Mais ce n'est pas le format d'entrée: les listes sont dans deux fichiers différents. Vous pourriez joinles d'abord.
Gilles 'SO- arrête d'être méchant'
@ Gilles - Je sais que ce n'est pas le format d'entrée, je crois même avoir dit que "... en supposant que le fichier filestoCompare.txt contenait 2 colonnes avec les noms de fichiers dans chaque ...". Je comprends votre affirmation et je ne suis pas en désaccord. Le PO n'a fourni aucune autre orientation sur cette question depuis sa publication.
slm
@ Gilles - et si j'ajoute un exemple montrant comment utiliser la commande pastepour joindre les 2 fichiers? Cela vous amènerait-il à sous-voter?
slm