Come posso invertire un ciclo for?


26

Come si esegue correttamente un forciclo in ordine inverso?

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

Ho bisogno di una soluzione che non si spezzi per i personaggi funky nei nomi dei file.


5
Basta sort -rpassare a prima del for, o lavarsi attraverso ls -r.
David Schwartz,

Risposte:


27

In bash o ksh, metti i nomi dei file in un array e fai scorrere su quell'array in ordine inverso.

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

Il codice sopra funziona anche in zsh se l' ksh_arraysopzione è impostata (è in modalità di emulazione ksh). C'è un metodo più semplice in zsh, che è quello di invertire l'ordine delle partite attraverso un qualificatore glob:

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

POSIX non include array, quindi se si desidera essere portatili, l'unica opzione per memorizzare direttamente un array di stringhe è i parametri posizionali.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done

Una volta che si utilizzano i parametri posizionali, non è necessario per le variabili ie f; basta eseguire un shift, utilizzare $1per la chiamata a bare testare [ -z $1 ]nel proprio while.
user1404316,

@ user1404316 Questo sarebbe un modo eccessivamente complicato di scorrere gli elementi in ordine crescente . Inoltre, sbagliato: non [ -z $1 ]ne [ -z "$1" ]sono le prove utili (cosa succede se il parametro è stato *, o una stringa vuota?). E in ogni caso non aiutano qui: la domanda è come fare il ciclo nell'ordine opposto.
Gilles 'SO- smetti di essere malvagio' il

La domanda specifica che sta iterando sui nomi dei file in / var / log, quindi è lecito ritenere che nessuno dei parametri sia "*" o vuoto. Rimango corretto, tuttavia, sul punto dell'ordine inverso.
user1404316,

7

Prova questo, a meno che non consideri le interruzioni di riga come "personaggi funky":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done

Esiste un metodo che funziona con le interruzioni di riga? (So ​​che è raro, ma almeno per motivi di apprendimento, vorrei sapere se è possibile scrivere il codice corretto.)
Mehrdad,

Creativo utilizzare tac per invertire il flusso e se ti piace sbarazzarti di alcuni personaggi indesiderati come le interruzioni di riga, puoi reindirizzare a tr -d '\ n'.
Johan,

4
Ciò si interrompe se i nomi dei file contengono nuove righe, barre rovesciate o caratteri non stampabili. Vedi mywiki.wooledge.org/ParsingLs e Come passare in rassegna le righe di un file? (e i thread collegati).
Gilles 'SO- smetti di essere malvagio' il

La risposta più votata ha rotto la variabile fnella mia situazione. Questa risposta, tuttavia, funge praticamente da rimpiazzo della forlinea ordinaria .
Serge Stroobandt,

2

Se qualcuno sta cercando di capire come invertire l'iterazione su un elenco di stringhe delimitato da spazi, funziona:

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

list="a bb ccc"

for i in `reverse $list`; do
  echo "$i"
done
> ccc
> bb 
> a

2
Non ho tac sul mio sistema; Non so se sia robusto, ma sto usando per x in $ {mylist}; do revv = "$ {x} $ {revv}"; fatto
arp

@arp È scioccante come tacfa parte dei coreutils GNU. Ma anche la tua soluzione è buona.
ACK_stoverflow l'

@ACK_stoverflow Esistono sistemi là fuori senza strumenti di userland Linux.
Kusalananda

@Kusalananda Stai dicendo che solo Linux usa coreutils GNU? LOL. Anche busybox ha tac. Sebbene dal numero di tacrisposte senza risposta qui indovino che forse OSX non ha tac. Nel qual caso usa una variante della soluzione di @ arp - è comunque più facile da capire.
ACK_stoverflow,

@ACK_stoverflow Questo è quello che sto dicendo, sì.
Kusalananda

1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Dovrebbe funzionare nel modo desiderato (questo è stato testato su Mac OS X e ho un avvertimento di seguito ...).

Dalla pagina man per trovare:

-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).

Fondamentalmente, stai trovando i file che corrispondono alla tua stringa + glob e terminando ciascuno con un carattere NUL. Se i tuoi nomi di file contengono nuove righe o altri strani caratteri, find dovrebbe gestirlo bene.

tail -r

prende l'input standard attraverso la pipe e lo inverte (si noti che tail -rstampa tutto l'input su stdout e non solo le ultime 10 righe, che è l'impostazione predefinita standard. man tailper maggiori informazioni).

Quindi lo instilliamo per 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).

Qui, xargs prevede di vedere gli argomenti separati dal carattere NUL, da cui è passato finde invertito tail.

Il mio avvertimento: ho letto che tailnon suona bene con le stringhe con terminazione null . Questo ha funzionato bene su Mac OS X, ma non posso garantire che sia il caso di tutti i * nix. Percorri attentamente.

Vorrei anche menzionare che GNU Parallel è spesso usato come xargsalternativa. Puoi verificarlo anche tu.

Potrei mancare qualcosa, quindi altri dovrebbero entrare.


2
+1 ottima risposta. Sembra che Ubuntu non supporti tail -rperò ... sto facendo qualcosa di sbagliato?
Mehrdad,

1
No, non penso che tu lo sia. Non ho installato la mia macchina linux ma un veloce google per "linux tail man" non lo mostra come opzione. sunaku menzionato taccome alternativa, quindi proverei questo, invece
tcdyl il

Ho anche modificato la risposta per includere un'altra alternativa
tcdyl il

whoops Ho dimenticato di +1 quando ho detto +1 :( Fatto! Mi dispiace per quello ahah :)
Mehrdad,

4
tail -rè specifico di OSX e inverte l'input delimitato da nuova riga, non l'input delimitato da null. La tua seconda soluzione non funziona affatto (stai eseguendo il piping input ls, a cui non importa); non esiste una soluzione semplice che possa farlo funzionare in modo affidabile.
Gilles 'SO- smetti di essere malvagio' il

1

Nel tuo esempio stai eseguendo il loop su più file, ma ho trovato questa domanda a causa del suo titolo più generale che potrebbe anche coprire il loop su un array o l'inversione in base a un numero qualsiasi di ordini.

Ecco come farlo in Zsh:

Se esegui il looping degli elementi in un array, usa questa sintassi ( sorgente )

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

Oinverte l'ordine specificato nella bandiera successiva; aè il normale ordine di array.

Come ha detto @Gilles, Oninvertirai l'ordine dei tuoi file danneggiati, ad es my/file/glob/*(On). Con . Questo perché Onè "inverso nome ordine."

Flag di ordinamento Zsh:

  • a ordine di array
  • L lunghezza del file
  • l numero di collegamenti
  • m data di modifica
  • n nome
  • ^oordine inverso ( oè ordine normale)
  • O ordine inverso

Per esempi, vedere https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt e http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- to-master-your-z-shell /


0

Mac OSX non supporta il taccomando. La soluzione di @tcdyl funziona quando si chiama un singolo comando in un forciclo. Per tutti gli altri casi, il seguente è il modo più semplice per aggirarlo.

Questo approccio non supporta l'inserimento di nuove righe nei nomi dei file. Il motivo di fondo è che tail -rordina i suoi input come delimitati da newline.

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

Tuttavia, esiste un modo per aggirare la limitazione di nuova riga. Se sai che i tuoi nomi di file non contengono un certo carattere (diciamo, "="), puoi usare trper sostituire tutte le nuove righe per diventare questo personaggio, quindi fare l'ordinamento. Il risultato sarebbe simile al seguente:

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

Nota: a seconda della versione di tr, potrebbe non essere supportato '\0'come personaggio. Questo di solito può essere aggirato cambiando le impostazioni locali in C (ma non ricordo esattamente come, dal momento che dopo averlo risolto una volta ora funziona sul mio computer). Se ricevi un messaggio di errore e non riesci a trovare la soluzione alternativa, pubblicalo come commento, così posso aiutarti a risolverlo.


0

un modo semplice è usare ls -rcome indicato da @David Schwartz

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

-4

Prova questo:

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

Penso che sia il modo più semplice.


3
La domanda ha chiesto " forloop in ordine inverso".
Manatwork
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.