
Есть ли у кого-нибудь шаблон скрипта оболочки для выполнения чего-либо со ls
списком имен каталогов, прохода по каждому из них и выполнения чего-либо?
Я планирую сделать так, ls -1d */
чтобы получить список имен каталогов.
решение1
Отредактировано так, чтобы не использовать ls там, где можно было бы использовать glob, как предлагали @shawn-j-goff и другие.
Просто используйте for..do..done
цикл:
for f in *; do
echo "File -> $f"
done
Вы можете заменить его *
на *.txt
или любой другой глоб, который возвращает список (файлов, каталогов или чего-либо еще), на команду, которая генерирует список, например, $(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
решение2
Использовать вывод 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
и не предназначены для исполняемой программы. Причина в том, \
что оболочка также обрабатывает его ;
особым образом — он представляет собой конец команды, поэтому нам нужно экранировать его, чтобы оболочка передала его как аргумент, а find
не использовала его для себя; другой способ заставить оболочку не обрабатывать его особым образом — заключить его в кавычки: ';'
работает так же хорошо, как и \;
для этой цели.
решение3
Для файлов с пробелами вам придется обязательно заключить переменную в кавычки, например:
for i in $(ls); do echo "$i"; done;
или вы можете изменить переменную среды разделителя полей ввода (IFS):
(IFS=$'\n';for i in $(ls); do echo $i; done) # subshell to restore IFS
Наконец, в зависимости от того, что вы делаете, вам может даже не понадобиться ls:
for i in *; do echo "$i"; done;
решение4
Чтобы дополнить ответ CoverosGene, вот способ вывести список только имен каталогов:
for f in */; do
echo "Directory -> $f"
done