Copier le dossier récursivement, à l'exclusion de certains dossiers

197

J'essaie d'écrire un script bash simple qui copiera tout le contenu d'un dossier, y compris les fichiers et dossiers cachés, dans un autre dossier, mais je veux exclure certains dossiers spécifiques. Comment pourrais-je y parvenir?

trobrock
la source
1
J'imagine quelque chose comme trouver. -name * redirigé vers grep / v "exclude-pattern" pour filtrer ceux que vous ne voulez pas, puis redirigé vers cp pour faire la copie.
i_am_jorf
1
J'essayais de faire quelque chose comme ça, mais je ne pouvais pas comprendre comment utiliser cp avec un tuyau
trobrock
1
Cela devrait probablement aller au super utilisateur. La commande que vous recherchez est xargs. Vous pouvez également faire quelque chose comme deux goudrons reliés par un tuyau.
Kyle Butt
1
Peut-être qu'il est tard et qu'il ne répond pas précisément à la question, mais voici un conseil: si vous souhaitez exclure uniquement les enfants immédiats du répertoire, vous pouvez profiter de la correspondance de motifs bash, par exemplecp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov
1
Notez que le !(dir1|dir2)motif doit extglobêtre activé ( shopt -s extglobpour l'activer).
Boris D. Teoharov

Réponses:

334

Utilisez rsync:

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

Notez que l'utilisation de sourceet source/est différente. Une barre oblique de fin signifie copier le contenu du dossier sourcedans destination. Sans la barre oblique de fin, cela signifie copier le dossier sourcedans destination.

Alternativement, si vous avez de nombreux répertoires (ou fichiers) à exclure, vous pouvez utiliser --exclude-from=FILE, où FILEest le nom d'un fichier contenant des fichiers ou des répertoires à exclure.

--exclude peut également contenir des caractères génériques, tels que --exclude=*/.svn*

Kaleb Pederson
la source
10
Je suggère d'ajouter le --dry-run afin de vérifier quels fichiers vont être copiés.
loretoparisi
1
@AmokHuginnsson - Quels systèmes utilisez-vous? Rsync est inclus par défaut dans toutes les distributions Linux traditionnelles que je connais, y compris RHEL, CentOS, Debian et Ubuntu, et je pense que c'est également dans FreeBSD.
siliconrockstar
1
Pour les distributions dérivées de RHEL: yum install rsync, ou sur les versions basées sur Debian: apt-get install rsync. À moins que vous ne construisiez votre serveur à partir d'une base absolue sur votre propre matériel, ce n'est pas un problème. rsync est installé par défaut sur mes boîtiers Amazon EC2, ainsi que sur mes boîtiers ZeroLag et RackSpace.
siliconrockstar
2
rsync semble être extrêmement lent par rapport à cp? C'était du moins mon expérience.
Kojo
2
Par exemple, pour ignorer le git dir:rsync -av --exclude='.git/' ../old-repo/ .
nycynik
40

Utilisez du goudron avec un tuyau.

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

Vous pouvez même utiliser cette technique sur ssh.

Kyle Butt
la source
Cette approche tarit inutilement d'abord la source cible (et exclut des répertoires particuliers dans l'archive), puis la décompresse sur la cible. Non recommandé!
Wouter Donders
4
@Waldheri vous vous trompez. C'est la meilleure solution. Il fait exactement ce que l'OP a demandé et il fonctionne sur l'installation par défaut de la plupart des OS * nix. Le tarage et le tarage se font à la volée sans artefact du système de fichiers (en mémoire), le coût de ce tar + untar est négligeable.
AmokHuginnsson
@WouterDonders Tar est une surcharge minimale. Il n'applique pas de compression.
Kyle Butt
9

Vous pouvez utiliser findavec l' -pruneoption.

Un exemple de man find:

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

       Cette commande copie le contenu de / source-dir dans / dest-dir, mais omet
       fichiers et répertoires nommés .snapshot (et tout ce qu'ils contiennent). Ça aussi
       omet les fichiers ou répertoires dont le nom se termine par ~, mais pas leur contenu
       tentes. La construction -prune -o \ (... -print0 \) est assez courante. le
       idée ici est que l'expression avant -prune correspond à des choses qui sont
       à tailler. Cependant, l'action -prune elle-même renvoie true, donc le
       -o garantit que le côté droit est évalué uniquement pour
       les répertoires qui n'ont pas été élagués (le contenu de l'élagage
       les répertoires ne sont même pas visités, donc leur contenu n'est pas pertinent).
       L'expression sur le côté droit du -o est entre parenthèses uniquement
       pour plus de clarté. Il souligne que l'action -print0 n'a lieu que
       pour les choses qui n'avaient pas -prune appliqué à eux. Parce que le
       la condition par défaut `et 'entre les tests se lie plus étroitement que -o, cette
       est la valeur par défaut de toute façon, mais les parenthèses aident à montrer ce qui se passe
       sur.
En pause jusqu'à nouvel ordre.
la source
Accessoires pour localiser un exemple hautement pertinent directement à partir d'une page de manuel.
David M
Ça a l'air bien! Ceci est également disponible dans les documents en ligne . Malheureusement, cpion'a pas encore été emballé pour MSYS2.
underscore_d
3

vous pouvez utiliser tar, avec l'option --exclude, puis le décompresser dans la destination. par exemple

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

voir la page de manuel de tar pour plus d'informations

ghostdog74
la source
2

Similaire à l'idée de Jeff (non testée):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
la source
Désolé, mais je ne comprends vraiment pas pourquoi 5 personnes ont voté pour cela alors qu'il n'a certes pas été testé et ne semble pas fonctionner sur un test simple: j'ai essayé cela dans un sous-répertoire de /usr/share/iconset j'ai immédiatement obtenu find: paths must precede expression: 22x22où ce dernier était l'un des sous-répertoires . Ma commande était find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(certes, je suis sur MSYS2, donc vraiment /mingw64/share/icons/Adwaita, mais je ne vois pas comment c'est la faute 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

Non testé ...

Steve Lazaridis
la source
Ceci est une erreur. Quelques problèmes: tel qu'écrit, il copiera un fichier qui n'est pas censé être exclu plusieurs fois (le nombre d'éléments à exclure qui dans ce cas est de 4). Même si vous essayez de copier 'foo', le premier élément de la liste d'exclusion, il sera toujours copié lorsque vous arriverez à x = bar et i est toujours foo. Si vous insistez pour le faire sans outils préexistants (par exemple rsync), déplacez la copie vers une instruction if en dehors de la boucle 'for x in ...' et faites en sorte que la boucle 'for x ...' change l'instruction logique dans le fichier de copie if (true). Cela vous empêchera de copier plusieurs fois.
Eric Bringley
0

inspiré par la réponse de @ SteveLazaridis, qui échouerait, voici une fonction shell POSIX - il suffit de copier et coller dans un fichier nommé cpxdans yout $PATHet de le rendre exécutable ( chmod a+x cpr). [La source est maintenant conservée dans mon 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 "$@"

Exemple d'utilisation

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"
go2null
la source
Il semble inutile de dire que la réponse de quelqu'un "échouerait" sans expliquer ce qui ne va pas et comment vous corrigez cela ...
underscore_d
@underscore_d: vrai, avec du recul, d'autant plus que je ne me souviens plus de ce qui a échoué :-(
go2null
Plusieurs choses: (1) il copie des fichiers plusieurs fois et (2) la logique copie toujours les fichiers à exclure. Parcourez les boucles en utilisant i = foo: il sera copié 3 fois au lieu de 4 pour tout autre fichier, par exemple i = test.txt.
Eric Bringley
1
remercie @EricBringley d'avoir clarifié les lacunes de la réponse de Steve. (Il a cependant dit que cela n'avait pas été testé .)
go2null