Multivariable für Schleifen

12

Gibt es eine Möglichkeit, mehrere Variablen (nicht nur ganze Zahlen) in forSchleifen anzugeben bash? Ich kann 2 Akten haben, die beliebigen Text enthalten, mit dem ich arbeiten müsste.

Was ich funktionell brauche, ist so etwas:

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

Irgendwelche Ideen?

user488244
quelle

Antworten:

11

Erstens, lesen Sie keine Zeilen mit for , da es einige unvermeidbare Probleme beim Lesen von Zeilen durch Wortteilung gibt.

Wenn Sie Dateien gleicher Länge annehmen oder nur eine Schleife ausführen möchten, bis die kürzere von zwei Dateien gelesen wurde, ist eine einfache Lösung möglich.

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

Es ist schwierig, eine allgemeinere Lösung zusammenzustellen, da die readRückgabe falsch ist und mehrere andere Gründe vorliegen. In diesem Beispiel kann eine beliebige Anzahl von Streams gelesen und nach der kürzesten oder längsten Eingabe zurückgegeben werden.

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

Also je nachdem, ob es readNgeht -l, ist die Ausgabe entweder

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

oder

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

Das Lesen mehrerer Streams in einer Schleife, ohne alles in mehreren Arrays zu speichern, ist nicht allzu häufig. Wenn Sie nur Arrays lesen möchten, sollten Sie einen Blick darauf werfen mapfile.

ormaaj
quelle
||funktioniert bei mir nicht Es verarbeitet Datei1 dann Datei2 , aber es hält die Dateien synchronisiert &&, wodurch die while- Schleife beim ersten Mal beendet wird . - GNU Bash 4.1.5
Peter.O
Ich hatte keinen freien Moment zum Testen - ja, Sie möchten wahrscheinlich beide Elemente pro Iteration. Der ||Operator "Liste" schließt wie in den meisten Sprachen kurz. Sicherlich wurde dies tausend Mal beantwortet, bevor ...
ormaaj
Der Punkt ist nicht die Häufigkeit, mit der etwas zuvor erwähnt wurde. Es ist vielmehr so, dass der Code, den Sie aktuell in Ihrer Antwort anzeigen, nicht funktioniert . Basierend auf Ihrem " wahrscheinlich wollen beide Elemente ..", klingt es, als hätten Sie es noch nicht getestet.
Peter.O
@ Peter.O Mir ist bewusst. Es wurde behoben.
Ormaaj
2

Tun getan; fertig - du brauchst zwei fors und zwei dones:

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

Datei2 wird natürlich für jedes Wort in Datei1 einmal verarbeitet.

Die Verwendung von <anstelle von cat sollte in den meisten Fällen einen unauffälligen Leistungszuwachs bedeuten. Kein Teilprozess involviert. (Unsicher über den letzten Satz).

Unkomprimiert:

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

Wenn Sie keine Wörter, sondern Zeilen lesen und das Leerzeichen beibehalten möchten, verwenden Sie while und read:

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

Wenn Sie 2 Dateien anhängen und nicht verschachteln möchten:

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

Wenn Sie 2 Dateien komprimieren möchten, verwenden Sie Einfügen:

paste file1 file2
Benutzer unbekannt
quelle
1
Sie sagen, es ist kein Unterprozess beteiligt. Ist das () nicht eine Unterschale? Ich möchte nicht hängend sein, aber ich bin mir nicht sicher, was jetzt passiert. Zum Beispiel weiß ich, dass wenn ich ein Skript habe und ich nicht möchte, dass es die aktuelle Shell mit CDs verschmutzt, ich es in einem () mache. Läuft auch (sleep 4) & zum Beispiel gefolgt von einem ps zeigt mir, dass der Prozess noch läuft. Können Sie das bitte näher erläutern?
Silverrocker
Tut mir leid, ich spiele hier sehr viel ohne Fundament. Ich dachte, ich hätte es gehört, aber vielleicht ist es nur der <Vergleich zu cat. Und ich bin mir nicht sicher, wie ich es testen soll und wann es semantisch wichtig wird. Wenn Pipes aufgerufen werden, sprudeln Variablen in Unterprozessen nicht in den Hauptprozess, aber wir haben hier keine Variablen. Ich schlage den kritischen Satz durch, bis jemand ihn klären kann.
Benutzer unbekannt
0

whilehat eine interessante Syntax. Sie können mehrere Befehle vor die do ... whileSchleife stellen, und der betreffende Fall muss möglicherweise mit dieser Funktion jonglieren. Dies hängt von Ihren speziellen Anforderungen ab: Lesen Sie bis zum Ende der längsten Datei oder nur bis zum Ende der kürzesten Datei.

Beispielsweise funktioniertread || read einfach nicht (gemäß den Anforderungen der Frage), da beim Lesen der ersten trueDatei das Lesen der zweiten Datei übersprungen wird, bis die erste Datei von Anfang bis Ende gelesen wurde ... Dann wegen des Status wird truedie while-Schleife fortgesetzt und liest die zweite Datei von Anfang bis Ende.

read && readliest Dateien gleichzeitig (synchron), wenn Sie nur bis zur kürzesten Datei lesen möchten. Wenn Sie jedoch beide Dateien lesen möchten, eofmüssen Sie mit den while'sSyntaxanforderungen arbeiten, d. H. durch den Befehl unmittelbar vor der do whileSchleife, der einen Rückkehrcode ungleich Null erzeugt, um aus der while-Schleife auszubrechen.

Hier ist ein Beispiel, wie Sie beide Dateien in eof einlesen können

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

(Möglicherweise möchten Sie eof3 und eof4 vor dem Lesen testen, aber die allgemeine Idee ist vorhanden, insbesondere im endgültigen Wahr / Falsch-Zustand.

Peter.O
quelle