Quiero revisar varios directorios y cambiar el nombre de todos los archivos que terminan en _test.rb para que terminen en _spec.rb. Es algo que nunca he descubierto del todo cómo hacer con bash, así que esta vez pensé en poner un poco de esfuerzo para lograrlo. Sin embargo, hasta ahora me he quedado corto, mi mejor esfuerzo es:
find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;
NB: hay un eco adicional después del exec para que el comando se imprima en lugar de ejecutarse mientras lo estoy probando.
Cuando lo ejecuto, la salida para cada nombre de archivo coincidente es:
mv original original
es decir, se ha perdido la sustitución por sed. ¿Cuál es el truco?

Respuestas:
Esto sucede porque
sedrecibe la cadena{}como entrada, como se puede verificar con:find . -exec echo `echo "{}" | sed 's/./foo/g'` \;que imprime
foofoopara cada archivo en el directorio, de forma recursiva. La razón de este comportamiento es que la tubería se ejecuta una vez, por el shell, cuando expande todo el comando.No hay forma de citar la
sedcanalización de tal manera que lafindejecute para cada archivo, yafindque no ejecuta comandos a través del shell y no tiene noción de canalizaciones o comillas inversas. El manual de GNU findutils explica cómo realizar una tarea similar colocando la tubería en un script de shell separado:#!/bin/sh echo "$1" | sed 's/_test.rb$/_spec.rb/'(Puede haber alguna forma perversa de usar
sh -cy un montón de comillas para hacer todo esto en un comando, pero no lo intentaré).fuente
Para resolverlo de la manera más cercana al problema original, probablemente se usaría la opción xargs "argumentos por línea de comando":
find . -name "*_test.rb" | sed -e "p;s/test/spec/" | xargs -n2 mvEncuentra los archivos en el directorio de trabajo actual de forma recursiva, repite el nombre del archivo original (
p) y luego un nombre modificado (s/test/spec/) y lo alimenta todomven pares (xargs -n2). Tenga en cuenta que, en este caso, la ruta en sí no debería contener una cadenatest.fuente
-zopción junto con-print0find y xargs-0:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mvtestcarpetas en una ruta.sedsolo cambiará el nombre del primero y elmvcomando fallará porNo such file or directoryerror.es posible que desee considerar de otra manera como
for file in $(find . -name "*_test.rb") do echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/` donefuente
echo $file | sed s/_test.rb$/_spec.rb/; hecho es una sola línea, ¿no?forlos dividirá en palabras separadas. Puede hacer que funcione indicando al bucle for que se divida solo en líneas nuevas. Consulte cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html para ver ejemplos.-execopción de buscar.Encuentro este más corto
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;fuente
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;funciona ? Como lo haríafind . -name '*_test.rb' -exec bash -c 'echo mv $1 ${1/test.rb/spec.rb}' iAmArgumentZero {} \;Puedes hacerlo sin sed, si quieres:
for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done${var%%suffix}se quitasuffixdel valor devar.o, para hacerlo usando sed:
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; donefuente
seduno) como se explica en la respuesta aceptada.for i in... ; do ... ; done, que ejecuta comandos a través de la cáscara y no entiende de acento grave.Mencionas que estás usando
bashcomo tu caparazón, en cuyo caso realmente no necesitasfindysedpara lograr el cambio de nombre por lotes que buscas ...Suponiendo que está usando
bashcomo su shell:$ echo $SHELL /bin/bash $ _... y suponiendo que haya habilitado la llamada
globstaropción de shell:$ shopt -p globstar shopt -s globstar $ _... y finalmente asumiendo que ha instalado la
renameutilidad (que se encuentra en elutil-linux-ngpaquete)$ which rename /usr/bin/rename $ _... entonces puede lograr el cambio de nombre del lote en una sola línea de bash de la siguiente manera:
(la
globstaropción de shell asegurará que bash encuentre todos los*_test.rbarchivos coincidentes , sin importar cuán profundamente estén anidados en la jerarquía de directorios ... utilícelohelp shoptpara averiguar cómo configurar la opción)fuente
La forma más sencilla :
find . -name "*_test.rb" | xargs rename s/_test/_spec/La forma más rápida (suponiendo que tenga 4 procesadores):
find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/Si tiene una gran cantidad de archivos para procesar, es posible que la lista de nombres de archivos enviados a xargs haga que la línea de comando resultante exceda la longitud máxima permitida.
Puede verificar el límite de su sistema usando
getconf ARG_MAXEn la mayoría de los sistemas Linux puede usar
free -bocat /proc/meminfopara encontrar la cantidad de RAM con la que tiene que trabajar; De lo contrario, utilicetopla aplicación de monitorización de actividad de su sistema.Una forma más segura (suponiendo que tenga 1000000 bytes de RAM para trabajar):
find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/fuente
Esto es lo que funcionó para mí cuando los nombres de los archivos tenían espacios. El siguiente ejemplo cambia el nombre de todos los archivos .dar a archivos .zip de forma recursiva:
find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;fuente
Para esto no es necesario
sed. Puede quedarse perfectamente solo con unwhilebucle alimentado con el resultado definduna sustitución de proceso .Entonces, si tiene una
findexpresión que selecciona los archivos necesarios, use la sintaxis:while IFS= read -r file; do echo "mv $file ${file%_test.rb}_spec.rb" # remove "echo" when OK! done < <(find -name "*_test.rb")Esto
findarchivará y cambiará el nombre de todos ellos separando la cadena_test.rbdesde el final y agregando_spec.rb.Para este paso usamos Shell Parameter Expansion donde
${var%string}elimina la "cadena" de patrón coincidente más corto$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.rbVea un ejemplo:
$ 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.rbfuente
while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")find .... -exec mv .... Además, tenga cuidado con$fileya que fallará si contiene espacios. Mejor use las comillas"$file".si tienes Ruby (1.9+)
ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'fuente
En la respuesta de ramtam que me gusta, la parte de búsqueda funciona bien, pero el resto no si la ruta tiene espacios. No estoy muy familiarizado con sed, pero pude modificar esa respuesta a:
find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mvRealmente necesitaba un cambio como este porque en mi caso de uso, el comando final se parece más a
find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mvfuente
No tengo el corazón para hacerlo de nuevo, pero escribí esto en respuesta a Commandline Find Sed Exec . Allí, el autor de la pregunta quería saber cómo mover un árbol completo, posiblemente excluyendo uno o dos directorios, y cambiar el nombre de todos los archivos y directorios que contienen la cadena "OLD" para que en su lugar contengan "NEW" .
Además de describir el cómo con minuciosa verbosidad continuación, este método también puede ser único en el sentido de que incorpora la depuración incorporada. Básicamente, no hace nada en absoluto como está escrito, excepto compilar y guardar en una variable todos los comandos que cree que debería hacer para realizar el trabajo solicitado.
También evita explícitamente los bucles tanto como sea posible. Además de la
sedbúsqueda recursiva de más de una coincidencia del patrón, no hay otra recursividad que yo sepa.Y por último, esto está completamente
nulldelimitado: no se tropieza con ningún carácter en ningún nombre de archivo excepto en elnull. No creo que debas tener eso.Por cierto, esto es REALMENTE rápido. Mira:
% _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_wordstarsNOTA: Lo anterior
functiones probable que requierenGNUversiones desedyfindpara manejar apropiadamente lafind printfesed -z -ey:;recursive regex test;tllamadas. Si estos no están disponibles para usted, es probable que la funcionalidad se pueda duplicar con algunos ajustes menores.Esto debería hacer todo lo que quería de principio a fin con muy poco alboroto. Lo hice
forkconsed, pero también estaba practicando algunassedtécnicas recursivas de ramificación y por eso estoy aquí. Es como conseguir un corte de pelo con descuento en una escuela de peluquería, supongo. Aquí está el flujo de trabajo:rm -rf ${UNNECESSARY}./apppodría no ser deseado. Elimínelo o muévalo a otro lugar de antemano o, alternativamente, puede crear una\( -path PATTERN -exec rm -rf \{\} \)rutinafindpara hacerlo de forma programática, pero esa es toda suya._mvnfind "${@}"${sh_io}es especialmente importante porque guarda el retorno de la función.${sed_sep}viene en un segundo cercano; esta es una cadena arbitraria utilizada para hacer referencia asedla recursividad en la función. Si${sed_sep}se establece en un valor que podría encontrarse en cualquiera de sus rutas o nombres de archivo sobre los que se actúa ... bueno, simplemente no lo permita.mv -n $1 $2-noclobberopción establecida paramv; como está escrito, esta función no pondrá${SRC_DIR}donde${TGT_DIR}ya existe.read -R SED <<HEREDOCfind . -name ${OLD} -printffindproceso. Confindsolo buscamos cualquier cosa que necesite cambiar de nombre porque ya hicimos todas lasmvoperaciones de lugar a lugar con el primer comando de la función. En lugar de realizar una acción directa confind, como unaexecllamada, por ejemplo, la usamos para construir la línea de comandos dinámicamente con-printf.%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'findubicar los archivos que necesitamos, compila e imprime directamente (la mayoría ) del comando que necesitaremos para procesar su cambio de nombre. La%dir-depthtachuela al principio de cada línea ayudará a asegurarnos de que no estamos intentando cambiar el nombre de un archivo o directorio en el árbol con un objeto principal que aún no se ha cambiado de nombre.findutiliza todo tipo de técnicas de optimización para recorrer el árbol del sistema de archivos y no es seguro que devuelva los datos que necesitamos en un orden seguro para las operaciones. Es por eso que a continuación ...sort -general-numerical -zero-delimitedfindlos resultados en función de%directory-depthpara que se trabajen primero las rutas más cercanas en relación a $ {SRC}. Esto evita posibles errores que involucrenmvarchivos en ubicaciones inexistentes y minimiza la necesidad de bucles recursivos. ( de hecho, es posible que tenga dificultades para encontrar un bucle )sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}%Pathimpreso para cada cadena en caso de que contenga más de un valor $ {OLD} que pueda necesitar ser reemplazado. Todas las demás soluciones que imaginé implicaban un segundosedproceso, y aunque un ciclo corto puede no ser deseable, sin duda es mejor que generar y bifurcar un proceso completo.sedhace aquí es buscar $ {sed_sep}, luego, habiéndolo encontrado, lo guarda y todos los caracteres que encuentra hasta que encuentra $ {OLD}, que luego reemplaza con $ {NEW}. Luego regresa a $ {sed_sep} y busca nuevamente $ {OLD}, en caso de que ocurra más de una vez en la cadena. Si no se encuentra, imprime la cadena modificadastdout(que luego captura de nuevo a continuación) y finaliza el ciclo.mvcadena de comando, que debe incluir $ {OLD} por supuesto, sí la incluya, y la segunda mitad se modifica tantas veces como sea necesario para borrar el $ {OLD} nombre demvla ruta de destino.sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out-execllamadas aquí ocurren sin un segundofork. En el primero, como hemos visto, modificamos elmvcomando proporcionado porfindel-printfcomando de función según sea necesario para alterar correctamente todas las referencias de $ {OLD} a $ {NEW}, pero para hacerlo tuvimos que usar algunos puntos de referencia arbitrarios que no deben incluirse en el resultado final. Así que una vezsedtermina todo lo que necesita hacer, le indicamos que borre sus puntos de referencia del búfer de retención antes de pasarlo.Y ahora estamos de vuelta
readrecibirá un comando que se ve así:% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000Va a
readconvertirá en${msg}como${sh_io}que se puede examinar a voluntad fuera de la función.Frio.
-Miguel
fuente
Pude manejar nombres de archivos con espacios siguiendo los ejemplos sugeridos por onitake.
Esto no se rompe si la ruta contiene espacios o la cadena
test:find . -name "*_test.rb" -print0 | while read -d $'\0' file do echo mv "$file" "$(echo $file | sed s/test/spec/)" donefuente
Este es un ejemplo que debería funcionar en todos los casos. Funciona de forma recursiva, solo necesita shell y admite nombres de archivos con espacios.
find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; donefuente
$ 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.rbfuente
Su pregunta parece ser sobre sed, pero para lograr su objetivo de cambio de nombre recursivo, sugeriría lo siguiente, extraído descaradamente de otra respuesta que di aquí: cambio de nombre recursivo en 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 .fuente
sedfunciona sin escapar()si no configura la-ropción?Una forma más segura de hacer el cambio de nombre con find utils y tipo de expresión regular sed:
mkdir ~/practice cd ~/practice touch classic.txt.txt touch folk.txt.txtElimine la extensión ".txt.txt" de la siguiente manera:
cd ~/practice find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;Si usa + en lugar de; para trabajar en modo por lotes, el comando anterior cambiará el nombre solo del primer archivo coincidente, pero no de la lista completa de coincidencias de archivos por 'buscar'.
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +fuente
Aquí hay un buen delineador que funciona. Sed no puede manejar esto correctamente, especialmente si xargs pasa múltiples variables con -n 2. Una sustitución de bash manejaría esto fácilmente como:
find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'Agregar -type -f limitará las operaciones de movimiento a archivos solamente, -print 0 manejará espacios vacíos en las rutas.
fuente
Comparto esta publicación porque está un poco relacionada con la pregunta. Perdón por no dar más detalles. Espero que ayude a alguien más. http://www.peteryu.ca/tutorials/shellscripting/batch_rename
fuente
Esta es mi solución de trabajo:
for FILE in {{FILE_PATTERN}}; do echo ${FILE} | mv ${FILE} $(sed 's/{{SOURCE_PATTERN}}/{{TARGET_PATTERN}}/g'); donefuente