12

Хотя я использую BASH в течение нескольких лет, мой опыт работы со сценариями BASH относительно ограничен.

Мой код, как показано ниже. Он должен получить всю структуру каталогов из текущего каталога и скопировать его в $OUTDIR .

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

Проблема, вот пример моей файловой структуры:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

Обратите внимание на пробелы:-S И for принимает параметры слово за словом, поэтому вывод моего скрипта выглядит примерно так:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

Но мне нужно получить целые имена файлов (по одной строке за раз) из результатов find . Я также попытался find двойные кавычки для каждого имени файла. Но это не помогает.

for DIR in `find . -type d -printf "\"%P\"\040"`

И вывод с этой измененной строкой:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

Теперь мне нужен какой-то способ, с помощью которого я могу выполнять итерации, как это, потому что я также хочу выполнить более сложную команду, включающую gstreamer для каждого файла в следующей аналогичной структуре. Как я должен это делать?

Редактировать: мне нужна структура кода, которая позволит мне запускать несколько строк кода для каждого каталога / файла / цикла. Извините, если мне неясно.

Решение: я изначально пробовал:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

Это работало нормально по большей части. Однако позже я обнаружил, что, поскольку конвейер приводит к выполнению цикла while в подоболочке, все переменные, установленные в цикле, впоследствии были недоступны, что затрудняло реализацию счетчика ошибок. Мое окончательное решение (из этого ответа на SO):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

Позже это позволило мне условно увеличивать переменные в цикле, которые позже будут доступны в скрипте.

8 ответов8

11

Вы должны трубам find в цикл.

find ... | while read -r dir
do
    something with "$dir"
done

Кроме того, вам не нужно будет использовать -printf в этом случае.

Вы можете сделать это доказательство для файлов с символами новой строки в их именах, если хотите, используя разделитель нулевым байтом (это единственный символ, который не может появиться в * nix filepath):

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

Вы также обнаружите, что использование $() вместо обратных кавычек будет более гибким и простым. Они могут быть вложены гораздо проще, а цитирование - гораздо проще. Этот надуманный пример проиллюстрирует эти моменты:

echo "$(echo "$(echo "hello")")"

Попробуйте сделать это с помощью галочек.

8

Посмотрите этот ответ, который я написал несколько дней назад, для примера скрипта, который обрабатывает имена файлов с пробелами.

Есть немного более запутанный (но более лаконичный) способ достичь того, что вы пытаетесь сделать:

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}

-print0 указывает find отделить аргументы от нуля; -0 для xargs говорит ему ожидать аргументы, разделенные нулями. Это означает, что он прекрасно обрабатывает пробелы.

-I {} говорит xargs заменить строку {} именем файла. Это также подразумевает, что в командной строке должно использоваться только одно имя файла (обычно xargs будет заполнять столько, сколько поместится в строке)

В остальном должно быть очевидно.

5

Проблема, с которой вы сталкиваетесь, заключается в том, что оператор for отвечает на поиск как отдельные аргументы. Разделитель пространства. Вам нужно использовать переменную IFS bash, чтобы не разделять пространство.

Вот ссылка, которая объясняет, как это сделать.

Внутренняя переменная IFS

Одним из способов решения этой проблемы является изменение внутренней переменной Bash IFS (Internal Field Separator), чтобы она разделяла поля чем-то отличным от пробела по умолчанию (пробел, табуляция, символ новой строки), в данном случае запятой.

#!/bin/bash
IFS=$';'

for I in `find -type d -printf \"%P\"\;`
do
   echo "== $I =="
done

Настройте свою находку для вывода разделителя полей после% P и установите свой IFS соответствующим образом. Я выбрал точку с запятой, так как это вряд ли можно найти в ваших именах файлов.

Другой альтернативой является вызов mkdir из поиска непосредственно через -exec , если вы можете вообще пропустить цикл for. Это если вам не нужно делать дополнительный анализ.

4

Если тело вашего цикла больше, чем одна команда, можно использовать xargs для запуска сценария оболочки:

export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
  printf "mkdir -p %q\\n" "${OUTPATH}${DIR}"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done' -

Обязательно добавьте завершающую черту (или другое слово), если оболочка относится к разновидности Bourne/POSIX (она используется для установки $ 0 в сценарии оболочки). Кроме того, следует соблюдать осторожность при заключении в кавычки, поскольку сценарий оболочки пишется внутри строки в кавычках, а не непосредственно в приглашении.

1
find . -type d -exec mkdir -p "{}\040" ';' -exec echo "Created {}\040" ';'
1

в вашем обновленном вопросе у вас есть

mkdir -p \"${OUTPATH}${DIR}\"

это должно быть

mkdir -p "${OUTPATH}${DIR}"
0

или сделать все это намного менее сложным:

% rsync -av --include='*/' --exclude='*' SRC DST

это копирует структуру каталогов SRC в DST.

0

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

find . -type d | parallel echo making {} ";" mkdir -p /tmp/outdir/{} ";" echo made {}

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

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