перебирая результаты `ls` в скрипте bash

94

Есть ли у кого-нибудь шаблонный скрипт для работы с lsсписком имен каталогов, циклический просмотр каждого из них и выполнение чего-либо?

Я планирую сделать, ls -1d */чтобы получить список имен каталогов.

Даниэль А. Уайт
источник

Ответы:

111

Отредактировано, чтобы не использовать ls там, где будет делать глоб, как предложили @ shawn-j-goff и другие.

Просто используйте for..do..doneцикл:

for f in *; do
  echo "File -> $f"
done

Вы можете заменить *с *.txtили любой другой Glob , который возвращает список (файлов, каталогов или что - нибудь в этом отношении), команда , которая генерирует список, например, $(cat filelist.txt)или на самом деле заменить его в списке.

Внутри doцикла вы просто ссылаетесь на переменную цикла с префиксом знака доллара (как $fв приведенном выше примере). Вы можете echoсделать это или сделать что-то еще, что хотите.

Например, чтобы переименовать все .xmlфайлы в текущем каталоге .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

Или даже лучше, если вы используете Bash, вы можете использовать расширения параметров Bash, а не порождать подоболочку:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done
CoverosGene
источник
22
Что если в имени файла есть пробел?
Дэниел А. Уайт
4
К сожалению, как сказал Дэниел, код в этом ответе сломается, если в каком-либо из файлов или папок будет пробел или символ новой строки в их имени. Он показывает очень распространенное неправильное использование forциклов и типичную ловушку при попытке анализа выходных данных ls. @ DanielA.Белый, вы можете подумать о том, чтобы не принять этот ответ, если он не помог (или потенциально вводит в заблуждение), поскольку, как вы сказали, вы действуете по каталогам. Ответ Шона Дж. Гоффа должен обеспечить более надежное и эффективное решение вашей проблемы.
slhck
4
-∞ Вы не должны анализировать lsвывод , вы не должны читать вывод в forцикле , вы должны использовать $()вместо `` и Use More Quotes ™ . Я уверен, что @CoverosGene имел в виду хорошо, но это просто ужасно.
10
1
Лучшая альтернатива, если вы действительно хотите использовать lsэто ls -1 | while read line; do stuff; done. По крайней мере, это не сломается для пробелов.
ejoubaud
1
с mv -vтобой не надоecho "moved $x -> $t"
ДмитрийСандалов
69

Использование вывода lsдля получения имен файлов - плохая идея . Это может привести к неправильной работе и даже опасным сценариям. Это происходит потому , что имя файла может содержать любые символы , кроме /и nullхарактер, и lsне использовать один из этих символов в качестве разделителей, так что если имя файла содержит пробел или символ новой строки, вы будете получать неожиданные результаты.

Есть два очень хороших способа перебора файлов. Здесь я просто echoпродемонстрировал, как что-то делать с именем файла; Вы можете использовать все, что угодно.

Во-первых, использовать встроенные функции оболочки.

for dir in */; do
  echo "$dir"
done

Оболочка расширяется */в отдельные аргументы, которые forчитает цикл; даже если в имени файла есть пробел, символ новой строки или любой другой странный символ, forкаждое полное имя будет рассматриваться как атомарная единица; это не разбирает список в любом случае.

Если вы хотите рекурсивно переходить в подкаталоги, то этого не произойдет, если ваша оболочка не имеет некоторых расширенных функций глобализации (например bash, s globstar. Если ваша оболочка не имеет этих функций или если вы хотите убедиться, что ваш скрипт будет работать на различных системах, то следующий вариант заключается в использовании find.

find . -type d -exec echo '{}' \;

Здесь findкоманда вызовет echoи передаст ей аргумент имени файла. Он делает это один раз для каждого найденного файла. Как и в предыдущем примере, нет разбора списка имен файлов; вместо этого имя файла отправляется полностью в качестве аргумента.

Синтаксис -execаргумента выглядит немного забавно. findпринимает первый аргумент после -execи обрабатывает его как программу для запуска, а каждый последующий аргумент - как аргумент для передачи этой программе. Есть два особых аргумента, которые -execнужно увидеть. Первый из них {}; этот аргумент заменяется именем файла, которое findгенерирует предыдущие части . Второй ;, который дает findпонять, что это конец списка аргументов для передачи в программу; findЭто необходимо, потому что вы можете продолжить с дополнительными аргументами, которые предназначены findи не предназначены для программы exec'd. Причина в том \, что оболочка также лечит;специально - она ​​представляет конец команды, поэтому нам нужно избегать ее, чтобы оболочка выдавала ее в качестве аргумента, findа не использовала ее для себя; Другой способ заставить оболочку не обрабатывать ее специально - заключить ее в кавычки: ';'работает так же, как и \;для этой цели.

Шон Дж. Гофф
источник
2
+1 это определенно путь, когда вам нужно сгенерировать список файлов и использовать его в команде. Команда find -exec ограничена возможностью запуска только одной команды. С помощью этой петли вы можете передать все, что душе угодно.
MaQleod
+1. Я хотел бы, чтобы больше людей использовали find. Есть волшебство, которое можно сделать, и даже воспринимаемые ограничения -execможно обойти. -print0Вариант также ценен для использования с xargs.
ghoti
Опция цикла не будет отображать скрытые каталоги. Опция поиска не будет отображать символические ссылки.
Джинави
16

Для файлов с пробелами вы должны обязательно заключить в кавычки переменную, например:

 for i in $(ls); do echo "$i"; done; 

или вы можете изменить переменную среды разделителя входных полей (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

Наконец, в зависимости от того, что вы делаете, вам может даже не понадобиться ls:

 for i in *; do echo "$i"; done;
Джон
источник
хорошее использование подоболочки в первом примере
Джереми Л
Зачем IFS нужен $ после назначения и перед новым персонажем?
Энди Ибанез
3
Имена файлов также могут содержать переводы строк. Взлом \nнедостаточен. Никогда не рекомендуется рекомендовать анализ выходных данных ls.
ghoti
4

Просто чтобы добавить ответ CoverosGene, вот способ перечислить только имена каталогов:

for f in */; do
  echo "Directory -> $f"
done
N3O
источник
1

Почему бы не установить IFS на новую строку, а затем записать вывод lsв массив? Установка IFS на новую строку должна решить проблемы с забавными символами в именах файлов; Использование lsможет быть хорошим, потому что оно имеет встроенную функцию сортировки.

(При тестировании у меня возникли проблемы с настройкой IFS, \nно настройка перевода строки на новую строку работает, как это предлагается в другом месте здесь):

Например (при условии, что желаемый lsшаблон поиска передан $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Это особенно удобно в OS X, например, для захвата списка файлов, отсортированных по дате создания (от самого старого до самого нового), lsкоманда имеет вид ls -t -U -r.

JetSet
источник
2
Имена файлов также могут содержать символы новой строки, и они часто появляются, когда пользователям разрешается называть свои собственные файлы. Взлом \nнедостаточен. Единственные допустимые решения используют цикл for с расширением пути или find.
ghoti
Единственный надежный способ передать список имен файлов - разделить их символом NUL, поскольку это единственный способ, который точно не содержится в пути к файлу.
glglgl
-3

Вот как я это делаю, но, возможно, есть более эффективные способы.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt
ДЕРЕВО
источник
6
Придерживайтесь труб вместо файла:>ls | while read i; do echo filename: $i; done
Джереми Л
Здорово. Я должен сказать, что вы также можете использовать $ EDITOR filelist.txt между двумя командами. Множество вещей, которые вы можете сделать в редакторе, проще, чем в командной строке. Не относится к этому вопросу, хотя.
TREE
Ваше решение совсем не решает проблему с именами файлов, содержащими переводы строк и другие интересные вещи.
glglgl