Multivariabile per loop


12

C'è un modo per specificare più variabili (non solo numeri interi) nei forloop in bash? Potrei avere 2 file contenenti testo arbitrario con cui avrei bisogno di lavorare.

Ciò di cui ho bisogno funzionalmente è qualcosa del genere:

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

Qualche idea?

Risposte:


11

Innanzitutto, non leggere le righe con for , poiché ci sono molti problemi inevitabili con le righe di lettura tramite la suddivisione in parole.

Supponendo file di uguale lunghezza o se si desidera eseguire il ciclo solo fino alla lettura del più breve dei due file, è possibile una soluzione semplice.

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

Mettere insieme una soluzione più generale è difficile a causa di quando readrestituisce false e diverse altre ragioni. Questo esempio può leggere un numero arbitrario di flussi e restituire dopo l'input più corto o più lungo.

#!/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:

Quindi, a seconda che readNottenga -l, anche l'output è

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

o

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

Dover leggere più stream in un ciclo senza salvare tutto in più array non è poi così comune. Se vuoi solo leggere gli array, dovresti dare un'occhiata mapfile.


||non funziona per me. Si elabora file1 quindi file2 , ma fa mantenere i file sincronizzati con &&, che fuoriesce mentre ciclo a prima eof . - GNU bash 4.1.5
Peter.O

Non ho avuto un momento libero per testare - sì, probabilmente vorresti entrambi gli elementi per iterazione. L' ||operatore "list" è in corto circuito come nella maggior parte delle lingue. Sicuramente questo è stato risposto mille volte prima ...
ormaaj,

Il punto non è la frequenza di quanto spesso qualcosa è stato menzionato prima. Piuttosto, è che il codice che stai mostrando nella tua risposta non funziona . Basato sul tuo " probabilmente vogliono entrambi gli elementi ..", sembra che tu non l'abbia ancora testato.
Peter

@ Peter.O Sono consapevole. È stato corretto.
ormaaj,

2

Do-fatto; fatto - hai bisogno di due fors e due doni:

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

Naturalmente File2 viene elaborato una volta per ogni parola in file1.

L'uso di <invece di cat dovrebbe essere un guadagno di prestazioni insignificante nella maggior parte dei casi. Nessun sottoprocesso coinvolto. (Incerto sull'ultima frase).

Non compresso:

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

Se non ti piace leggere le parole, ma le righe e preservare lo spazio bianco, usa while e leggi:

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

Se si desidera aggiungere 2 file e non annidarli:

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

Se vuoi comprimere 2 file, usa incolla:

paste file1 file2

1
Dici che non ci sono sottoprocessi coinvolti. Il () non è una subshell? Non voglio essere pendente ma non sono sicuro di ciò che sta accadendo ora. Per esempio, so che quando ho uno script e non voglio che poluti la shell corrente con i cd per esempio, lo faccio all'interno di un (). Anche in esecuzione (sleep 4) e ad esempio seguito da un ps mi mostra che il processo è ancora in esecuzione. Puoi per favore elaborare.
Silverrocker,

Bene - mi dispiace, sto recitando molto senza fondamento qui. Pensavo di aver sentito così, ma forse è solo il <rispetto a cat. E non sono sicuro di come testarlo e quando diventerà semanticamente importante. Se vengono invocate le pipe, le variabili nei sottoprocessi non raggiungono il processo principale, ma qui non abbiamo variabili. Colpisco la frase critica, finché qualcuno non può chiarirla.
utente sconosciuto

0

whileha una sintassi interesitante. È possibile inserire più comandi prima del do ... whileciclo e potrebbe essere necessario destreggiarsi tra il caso in questione, a seconda delle esigenze particolari di: leggi fino alla fine del file più lungo o solo alla fine del più breve.

Ad esempio, read || readsemplicemente non funziona (secondo i requisiti della domanda), poiché quando una lettura del primo file è true, la lettura del secondo file viene ignorata fino a quando il primo file non è stato letto dall'inizio alla fine ... Quindi, perché lo stato è fermo true, il ciclo while continua e legge il secondo file dall'inizio alla fine.

read && readleggerà i file contemporaneamente (in modo sincrono) se si desidera leggere solo il file più corto. Tuttavia, se si desidera leggere entrambi i file eof, è necessario lavorare con i while'srequisiti di sintassi, ad es. dal comando immediatamente prima del do whileciclo producendo un codice di ritorno diverso da zero per uscire dal ciclo while.

Ecco un esempio di come leggere entrambi i file su 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

(potresti voler testare eof3 ed eof4 prima della lettura, ma l'idea generale è lì, specialmente nella condizione finale vero / falso.

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.