85

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

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

7 ответов7

94

Отредактировано, чтобы не использовать ls там, где будет делать глоб, как предложили @ 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
64

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

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

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

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

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

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

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

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

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

11

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

 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;
5

Если у вас установлен 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

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

for f in */; do
  echo "Directory -> $f"
done
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 .

-3

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

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .