Как поменять цикл for?

26

Как правильно сделать forцикл в обратном порядке?

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

Мне нужно решение, которое не ломается от прикольных символов в именах файлов.

Mehrdad
источник
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 есть более простой метод, который заключается в изменении порядка совпадений с помощью квалификатора glob:

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.
user1404316
@ user1404316 Это был бы слишком сложный способ перебора элементов в порядке возрастания . Кроме того , не так: ни , [ -z $1 ]ни [ -z "$1" ]полезные тесты (что , если параметр был *, или пустая строка?). И в любом случае они здесь не помогают: вопрос в том, как сделать цикл в обратном порядке.
Жиль "ТАК - перестань быть злым"
В частности, вопрос говорит, что он перебирает имена файлов в / var / log, поэтому можно предположить, что ни один из параметров не будет "*" или пустым. Тем не менее, я исправлен в отношении обратного порядка.
user1404316
7

Попробуйте это, если только вы не считаете разрывы строк «прикольными символами»:

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
sunaku
источник
Есть ли метод, который работает с переносами строк? (Я знаю, что это редко, но, по крайней мере, ради обучения я хотел бы знать, можно ли написать правильный код.)
Mehrdad
Творческий подход к использованию 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
ACK_stoverflow
источник
2
У меня нет так в моей системе; Я не знаю, надежен ли он, но я использовал для x в $ {mylist}; do revv = "$ {x} $ {revv}"; сделано
arp
@arp Это шокирует, так как tacявляется частью GNU coreutils. Но ваше решение тоже хорошее.
ACK_stoverflow
@ACK_stoverflow Существуют системы без инструментов пользователя Linux.
Кусалананда
@Kusalananda Вы говорите, что только Linux использует GNU coreutils? СМЕШНО. Даже busybox есть 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. Если ваши имена файлов содержат символы новой строки или другие странные символы, команда find должна с этим справиться.

tail -r

принимает стандартный ввод через канал и переворачивает его (обратите внимание, что все вводимые данные tail -rпечатаются в стандартный вывод, а не только последние 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 Parallel часто используется в качестве xargsальтернативы. Вы можете проверить это тоже.

Я могу что-то упустить, поэтому другие должны вмешиваться.

tcdyl
источник
2
+1 отличный ответ. Похоже, Ubuntu не поддерживает tail -r... я делаю что-то не так?
Мердад
1
Нет, я так не думаю. У меня не работает мой linux-компьютер, но быстрый поиск по запросу «linux tail man» не показывает его в качестве опции. Сунаку упоминается tacв качестве альтернативы, поэтому я бы попробовал вместо этого
tcdyl
Я также отредактировал ответ, чтобы включить другую альтернативу
tcdyl
Ой, я забыл +1, когда сказал +1: Готово! Извините за это, ха-ха :)
Mehrdad
4
tail -rспецифичен для OSX и инвертирует ввод с разделителями новой строки, а не ввод с нулем. Ваше второе решение вообще не работает (вы вводите данные ls, это не важно); нет простого решения, которое могло бы сделать его надежным.
Жиль "ТАК - перестань быть злым"
1

В вашем примере вы перебираете несколько файлов, но я нашел этот вопрос из-за его более общего заголовка, который также может охватывать зацикливание над массивом или реверсирование на основе любого количества порядков.

Вот как это сделать в Zsh:

Если вы перебираете элементы массива, используйте этот синтаксис ( источник )

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

Oменяет порядок, указанный в следующем флаге; aнормальный порядок массива.

Как сказал @Gilles, Onобратный порядок ваших файлов, например, с 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/outrageously-useful-tips- в мастер-ваш-Z-оболочка /

Генри
источник
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 (но я не помню как именно, так как после исправления однажды она теперь работает на моем компьютере). Если вы получаете сообщение об ошибке и не можете найти обходной путь, то, пожалуйста, опубликуйте его как комментарий, чтобы я мог помочь вам устранить его.

Alex
источник
0

простой способ использования, ls -rкак упомянуто @David Schwartz

for f in $(ls -r /var/logs/foo*.log); do
    bar "$f"
done
Megver83
источник
-4

Попробуй это:

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

Я думаю, что это самый простой способ.

Syhra
источник
3
Вопрос задан для « forпетли в обратном порядке».
manatwork