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.


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

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

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

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ść.


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 /


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.


0

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

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

-4

Spróbuj tego:

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

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


3
Pytanie dotyczyło „ forpętli w odwrotnej kolejności”.
— manatwork
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.