2008-08-26 6 views
34

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 con una extensión específica desde una jerarquía de carpetas (incluyendo subdirectorios) a la misma carpeta de destino (sin subcarpetas)?estructura de carpeta de aplanamiento de copia de archivos de shell Unix

Obviamente, existe el problema de tener duplicados en la jerarquía de origen. No me importaría si se sobrescriben.

Ejemplo: Tengo que copiar todos los archivos .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 obtener:

/dest/a.txt 
/dest/b.txt 

Respuesta

51

En bash:

find /foo -iname '*.txt' -exec cp \{\} /dest/ \; 

find encontrará todos los archivos en la ruta /foo que coincida con el comodín rd *.txt, case insensitively (Eso es lo que significa -iname). Para cada archivo, find ejecutará cp {} /dest/, con el archivo encontrado en lugar de {}.

+3

-exec cp -t dest/{} + será más rápido, ya que solo tiene que ejecutar cp una vez, con múltiples argumentos. -t es la abreviatura de --target-directory. -l puede ser útil aquí, para hacer enlaces duros. en lugar. Y tal vez -u, para terminar con la versión más nueva para cada nombre de archivo, en lugar del primer hallazgo. –

+0

La pregunta general de bash ... ¿es {} específica para encontrar o es una forma de representar un valor de canalización? –

+0

@BrianBolton '{}' es específico de 'find' –

13

El único problema con la solución de Magnus es que genera un nuevo proceso "cp" para cada archivo, que no es muy eficiente, especialmente si hay una gran cantidad de archivos.

En Linux (u otros sistemas con GNU coreutils) que puede hacer:

find . -name "*.xml" -print0 | xargs -0 echo cp -t a 

(. El -0 le permite trabajar cuando sus nombres de archivo tienen caracteres extraños - como espacios - en ellos)

Desafortunadamente, creo que los Mac vienen con herramientas de estilo BSD. ¿Alguien sabe un equivalente "estándar" al interruptor "-t"?

+0

El único problema con esto es que puede exceder la línea de comando buffer utilizando xargs si la cantidad de archivos es muy grande. –

+0

No es cierto. Desde la página de manual de OS X (pero es cierto en cada Unix que he usado): "Cualquier argumento especificado en la línea de comando se da a la utilidad en cada invocación, seguido de un cierto número de argumentos leídos de la entrada estándar de xargs. la utilidad se ejecuta repetitivamente hasta que se agote la entrada estándar. " (Énfasis mío.) –

+0

Creo que vi el comportamiento en cygwin hace algún tiempo donde no hizo eso. Luego llegué a la conclusión de que era el comportamiento posix (mi culpa es por no verificar realmente el documento). –

1

En cuanto a la página de manual para cp en un recuadro de FreeBSD, no hay necesidad de un modificador -t. cp asumirá que el último argumento en la línea de comando será el directorio de destino si se pasan más de dos nombres.

+0

el punto de -t es que te permite poner el objetivo como uno de los primeros argumentos. xargs no hace que sea fácil poner args en el medio. –

3

Si realmente desea ejecutar solo un comando, ¿por qué no utilizar uno y ejecutarlo? Como lo siguiente:

$ find /foo -name '*.txt' | xargs echo | sed -e 's/^/cp /' -e 's|$| /dest|' | bash -sx 

Pero eso no importará demasiado en cuanto a rendimiento a menos que haga esto mucho o tenga muchos archivos. Sin embargo, ten cuidado con las colusiones de nombres. Me di cuenta de que en la prueba de GNU cp, al menos, advierte de colisiones:

cp: will not overwrite just-created `/dest/tubguide.tex' with `./texmf/tex/plain/tugboat/tubguide.tex' 

Creo que es el más limpio:

$ find /foo -name '*.txt' | xargs -i cp {} /dest 

Menos sintaxis para recordar que la opción -exec.

10

Las respuestas anteriores no permiten colisiones de nombres, ya que al solicitante no le importó que se sobrescribieran los archivos.

Me preocupo de que se sobrescriban los archivos, por lo que surgió un enfoque diferente. Reemplazando cada/en la ruta con - mantenga la jerarquía en los nombres, y coloca 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 pasarlos a bash para ser ejecutados.

find ./from -type f | awk '{ str=$0; sub(/\.\//, "", str); gsub(/\//, "-", str); print "mv " $0 " ./to/" str }' | bash 

donde ./from y ./to son directorios de mv desde y hacia.

+2

Esto no tiene en cuenta los caracteres impares en los nombres de los archivos, como espacios. –

+3

@ NathanJ.Brauer Puede arreglar esto con la colocación juiciosa de \ "en el comando mv al final. (Traté de editar la respuesta para mostrar esto pero me rechazaron. Gracias moderadores). – Paul

Cuestiones relacionadas