Jak odwrócić pętlę for?

26

Jak prawidłowo wykonać forpętlę w odwrotnej kolejności?

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

Potrzebuję rozwiązania, które nie łamie się z funky w nazwach plików.

Mehrdad
źródło
5
Po prostu przesuń się sort -rprzed forlub wypierz ls -r.
David Schwartz,

Odpowiedzi:

27

W bash lub ksh umieść nazwy plików w tablicy i powtórz tę tablicę w odwrotnej kolejności.

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

Powyższy kod działa również w zsh, jeśli ksh_arraysopcja jest ustawiona (działa w trybie emulacji ksh). W zsh istnieje prostsza metoda, polegająca na odwróceniu kolejności dopasowań za pomocą kwalifikatora glob:

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

POSIX nie obejmuje tablic, więc jeśli chcesz być przenośny, jedyną opcją do bezpośredniego przechowywania tablicy ciągów są parametry pozycyjne.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done
Gilles „SO- przestań być zły”
źródło
Kiedy już użyjesz parametrów pozycyjnych, nie potrzebujesz zmiennych ii f; po prostu wykonaj shift, użyj $1do połączenia bari przetestuj [ -z $1 ]w swoim while.
użytkownik1404316,
@ user1404316 Byłby to zbyt skomplikowany sposób iteracji elementów w porządku rosnącym . Również źle: ani [ -z $1 ]nie [ -z "$1" ]są użytecznymi testami (co jeśli parametr był *lub pusty ciąg?). W każdym razie nie pomagają tutaj: pytanie brzmi, jak zapętlić w odwrotnej kolejności.
Gilles „SO- przestań być zły”
Pytanie wyraźnie mówi, że iteruje nazwy plików w / var / log, więc można bezpiecznie założyć, że żaden z parametrów nie będzie „*” ani pusty. Naprawdę jednak poprawiam punkt odwrotnej kolejności.
użytkownik1404316,
7

Spróbuj tego, chyba że rozpatrujesz łamanie linii jako „funky znaków”:

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
sunaku
źródło
Czy istnieje metoda, która działa z podziałem linii? (Wiem, że to rzadkie, ale przynajmniej dla celów nauki chciałbym wiedzieć, czy można napisać poprawny kod.)
Mehrdad,
Twórczo używaj tac do odwracania przepływu, a jeśli chcesz pozbyć się niechcianych znaków, takich jak łamanie linii, możesz przesłać potokiem do tr -d '\ n'.
Johan
4
Dzieje się tak, jeśli nazwy plików zawierają znaki nowej linii, ukośniki odwrotne lub znaki niedrukowalne. Zobacz mywiki.wooledge.org/ParsingLs i jak zapętlać linie wiersza pliku? (i powiązane wątki).
Gilles „SO- przestań być zły”
Najczęściej głosowana odpowiedź złamała zmienną fw mojej sytuacji. Ta odpowiedź działa jednak prawie jak zwykły zamiennik zwykłej forlinii.
Serge Stroobandt,
2

Jeśli ktoś próbuje wymyślić sposób odwrócenia iteracji po liście ciągów rozdzielanych spacjami, działa to:

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
źródło
2
Nie mam tac w moim systemie; Nie wiem, czy jest solidny, ale używałem dla x w $ {mylist}; do revv = "$ {x} $ {revv}"; zrobione
arp
@arp To trochę szokujące, ponieważ tacjest częścią jądra GNU. Ale twoje rozwiązanie jest również dobre.
ACK_stoverflow
@ACK_stoverflow Istnieją systemy bez narzędzi użytkownika Linux.
Kusalananda
@Kusalananda Czy mówisz, że tylko Linux używa GNU coreutils? LOL. Nawet zajęty ma tac. Chociaż na podstawie liczby tacbez-odpowiedzi tutaj zgaduję, że może OSX nie ma tac. W takim przypadku użyj wariantu rozwiązania @ arp - i tak łatwiej to zrozumieć.
ACK_stoverflow
@ACK_stoverflow Tak mówię, tak.
Kusalananda
1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Powinien działać tak, jak chcesz (zostało to przetestowane na Mac OS X i mam zastrzeżenie poniżej ...).

Ze strony podręcznika użytkownika dla find:

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

Zasadniczo znajdujesz pliki pasujące do łańcucha + glob i kończysz każdy znakiem NUL. Jeśli twoje nazwy plików zawierają znaki nowej linii lub inne dziwne znaki, find powinien sobie z tym poradzić.

tail -r

pobiera standardowe wejście przez potok i odwraca go (zwróć uwagę, że tail -rwypisuje wszystkie dane wejściowe na standardowe wyjście, a nie tylko na 10 ostatnich wierszy, co jest standardowym ustawieniem domyślnym. man tailwięcej informacji).

Następnie przesyłamy to do 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).

Tutaj xargs oczekuje, że argumenty zostaną oddzielone znakiem NUL, który przekazałeś findi odwróciłeś tail.

Moje zastrzeżenie: przeczytałem, że tailnie gra dobrze z ciągami zakończonymi zerem . Działa to dobrze w systemie Mac OS X, ale nie mogę zagwarantować, że tak jest w przypadku wszystkich * nix. Stąpaj ostrożnie.

Powinienem również wspomnieć, że GNU Parallel jest często używany jako xargsalternatywa. Możesz to również sprawdzić.

Być może czegoś mi brakuje, więc inni powinni wejść.

tcdyl
źródło
2
+1 świetna odpowiedź. Wygląda na to, że Ubuntu nie obsługuje tail -r... czy robię coś złego?
Mehrdad
1
Nie, nie sądzę, że jesteś. Nie mam włączonej maszyny linux, ale szybkie google dla „linux tail man” nie pokazuje jej jako opcji. sunaku wspomniane tacjako alternatywa, więc spróbowałbym tego zamiast tego
tcdyl
Zredagowałem również odpowiedź, aby uwzględnić inną alternatywę
tcdyl,
Ups, zapomniałem dać +1, kiedy powiedziałem +1 :( Gotowe! Przepraszam za haha ​​:)
Mehrdad
4
tail -rjest specyficzny dla OSX i odwraca dane rozdzielane znakami nowej linii, a nie danych rozdzielanych znakiem zerowym. Twoje drugie rozwiązanie w ogóle nie działa (przesyłasz dane wejściowe ls, co nie ma znaczenia); nie ma łatwej poprawki, która sprawiłaby, że działałby niezawodnie.
Gilles „SO- przestań być zły”
1

W twoim przykładzie zapętlasz kilka plików, ale znalazłem to pytanie ze względu na bardziej ogólny tytuł, który może również obejmować zapętlanie tablicy lub odwracanie w oparciu o dowolną liczbę zamówień.

Oto jak to zrobić w Zsh:

Jeśli zapętlasz elementy w tablicy, użyj tej składni ( źródło )

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

Oodwraca kolejność określoną w następnej fladze; ajest normalną kolejnością tablic.

Jak powiedział @Gilles, Onodwróci kolejność twoich globowanych plików, np my/file/glob/*(On). Z. To dlatego, że Onjest „odwrotną kolejnością nazw ”.

Flagi sortowania Zsh:

  • a kolejność tablic
  • L długość pliku
  • l liczba linków
  • m Data modyfikacji
  • n imię
  • ^oodwrotna kolejność ( onormalna kolejność)
  • O Odwrotna kolejność

Przykłady można znaleźć na https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt i http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- to-master-your-z-shell /

henz
źródło
0

Mac OSX nie obsługuje tacpolecenia. Rozwiązanie @tcdyl działa, gdy wywołujesz pojedyncze polecenie w forpętli. We wszystkich innych przypadkach najprostszym sposobem obejścia tego jest następujący sposób.

To podejście nie obsługuje posiadania nowych linii w nazwach plików. Podstawowym powodem jest to, że tail -rsortuje on dane wejściowe w postaci znaku nowej linii.

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

Istnieje jednak sposób na obejście ograniczenia nowej linii. Jeśli wiesz, że twoje nazwy plików nie zawierają określonego znaku (powiedz „=”), możesz użyć, traby zastąpić wszystkie znaki nowej linii, aby stać się tym znakiem, a następnie wykonać sortowanie. Wynik wyglądałby następująco:

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

Uwaga: w zależności od wersji trmoże nie obsługiwać '\0'postaci. Można to zwykle obejść, zmieniając ustawienia regionalne na C (ale nie pamiętam, jak dokładnie, ponieważ po naprawieniu raz działa teraz na moim komputerze). Jeśli pojawi się komunikat o błędzie i nie możesz znaleźć obejścia, opublikuj go jako komentarz, aby pomóc w rozwiązaniu problemu.

Alex
źródło
0

prostym sposobem jest użycie, ls -rjak wspomniano @David Schwartz

for f in $(ls -r /var/logs/foo*.log); do
    bar "$f"
done
Megver83
źródło
-4

Spróbuj tego:

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

Myślę, że to najprostszy sposób.

Syhra
źródło
3
Pytanie dotyczyło „ forpętli w odwrotnej kolejności”.
manatwork