it-swarm.xyz

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

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

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

87
Daniel A. White

Отредактировано, чтобы не использовать 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
100
CoverosGene

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

64
Shawn J. Goff

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

 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;
14
john

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

5
Ole Tange

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

for f in */; do
  echo "Directory -> $f"
done
4
n3o

Почему бы не установить 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.

1
jetset