Come copiare una cartella in modo ricorsivo in modo idempotente usando cp?


46

Quando uso

cp -R inputFolder outputFolder

il risultato dipende dal contesto :

  1. se outputFoldernon esiste, verrà creato e sarà il percorso della cartella clonata outputFolder.
  2. se outputFolderesiste, allora sarà il clone creatooutputFolder/inputFolder

Questo è orribile , perché voglio creare qualche script di installazione, e se l'utente esegue due volte per errore, avrà outputFoldercreato la prima volta, poi la seconda manche tutte le cose sarà creato ancora una volta outputFolder/inputFolder.

  • Voglio sempre il primo comportamento: creare un clone accanto all'originale (come un fratello).
  • Voglio usare cpper essere portatile (es. MINGW non è stato rsyncspedito)
  • Ho controllato cp -R --parentsma questo ricrea il percorso fino all'albero della directory (quindi il clone non sarà outputFolderma some/path/outputFolder)
  • --remove-destinationo --updatenel caso in cui 2 non cambi nulla, vengono comunque copiate le coseoutputFolder/inputFolder

C'è un modo per farlo senza prima verificare l'esistenza del outputFolder(se la cartella non esiste quindi ...) o usare rm -rf outputFolder?

Qual è il modo UNIX concordato e portatile per farlo?



2
Alcune di queste opzioni non sono POSIX, quindi quelle che hai provato non sono molto portatili. Se riesci a vivere con una piccola mancanza di portabilità, perché non utilizzarlo rsync?
muru,

1
Se non è necessario utilizzare cp, il piping tra due tarcomandi è un modo affidabile e affidabile per copiare gli alberi.
R ..

@R .. puoi fornire un esempio? @muru Mi piacerebbe che funzionasse in MINGW che non è stato rsyncspedito.
jakub.g

Con GNU tar, tar -C input -cf - . | tar -C output -xf -funziona. -Ccambia la directory di lavoro, ma non è un'opzione standard, quindi se non è supportato è necessario eseguire le due tar nelle directory di lavoro giuste per iniziare, ad es. ( cd input && tar -cf - . ) | ( cd output && tar -xf - )Tuttavia, se si ha a che fare con Windows, utilizzarei solo la risposta di roaima.
R ..

Risposte:


54

Usa questo invece:

cp -R inputFolder/. outputFolder

Funziona esattamente allo stesso modo, diciamo, cp -R aaa/bbb cccfunziona: se cccnon esiste, viene creato come una copia del bbbsuo contenuto; ma se cccesiste già, ccc/bbbviene creato come copia di bbbe il suo contenuto.

Per quasi ogni istanza di bbbciò si ottiene il comportamento indesiderato che hai notato nella tua domanda. Tuttavia, in questa situazione specifica bbbè giusto ., quindi aaa/bbbè davvero aaa/., che a sua volta è davvero giusto aaama con un altro nome. Quindi abbiamo questi due scenari:

  1. ccc non esiste:

    Il comando cp -R aaa/. cccsignifica "crea ccce copia il contenuto di aaa/.in ccc/., ovvero copia aaain ccc.

  2. ccc esiste:

    Il comando cp -R aaa/. cccsignifica "copia il contenuto di aaa/.in ccc/., ovvero copia aaain ccc.


3
Huh, quello è nuovo per me, ma sembra funzionare! Questo è documentato ovunque?
Ilmari Karonen,

1
Ho testato inputFolder/.invece inputFoldere in effetti funziona per me su Windows / Git Bash. (Non funziona però solo con un finale /, ho bisogno anche del punto dopo la barra). Ho dato +1 poiché è interessante, anche se probabilmente è un po 'troppo complicato usarlo nel codice pubblico :)
jakub.g

@IlmariJaronen non ha bisogno di essere documentato esplicitamente. Segui la spiegazione e vedrai come semplicemente "segue le regole".
roaima,

18

Non copiare la cartella, copia solo il contenuto:

## Create the target directory. The -p suppresses error messages
## if the directory already exists
mkdir -p outputFolder

## Copy the contents recursively, this will not recreate the parent
cp -R inputfolder/* outputfolder/

In questo modo entrambi si assicurano che la directory di destinazione venga creata la prima volta che viene eseguito lo script ed evitare il problema quando viene eseguita una seconda volta.

Chris Down sottolinea molto correttamente che in bash, questo salterà i file il cui nome inizia con a .. Per evitare ciò, è possibile eseguire shopt -s dotglobprima di eseguire il comando sopra.

Sia -ppermkdir che -Rpercp sono definiti da POSIX, quindi questo dovrebbe essere perfettamente portatile.


6
Nota: questo non copia i file che iniziano con .. In bash, puoi usarlo dotglobper quello.
Chris Down,

2
Si noti che questo metodo potrebbe non riuscire se il numero di voci della directory nella cartella di input è elevato, poiché l'espansione glob potrebbe superare la lunghezza massima della riga di comando.
Tim Cutts,

11

Prova l' -Topzione a cp. Questo esiste in GNU coreutils cpversione 8.22; potrebbe non essere portatile al di fuori di questo.


1
Sì, sembra che sia esattamente quello che volevo: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… Purtroppo la mia versione di cpè molto più vecchia.
jakub.g,

1
+1 Utilizzato solo oggi. -uè un'ottima opzione da aggiungere per renderlo pigro.
PSkocik,

4

Puoi anche usare rsync:

rsync -uav inputFolder/ outputFolder/

(nota le barre, specialmente dopo la prima)


0

Secondo me, non c'è niente di più semplice e portatile di rm -rf outputFolder. Quindi, mi attengo sempre a quello. Capisco che la tua domanda è diversa, ma penso che questa sia la migliore pratica.


Ciò fallisce se sul target c'erano autorizzazioni o proprietà specifiche che dovevano essere mantenute. O se fosse un collegamento simbolico.
roaima,

1
La domanda vuole che il risultato cpsia lo stesso, sia quando la directory non esiste che quando esiste. Di cosa stai parlando?
dashohoxha,

Il cpcomando fornito dall'OP non mantiene le autorizzazioni (o i timestamp).
roaima,

0

È possibile utilizzare l' -topzione di cpcomando come:

cp -R inputFolder -t outputFolder

ora se la cartella di destinazione non esiste, verrà generato un errore:

cp: failed to access ‘outputFolder’: No such file or directory

nota sopra il comando verrà copiato inputFolderinsieme al suo contenuto (e non solo al suo interno)

se vuoi copiare solo il suo contenuto inputFolderdiventa un po 'complicato (dato che devi stare attento quando usi il globbing della shell mentre usi l'asterisco *)

cp -R  -t outputFolder/ -- inputFolder/*

ora se la cartella di destinazione non esiste, verrà generato un errore:

cp: failed to access ‘outputFolder’: No such file or directory

lavora con cp (GNU coreutils) 8.23

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.