Створення сценарію BASH `for` обробляти імена файлів з пробілами (або вирішенням)


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 read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

Пізніше це дозволило мені умовно збільшувати змінні в циклі, які залишатимуться доступними пізніше у сценарії.


Why_would_you_ever_need_a_space_in_a_file_name?
Кевін Панько

Щоправда, не мої уподобання. Хоча, щоб видалити пробіли, потрібно спочатку обробити файли з пробілами;)
Самуель Джешке

1
Власне, імена файлів повинні містити пробіли. Я дозволяв би що-небудь, крім /і недрукованих символів. Але дозволено все, крім цього, /і \0тому ви повинні їх дозволити.
Кевін Панько

Відповіді:


11

Потрібно прокласти трубу findв whileпетлю.

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

Також вам не потрібно буде користуватися -printfв цьому випадку.

Ви можете зробити це підтвердженням для файлів з новими рядками в їх іменах, якщо хочете, використовуючи роздільник нульбайт (який є єдиним символом, який не може відображатися в файлі * nix):

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

Ви також знайдете використання $()замість підказок більш універсальним та простішим. Їх можна вкласти набагато легше, а цитування можна зробити набагато простіше. Цей надуманий приклад ілюструє ці моменти:

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

Спробуйте це зробити за допомогою підказок.


2
Також замість "$dir"цього бажано використовувати "${dir}"- легко визначити різницю між ім'ям $ {dir} та $ {dirname}, але $ dirname можна інтерпретувати будь-яким способом.
Джеймс Поллі

Тут важливо те, що readчитає цілий рядок ${dir}, тому IFS не має значення.
Джеймс Поллі

1
Дякуємо за те, що знайшли помилку $ / ". Підтяжки не потрібні, якщо за ім'ям змінної немає нічого.
Призупинено до подальшого повідомлення.

4
Це обробляє назви шляхів з пробілами (U + 0020), але все одно не вдасться належним чином обробити назви шляхів з каналами рядків (U + 000A). Я вважаю за краще, find … -print0 | xargs -0 …оскільки роздільник, який він використовує, відповідає точно єдиному символу, який не дозволений у патаменах POSIX: NUL (U + 0000).
Кріс Джонсен

2
Ідеально! Тільки те, що я шукав. Мені ніколи не приходило в голову, що ви, можливо, зможете підключитися while. @Chris Johnsen: Правда, але навіть програми з копіювання музики, як правило, не вводять канали ліній у свої імена файлів. І якщо вони будуть, я хочу знати (тобто: щось піде не так) і позбутися від них негайно ...
Самуель Джешке

8

Дивіться цю відповідь, яку я написав кілька днів тому на прикладі сценарію, який обробляє назви файлів з пробілами.

Існує дещо більш складний (але більш стислий) спосіб досягти того, що ви намагаєтесь зробити:

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

-print0розповідає знайти для розділення аргументів з нулем; від -0 до xargs вказує на очікування аргументів, розділених нулями. Це означає, що він обробляє пробіли прекрасно.

-I {}повідомляє xargs замінити рядок {}на ім'я файлу. Це також означає, що для командного рядка слід використовувати лише одне ім’я файлу (xargs, як правило, містить стільки, скільки вміститься у рядку)

Решта має бути очевидним.


Однак пропозиція Денніса Вільямсона (окрім помилок друку) набагато легше читається, і, таким чином, є переважною майже в усіх відношеннях.
Джеймс Поллі

Працює для mkdir, але вибачте, я мав би бути більш зрозумілим - я хочу виконати ряд команд для кожного файлу. Розумієте, для мого подібного розпорядку пізніше я хочу створити назву вихідного файлу на основі імені вхідного файлу (що включає зняття розширення .ogg та додавання .mp3), а потім використовувати ці декілька змінних у моєму конвеєрі під час виклику gst-start.
Самуель Джешке

5

Проблема, з якою ви стикаєтесь, полягає у тому, що заява for for - це відповідь на знаходження як окремі аргументи. Розмежувач простору. Вам потрібно використовувати змінну IFS bash, щоб не розділити на простір.

Ось посилання, що пояснює, як це зробити.

Внутрішня змінна IFS

Один із способів вирішення цієї проблеми - змінити внутрішню змінну IFS (Internal Field Separator) Баша, щоб вона розбила поля на щось інше, ніж пробіл пробілу (пробіл, вкладка, новий рядок), в цьому випадку - кома.

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

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

Встановіть висновок для виведення роздільника поля після% P та встановіть IFS відповідним чином. Я вибрав напівкрапку, оскільки навряд чи знайдеться у ваших іменах.

Інша альтернатива - зателефонувати на mkdir із знахідки безпосередньо через -execте, чи можна взагалі пропустити цикл for. Це якщо вам не потрібно робити додатковий аналіз.


Що робити, якщо ім'я файлу містить IFS? Тоді вам доведеться вибрати інший. Але тоді, що, якби…
Призупинився до подальшого повідомлення.

3
Ви можете вибрати /на POSIX та :файлових системах DOS. Існують незаконні символи для різних файлових систем, які ви можете вибрати для IFS. Все складніше, і вам краще скористатися perl.
Даррен Хол

2
Проблема використання / полягає в тому, що це розділювач каталогів і findповертає назви файлів із шляхами, включаючи косу рису. Спробуйте змінити крапку з комою в сценарії на нахил, і луна надрукує каталог та ім'я файлу в окремих рядках.
Призупинено до подальшого повідомлення.

Це також виглядає досить корисно. Я пішов з трубою на whileваріант, але це також виглядає досить працездатним. Так, в моїй подібній структурі пізніше мені потрібно було зробити подальший аналіз. (Ім'я вхідного файлу буде .ogg, яке передаватиметься як filesrcу gst-конвеєрі, але еквівалентне закінчення .mp3, засноване на вихідному каталозі, буде генеровано та передане також у конвеєр як filesink, і це, звичайно, потрібно зробити для кожного файлу разом з деякими echoдля користувача.)
Самуель Джешке

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

у вашому оновленому запитанні

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

це має бути

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

Спасибі. Виправлено. Він також читав у FILENAME замість DIR - copy-paste: P
Samuel Jaeschke


0

або зробити всю справу набагато менш складною:

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

це копіює структуру каталогів SRC в DST.


Ні, мені потрібна така ітеративна структура, яка дозволяє мені запускати кілька рядків коду для кожного файлу. "Тепер мені потрібен певний шлях, який я можу повторити через це, тому що я також хочу запустити більш складну команду, що включає gstreamer для кожного файлу, у такій подібній структурі." Вибачте, якщо мені було незрозуміло.
Самуель Джешке

команда, яку я дав, вирішує проблему, яку ви попросили, не має значення, чи це лише частина більшого "конвеєра" на вашій стороні. для когось іншого, який має проблему, як описано в питанні, підхід rsync буде працювати. значить, не потрібно шкодувати про потенційну незрозумілість :)
akira

Так. Ні, я маю в виду , я б використовувати схожі while... do... doneструктуру пізніше зробити аналогічну обробку з знахідки, що вимагало б кілька рядків коди , які будуть виконуватися на кожен файл (змінити рядок, відлуння, GST-запуск і т.д. ) і rsyncне досяг би цього. Ось чому я уточнив, що мені потрібно мати можливість виконувати більш складний набір команд в межах подібної структури. Мій сценарій використовує цю структуру циклу двічі, тому для питання я розмістив ту, що має менше грубості посередині.
Самуель Джешке

0

Якщо у вас встановлений паралельний параметр GNU 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.