Wie kehre ich eine for-Schleife um?

26

Wie mache ich eine Schleife richtigfor in umgekehrter Reihenfolge?

for f in /var/logs/foo*.log; do
    bar "$f"
done

Ich brauche eine Lösung, die nicht für flippige Zeichen in den Dateinamen kaputt geht.

Mehrdad
quelle
5
Einfach sort -rvor dem foranpfeifen, oder durchwaschen ls -r.
David Schwartz

Antworten:

27

Fügen Sie in bash oder ksh die Dateinamen in ein Array ein und durchlaufen Sie das Array in umgekehrter Reihenfolge.

files=(/var/logs/foo*.log)
for ((i=${#files[@]}-1; i>=0; i--)); do
  bar "${files[$i]}"
done

Der obige Code funktioniert auch in zsh, wenn die ksh_arraysOption gesetzt ist (im ksh-Emulationsmodus). Es gibt eine einfachere Methode in zsh, die darin besteht, die Reihenfolge der Übereinstimmungen durch ein Glob-Qualifikationsmerkmal umzukehren:

for f in /var/logs/foo*.log(On); do bar $f; done

POSIX enthält keine Arrays. Wenn Sie also portabel sein möchten, können Sie nur die Positionsparameter verwenden, um ein Array von Strings direkt zu speichern.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done
Gilles 'SO - hör auf böse zu sein'
quelle
Sobald Sie Positionsparameter verwenden, sind Ihre Variablen iund nicht mehr erforderlich f. führen Sie einfach ein shift, verwenden Sie es $1für Ihren Anruf barund testen Sie es [ -z $1 ]in Ihrem while.
user1404316
@ user1404316 Dies wäre eine übermäßig komplizierte Methode, um die Elemente in aufsteigender Reihenfolge zu durchlaufen . Auch falsch: Weder sind [ -z $1 ]noch [ -z "$1" ]nützliche Tests (was, wenn der Parameter war *, oder eine leere Zeichenfolge?). Und auf jeden Fall helfen sie hier nicht weiter: Die Frage ist, wie in umgekehrter Reihenfolge geschleift werden soll.
Gilles 'SO- hör auf böse zu sein'
Die Frage besagt ausdrücklich, dass die Dateinamen in / var / log durchlaufen werden. Daher kann man davon ausgehen, dass keiner der Parameter "*" oder leer ist. Ich stehe jedoch in der umgekehrten Reihenfolge korrigiert da.
user1404316
7

Versuchen Sie dies, es sei denn, Sie betrachten Zeilenumbrüche als "flippige Zeichen":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
Sunaku
quelle
Gibt es eine Methode, die mit Zeilenumbrüchen funktioniert? (Ich weiß, es ist selten, aber zumindest um zu lernen, möchte ich wissen, ob es möglich ist, korrekten Code zu schreiben.)
Mehrdad
Kreativ, um den Fluss mit tac umzukehren. Wenn Sie unerwünschte Zeichen wie Zeilenumbrüche entfernen möchten, können Sie die Pipe nach tr -d '\ n' ausführen.
Johan
4
Dies bricht ab, wenn die Dateinamen Zeilenumbrüche, umgekehrte Schrägstriche oder nicht druckbare Zeichen enthalten. Siehe mywiki.wooledge.org/ParsingLs und Wie werden die Zeilen einer Datei durchlaufen ? (und die verknüpften Threads).
Gilles 'SO - hör auf böse zu sein'
Die am häufigsten gewählte Antwort hat die Variable fin meiner Situation gebrochen . Diese Antwort fungiert jedoch als Ersatz für die normale forLeitung.
Serge Stroobandt
2

Wenn jemand versucht, herauszufinden, wie er über eine durch Leerzeichen getrennte Zeichenfolgenliste iterieren kann, funktioniert Folgendes:

reverse() {
  tac <(echo "$@" | tr ' ' '\n') | tr '\n' ' '
}

list="a bb ccc"

for i in `reverse $list`; do
  echo "$i"
done
> ccc
> bb 
> a
ACK_stoverflow
quelle
2
Ich habe kein tac auf meinem System; Ich weiß nicht, ob es robust ist, aber ich habe für x in $ {mylist} verwendet. do revv = "$ {x} $ {revv}"; done
arp
@arp Das ist eine Art Schock, wie taces ein Teil von GNU Coreutils ist. Ihre Lösung ist aber auch eine gute.
ACK_stoverflow
@ACK_stoverflow Es gibt Systeme ohne Linux-Userland-Tools.
Kusalananda
@Kusalananda Wollen Sie damit sagen, dass nur Linux GNU-Coreutils verwendet? LOL. Sogar die Busybox hat tac. Obwohl von der Anzahl der tac-losen Antworten hier würde ich vermuten, dass OSX möglicherweise nicht tac hat. Verwenden Sie in diesem Fall eine Variante der @ arp-Lösung - es ist ohnehin einfacher zu verstehen.
ACK_stoverflow
@ACK_stoverflow Das sage ich ja.
Kusalananda
1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Sollte so funktionieren, wie Sie möchten (dies wurde unter Mac OS X getestet und ich habe eine Einschränkung unten ...).

Auf der Manpage finden Sie:

-print0
         This primary always evaluates to true.  It prints the pathname of the current file to standard output, followed by an ASCII NUL character (charac-
         ter code 0).

Grundsätzlich finden Sie die Dateien, die Ihrem String + glob entsprechen, und schließen jedes mit einem NUL-Zeichen ab. Wenn Ihre Dateinamen Zeilenumbrüche oder andere seltsame Zeichen enthalten, sollte find dies gut handhaben.

tail -r

nimmt die Standardeingabe durch das Rohr und kehrt es (beachten Sie, dass tail -rDrucke alle auf der Standardausgabe des Eingangs, und nicht nur die letzten 10 Zeilen, die die Standard - Voreinstellung ist.man tail für weitere Informationen).

Das leiten wir dann weiter an xargs -0:

-0      Change xargs to expect NUL (``\0'') characters as separators, instead of spaces and newlines.  This is expected to be used in concert with the
         -print0 function in find(1).

Hier erwartet xargs Argumente, die durch das NUL-Zeichen getrennt sind, das Sie übergeben findund mit dem Sie umgekehrt habentail .

Meine Einschränkung: Ich habe gelesen, dass taildas mit nullterminierten Strings nicht gut funktioniert . Dies hat unter Mac OS X gut funktioniert, aber ich kann nicht garantieren, dass dies bei allen * nixen der Fall ist. Vorsichtig auftreten.

Ich sollte auch erwähnen, dass GNU Parallel oft als xargsAlternative verwendet wird. Sie können das auch überprüfen.

Ich kann etwas vermissen, also sollten sich andere melden.

tcdyl
quelle
2
+1 tolle Antwort. Es scheint aber, dass Ubuntu nicht unterstützt tail -r... mache ich etwas falsch?
Mehrdad
1
Nein, das glaube ich nicht. Ich habe meinen Linux-Rechner nicht in Betrieb, aber eine schnelle Google-Suche nach "Linux Tail Man" zeigt ihn nicht als Option an. Sunaku wird tacals Alternative erwähnt, also würde ich es stattdessen versuchen
tcdyl
Ich habe auch die Antwort bearbeitet, um eine andere Alternative aufzunehmen
tcdyl
whoops Ich habe vergessen zu +1, als ich sagte +1 :( Fertig! Entschuldigung, haha ​​:)
Mehrdad
4
tail -rist OSX-spezifisch und kehrt die durch Zeilenumbrüche getrennte Eingabe und nicht die durch Nullen getrennte Eingabe um. Ihre zweite Lösung funktioniert überhaupt nicht (Sie leiten Eingaben an weiter ls, was nicht wichtig ist). Es gibt keine einfache Lösung, mit der es zuverlässig funktioniert.
Gilles 'SO - hör auf böse zu sein'
1

In Ihrem Beispiel durchlaufen Sie mehrere Dateien, aber ich fand diese Frage aufgrund des allgemeineren Titels, der auch das Durchlaufen eines Arrays oder das Umkehren basierend auf einer beliebigen Anzahl von Aufträgen abdecken kann.

So geht's in Zsh:

Wenn Sie die Elemente in einem Array durchlaufen, verwenden Sie diese Syntax ( Quelle ).

for f in ${(Oa)your_array}; do
    ...
done

O Kehrt die in der nächsten Markierung angegebene Reihenfolge um. aist die normale Array-Reihenfolge.

Wie @Gilles sagte, Onwerden Ihre globbed-Dateien in umgekehrter Reihenfolge, zB mit my/file/glob/*(On). Das liegt daran, dass die OnReihenfolge der Namen umgekehrt ist .

Zsh-Sortierflags:

  • a Array-Reihenfolge
  • L Dateilänge
  • l Anzahl der Links
  • m Änderungsdatum
  • n Name
  • ^oumgekehrte Reihenfolge ( oist normale Reihenfolge)
  • O umgekehrte Reihenfolge

Beispiele finden Sie unter https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt und http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- to-master-your-z-shell /

Henry
quelle
0

Mac OSX unterstützt den tacBefehl nicht. Die Lösung von @tcdyl funktioniert, wenn Sie einen einzelnen Befehl in a aufrufenfor Schleife . In allen anderen Fällen ist das Folgende der einfachste Weg, um es zu umgehen.

Dieser Ansatz unterstützt keine Zeilenumbrüche in Ihren Dateinamen. Der Grund dafür ist, dass tail -rdie Eingabe durch Zeilenumbrüche getrennt sortiert wird.

for i in `ls -1 [filename pattern] | tail -r`; do [commands here]; done

Es gibt jedoch eine Möglichkeit, die Zeilenumbruchsbeschränkung zu umgehen. Wenn Sie wissen, dass Ihre Dateinamen kein bestimmtes Zeichen enthalten (sagen Sie '='), können Sie tralle Zeilenumbrüche ersetzen, um dieses Zeichen zu werden, und dann die Sortierung durchführen. Das Ergebnis würde folgendermaßen aussehen:

for i in `find [directory] -name '[filename]' -print0 | tr '\n' '=' | tr '\0' '\n'
| tail -r | tr '\n' '\0' | tr '=' '\n' | xargs -0`; do [commands]; done

Hinweis: Abhängig von Ihrer Version von trwird dies möglicherweise nicht '\0'als Zeichen unterstützt. Dies kann normalerweise umgangen werden, indem das Gebietsschema in C geändert wird (aber ich weiß nicht mehr, wie genau, da es nach dem Reparieren jetzt auf meinem Computer funktioniert). Wenn eine Fehlermeldung angezeigt wird und Sie die Problemumgehung nicht finden können, posten Sie sie bitte als Kommentar, damit ich Sie bei der Problembehandlung unterstützen kann.

Alex
quelle
0

Eine einfache Möglichkeit ist die Verwendung ls -rvon @David Schwartz

for f in $(ls -r /var/logs/foo*.log); do
    bar "$f"
done
Megver83
quelle
-4

Versuche dies:

for f in /var/logs/foo*.log; do
bar "$f"
done

Ich denke, es ist der einfachste Weg.

Syhra
quelle
3
Die Frage lautete " forSchleife in umgekehrter Reihenfolge".
Manatwork