Como copiar uma pasta recursivamente de maneira idempotente usando cp?

46

Quando eu uso

cp -R inputFolder outputFolder

o resultado depende do contexto :

  1. se outputFoldernão existir, ele será criado e o caminho da pasta clonada será outputFolder.
  2. Se outputFolderexistir, o clone criado seráoutputFolder/inputFolder

Isso é horrível , porque eu quero criar algum script de instalação e, se o usuário outputFolderexecutá- lo duas vezes por engano, ele será criado pela primeira vez e, na segunda execução, todas as coisas serão criadas novamente outputFolder/inputFolder.

  • Eu quero sempre o primeiro comportamento: crie um clone ao lado do original (como um irmão).
  • Eu quero usar cppara ser portátil (por exemplo, o MINGW não foi rsyncenviado)
  • Eu verifiquei, cp -R --parentsmas isso recria o caminho até a árvore de diretórios (para que o clone não seja outputFoldermas some/path/outputFolder)
  • --remove-destinationou --updateno caso 2 não mudar nada, ainda assim as coisas são copiadas paraoutputFolder/inputFolder

Existe uma maneira de fazer isso sem primeiro verificar a existência do outputFolder(se a pasta não existir, então ...) ou usar rm -rf outputFolder?

Qual é a maneira portátil e acordada do UNIX de fazer isso?

jakub.g
fonte
2
Algumas dessas opções não são POSIX, portanto, as que você tentou não são muito portáteis. Se você pode viver com um pouco de portabilidade, por que não usar rsync?
muru 9/09/15
1
Se você não precisar usar cp, a canalização entre dois tarcomandos é uma boa maneira confiável de copiar árvores.
R ..
@R .. você pode dar um exemplo? @muru Eu gostaria que isso funcionasse em MINGW, que não foi rsyncenviado.
jakub.g
Com o GNU tar, tar -C input -cf - . | tar -C output -xf -funciona. -Cmuda o diretório de trabalho, mas não é uma opção padrão; portanto, se não houver suporte, você precisará executar os dois alcances nos diretórios de trabalho certos, por exemplo, no ( cd input && tar -cf - . ) | ( cd output && tar -xf - )entanto, se você estiver lidando com o Windows, usaria a resposta de roaima.
R ..

Respostas:

54

Use isto:

cp -R inputFolder/. outputFolder

Isso funciona exatamente da mesma maneira que, digamos, cp -R aaa/bbb cccfunciona: se cccnão existe, é criado como uma cópia bbbe seu conteúdo; mas se cccjá existir, ccc/bbbserá criado como a cópia bbbe seu conteúdo.

Para quase qualquer instância bbbdisso, é apresentado o comportamento indesejável que você anotou na sua pergunta. No entanto, nessa situação específica, bbbé justo ., aaa/bbbrealmente é aaa/., o que, por sua vez, é realmente justo, aaamas com outro nome. Portanto, temos esses dois cenários:

  1. ccc não existe:

    O comando cp -R aaa/. cccsignifica "criar ccce copiar o conteúdo de aaa/.para ccc/., ou seja, copiar aaapara ccc.

  2. ccc existe:

    O comando cp -R aaa/. cccsignifica "copiar o conteúdo de aaa/.para ccc/., ou seja, copiar aaapara ccc.

roaima
fonte
3
Huh, isso é novo para mim, mas parece funcionar! Isso está documentado em algum lugar?
Ilmari Karonen 9/09/2015
1
Eu testei o em inputFolder/.vez de inputFoldere realmente funciona para mim no Windows / Git Bash. (No entanto, não funciona apenas com um final /, preciso também do ponto após a barra). Eu dei uma vez que é interessante, embora seja provavelmente um pouco demasiado complicado para usá-lo em código público :)
jakub.g
@IlmariJaronen, não precisa ser documentado explicitamente. Siga a explicação e você verá como simplesmente "segue as regras".
roaima 9/02
18

Não copie a pasta, apenas copie o conteúdo:

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

Dessa forma, você garante que o diretório de destino seja criado na primeira vez em que o script é executado e evita o problema ao executá-lo pela segunda vez.

Chris Down indica muito corretamente que, no bash, isso ignorará arquivos cujo nome começa com a .. Para evitar isso, você pode executar shopt -s dotglobantes de executar o comando acima.

Tanto -pparamkdir quanto -Rparacp são definidos pelo POSIX, portanto, isso deve ser perfeitamente portátil.

terdon
fonte
6
Nota: isso não copiará arquivos começando com .. No bash, você pode usar dotglobpara isso.
Chris Baixo
2
Observe que esse método provavelmente falhará se o número de entradas de diretório na pasta de entrada for grande, porque a expansão glob pode exceder o comprimento máximo da linha de comando.
precisa saber é o seguinte
11

Tente a -Topção para cp. Isso existe no GNU coreutils cpversão 8.22; pode não ser portátil fora disso.

Tom Hunt
fonte
1
Sim, parece que é exatamente isso que eu queria: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/… Infelizmente minha versão do cpé muito mais antiga.
jakub.g
1
+1 Usou isso hoje. -ué uma ótima opção para adicionar para torná-lo preguiçoso.
PSKocik 10/09/2015
4

Você também pode usar rsync:

rsync -uav inputFolder/ outputFolder/

(observe as barras, especialmente após a primeira)

Ned64
fonte
0

Na minha opinião, não há nada mais simples e mais portátil do que rm -rf outputFolder. Então, eu sempre fico com isso. Entendo que sua pergunta é diferente, mas acho que essa é a melhor prática.

dashohoxha
fonte
Isso falha se houver permissões ou propriedades específicas no destino que precisem ser mantidas. Ou se fosse um link simbólico.
roaima
1
A pergunta quer que o resultado cpseja o mesmo, quando o diretório não existe e quando ele existe. Então, do que você está falando?
Dashohoxha # /
O cpcomando dado pelo OP não mantém permissões (ou registros de data e hora).
roaima
0

Você pode usar a -topção de cpcomando como:

cp -R inputFolder -t outputFolder

Agora, se a pasta de destino não existir, ocorrerá um erro:

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

O comando note acima será copiado inputFolderjunto com o conteúdo (e não apenas o conteúdo)

se você deseja copiar apenas o conteúdo inputFolder, fica um pouco complicado (já que você precisa ter cuidado ao usar o shell globbing ao usar o asterisco *)

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

Agora, se a pasta de destino não existir, ocorrerá um erro:

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

funciona com cp (GNU coreutils) 8.23

Edward Torvalds
fonte