Domanda

Sulla shell bash UNIX (in particolare Mac OS X Leopard) quale sarebbe il modo più semplice per copiare ogni file avente un'estensione specifica da una gerarchia di cartelle (comprese le sottodirectory) nella stessa cartella di destinazione (senza sottocartelle)?

Ovviamente c'è il problema di avere duplicati nella gerarchia delle sorgenti.Non mi dispiacerebbe se venissero sovrascritti.

Esempio:Devo copiare ogni file .txt nella seguente gerarchia

/foo/a.txt
/foo/x.jpg
/foo/bar/a.txt
/foo/bar/c.jpg
/foo/bar/b.txt

In una cartella denominata "dest" e ottieni:

/dest/a.txt
/dest/b.txt
È stato utile?

Soluzione

In bash:

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

find troverà tutti i file sotto il percorso /foo corrispondente al carattere jolly *.txt, senza distinzione tra maiuscole e minuscole (Ecco cosa -iname significa).Per ogni file, find eseguirà cp {} /dest/, con il file trovato al posto di {}.

Altri suggerimenti

L'unico problema con la soluzione di Magnus è che crea un nuovo processo "cp" per ogni file, il che non è particolarmente efficiente soprattutto se è presente un numero elevato di file.

Su Linux (o altri sistemi con coreutils GNU) puoi fare:

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

(Il -0 gli consente di funzionare quando i nomi dei file contengono caratteri strani, come gli spazi.)

Sfortunatamente penso che i Mac siano dotati di strumenti in stile BSD.Qualcuno conosce un equivalente "standard" dell'opzione "-t"?

Le risposte di cui sopra non consentono collisioni di nomi poiché al richiedente non importava che i file venissero sovrascritti.

Mi preoccupo che i file vengano sovrascritti, quindi ho adottato un approccio diverso.Sostituendo ogni / nel percorso con - mantieni la gerarchia nei nomi e inserisce tutti i file in un'unica cartella flat.

Usiamo find per ottenere l'elenco di tutti i file, quindi awk per creare un comando mv con il nome file originale e il nome file modificato, quindi li passiamo a bash per essere eseguiti.

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

dove ./from e ./to sono le directory da e verso le quali mv.

Se vuoi davvero eseguire un solo comando, perché non selezionarne uno ed eseguirlo?Così:

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

Ma ciò non avrà molta importanza dal punto di vista delle prestazioni, a meno che tu non lo faccia molto o abbia un sacco di file.Attenzione però alle collusioni sui nomi.Durante i test ho notato che GNU cp almeno avvisa delle collisioni:

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

Penso che il più pulito sia:

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

Meno sintassi da ricordare rispetto all'opzione -exec.

Per quanto riguarda la pagina man di cp su un box FreeBSD, non è necessario un interruttore -t.cp assumerà che l'ultimo argomento sulla riga di comando sia la directory di destinazione se vengono passati più di due nomi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top