У мене немає серця робити все заново, але я написав це у відповідь на 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 printf
and 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}
що можна дослідити за бажанням поза функцією.
Класно.
-Майк