Ordner rekursiv kopieren, mit Ausnahme einiger Ordner

197

Ich versuche, ein einfaches Bash-Skript zu schreiben, das den gesamten Inhalt eines Ordners einschließlich versteckter Dateien und Ordner in einen anderen Ordner kopiert, möchte jedoch bestimmte Ordner ausschließen. Wie könnte ich das erreichen?

Trobrock
quelle
1
Ich stelle mir so etwas wie Fund vor. -name * an grep / v "exclude-pattern" weitergeleitet, um diejenigen zu filtern, die Sie nicht möchten, und dann an cp weitergeleitet, um die Kopie zu erstellen.
i_am_jorf
1
Ich habe versucht , so etwas zu tun, aber habe nicht herauszufinden , wie cp zu verwenden , um mit einem Rohr
trobrock
1
Dies sollte wahrscheinlich an Superuser gehen. Der gesuchte Befehl ist xargs. Sie könnten auch so etwas wie zwei Teer machen, die durch ein Rohr verbunden sind.
Kyle Butt
1
Vielleicht ist es spät und es beantwortet die Frage nicht genau, aber hier ist ein Tipp: Wenn Sie nur unmittelbare Kinder des Verzeichnisses ausschließen möchten, können Sie den Bash-Pattern-Matching nutzen, z. B.cp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov,
1
Beachten Sie, dass das !(dir1|dir2)Muster aktiviert extglobsein muss ( shopt -s extglobum es einzuschalten).
Boris D. Teoharov

Antworten:

334

Verwenden Sie rsync:

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

Beachten Sie, dass die Verwendung von sourceund source/unterschiedlich sind. Slash Mittel , um die Inhalte des Ordners zu kopieren sourcein destination. Ohne den abschließenden Schrägstrich bedeutet dies, dass der Ordner sourcein kopiert wird destination.

Alternativ können Sie , wenn Sie viele Verzeichnisse (oder Dateien) müssen auszuschließen, verwenden Sie --exclude-from=FILE, wo FILEist der Name einer Datei , Dateien oder Verzeichnisse enthalten , auszuschließen.

--exclude kann auch Platzhalter enthalten, wie z --exclude=*/.svn*

Kaleb Pederson
quelle
10
Ich schlage vor, --dry-run hinzuzufügen, um zu überprüfen, welche Dateien kopiert werden sollen.
Loretoparisi
1
@AmokHuginnsson - Welche Systeme verwenden Sie? Rsync ist standardmäßig in allen mir bekannten Mainstream-Linux-Distributionen enthalten, einschließlich RHEL, CentOS, Debian und Ubuntu, und ich glaube, dass es auch in FreeBSD enthalten ist.
Siliconrockstar
1
Für von RHEL abgeleitete Distributionen: yum install rsync oder auf Debian-basierten Releases: apt-get install rsync. Dies ist kein Problem, es sei denn, Sie bauen Ihren Server auf absoluter Basis auf Ihrer eigenen Hardware auf. rsync wird standardmäßig auch auf meinen Amazon EC2-Boxen und meinen Boxen von ZeroLag und RackSpace installiert.
Siliconrockstar
2
rsync scheint im Vergleich zu cp extrem langsam zu sein? Zumindest war das meine Erfahrung.
Kojo
2
Zum Beispiel, um das Git- rsync -av --exclude='.git/' ../old-repo/ .
Verzeichnis
40

Verwenden Sie Teer zusammen mit einem Rohr.

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

Sie können diese Technik sogar für ssh verwenden.

Kyle Butt
quelle
Dieser Ansatz tariert unnötigerweise zuerst die Zielquelle (und schließt bestimmte Verzeichnisse im Archiv aus) und entlarvt sie dann am Ziel. Nicht empfohlen!
Wouter Donders
4
@ Waldheri du liegst falsch. Dies ist die beste Lösung. Es macht genau das, was OP angefordert hat, und es funktioniert bei der Standardinstallation der meisten * nix-ähnlichen Betriebssysteme. Das Tarieren und Enttarieren erfolgt im laufenden Betrieb ohne Artefakt des Dateisystems (im Speicher). Die Kosten für dieses Tarieren und Entpacken sind vernachlässigbar.
AmokHuginnsson
@WouterDonders Tar ist minimaler Overhead. Es wird keine Komprimierung angewendet.
Kyle Butt
9

Sie können findmit dem verwenden-prune Option verwenden.

Ein Beispiel aus man find :

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

       Dieser Befehl kopiert den Inhalt von / source-dir nach / dest-dir, lässt ihn jedoch aus
       Dateien und Verzeichnisse mit dem Namen .snapshot (und alles in ihnen). Es auch
       lässt Dateien oder Verzeichnisse aus, deren Name mit ~ endet, aber nicht deren Inhalt
       Zelte. Das Konstrukt -prune -o \ (... -print0 \) ist weit verbreitet. Das
       Die Idee hier ist, dass der Ausdruck vor -prune mit Dingen übereinstimmt, die sind
       beschnitten werden. Die Aktion -prune selbst gibt jedoch true zurück
       Mit -o wird sichergestellt, dass die rechte Seite nur für ausgewertet wird
       jene Verzeichnisse, die nicht beschnitten wurden (der Inhalt der beschnittenen
       Verzeichnisse werden nicht einmal besucht, daher sind ihre Inhalte irrelevant.
       Der Ausdruck auf der rechten Seite des -o steht nur in Klammern
       zur Klarheit. Es wird betont, dass die Aktion -print0 nur stattfindet
       für Dinge, auf die -prune nicht angewendet wurde. Weil das
       Die Standardbedingung "und" zwischen den Tests ist enger gebunden als -o
       ist sowieso die Standardeinstellung, aber die Klammern helfen zu zeigen, was los ist
       auf.
Bis auf weiteres angehalten.
quelle
Requisiten zum Auffinden eines hochrelevanten Beispiels direkt von einer Manpage.
David M
Sieht wirklich gut aus! Dies ist auch in den Online-Dokumenten verfügbar . Leider cpiowurde noch nicht für MSYS2 gepackt.
underscore_d
3

Sie können tar mit der Option --exclude verwenden und dann im Ziel entpacken. z.B

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

Weitere Informationen finden Sie in der Manpage von tar

Ghostdog74
quelle
2

Ähnlich wie Jeffs Idee (ungetestet):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
quelle
Tut mir leid, aber ich verstehe wirklich nicht, warum 5 Leute dies positiv bewertet haben, als es zugegebenermaßen nicht getestet wurde und nicht an einem einfachen Test zu arbeiten scheint: Ich habe dies in einem Unterverzeichnis von versucht /usr/share/iconsund bin sofort dahin gekommen, find: paths must precede expression: 22x22wo letzteres eines der Unterverzeichnisse darin ist . Mein Befehl war find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(zugegeben, ich bin auf MSYS2, also wirklich in /mingw64/share/icons/Adwaita, aber ich kann nicht sehen, wie dies die Schuld von MSYS2 ist)
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

Ungetestet...

Steve Lazaridis
quelle
Das ist falsch. Einige Probleme: Wie geschrieben, wird eine Datei kopiert, die nicht mehrmals ausgeschlossen werden soll (die Anzahl der auszuschließenden Elemente beträgt in diesem Fall 4). Selbst wenn Sie versuchen, 'foo', das erste Element in der Ausschlussliste, zu kopieren, wird es immer noch kopiert, wenn Sie zu x = bar gelangen und i immer noch foo ist. Wenn Sie darauf bestehen, dies ohne bereits vorhandene Tools (z. B. rsync) zu tun, verschieben Sie die Kopie in eine if-Anweisung außerhalb der Schleife 'for x in ...' und lassen Sie die Schleife 'for x ...' die logische Anweisung in ändern die if (true) Kopierdatei. Dadurch können Sie nicht mehrmals kopieren.
Eric Bringley
0

Inspiriert von der Antwort von @ SteveLazaridis, die fehlschlagen würde, ist hier eine POSIX-Shell-Funktion - kopieren Sie sie einfach und fügen Sie sie in eine Datei mit dem Namen cpxyout ein $PATHund machen Sie sie ausführbar ( chmod a+x cpr). [Die Quelle wird jetzt in meinem GitLab gepflegt .

#!/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 "$@"

Anwendungsbeispiel

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"
go2null
quelle
Es scheint nicht hilfreich zu sein zu sagen, dass jemandes Antwort "scheitern würde", ohne zu erklären, was daran falsch ist und wie Sie das beheben ...
underscore_d
@underscore_d: wahr, im Nachhinein, zumal ich mich jetzt nicht erinnern kann, was fehlgeschlagen ist :-(
go2null
Mehrere Dinge: (1) Es kopiert Dateien mehrmals und (2) Die Logik kopiert weiterhin Dateien, die ausgeschlossen werden sollen. Führen Sie die Schleifen mit i = foo durch: Sie werden dreimal anstelle von viermal für jede andere Datei kopiert, z. B. i = test.txt.
Eric Bringley
1
Vielen Dank an @EricBringley für die Klärung der Mängel von Steves Antwort. (Er sagte jedoch, es sei ungetestet .)
go2null