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

269042
Daniel A. White

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

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

75

7 ответов на вопрос

78
CoverosGene

Отредактировано, чтобы не использовать 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=$.txt mv $x $t && echo "moved $x -> $t" done 
Что если в имени файла есть пробел? Daniel A. White 15 лет назад 19
Я перебираю все папки. Daniel A. White 15 лет назад 0
К сожалению, как сказал Дэниел, код в этом ответе сломается, если в каком-либо из файлов или папок будет пробел или символ новой строки в их имени. Он показывает очень распространенное неправильное использование циклов `for` и типичную ловушку при попытке анализа выходных данных` ls`. @ DanielA.White, вы * можете * подумать о непринятии этого ответа, если он бесполезен (или потенциально вводит в заблуждение), поскольку, как вы сказали, вы действуете по каталогам. Ответ Шона Дж. Гоффа должен обеспечить более надежное и эффективное решение вашей проблемы. slhck 11 лет назад 4
-∞ [Вы не должны анализировать вывод `ls`] (http://mywiki.wooledge.org/ParsingLs), [вы не должны читать вывод в цикле` for`] (http: //mywiki.wooledge. org / BashFAQ / 001), вы должны использовать `$ ()` вместо `` и [Use More Quotes ™] (http://mywiki.wooledge.org/Quotes). Я уверен, что @CoverosGene имел в виду хорошо, но это просто ужасно. l0b0 10 лет назад 4
Лучшая альтернатива, если вы действительно хотите использовать `ls`, это` ls -1 | пока читаешь строку; делать вещи; done`. По крайней мере, это не сломается для пробелов. Emmanuel Joubaud 10 лет назад 1
с `mv -v` вам не нужно` echo "переместил $ x -> $ t" ` DmitrySandalov 8 лет назад 1
63
Shawn J. Goff

Использование вывода 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а не использовала ее для себя; Другой способ заставить оболочку не обрабатывать ее специально - заключить ее в кавычки: ';'работает так же, как и \;для этой цели.

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

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

 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; 
хорошее использование подоболочки в первом примере Jeremy L 15 лет назад 0
Зачем IFS нужен $ после назначения и перед новым персонажем? Andy Ibanez 11 лет назад 0
Имена файлов также могут содержать переводы строк. Взлом на `\ n` недостаточен. Никогда не рекомендуется рекомендовать синтаксический анализ вывода `ls`. ghoti 10 лет назад 3
5
Ole Tange

Если у вас установлен GNU Parallel http://www.gnu.org/software/parallel/, вы можете сделать это:

ls | parallel echo {} is in this dir 

Чтобы переименовать все .txt в .xml:

ls *.txt | parallel mv {} {.}.xml 

Посмотрите вступительное видео для GNU Parallel, чтобы узнать больше: http://www.youtube.com/watch?v=OpaiGYxkSuQ

4
n3o

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

for f in */; do echo "Directory -> $f" done 
1
jetset

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

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

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

SAVEIFS=$IFS IFS=$(echo -en "\n\b")  FILES=($(/bin/ls "$1"))  for AFILE in $ do ... do something with a file ... done  IFS=$SAVEIFS 

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

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

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

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

Похожие вопросы