У мене немає серця робити все заново, але я написав це у відповідь на Commandline Find Sed Exec . Там запитувач хотів знати, як перемістити ціле дерево, можливо, за винятком каталогу або двох, і перейменувати всі файли та каталоги, що містять рядок "СТАРИЙ", а не "НОВИЙ" .
Окрім опису того, як із кропіткою багатослівністю нижче, цей метод може бути унікальним тим, що включає вбудовану налагодження. По суті, він взагалі нічого не робить, як написано, крім компіляції та збереження у змінну всіх команд, які, на її переконання, повинен робити для виконання запитуваної роботи.
Він також явно уникає циклів , наскільки це можливо. Окрім sedрекурсивного пошуку більш ніж одного збігу шаблону , наскільки мені відомо, немає жодної іншої рекурсії.
І нарешті, це повністю nullрозмежовано - воно не спрацьовує на будь-який символ будь-якого імені файлу, крім null. Я не думаю, що у вас це повинно бути.
До речі, це ДІЙСНО швидко. Подивіться:
% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" | tail -n 2 )
<actual process time used:>
0.06s user 0.03s system 106% cpu 0.090 total
<output from wc:>
Lines Words Bytes
115 362 20691 -
<output from tail:>
mv .config/replacement_word-chrome-beta/Default/.../googlestars \
.config/replacement_word-chrome-beta/Default/.../replacement_wordstars
ПРИМІТКА . Вищевказане function, швидше за все, потребуватиме GNUверсій sedта, findщоб правильно обробляти виклики find printfand sed -z -eта :;recursive regex test;t. Якщо вони недоступні для вас, функціонал, швидше за все, можна продублювати за допомогою кількох незначних коригувань.
Це має зробити все, що ви хотіли від початку до кінця, з дуже невеликою суєтою. Я зробив forkз sed, але я також практикуючи деякі sedрекурсивні методи розгалуження так ось чому я тут. Це начебто, як отримати стрижку зі знижкою в перукарні, я думаю. Ось робочий процес:
rm -rf ${UNNECESSARY}
- Я навмисно пропустив будь-який функціональний виклик, який може видалити або знищити будь-які дані. Ви згадуєте, що
./appможе бути небажаним. Заздалегідь видаліть його або перенесіть в інше місце, або, як варіант, ви можете побудувати \( -path PATTERN -exec rm -rf \{\} \)рутину, findщоб робити це програмно, але це все ваше.
_mvnfind "${@}"
- Оголосіть його аргументи та викличте робочу функцію.
${sh_io}особливо важливий тим, що він економить віддачу від функції. ${sed_sep}приходить у близьку секунду; це довільний рядок, що використовується для посилання sedна рекурсію у функції. Якщо ${sed_sep}встановлено значення, яке потенційно може бути знайдено в будь-якому з ваших імен шляхів чи файлів, за якими діяли ... ну, просто не дозволяйте.
mv -n $1 $2
- Усе дерево рухається з самого початку. Це врятує багато головного болю; Повір мені. Решта того, що ви хочете зробити - перейменування - це просто питання метаданих файлової системи. Якщо ви, наприклад, переміщували це з одного диска на інший або через будь-які межі файлової системи, вам краще зробити це одразу за допомогою однієї команди. Це також безпечніше. Зверніть увагу на
-noclobberопцію, встановлену для mv; як написано, ця функція не буде розміщена ${SRC_DIR}там, де ${TGT_DIR}вже існує.
read -R SED <<HEREDOC
- Я розмістив тут усі команди sed, щоб заощадити на уникненні клопоту та прочитати їх у змінну для подання до sed нижче. Пояснення нижче.
find . -name ${OLD} -printf
- Ми починаємо
findпроцес. З findми шукаємо тільки для чого - небудь , що потребує в перейменуванні , тому що ми вже зробили все місця, в місці mvоперації з першою командою функції. Замість того, щоб виконувати будь-які прямі дії find, наприклад, як execвиклик, ми замість цього використовуємо його для динамічного побудови командного рядка за допомогою -printf.
%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
- Після
findзнаходження потрібних нам файлів він безпосередньо збирає та роздруковує ( більшість ) команди, яка нам знадобиться для обробки вашого перейменування. %dir-depthПришиті початок кожного рядка буде сприяти тому , щоб ми не намагалися перейменувати файл або папку в дереві з батьківським об'єктом , який ще повинен бути перейменований. findвикористовує всілякі методи оптимізації для обробки дерева вашої файлової системи, і не впевнено, що він поверне нам потрібні дані в безпечному для операцій порядку. Ось чому ми далі ...
sort -general-numerical -zero-delimited
- Ми сортуємо всі
findвихідні дані, виходячи з %directory-depthтого, що спочатку працюють шляхи, найближчі до $ {SRC}. Це дозволяє уникнути можливих помилок, пов’язаних із mvвкладанням файлів у неіснуючі місця, і мінімізує потребу в рекурсивному циклі. ( насправді вам може бути важко знайти цикл взагалі )
sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
- Я думаю, що це єдиний цикл у всьому сценарії, і він перемикається лише над другим
%Pathнадрукованим для кожного рядка, якщо він містить більше одного значення $ {OLD}, яке, можливо, потребує заміни. Всі інші рішення, які я собі уявляв, стосуються другого sedпроцесу, і хоча короткий цикл може бути не бажаним, звичайно, він перевершує нерест і розгалуження цілого процесу.
- Отже, в основному
sedтут виконується пошук за $ {sed_sep}, потім, знайшовши його, зберігає його та всі символи, з якими він стикається, поки не знайде $ {OLD}, який потім замінить на $ {NEW}. Потім він повертається до $ {sed_sep} і знову шукає $ {OLD}, якщо це трапляється більше одного разу в рядку. Якщо його не знайти, він друкує модифікований рядок stdout(який потім знову ловить) і закінчує цикл.
- Це дозволяє уникнути необхідності аналізувати весь рядок і гарантує, що перша половина
mvкомандного рядка, яка, звичайно, повинна включати $ {OLD}, включає його, а друга половина змінюється стільки разів, скільки потрібно для стирання $ {OLD} ім'я із mvшляху призначення.
sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
- Два
-execдзвінки тут відбуваються без секунди fork. У першому, як ми вже бачили, ми модифікуємо mvкоманду, надану командою функції find', -printfза необхідності, щоб правильно змінити всі посилання $ {OLD} на $ {NEW}, але для цього нам довелося використовувати деякі довільні контрольні точки, які не слід включати в кінцевий результат. Отож, як тільки sedзакінчить все, що йому потрібно зробити, ми доручаємо йому видалити свої контрольні точки з буфера утримання, перш ніж передавати їх.
І ЗАРАЗ МИ ПОВЕРНУЛИСЯ
read отримає команду, яка виглядає так:
% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
Він readперетвориться на те ${msg}, ${sh_io}що можна дослідити за бажанням поза функцією.
Класно.
-Майк