Copia de archivos de shell Unix aplanando la estructura de carpetas
Pregunta
En el shell bash de UNIX (específicamente Mac OS X Leopard), ¿cuál sería la forma más sencilla de copiar cada archivo que tenga una extensión específica de una jerarquía de carpetas (incluidos los subdirectorios) a la misma carpeta de destino (sin subcarpetas)?
Obviamente existe el problema de tener duplicados en la jerarquía de fuentes.No me importaría si se sobrescriben.
Ejemplo:Necesito copiar cada archivo .txt en la siguiente jerarquía
/foo/a.txt
/foo/x.jpg
/foo/bar/a.txt
/foo/bar/c.jpg
/foo/bar/b.txt
A una carpeta llamada 'dest' y obtenga:
/dest/a.txt
/dest/b.txt
Solución
En fiesta:
find /foo -iname '*.txt' -exec cp \{\} /dest/ \;
find
encontrará todos los archivos en la ruta /foo
coincidente con el comodín *.txt
, sin distinguir entre mayúsculas y minúsculas (eso es lo que -iname
medio).Para cada archivo, find
ejecutará cp {} /dest/
, con el archivo encontrado en lugar de {}
.
Otros consejos
El único problema con la solución de Magnus es que crea un nuevo proceso "cp" para cada archivo, lo cual no es muy eficiente, especialmente si hay una gran cantidad de archivos.
En Linux (u otros sistemas con GNU coreutils) puedes hacer:
find . -name "*.xml" -print0 | xargs -0 echo cp -t a
(El -0 le permite funcionar cuando los nombres de sus archivos tienen caracteres extraños, como espacios,).
Desafortunadamente, creo que las Mac vienen con herramientas estilo BSD.¿Alguien conoce un equivalente "estándar" al interruptor "-t"?
Las respuestas anteriores no permiten colisiones de nombres ya que al autor de la pregunta no le importaba que se sobrescribieran los archivos.
Me importa que se sobrescriban los archivos, así que se me ocurrió un enfoque diferente.Reemplazar cada / en la ruta con - mantenga la jerarquía en los nombres y coloque todos los archivos en una carpeta plana.
Usamos find para obtener la lista de todos los archivos, luego awk para crear un comando mv con el nombre de archivo original y el nombre de archivo modificado y luego los pasamos a bash para que los ejecute.
find ./from -type f | awk '{ str=$0; sub(/\.\//, "", str); gsub(/\//, "-", str); print "mv " $0 " ./to/" str }' | bash
donde ./from y ./to son directorios desde y hacia mv.
Si realmente desea ejecutar un solo comando, ¿por qué no configurar uno y ejecutarlo?Al igual que:
$ find /foo -name '*.txt' | xargs echo | sed -e 's/^/cp /' -e 's|$| /dest|' | bash -sx
Pero eso no importará demasiado en cuanto al rendimiento a menos que hagas esto mucho o tengas un montón de archivos.Sin embargo, tenga cuidado con las connivencias de nombres.Al realizar pruebas, noté que GNU cp al menos advierte sobre colisiones:
cp: will not overwrite just-created `/dest/tubguide.tex' with `./texmf/tex/plain/tugboat/tubguide.tex'
Creo que lo más limpio es:
$ find /foo -name '*.txt' | xargs -i cp {} /dest
Menos sintaxis para recordar que la opción -exec.
En lo que respecta a la página de manual para cp en un cuadro FreeBSD, no es necesario cambiar -t.cp asumirá que el último argumento en la línea de comando es el directorio de destino si se pasan más de dos nombres.