Copie la carpeta de forma recursiva, excluyendo algunas carpetas

197

Estoy tratando de escribir un script bash simple que copiará todo el contenido de una carpeta, incluidos los archivos y carpetas ocultos en otra carpeta, pero quiero excluir ciertas carpetas específicas. ¿Cómo podría lograr esto?

trobrock
fuente
1
Me imagino algo como encontrar. -name * canalizado a grep / v "exclude-pattern" para filtrar los que no desea y luego canalizado a cp para hacer la copia.
i_am_jorf
1
Estaba tratando de hacer algo así, pero no podía entender cómo usar CP con una tubería
trobrock
1
Esto probablemente debería ir a super usuario. El comando que estás buscando es xargs. También podría hacer algo como dos alquitranes conectados por una tubería.
Kyle Butt
1
Tal vez sea tarde y no responda la pregunta con precisión, pero aquí hay un consejo: si desea excluir solo a los hijos inmediatos del directorio, puede aprovechar la coincidencia de patrones de bash, por ejemplocp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov
1
Tenga en cuenta que el !(dir1|dir2)patrón debe extglobestar activado ( shopt -s extglobpara activarlo).
Boris D. Teoharov

Respuestas:

334

Use rsync:

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

Tenga en cuenta que el uso sourcey source/son diferentes. Una barra diagonal significa copiar el contenido de la carpeta sourceen destination. Sin la barra inclinada final, significa copiar la carpeta sourceen destination.

Alternativamente, si tiene muchos directorios (o archivos) para excluir, puede usar --exclude-from=FILE, donde FILEestá el nombre de un archivo que contiene archivos o directorios para excluir.

--exclude también puede contener comodines, como --exclude=*/.svn*

Kaleb Pederson
fuente
10
Sugiero agregar --dry-run para verificar qué archivos se van a copiar.
loretoparisi
1
@AmokHuginnsson - ¿Qué sistemas estás usando? Rsync se incluye por defecto en todas las distribuciones de Linux convencionales que conozco, incluidas RHEL, CentOS, Debian y Ubuntu, y creo que también está en FreeBSD.
siliconrockstar
1
Para distribuciones derivadas de RHEL: yum install rsync, o en versiones basadas en Debian: apt-get install rsync. A menos que esté construyendo su servidor desde una base absoluta en su propio hardware, esto no es un problema. rsync se instala de manera predeterminada en mis cajas de Amazon EC2, así como en mis cajas de ZeroLag y RackSpace.
siliconrockstar
2
rsync parece ser extremadamente lento en comparación con cp? Al menos esta fue mi experiencia.
Kojo
2
Por ejemplo, para ignorar el directorio git:rsync -av --exclude='.git/' ../old-repo/ .
nycynik
40

Use alquitrán junto con una pipa.

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

Incluso puedes usar esta técnica en ssh.

Kyle Butt
fuente
Este enfoque innecesariamente primero alquila la fuente de destino (y excluye directorios particulares en el archivo) y luego lo desestima en el destino. ¡No recomendado!
Wouter Donders
44
@ Waldheri estás equivocado. Esta es la mejor solución. Hace exactamente lo que OP solicitó y funciona en la instalación predeterminada de la mayoría de los sistemas operativos tipo * nix. La tarificación y la descompresión se realizan sobre la marcha sin artefactos del sistema de archivos (en la memoria), el costo de este tar + untar es insignificante.
AmokHuginnsson
@WouterDonders Tar es una sobrecarga mínima. No aplica compresión.
Kyle Butt
9

Puedes usar findcon la -pruneopción.

Un ejemplo de man find:

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

       Este comando copia el contenido de / source-dir a / dest-dir, pero omite
       archivos y directorios llamados .snapshot (y cualquier cosa en ellos). También
       omite archivos o directorios cuyo nombre termina en ~, pero no su con‐
       carpas La construcción -prune -o \ (... -print0 \) es bastante común. los
       idea aquí es que la expresión antes de -prune coincide con cosas que son
       ser podado Sin embargo, la acción -prune en sí misma devuelve verdadero, por lo que el
       siguiente -o asegura que el lado derecho se evalúe solo para
       aquellos directorios que no fueron podados (el contenido de los podados
       los directorios ni siquiera se visitan, por lo que su contenido es irrelevante).
       La expresión en el lado derecho de -o está solo entre paréntesis
       para mayor claridad. Hace hincapié en que la acción -print0 solo tiene lugar
       para cosas que no tenían - se aplicó la ciruela. Porque el
       la condición predeterminada `y 'entre pruebas se une más estrechamente que -o, esto
       es el valor predeterminado de todos modos, pero los paréntesis ayudan a mostrar lo que está sucediendo
       en.
Pausado hasta nuevo aviso.
fuente
Accesorios para localizar un ejemplo muy relevante directamente desde una página de manual.
David M
Se ve bien de hecho! Esto también está disponible en los documentos en línea . Lamentablemente cpioaún no se ha empaquetado para MSYS2.
underscore_d
3

puede usar tar, con la opción --exclude, y luego descomprimirlo en el destino. p.ej

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

vea la página de manual de tar para más información

ghostdog74
fuente
2

Similar a la idea de Jeff (no probado):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
fuente
Lo siento, pero realmente no entiendo por qué 5 personas votaron por esto cuando no se ha probado y parece que no funciona en una prueba simple: intenté esto en un subdirectorio /usr/share/iconse inmediatamente llegué a find: paths must precede expression: 22x22donde este es uno de los subdirectores allí . Mi comando fue find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(es cierto que estoy en MSYS2, así que realmente /mingw64/share/icons/Adwaita, pero no puedo ver cómo esto es 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

No probado ...

Steve Lazaridis
fuente
Esto es incorrecto. Algunos problemas: tal como está escrito, copiará un archivo que no se debe excluir varias veces (el número de elementos que se excluirán, que en este caso es 4). Incluso si intentas copiar 'foo', el primer elemento de la lista de exclusión, se copiará cuando llegues a x = bar y sigo siendo foo. Si insiste en hacer esto sin herramientas preexistentes (por ejemplo, rsync), mueva la copia a una declaración if fuera del ciclo 'for x in ...' y haga que el ciclo 'for x ...' cambie la declaración lógica en el archivo de copia if (verdadero). Esto le impedirá copiar varias veces.
Eric Bringley
0

inspirado en la respuesta de @ SteveLazaridis, que fallaría, aquí hay una función de shell POSIX: simplemente copie y pegue en un archivo nombrado cpxen usted $PATHy hágalo ejecutable ( chmod a+x cpr). [La fuente ahora se mantiene en mi 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 "$@"

Ejemplo de uso

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"
go2null
fuente
Parece inútil decir que la respuesta de alguien "fallaría" sin explicar qué está mal y cómo se soluciona eso ...
underscore_d
@underscore_d: cierto, en retrospectiva, especialmente porque ahora no puedo recordar lo que falló :-(
go2null
Múltiples cosas: (1) copia archivos varias veces y (2) la lógica todavía copia archivos para ser excluidos. Ejecute los bucles con i = foo: se copiará 3 veces en lugar de 4 para cualquier otro archivo, por ejemplo, i = test.txt.
Eric Bringley
1
gracias @EricBringley por aclarar las deficiencias de la respuesta de Steve. (Sin embargo, dijo que no había sido probado .)
go2null