Copie a pasta recursivamente, excluindo algumas pastas

197

Estou tentando escrever um script simples do bash que copie todo o conteúdo de uma pasta, incluindo arquivos e pastas ocultos em outra pasta, mas quero excluir determinadas pastas específicas. Como eu consegui isso?

trobrock
fonte
1
Eu imagino algo como encontrar. -name * canalizado para grep / v "padrão de exclusão" para filtrar os que você não deseja e, em seguida, canalizado para o cp para fazer a cópia.
i_am_jorf
1
Eu estava tentando fazer algo assim, mas a figura não poderia descobrir como usar cp com um tubo
trobrock
1
Provavelmente isso deve ir para o superusuário. O comando que você está procurando é xargs. Você também pode fazer algo como dois alcatrões conectados por um cano.
Kyle Butt
1
Talvez seja tarde e ele não responder à pergunta com precisão, mas aqui vai uma dica: Se você quiser excluir as crianças só imediatos do diretório que você poderia tirar proveito do bash padrão de correspondência, por exemplocp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov
1
Observe que o !(dir1|dir2)padrão precisa extglobestar ativado ( shopt -s extglobpara ativá-lo).
Boris D. Teoharov

Respostas:

334

Use rsync:

rsync -av --exclude='path1/to/exclude' --exclude='path2/to/exclude' source destination

Observe que usar sourcee source/é diferente. Uma barra à direita significa copiar o conteúdo da pasta sourcepara destination. Sem a barra final, significa copiar a pasta sourcepara destination.

Como alternativa, se você tiver muitos diretórios (ou arquivos) a serem excluídos, poderá usar --exclude-from=FILE, onde FILEé o nome de um arquivo que contém arquivos ou diretórios a serem excluídos.

--exclude também pode conter curingas, como --exclude=*/.svn*

Kaleb Pederson
fonte
10
Sugiro adicionar o --dry-run para verificar quais arquivos serão copiados.
Loretoparisi
1
@AmokHuginnsson - Quais sistemas você está usando? O Rsync está incluído por padrão em todas as distros principais do Linux que eu conheço, incluindo RHEL, CentOS, Debian e Ubuntu, e acredito que também esteja no FreeBSD.
siliconrockstar
1
Para distribuições derivadas do RHEL: yum install rsync ou em versões baseadas no Debian: apt-get install rsync. A menos que você esteja construindo seu servidor com base absoluta em seu próprio hardware, isso não é problema. O rsync também é instalado por padrão nas minhas caixas do Amazon EC2 e nas caixas do ZeroLag e RackSpace.
Siliconrockstar 02/02
2
rsync parece ser extremamente lento em comparação com cp? Pelo menos essa foi a minha experiência.
Kojo
2
Por exemplo, para ignorar o dir git:rsync -av --exclude='.git/' ../old-repo/ .
nycynik 05/04
40

Use alcatrão junto com um cano.

cd /source_directory
tar cf - --exclude=dir_to_exclude . | (cd /destination && tar xvf - )

Você pode até usar essa técnica no ssh.

Kyle Butt
fonte
Essa abordagem desnecessariamente primeiro tarra a fonte de destino (e exclui diretórios específicos no arquivo morto) e depois a tarta no destino. Não recomendado!
Wouter Donders
4
@Waldheri você está errado. essa é a melhor solução. Ele faz exatamente o que o OP solicitou e funciona na instalação padrão da maioria dos sistemas operacionais * nix. Tarar e desarmar é feito em tempo real sem artefato do sistema de arquivos (na memória), o custo desse tar + desarmar é insignificante.
AmokHuginnsson
@WouterDonders O alcatrão é uma sobrecarga mínima. Não aplica compactação.
Kyle Butt
9

Você pode usar findcom a -pruneopção

Um exemplo de man find:

       cd / fonte-dir
       encontrar . -name .snapshot -une -o \ (\! -name * ~ -print0 \) |
       cpio -pmd0 / dest-dir

       Este comando copia o conteúdo de / source-dir para / dest-dir, mas omite
       arquivos e diretórios denominados .snapshot (e qualquer coisa neles). Isso também
       omite arquivos ou diretórios cujo nome termina em ~, mas não seus
       tendas. A construção -prune -o \ (... -print0 \) é bastante comum. o
       A idéia aqui é que a expressão before -une corresponda a coisas que são
       ser podado. No entanto, a própria ação -prune retorna true, portanto, o
       -o garante que o lado direito seja avaliado apenas para
       os diretórios que não foram removidos (o conteúdo do podado
       os diretórios nem são visitados, portanto, seu conteúdo é irrelevante).
       A expressão no lado direito do -o está entre parênteses
       para maior clareza. Ele enfatiza que a ação -print0 ocorre apenas
       por coisas que não tinham - aplicado a eles. Porque o
       condição padrão `e 'entre testes se liga mais firmemente que -o, isso
       é o padrão de qualquer maneira, mas os parênteses ajudam a mostrar o que está acontecendo
       em.
Pausado até novo aviso.
fonte
Adereços para localizar um exemplo altamente relevante diretamente de uma página de manual.
David M
Parece bom mesmo! Isso também está disponível nos documentos on-line . Infelizmente cpioainda não foi empacotado para o MSYS2.
Underscore_d
3

você pode usar o tar, com a opção --exclude, e depois descompactá-lo no destino. por exemplo

cd /source_directory
tar cvf test.tar --exclude=dir_to_exclude *
mv test.tar /destination 
cd /destination  
tar xvf test.tar

veja a página de manual do tar para mais informações

ghostdog74
fonte
2

Semelhante à ideia de Jeff (não testada):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
fonte
Desculpe, mas eu realmente não entendo por que 5 pessoas votaram nisto quando foi admitidamente não testado e não parecem funcionar em um teste simples: tentei isso em um subdiretório /usr/share/iconse cheguei imediatamente find: paths must precede expression: 22x22onde o último é um dos subdiretores. . Meu comando foi find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(na verdade, eu estou no MSYS2, então realmente /mingw64/share/icons/Adwaita, mas eu não posso ver como isso é culpa de MSYS2)
underscore_d
0
EXCLUDE="foo bar blah jah"                                                                             
DEST=$1

for i in *
do
    for x in $EXCLUDE
    do  
        if [ $x != $i ]; then
            cp -a $i $DEST
        fi  
    done
done

Não testado...

Steve Lazaridis
fonte
Isto está incorreto. Alguns problemas: Conforme escrito, ele copiará um arquivo que não deve ser excluído várias vezes (o número de itens a serem excluídos, que neste caso é 4). Mesmo se você tentar copiar 'foo', o primeiro item da lista de exclusões, ele ainda será copiado quando você chegar a x = bar e eu ainda estiver foo. Se você insistir em fazer isso sem ferramentas pré-existentes (por exemplo, rsync), mova a cópia para uma instrução if fora do loop 'for x in ...' e faça com que o loop 'for x ...' altere a instrução lógica em o arquivo de cópia if (true). Isso impedirá que você copie várias vezes.
Eric Bringley
0

Inspirado na resposta de SteveLazaridis, que falharia, aqui está uma função shell POSIX - basta copiar e colar em um arquivo nomeado cpxem você $PATHe torná-lo executável ( chmod a+x cpr). [A fonte agora é mantida no meu GitLab .

#!/bin/sh

# usage: cpx [-n|--dry-run] "from_path" "to_path" "newline_separated_exclude_list"
# limitations: only excludes from "from_path", not it's subdirectories

cpx() {
# run in subshell to avoid collisions
  (_CopyWithExclude "$@")
}

_CopyWithExclude() {
  case "$1" in
    -n|--dry-run) { DryRun='echo'; shift; } ;;
  esac

  from="$1"
  to="$2"
  exclude="$3"

  $DryRun mkdir -p "$to"

  if [ -z "$exclude" ]; then
      cp "$from" "$to"
      return
  fi

  ls -A1 "$from" \
    | while IFS= read -r f; do
        unset excluded
        if [ -n "$exclude" ]; then
          for x in $(printf "$exclude"); do
          if [ "$f" = "$x" ]; then
              excluded=1
              break
          fi
          done
        fi
        f="${f#$from/}"
        if [ -z "$excluded" ]; then
          $DryRun cp -R "$f" "$to"
        else
          [ -n "$DryRun" ] && echo "skip '$f'"
        fi
      done
}

# Do not execute if being sourced
[ "${0#*cpx}" != "$0" ] && cpx "$@"

Exemplo de uso

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"
go2null
fonte
Parece inútil dizer resposta que alguém "seria um fracasso" sem explicar o que está errado com ele e como você corrigir isso ...
underscore_d
@underscore_d: true, em retrospectiva, especialmente porque agora não consigo me lembrar do que falhou :-(
go2null
Múltiplas coisas: (1) copia os arquivos várias vezes e (2) a lógica ainda copia os arquivos a serem excluídos. Execute os loops usando i = foo: ele será copiado 3 vezes em vez de 4 para qualquer outro arquivo, por exemplo, i = test.txt.
Eric Bringley
1
obrigado @EricBringley por esclarecer as deficiências da resposta de Steve. (Ele disse que era testado embora.)
go2null