Як повернути цикл a для циклу?


26

Як правильно зробити forцикл у зворотному порядку?

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

Мені потрібно рішення, яке не порушує фанкі символів у назвах файлів.


5
Просто труба , sort -rдо forабо відмивати через ls -r.
Девід Шварц

Відповіді:


27

У bash або ksh введіть імена файлів у масив та повторіть цей масив у зворотному порядку.

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

Код вище також працює в zsh, якщо ksh_arraysпараметр встановлений (він знаходиться в режимі емуляції ksh). У zsh є більш простий метод, який полягає в тому, щоб змінити порядок поєдинків через глобальний класифікатор:

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

POSIX не включає масиви, тому, якщо ви хочете бути портативними, ваш єдиний варіант безпосередньо зберігати масив рядків - це позиційні параметри.

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

Після того, як ви використовуєте позиційні параметри, немає необхідності для змінних iі f; просто виконайте shift, використовуйте $1для свого дзвінка barта перевіряйте [ -z $1 ]свій while.
користувач1404316

@ user1404316 Це був би надто складний спосіб ітерації елементів у порядку зростання . Також неправильно: ні корисні тести, [ -z $1 ]ні [ -z "$1" ]корисні тести (що робити, якщо параметр був *, або порожній рядок?). І в будь-якому випадку вони тут не допомагають: питання полягає в тому, як зациклюватися в протилежному порядку.
Жил "ТАК - перестань бути злим"

Питання конкретно говорить про те, що воно повторюється над іменами файлів у / var / log, тому з впевненістю можна припустити, що жоден з параметрів не буде "*" чи порожнім. Я все-таки виправляюсь у точці зворотного порядку.
користувач1404316

7

Спробуйте це, якщо ви не вважаєте розриви рядків "прикольними символами":

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

Чи існує метод, який працює з розривами рядків? (Я знаю, що це рідко, але хоча б заради навчання, я хотів би знати, чи можна написати правильний код.)
Мехрдад

Креативно використовувати tac, щоб повернути потік, і якщо ви хочете позбутися деяких небажаних символів, таких як розриви рядків, ви можете передати на tr -d '\ n'.
Йоган

4
Це порушується, якщо назви файлів містять нові рядки, зворотні косої риси чи символи, що не можна друкувати. Див. Розділ mywiki.wooledge.org/ParsingLs та як переходити до рядків файлу? (і пов'язані нитки).
Жил "ТАК - перестань бути злим"

Найбільш голосована відповідь зламала змінну fв моїй ситуації. Однак ця відповідь є значною мірою як заміна звичайного forрядка.
Серж Стройбандт

2

Якщо хтось намагається зрозуміти, як повернути ітерацію через список рядків з обмеженим пробілом, це працює:

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

list="a bb ccc"

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

2
Я не маю tac у своїй системі; Я не знаю, чи він надійний, але я використовував для x у $ {mylist}; do revv = "$ {x} $ {revv}"; зроблено
арп

@arp Це шокуюче, оскільки tacє частиною GNU coreutils. Але ваше рішення також є хорошим.
ACK_stoverflow

@ACK_stoverflow Там є системи без інструментів для користування Linux.
Kusalananda

@Kusalananda Ви хочете сказати, що лише Linux використовує GNU coreutils? ЛОЛ. Навіть зайнятий є tac. Хоча з числа tacбезрезультатних відповідей тут я здогадуюсь, що, можливо, OSX не має tac. У такому випадку використовуйте якийсь варіант рішення @ arp - це все одно простіше зрозуміти.
ACK_stoverflow

@ACK_stoverflow Ось що я говорю, так.
Кусалаланда

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

Потрібно працювати так, як вам хочеться (це було перевірено на Mac OS X, і в мене нижче застереження ...).

На сторінці чоловіка для пошуку:

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

В основному ви знаходите файли, які відповідають вашому рядку + glob і закінчують кожен із символів NUL. Якщо ваші імена файлів містять нові рядки або інші дивні символи, пошук має це добре впоратися.

tail -r

приймає стандартний вхід через трубу і повертає його назад (зверніть увагу, що tail -rдрукується весь вхід у stdout, а не лише останні 10 рядків, що є стандартним за замовчуванням. man tailДля отримання додаткової інформації).

Потім ми передаємо це 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).

Тут xargs очікує на те, щоб побачити аргументи, розділені символом NUL, з якого ви перейшли findта переймено tail.

Мій застереження: Я читав, що tailне добре співпадає з нульовими строками . Це добре працювало на Mac OS X, але я не можу гарантувати, що це стосується всіх * nixes. Стежте обережно.

Слід також зазначити, що паралель GNU часто використовується як xargsальтернатива. Ви можете це також перевірити.

Можливо, мені чогось не вистачає, тому інші повинні задзвеніти.


2
+1 чудова відповідь. Схоже, Ubuntu не підтримує, tail -rхоча я щось роблю не так?
Мехрдад

1
Ні, я не думаю, що ти є. У мене не працює Linux машина, але швидкий google для "linux tail man" не відображає це як варіант. sunaku згаданий tacяк альтернатива, тому я б спробував це, замість цього
tcdyl

Я також відредагував відповідь, щоб включити ще одну альтернативу
tcdyl

ой, я забув поставити +1, коли сказав +1 :( Готово! Вибачте за це ха-ха :)
Мехрдад

4
tail -rє специфічним для OSX, і обертає введений новий рядком вхід, а не введений нулем вхід. Ваше друге рішення взагалі не працює (ви працюєте з підключенням ls, що не хвилює); не існує простого виправлення, яке б змусило його надійно працювати.
Жиль "ТАК - перестань бути злим"

1

У вашому прикладі ви перебираєте декілька файлів, але я знайшов це питання через його більш загальний заголовок, який також може охоплювати циклічність через масив або реверсування на основі будь-якої кількості замовлень.

Ось як це зробити в Zsh:

Якщо ви перебираєте елементи в масиві, використовуйте цей синтаксис ( джерело )

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

Oвідміни порядку, зазначеного в наступному прапорі; a- це нормальний порядок масиву.

За словами @Gilles, Onбуде зворотний порядок сортування ваших globbed файлів, наприклад , з my/file/glob/*(On). Це тому, що Onце "зворотний порядок імен ".

Zsh сортування прапорів:

  • a порядок масиву
  • L довжина файлу
  • l кількість посилань
  • m дата зміни
  • n назва
  • ^oзворотний порядок ( oце нормальний порядок)
  • O зворотний порядок

Наприклад, див. Https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt та http://reasoniamhere.com/2014/01/11/outragerely-useful-tips- to master-your-z-shell /


0

Mac OSX не підтримує tacкоманду. Рішення від @tcdyl працює, коли ви викликаєте одну команду в forциклі. Для всіх інших випадків наступним є найпростіший спосіб обійти його.

Цей підхід не підтримує нових рядків у ваших іменах. Основна причина полягає в тому, що tail -rвпорядковується його введення як обмежене новими рядками.

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

Однак є спосіб обійти обмеження нового рядка. Якщо ви знаєте, що ваші назви файлів не містять певного символу (скажімо, '='), ви можете використовувати trдля заміни всіх нових рядків, щоб стати цим символом, а потім виконувати сортування. Результат виглядатиме так:

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

Примітка: залежно від вашої версії tr, вона може не підтримуватись '\0'як символ. Зазвичай це можна вирішити, змінивши локальну мову на C (але я не пам'ятаю, як саме, оскільки після її виправлення це працює зараз на моєму комп'ютері). Якщо ви отримаєте повідомлення про помилку, і ви не можете знайти вирішення, будь ласка, опублікуйте його як коментар, щоб я міг допомогти вам усунути його.


0

простий спосіб - це використання, ls -rяк згадував @David Schwartz

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

-4

Спробуйте це:

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

Я думаю, що це найпростіший спосіб.


3
У питанні було задано " forцикл у зворотному порядку".
манатура
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.