Como sobrescrever arquivos de destino com mv?

155

Eu tenho uma tonelada de arquivos e diretórios em um subdiretório que desejo mover para o diretório pai. Já existem alguns arquivos e diretórios no diretório de destino que precisam ser substituídos. Os arquivos presentes apenas no destino devem ser mantidos intactos. Posso forçar mva fazer isso? ( mv * ..) Reclama

mv: cannot move `xyz' to `../xyz': Directory not empty

o que estou perdendo?

EricSchaefer
fonte
3
Você já tentou mv -f?
22411 sakisk
Eu me pergunto por que mv -fa resposta não está correta.
Pedro Lobito
@PedroLobito: Porque não funciona? -f substitui apenas os arquivos, mas não funciona se você mover subdiretórios que existem no destino e não estão vazios.
EricSchaefer 26/03

Respostas:

118

Você precisará copiá-los para o destino e excluir a fonte, usando os comandos cp -r * ..seguidos por rm -rf *.

Eu não acho que você pode "mesclar" diretórios usando mv.

dogbane
fonte
4
Bem, era isso que eu não queria fazer, porque levaria muito tempo ... Obrigado mesmo assim.
EricSchaefer
12
Presumivelmente, mvé mais rápido porque você está no mesmo sistema de arquivos? E se você usar cp -lpara criar hardlinks em vez de realmente mover os arquivos?
mattdm
6
Você deve usar em cp -avez de cp -rpara preservar os atributos do arquivo (registro de data e hora, permissões etc.).
dotancohen
3
Para as pessoas que chegam atrasadas pelo google, a resposta abaixo de @palswim emula o comportamento do mv criando novos links físicos para os dados e excluindo os links antigos. Resposta curta cp -rl source destination && rm -r source.
William Everett
72

rsyncprovavelmente seria uma opção melhor aqui. É tão simples quanto rsync -a subdir/ ./.

Minha árvore de teste em filename: contentsformato:

./file1:root
./file2:root
./dir/file3:dir
./dir/file4:dir
./subdir/dir/file3:subdir
./subdir/file1:subdir

Em execução rsync:

$ rsync -a -v subdir/ ./
sending incremental file list
./
file1
dir/
dir/file3

Dá:

./file1:subdir
./file2:root
./dir/file3:subdir
./dir/file4:dir
./subdir/dir/file3:subdir
./subdir/file1:subdir

E então, para emular mv, você provavelmente deseja remover o diretório de origem:

$ rm -r subdir/

Dando:

./file1:subdir
./file2:parent
./dir/file3:subdir
./dir/file4:dir

Se isso estiver errado, você pode fornecer um exemplo semelhante (por exemplo, usando minha árvore de teste perto do topo desta resposta) com o resultado desejado?

Mikel
fonte
1
cópias rsync. Esta questão é sobre a mudança.
Gilles
1
@ Gilles: Obrigado. Eu adicionei rm -rno final para torná-lo basicamente o mesmo que mv.
Mikel
3
copiar e excluir não é equivalente a mv quando a origem e o destino estão no mesmo sistema de arquivos. mvé atômico, preserva os números de inode (para que o arquivo possa permanecer aberto) e não leva tempo e espaço para fazer uma cópia.
Gilles
5
@ Gilles: Sei disso, mas atualmente a resposta principal é cp -r; rm -r. Eu acho que nesse sentido, rsyncvale a pena mencionar também.
Mikel
Eu já fiz isso com cp / rm (era urgente). Realmente demorou muito tempo. O roteiro de Gilles provavelmente teria sido muito mais rápido, mas ele era tarde demais também.
23811 EricSchaefer
47

rsyncpode excluir a fonte após as cópias com o --remove-source-filesparâmetro Essa deve ser uma maneira conveniente de fazer o que você deseja.

De rsync man page:

        --remove-source-files   sender removes synchronized files (non-dir)
Mike Chen
fonte
Esta é realmente a melhor resposta. Não pude usar cp -r; rmpor falta de espaço livre. Em vez disso, rsync --remove-source-fileso espaço em disco usado minimizado evitou a cópia exatamente dos mesmos arquivos.
Guaka
20

Você pode fazer isso com cpe rm, mas sem copiar a enorme quantidade de dados que você está (presumivelmente) tentando evitar a transferência. @mattdm fez alusão a isso em seu comentário , e uma resposta para outra pergunta tem uma discussão mais completa sobre várias opções.

cp -rlf source destination
rm -r source

Essencialmente, a -lopção para o cpcomando cria links físicos para arquivos em vez de copiar seus dados para novos arquivos.

palswim
fonte
1
Sei que o OP já concluiu sua tarefa que levou à pergunta, mas espero que esta resposta possa ajudar qualquer pessoa com esse problema no futuro.
palswim
Esta é realmente a resposta que eles precisavam. Acabei de fazer isso com 60 GB de milhares de pequenos arquivos de email cyrus e levou apenas 21 segundos.
Labradort #
Essa também foi a rota que eu segui - apenas mudei cp -al source destinationpara preservar informações e permissões do proprietário.
piit79
Eu acho que para realmente fazer o que o OP pediu, você deve adicionar a opção -f, caso contrário ele não substituirá se o arquivo existir. Você pode confirmar isso ou estou fazendo algo errado?
Das Keks
@dasKeks: Na verdade, cpsobrescreve por padrão. A -fopção tenta remover o arquivo (como em rm) antes de tentar copiar novamente, o que pode ajudar se o processo não puder abrir o arquivo para gravação, embora algumas pessoas prefiram ver que houve um erro. Não sei se isso importava para o OP, mas adicionei a -fbandeira à minha resposta de qualquer maneira.
palswim
8

Aqui está um script que move os arquivos de baixo /path/to/source/rootpara o caminho correspondente em /path/to/destination/root.

  • Se existir um diretório na origem e no destino, o conteúdo será movido e mesclado recursivamente.
  • Se um arquivo ou diretório existir na origem, mas não no destino, ele será movido.
  • Qualquer arquivo ou diretório que já exista no destino é deixado para trás. (Em particular, os diretórios mesclados são deixados para trás na fonte. Isso não é fácil de corrigir.)

Cuidado, código não testado.

export dest='/path/to/destination/root'
cd /path/to/source/root
find . -type d \( -exec sh -c '[ -d "$dest/$0" ]' \; -o \
                  -exec sh -c 'mv "$0" "$dest/$0"' {} \; -prune \) \
    -o -exec sh -c '
        if ! [ -e "$dest/$0" ]; then
          mv -f "$0" "$dest/$0"
        fi
' {} \;
Gilles
fonte
Duas correções: você precisa de um \;antes do -ona primeira linha do findcomando, e você não deve escapar !no if- é apenas !, não\!
llhuii
2

Esta discussão está lá fora há anos e ainda está no 1º lugar no google, então eu queria adicionar outro método. Como eu costumo fazer isso: empacotar o conteúdo subdir em um tarball, movendo o tarball para o diretório pai e depois extraí-lo com o comportamento padrão - overwrite. Isso faz exatamente o que você está procurando. Depois, você pode remover seu subdiretório.

cd xyz
tar -cvzpf tmp.tar.gz *
mv tmp.tar.gz ../tmp.tar.gz
cd ..
tar -xvzpf tmp.tar.gz
rm -rf xyz
rm -f tmp.tar.gz
Simon Kraus
fonte
Isso funciona apenas se você tiver espaço extra para o arquivo tar compactado. E você realmente deseja manter o arquivo tar temporário após a extração?
Anthon
Código editado para remover o arquivo tmp. Atualmente, o espaço não é o problema na maioria dos casos. #terabyteages
Simon Kraus
0

Se você tiver armazenamento suficiente, poderá fazê-lo da seguinte maneira:

mv -bfv directory_1/* directory_2/ # all duplicate source files/directories 
                                   # will have ~ appended to them
find -name "*~" -delete            # will recursively find and delete all files 
                                   # with ~ on the end

Verifique se não há arquivos importantes com um ~ no final deles, mas se houver, você pode adicionar em --suffix=whateveryouwantvez do padrão.

4D3H2
fonte