У меня нет духа делать это снова и снова, но я написал это в ответ на Commandline Find Sed Exec . Здесь спрашивающий хотел знать, как переместить все дерево, возможно, исключая один или два каталога, и переименовать все файлы и каталоги, содержащие строку «OLD», чтобы вместо этого содержать «NEW» .
Помимо описания того, как с кропотливой детализацией ниже, этот метод также может быть уникальным тем, что включает встроенную отладку. По сути, он вообще ничего не делает, как написано, за исключением компиляции и сохранения в переменной всех команд, которые, по его мнению, должны выполняться для выполнения запрошенной работы.
Он также явно избегает циклов, насколько это возможно. Насколько мне известно, кроме 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«s -printfкоманды функции , как необходимо правильно изменить все ссылки $ {OLD} до $ {NEW}, но для того , чтобы сделать это , мы должны были использовать некоторые произвольные ориентиры, которые не следует включать в окончательный результат. Итак, как только sedзакончится все, что ему нужно сделать, мы даем ему указание стереть свои контрольные точки из буфера удержания перед тем, как передать его.
И теперь мы вернулись
read получит команду, которая выглядит так:
% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
Это будет readего в ${msg}качестве ${sh_io}которой может быть рассмотрена по желанию за пределы функции.
Круто.
-Майк