¿Cómo copiar una carpeta de forma recursiva de forma idempotente usando cp?

46

Cuando uso

cp -R inputFolder outputFolder

el resultado depende del contexto :

  1. si outputFolderno existe, se creará y se creará la ruta de la carpeta clonada outputFolder.
  2. si outputFolderexiste, entonces el clon creado seráoutputFolder/inputFolder

Esto es horrible , porque quiero crear un script de instalación, y si el usuario lo ejecuta dos veces por error, lo habrá outputFoldercreado la primera vez, luego, en la segunda ejecución, todo se creará nuevamente outputFolder/inputFolder.

  • Siempre quiero el primer comportamiento: crear un clon al lado del original (como hermano).
  • Quiero usar cppara ser portátil (por ejemplo, MINGW no se ha rsyncenviado)
  • Lo comprobé cp -R --parentspero esto recrea la ruta hasta el árbol de directorios (por lo que el clon no será outputFolderpero some/path/outputFolder)
  • --remove-destinationo --updateen el caso 2 no cambia nada, aún las cosas se copian enoutputFolder/inputFolder

¿Hay alguna manera de hacer esto sin verificar primero la existencia de outputFolder(si la carpeta no existe, entonces ...) o usar rm -rf outputFolder?

¿Cuál es la forma acordada y portátil de UNIX de hacer eso?

jakub.g
fuente
2
Algunas de esas opciones no son POSIX, por lo que las que probó no son muy portátiles. Si puede vivir con una pequeña falta de portabilidad, ¿por qué no usarlo rsync?
muru
1
Si no necesita usar cp, canalizar entre dos tarcomandos es una buena forma confiable de copiar árboles.
R ..
@R ... ¿puedes dar un ejemplo? @muru Me gustaría que esto funcione en MINGW que no se ha rsyncenviado.
jakub.g
Con GNU tar, tar -C input -cf - . | tar -C output -xf -funciona. -Ccambia el directorio de trabajo, pero no es una opción estándar, por lo que si no es compatible, debe comenzar a ejecutar los dos tars en los directorios de trabajo correctos, por ejemplo, sin ( cd input && tar -cf - . ) | ( cd output && tar -xf - )embargo, si se trata de Windows, usaría la respuesta de Roaima.
R ..

Respuestas:

54

Use esto en su lugar:

cp -R inputFolder/. outputFolder

Esto funciona exactamente de la misma manera que, por ejemplo, cp -R aaa/bbb cccfunciona: si cccno existe, entonces se crea como una copia bbby su contenido; pero si cccya existe, ccc/bbbse crea como la copia bbby sus contenidos.

Para casi cualquier caso, bbbesto da el comportamiento indeseable que anotó en su Pregunta. Sin embargo, en esta situación específica, bbbes justo ., así aaa/bbbes realmente aaa/., que a su vez es realmente justo aaapero con otro nombre. Entonces tenemos estos dos escenarios:

  1. ccc no existe:

    El comando cp -R aaa/. cccsignifica "crear cccy copiar el contenido de aaa/.en ccc/., es decir, copiar aaaen ccc.

  2. ccc existe:

    El comando cp -R aaa/. cccsignifica "copiar el contenido de aaa/.en ccc/., es decir, copiar aaaen ccc.

roaima
fuente
3
Huh, es nuevo para mí, ¡pero parece funcionar! ¿Esta documentado en alguna parte?
Ilmari Karonen
1
He probado el en inputFolder/.lugar de inputFoldery, de hecho, funciona para mí en Windows / Git Bash. (Sin embargo, no funciona solo con un final /, también necesito el punto después de la barra). Le di +1 porque es interesante, aunque probablemente sea un poco complicado usarlo en código público :)
jakub.g
@IlmariJaronen no necesita ser documentado explícitamente. Siga la explicación y verá cómo simplemente "sigue las reglas".
Roaima
18

No copie la carpeta, solo copie el contenido:

## 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/

De esta manera, ambos se aseguran de que el directorio de destino se cree la primera vez que se ejecuta el script y evitan el problema al ejecutarlo por segunda vez.

Chris Down señala muy correctamente que en bash, esto omitirá los archivos cuyo nombre comienza con a .. Para evitar esto, puede ejecutar shopt -s dotglobantes de ejecutar el comando anterior.

POSIX define tanto -pparamkdir como -Rparacp , por lo que debería ser perfectamente portátil.

terdon
fuente
66
Nota: esto no copiará archivos a partir de .. En bash, puedes usar dotglobpara eso.
Chris Down
2
Tenga en cuenta que es probable que este método falle si el número de entradas de directorio en la carpeta de entrada es grande, porque la expansión global podría exceder la longitud máxima de la línea de comando.
Tim Cutts el
11

Prueba la -Topción de cp. Esto existe en GNU coreutils cpversión 8.22; Puede que no sea portátil fuera de eso.

Tom Hunt
fuente
1
Sí, parece que esto es exactamente lo que quería: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… Lamentablemente, mi versión de cpes mucho más antigua.
jakub.g
1
+1 Usó esto solo hoy. -ues una gran opción para agregar para hacerlo perezoso.
PSkocik
4

También puedes usar rsync:

rsync -uav inputFolder/ outputFolder/

(tenga en cuenta las barras, especialmente después de la primera)

Ned64
fuente
0

En mi opinión, no hay nada más simple y más portátil que rm -rf outputFolder. Entonces, siempre me quedo con eso. Entiendo que su pregunta es diferente, pero creo que esta es la mejor práctica.

dashohoxha
fuente
Eso falla si hubiera permisos específicos o propiedades sobre el objetivo que debían mantenerse. O si fuera un enlace simbólico.
Roaima
1
La pregunta quiere que el resultado cpsea ​​el mismo, tanto cuando el directorio no existe como cuando existe. ¿Entonces, de qué estás hablando?
dashohoxha
El cpcomando dado por el OP no mantiene permisos (o marcas de tiempo).
roaima
0

Puede usar la -topción de cpcomando como:

cp -R inputFolder -t outputFolder

ahora si la carpeta de destino no existe, arrojará un error:

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

Nota: el comando anterior se copiará inputFolderjunto con su contenido (y no solo el contenido que contiene)

si desea copiar solo el contenido del inputFoldermismo se vuelve un poco complicado (ya que debe tener cuidado al usar el glob de shell mientras usa el asterisco *)

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

ahora si la carpeta de destino no existe, arrojará un error:

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

funciona con cp (GNU coreutils) 8.23

Edward Torvalds
fuente