Multivariable pour les boucles

12

Existe-t-il un moyen de spécifier plusieurs variables (pas seulement des entiers) dans les forboucles de bash? Je peux avoir 2 fichiers contenant du texte arbitraire avec lesquels je devrais travailler.

Ce dont j'ai besoin fonctionnellement est quelque chose comme ceci:

for i in $(cat file1) and j in $(cat file2); do command $i $j; done

Des idées?

user488244
la source

Réponses:

11

Tout d'abord, ne lisez pas les lignes avec for , car il y a plusieurs problèmes inévitables avec la lecture des lignes via le fractionnement de mots.

En supposant des fichiers de longueur égale, ou si vous souhaitez boucler uniquement jusqu'à ce que le plus court des deux fichiers soit lu, une solution simple est possible.

while read -r x && read -r y <&3; do
    ...
done <file1 3<file2

Mettre en place une solution plus générale est difficile à cause des readretours faux et de plusieurs autres raisons. Cet exemple peut lire un nombre arbitraire de flux et retourner après l'entrée la plus courte ou la plus longue.

#!/usr/bin/env bash

# Open the given files and assign the resulting FDs to arrName.
# openFDs arrname file1 [file2 ...]
openFDs() {
    local x y i arr=$1

    [[ -v $arr ]] || return 1
    shift

    for x; do
        { exec {y}<"$x"; } 2>/dev/null || return 1
        printf -v "${arr}[i++]" %d "$y"
    done
}

# closeFDs FD1 [FD2 ...]
closeFDs() {
    local x
    for x; do
        exec {x}<&-
    done
}

# Read one line from each of the given FDs and assign the output to arrName.
# If the first argument is -l, returns false only when all FDs reach EOF.
# readN [ -l ] arrName FD1 [FD2 ...]
readN() {
    if [[ $1 == -l ]]; then
        local longest
        shift
    else
        local longest=
    fi

    local i x y status arr=$1
    [[ -v $arr ]] || return 1
    shift

    for x; do
        if IFS= read -ru "$x" "${arr}[i]" || { unset -v "${arr}[i]"; [[ ${longest+_} ]] && return 1; }; then
            status=0
        fi
        ((i++))
    done
    return ${status:-1}
}

# readLines file1 [file2 ...]
readLines() {
    local -a fds lines
    trap 'closeFDs "${fds[@]}"' RETURN
    openFDs fds "$@" || return 1

    while readN -l lines "${fds[@]}"; do
        printf '%-1s ' "${lines[@]}"
        echo
    done
}

{
    readLines /dev/fd/{3..6} || { echo 'error occured' >&2; exit 1; }
} <<<$'a\nb\nc\nd' 3<&0 <<<$'1\n2\n3\n4\n5' 4<&0 <<<$'x\ny\nz' 5<&0 <<<$'7\n8\n9\n10\n11\n12' 6<&0

# vim: set fenc=utf-8 ff=unix ts=4 sts=4 sw=4 ft=sh nowrap et:

Donc, selon que l'on readNobtient -l, la sortie est soit

a 1 x 7
b 2 y 8
c 3 z 9
d 4 10
5 11
12

ou

a 1 x 7
b 2 y 8
c 3 z 9

Devoir lire plusieurs flux en boucle sans tout enregistrer dans plusieurs tableaux n'est pas si courant. Si vous voulez simplement lire des tableaux, vous devriez y jeter un œil mapfile.

ormaaj
la source
||ne fonctionne pas pour moi. Il traite fichier1 alors fichier2 , mais il ne conserve les fichiers synchronisés avec &&qui sort de l' en boucle lors de la première EOF . - GNU bash 4.1.5
Peter.O
Je n'ai pas eu de moment libre pour tester - oui, vous voudriez probablement les deux éléments par itération. L' ||opérateur "liste" court-circuite comme dans la plupart des langues. Cela a sûrement été répondu mille fois auparavant ...
ormaaj
Le point n'est pas la fréquence de la fréquence à laquelle quelque chose a été mentionné auparavant. C'est plutôt que le code que vous affichez actuellement dans votre réponse ne fonctionne pas . Sur la base de votre " voulez probablement les deux éléments ..", il semble que vous ne l'ayez toujours pas testé.
Peter.O
@ Peter.O je suis au courant. Il a été corrigé.
ormaaj
2

Faire; terminé - vous avez besoin de deux fors et de deux dones:

for i in $(< file1); do for j in $(< file2); do echo $i $j;done ; done

Le fichier2 est, bien entendu, traité une fois pour chaque mot du fichier1.

Dans la plupart des cas, l'utilisation de <à la place de cat devrait être un gain de performance banal. Aucun sous-processus impliqué. (Pas sûr de la dernière phrase).

Non compressé:

for i in $(< file1)
do
  for j in $(< file2)
  do
    echo $i $j
  done
done

Si vous n'aimez pas lire les mots, mais les lignes, et conservez les espaces, utilisez while et lisez:

while read line; do while read second; do echo "${line}${second}"; done <file2 ; done <file1

Si vous souhaitez ajouter 2 fichiers et ne pas les imbriquer:

cat file1 file2 | while read line; do echo "${line}"; done

Si vous voulez compresser 2 fichiers, utilisez coller:

paste file1 file2
Utilisateur inconnu
la source
1
Vous dites qu'aucun sous-processus n'est impliqué. Le () n'est-il pas un sous-shell? Je ne veux pas être pendentif, mais je ne sais pas ce qui se passe actuellement. Par exemple, je sais que quand j'ai un script et que je ne veux pas qu'il polue le shell courant avec des cd par exemple, je le fais dans un (). Également en cours d'exécution (sommeil 4) et par exemple suivi d'un ps me montre que le processus est toujours en cours. Pouvez-vous s'il vous plaît développer.
Silverrocker
Eh bien - je suis désolé, j'agis sans fondements ici. Je pensais l'avoir entendu, mais ce n'est peut-être que le <comparé à cat. Et je ne sais pas comment le tester et quand il deviendra sémantiquement important. Si des canaux sont invoqués, les variables des sous-processus ne remontent pas au processus principal, mais nous n'avons pas de variables ici. Je raye la phrase critique, jusqu'à ce que quelqu'un puisse la clarifier.
utilisateur inconnu
0

whilea une syntaxe intéressante. Vous pouvez placer plusieurs commandes avant la do ... whileboucle, et le cas en question peut avoir besoin de jongler avec cette fonctionnalité, en fonction de vos besoins particuliers: lisez-vous jusqu'à la fin du fichier le plus long, ou seulement jusqu'à la fin du plus court.

Par exemple, ne fonctionneread || read tout simplement pas (conformément aux exigences de la question), car lorsqu'une lecture du premier fichier est effectuée true, la lecture du deuxième fichier est ignorée jusqu'à ce que le premier fichier ait été lu du début à la fin ... Ensuite, parce que l'état est toujours true, la boucle while continue et lit le deuxième fichier du début à la fin.

read && readlira les fichiers simultanément (de manière synchrone) si vous ne souhaitez lire que le fichier le plus court. Cependant, si vous souhaitez lire les deux fichiers eof, vous devez travailler avec les while'sexigences de syntaxe, par exemple. par la commande immédiatement avant la do whileboucle produisant un code retour différent de zéro pour sortir de la boucle while.

Voici un exemple de lecture des deux fichiers sur eof

while IFS= read -r line3 <&3 || ((eof3=1))
      IFS= read -r line4 <&4 || ((eof4=1))
      !((eof3 & eof4))
do
    echo "$line3, $line4"
done 3<file3 4<file4

(vous voudrez peut-être tester eof3 et eof4 avant la lecture, mais l'idée générale est là, en particulier dans l'état final vrai / faux.

Peter.O
la source