Я хочу просмотреть кучу каталогов и переименовать все файлы, которые заканчиваются на _test.rb, чтобы вместо этого заканчиваться на _spec.rb. Я так и не понял, что делать с bash, поэтому на этот раз подумал, что приложу некоторые усилия, чтобы добиться этого. Я пока что не понял, мои лучшие усилия:
find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;
NB: после exec появляется дополнительное эхо, так что команда печатается, а не выполняется, пока я ее тестирую.
Когда я запускаю его, вывод для каждого совпадающего имени файла:
mv original original
т.е. замена на sed была потеряна. Что за хитрость?
Ответы:
Это происходит потому, что
sed
получает строку в{}
качестве входных данных, что можно проверить с помощью:find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
который
foofoo
рекурсивно печатает для каждого файла в каталоге. Причина такого поведения в том, что конвейер выполняется оболочкой один раз, когда она раскрывает всю команду.Нет способа цитировать
sed
конвейер таким образом, чтобыfind
он выполнялся для каждого файла, посколькуfind
не выполняет команды через оболочку и не имеет понятия о конвейерах или обратных кавычках. В руководстве GNU findutils объясняется, как выполнить аналогичную задачу, поместив конвейер в отдельный сценарий оболочки:#!/bin/sh echo "$1" | sed 's/_test.rb$/_spec.rb/'
(Может быть какой-то извращенный способ использования
sh -c
и множество кавычек, чтобы сделать все это в одной команде, но я не собираюсь пробовать.)источник
Чтобы решить ее способом, наиболее близким к исходной проблеме, вероятно, можно было бы использовать параметр xargs «args per command line»:
find . -name "*_test.rb" | sed -e "p;s/test/spec/" | xargs -n2 mv
Он рекурсивно находит файлы в текущем рабочем каталоге, повторяет исходное имя файла (
p
), а затем измененное имя (s/test/spec/
) и передает все этоmv
попарно (xargs -n2
). Помните, что в этом случае сам путь не должен содержать строкуtest
.источник
-z
опцию вместе с находками-print0
и xargs-0
:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mv
test
на одном пути есть несколько папок.sed
переименует только первый, иmv
команда завершитсяNo such file or directory
ошибкой.Вы можете рассмотреть другой способ, например
for file in $(find . -name "*_test.rb") do echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/` done
источник
echo $file | sed s/_test.rb$/_spec.rb/
; done является однострочным, не так ли?for
разделит их на отдельные слова. Вы можете заставить его работать, указав цикл for разделять только символы новой строки. См. Примеры на cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html .-exec
опцию find.Я считаю это короче
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
источник
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
работает ? Как быfind . -name '*_test.rb' -exec bash -c 'echo mv $1 ${1/test.rb/spec.rb}' iAmArgumentZero {} \;
Вы можете сделать это без sed, если хотите:
for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done
${var%%suffix}
полоскиsuffix
от стоимостиvar
.или, чтобы сделать это с помощью sed:
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
источник
sed
тот), как объясняется принятым ответом.for i in... ; do ... ; done
, который выполняет команды с помощью оболочки и делает понять кавычку.Вы упоминаете, что используете в
bash
качестве оболочки, и в этом случае вам на самом деле не нужно,find
иsed
чтобы добиться пакетного переименования, которое вам нужно ...Предполагая, что вы используете в
bash
качестве оболочки:$ echo $SHELL /bin/bash $ _
... и если вы включили так называемую
globstar
опцию оболочки:$ shopt -p globstar shopt -s globstar $ _
... и, наконец, если вы установили
rename
утилиту (находится вutil-linux-ng
пакете)$ which rename /usr/bin/rename $ _
... тогда вы можете выполнить пакетное переименование в однострочном bash следующим образом:
(параметр
globstar
оболочки гарантирует, что bash найдет все совпадающие*_test.rb
файлы, независимо от того, насколько глубоко они вложены в иерархию каталогов ... используйте,help shopt
чтобы узнать, как установить параметр)источник
Самый простой способ :
find . -name "*_test.rb" | xargs rename s/_test/_spec/
Самый быстрый способ (при условии, что у вас 4 процессора):
find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/
Если у вас есть большое количество файлов для обработки, возможно, что список имен файлов, переданный в xargs, приведет к тому, что итоговая командная строка превысит максимально допустимую длину.
Вы можете проверить лимит вашей системы, используя
getconf ARG_MAX
В большинстве систем Linux вы можете использовать
free -b
илиcat /proc/meminfo
узнать, сколько оперативной памяти вам необходимо для работы; В противном случае используйтеtop
приложение для мониторинга активности вашей системы.Более безопасный способ (при условии, что у вас есть 1000000 байт оперативной памяти для работы):
find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/
источник
Вот что у меня сработало, когда в именах файлов были пробелы. В приведенном ниже примере все файлы .dar рекурсивно переименовываются в файлы .zip:
find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;
источник
Для этого вам не нужно
sed
. Вы можете получить совершенно одна сwhile
петлей , питаемой результатеfind
через подстановки процессов .Итак, если у вас есть
find
выражение, которое выбирает необходимые файлы, используйте синтаксис:while IFS= read -r file; do echo "mv $file ${file%_test.rb}_spec.rb" # remove "echo" when OK! done < <(find -name "*_test.rb")
Это будет
find
файлы и переименовать все из них, чередуя строку_test.rb
с конца и добавляя_spec.rb
.Для этого шага мы используем расширение параметров оболочки, из которого
${var%string}
удаляется самый короткий совпадающий шаблон "строка"$var
.$ file="HELLOa_test.rbBYE_test.rb" $ echo "${file%_test.rb}" # remove _test.rb from the end HELLOa_test.rbBYE $ echo "${file%_test.rb}_spec.rb" # remove _test.rb and append _spec.rb HELLOa_test.rbBYE_spec.rb
См. Пример:
$ tree . ├── ab_testArb ├── a_test.rb ├── a_test.rb_test.rb ├── b_test.rb ├── c_test.hello ├── c_test.rb └── mydir └── d_test.rb $ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb") mv ./b_test.rb ./b_spec.rb mv ./mydir/d_test.rb ./mydir/d_spec.rb mv ./a_test.rb ./a_spec.rb mv ./c_test.rb ./c_spec.rb
источник
while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")
find .... -exec mv ...
. Также будьте осторожны,$file
так как он завершится неудачно, если содержит пробелы. Лучше использовать цитаты"$file"
.если у вас Ruby (1.9+)
ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'
источник
В ответе ramtam, который мне нравится, часть поиска работает нормально, а остальная часть - нет, если в пути есть пробелы. Я не слишком знаком с sed, но мне удалось изменить этот ответ на:
find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv
Мне действительно нужно было такое изменение, потому что в моем случае финальная команда больше похожа на
find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv
источник
У меня нет духа делать это снова и снова, но я написал это в ответ на 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 printf
andsed -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
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}
которой может быть рассмотрена по желанию за пределы функции.Круто.
-Майк
источник
Я смог обработать имена файлов с пробелами, следуя примерам, предложенным onitake.
Это не нарушается, если путь содержит пробелы или строку
test
:find . -name "*_test.rb" -print0 | while read -d $'\0' file do echo mv "$file" "$(echo $file | sed s/test/spec/)" done
источник
Это пример, который должен работать во всех случаях. Работает рекурсивно, нужна только оболочка, и поддерживает имена файлов с пробелами.
find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done
источник
$ find spec -name "*_test.rb" spec/dir2/a_test.rb spec/dir1/a_test.rb $ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);' `spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb' `spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb' $ find spec -name "*_spec.rb" spec/dir2/b_spec.rb spec/dir2/a_spec.rb spec/dir1/a_spec.rb spec/dir1/c_spec.rb
источник
Кажется, ваш вопрос касается sed, но для достижения вашей цели рекурсивного переименования я бы предложил следующее, беззастенчиво вырванное из другого ответа, который я дал здесь: рекурсивное переименование в bash
#!/bin/bash IFS=$'\n' function RecurseDirs { for f in "$@" do newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g' echo "${f}" "${newf}" mv "${f}" "${newf}" f="${newf}" if [[ -d "${f}" ]]; then cd "${f}" RecurseDirs $(ls -1 ".") fi done cd .. } RecurseDirs .
источник
sed
работает без побега,()
если не установить-r
опцию?Более безопасный способ переименования с помощью типа регулярного выражения find utils и sed:
mkdir ~/practice cd ~/practice touch classic.txt.txt touch folk.txt.txt
Удалите расширение ".txt.txt" следующим образом -
cd ~/practice find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;
Если вы используете + вместо; для работы в пакетном режиме указанная выше команда переименует только первый совпадающий файл, но не весь список совпадений файлов с помощью 'find'.
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +
источник
Вот хороший лайнер, который делает свое дело. Sed не может справиться с этим правильно, особенно если xargs передает несколько переменных с -n 2. Подстановка bash справится с этим легко, например:
find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'
Добавление -type -f ограничит операции перемещения только файлами, -print 0 будет обрабатывать пустые места в путях.
источник
Я делюсь этим постом, так как он немного связан с вопросом. Извините за то, что не предоставил более подробную информацию. Надеюсь, это поможет кому-то другому. http://www.peteryu.ca/tutorials/shellscripting/batch_rename
источник
Это мое рабочее решение:
for FILE in {{FILE_PATTERN}}; do echo ${FILE} | mv ${FILE} $(sed 's/{{SOURCE_PATTERN}}/{{TARGET_PATTERN}}/g'); done
источник