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
sed
recibe la cadena{}
como entrada, como se puede verificar con:find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
que imprime
foofoo
para 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
sed
canalización de tal manera que lafind
ejecute para cada archivo, yafind
que 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 -c
y 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 mv
Encuentra 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 todomv
en pares (xargs -n2
). Tenga en cuenta que, en este caso, la ruta en sí no debería contener una cadenatest
.fuente
-z
opción junto con-print0
find y xargs-0
:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mv
test
carpetas en una ruta.sed
solo cambiará el nombre del primero y elmv
comando fallará porNo such file or directory
error.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/` done
fuente
echo $file | sed s/_test.rb$/_spec.rb/
; hecho es una sola línea, ¿no?for
los 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.-exec
opció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 quitasuffix
del valor devar
.o, para hacerlo usando sed:
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
fuente
sed
uno) 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
bash
como tu caparazón, en cuyo caso realmente no necesitasfind
ysed
para lograr el cambio de nombre por lotes que buscas ...Suponiendo que está usando
bash
como su shell:$ echo $SHELL /bin/bash $ _
... y suponiendo que haya habilitado la llamada
globstar
opción de shell:$ shopt -p globstar shopt -s globstar $ _
... y finalmente asumiendo que ha instalado la
rename
utilidad (que se encuentra en elutil-linux-ng
paquete)$ 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
globstar
opción de shell asegurará que bash encuentre todos los*_test.rb
archivos coincidentes , sin importar cuán profundamente estén anidados en la jerarquía de directorios ... utilícelohelp shopt
para 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_MAX
En la mayoría de los sistemas Linux puede usar
free -b
ocat /proc/meminfo
para encontrar la cantidad de RAM con la que tiene que trabajar; De lo contrario, utilicetop
la 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 unwhile
bucle alimentado con el resultado defind
una sustitución de proceso .Entonces, si tiene una
find
expresió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
find
archivará y cambiará el nombre de todos ellos separando la cadena_test.rb
desde 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.rb
Vea 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.rb
fuente
while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")
find .... -exec mv ...
. Además, tenga cuidado con$file
ya 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 mv
Realmente 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 mv
fuente
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
sed
búsqueda recursiva de más de una coincidencia del patrón, no hay otra recursividad que yo sepa.Y por último, esto está completamente
null
delimitado: 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_wordstars
NOTA: Lo anterior
function
es probable que requierenGNU
versiones desed
yfind
para manejar apropiadamente lafind printf
esed -z -e
y:;recursive regex test;t
llamadas. 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
fork
consed
, pero también estaba practicando algunassed
té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}
./app
podría no ser deseado. Elimínelo o muévalo a otro lugar de antemano o, alternativamente, puede crear una\( -path PATTERN -exec rm -rf \{\} \)
rutinafind
para 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 ased
la 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
-noclobber
opción establecida paramv
; como está escrito, esta función no pondrá${SRC_DIR}
donde${TGT_DIR}
ya existe.read -R SED <<HEREDOC
find . -name ${OLD} -printf
find
proceso. Confind
solo buscamos cualquier cosa que necesite cambiar de nombre porque ya hicimos todas lasmv
operaciones de lugar a lugar con el primer comando de la función. En lugar de realizar una acción directa confind
, como unaexec
llamada, 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:'
find
ubicar los archivos que necesitamos, compila e imprime directamente (la mayoría ) del comando que necesitaremos para procesar su cambio de nombre. La%dir-depth
tachuela 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.find
utiliza 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-delimited
find
los resultados en función de%directory-depth
para que se trabajen primero las rutas más cercanas en relación a $ {SRC}. Esto evita posibles errores que involucrenmv
archivos 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}
%Path
impreso 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 segundosed
proceso, y aunque un ciclo corto puede no ser deseable, sin duda es mejor que generar y bifurcar un proceso completo.sed
hace 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.mv
cadena 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 demv
la ruta de destino.sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
-exec
llamadas aquí ocurren sin un segundofork
. En el primero, como hemos visto, modificamos elmv
comando proporcionado porfind
el-printf
comando 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 vezsed
termina 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
read
recibirá un comando que se ve así:% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
Va a
read
convertirá 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/)" done
fuente
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/`"; done
fuente
$ 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
fuente
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
sed
funciona sin escapar()
si no configura la-r
opció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.txt
Elimine 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'); done
fuente