So tarieren Sie eine Liste von Verzeichnissen nur, wenn sie vorhanden sind

7

Ich habe eine Liste von Verzeichnissen, die ich archivieren möchte. Aber manchmal existieren sie nicht alle.

Ich möchte in der Lage sein, ein Archiv zu erstellen, indem ich eine Liste von Verzeichnissen bereitstelle. Dabei werden nur die vorhandenen archiviert und fehlende Verzeichnisse ignoriert. (sollte aber trotzdem fehlschlagen, wenn keine Verzeichnisse vorhanden sind)

Dies ist nützlich für mich, da ich eine kontinuierliche Integration durchführe und einige der Prozesse bestimmte Artefakte erstellen, die ich für die Zukunft in einem Archiv behalten möchte. Ich kenne alle möglichen Pfade, die erstellt werden können, bin mir aber nicht immer sicher, welche erstellt werden.

Angenommen, die möglichen Pfade sind: here_is_a_dir here_is_another_one yet_another_dir

Normalerweise erstelle ich das Archiv mit dem folgenden Befehl:

tar -czf archive.tgz here_is_a_dir here_is_another_one yet_another_dir

Was natürlich fehlschlägt, wenn eines der Verzeichnisse fehlt.

Im Idealfall sollte es sich um einen einfachen Befehl handeln, für den kein Skript erforderlich ist. (Insbesondere ist nur in meiner Umgebung shverfügbar, daher kann ich keine ausgefallenen Shells wie bashoder andere verwenden, aber das ist spezifisch für meine Umgebung und könnte sich in Zukunft ändern. Antworten mit anderen Shells sind wahrscheinlich auch gut, denke ich. )

Inbar Rose
quelle
2
Sie haben unten gesagt, dass Bash in Ihrer Umgebung keine Option ist. Welche Shell müssen Sie verwenden? Ist ksh verfügbar? Oder haben Sie nur Busybox oder eine andere POSIX sh?
Ilkkachu
@ilkkachu Danke für die Frage, ich werde auch den ursprünglichen Beitrag aktualisieren. Nur shsteht mir momentan zur Verfügung. Dies ist jedoch eine Einschränkung, die sich nur auf meine spezifische Situation auswirkt. Antworten mit anderen Shells sind meiner Meinung nach immer noch gute Antworten.
Inbar Rose
Vielleicht möchten Sie danach suchen cpio.
mpez0

Antworten:

8

Die Verwendung der Ausgabe von lsist im Allgemeinen unklug und unsicher. Denken Sie daran, dass sowohl Leerzeichen als auch Zeilenumbrüche und andere Shell-Metazeichen gültige Zeichen in einem Datei- oder Verzeichnisnamen sind. In vielen Fällen ist es möglich, dieses Problem zu umgehen. Dies ist jedoch in der Regel aufwändiger als die Verwendung des richtigen Tools für den Job (dh find).

Verwenden Sie findstattdessen. Zum Beispiel:

find . -maxdepth 1 -type d \( -name here_is_a_dir -o -name here_is_another_one \
  -o -name yet_another_dir \) -exec tar cfz archive.tgz {} +

Dadurch wird eines der übereinstimmenden Verzeichnisse ( -type d) im aktuellen Verzeichnis ( .) gefunden und als Argument für den tarBefehl verwendet. Das \(zu\) ist ein Ausdruck, bei dem jeder der Unterausdrücke zusammen mit ODER verknüpft wird -o(standardmäßig sind die Prädikate von find UND-verknüpft). dh es lautet "maxdepth 1 AND type directory AND (dir1 OR dir2 OR dir3)".

Beachten Sie, dass ohne die Klammern zum Erzwingen der Priorität "maxdepth 1 AND type type AND dir1 OR (dir2 OR dir3)" interpretiert wird, wodurch nicht die vollständige Liste aller vorhandenen Verzeichnisse zurückgegeben wird. Meistens würde es entweder nichts oder nur dir3 zurückgeben, je nachdem, ob dir1 existiert oder nicht.

Wenn Sie möchten, dass die Unterverzeichnisse irgendwo unter dem aktuellen Verzeichnis gefunden werden, löschen Sie das -maxdepth 1Argument.

Wenn bei den Verzeichnisübereinstimmungen die Groß- und Kleinschreibung nicht berücksichtigt werden soll, verwenden Sie -inamestattdessen -name. Beachten Sie, dass das Argument für -nameoder -inameein Muster anstelle einer festen Zeichenfolge sein kann. Dies ist nützlich, wenn die gewünschten Verzeichnisnamen sehr ähnlich sind. z.B

find . -maxdepth 1 -type d -iname 'dir[123]' -exec tar cfz archive.tgz {} +

Das -exec ....funktioniert sehr ähnlich wie Rohrleitungen, xargsist aber eingebaut find. In der Tat könnten Sie verwenden, xargswenn Sie möchten, indem Sie alles von nun -execan durch ersetzen -print0 | xargs -0 -r tar cfz archive.tgz. z.B

find . -maxdepth 1 -type d -iname 'dir[123]' -print0 | \
  xargs -0 -r tar cfz archive.tgz 

(Dies verwendet ein NUL-Zeichen als Ausgabetrennzeichen, daher ist die Verwendung mit Dirnamen, die Leerzeichen usw. enthalten, genauso sicher wie die Verwendung -exec. Die -rOption weist xargs an, nichts zu tun, wenn keine Eingabe erfolgt.)

cas
quelle
Das ist sehr gut. Für die xargsVariante kann es erwähnenswert sein, dass -res sich um eine GNU-Erweiterung handelt (laut Manpage).
David Conrad
2
@ DavidConrad das stimmt. Heutzutage gehe ich von einer GNU-Umgebung aus, sofern nicht ausdrücklich anders angegeben (dies ist das häufigste * nix-ähnliche Benutzerland, macht also einen vernünftigen Standard). Auch nicht alle findVersionen unterstützen -print0, aber Sie müssen ziemlich weit in die Geschichte zurückgehen (Paläo-Unix :-), um eine zu finden, die dies nicht tut. Übrigens habe ich bemerkt, dass busyboxdies in anderen Kommentaren hier erwähnt wurde - man busyboxzeigt, dass es unterstützt xargs -r. ebenso wie die Version von freebsd von xargs(eine Null-Option, die eigentlich nicht benötigt wird, standardmäßig nur für die Kompatibilität mit GNU).
Cas
Gut zu wissen über Busybox. Ich hätte xargs -r nicht einmal erwähnt, außer dass OP erwähnt hat, dass es so begrenzt ist, dass es sich um eine Shell handelt.
David Conrad
7

Mit zsh:

dirs_to_archive=(some/dir /some/other/dir and/more/dirs)
existing_dirs=($^dirs_to_archive(/N))
if (($#existing_dirs)); then
  tar -cf - -- $existing_dirs | xz > file.tar.xz
else
  echo >&2 Error: none of the dirs were found
fi

Das POSIX-Äquivalent (obwohl weder POSIX-Befehle tarnoch xzPOSIX-Befehle vorhanden sind) wäre etwa:

# The list of dirs in "$@" (the only array in POSIX sh language)
set -- some/dir /some/other/dir and/more/dirs

for dir do
  # remove from the array the elements that are not directories like with
  # zsh's / glob qualifier above
  [ -d "$dir" ] && [ ! -L "$dir" ] && set -- "$@" "$dir"
  shift
done
if [ "$#" -gt 0 ]; then
  tar -cf - -- "$@" | xz > file.tar.xz
else
  echo >&2 Error: none of the dirs were found
fi
Stéphane Chazelas
quelle
3

Wenn Sie bash verwenden können, dann mit erweitertem globbing ( extglob):

$ shopt -s extglob; set -x
$ tar -czf archive.tgz *(here_is_a_dir|here_is_another_one|yet_another_dir)
+ tar -czf archive.tgz
tar: no files or directories specified

Und wenn einer oder mehrere davon existieren:

$ touch here_is_a_dir yet_another_dir
+ touch here_is_a_dir yet_another_dir
$ tar -czvf archive.tgz *(here_is_a_dir|here_is_another_one|yet_another_dir)
+ tar -czvf archive.tgz here_is_a_dir yet_another_dir
a here_is_a_dir
a yet_another_dir

(Ich habe verwendet, set -xdamit Sie die Ergebnisse der Glob-Erweiterung sehen können.)

muru
quelle
das wäre sehr nützlich, bashsteht mir in dieser umgebung leider nicht zur verfügung. Aber die Antwort ist immer noch richtig und bekommt meine +1
Inbar Rose
1

Dies kann relativ einfach durchgeführt werden, indem lseine Liste vorhandener Verzeichnisse erstellt und dann xargsin den tarBefehl weitergeleitet wird.

Lösung:

ls -d here_is_a_dir here_is_another_one yet_another_dir 2> /dev/null | xargs tar -czf archive.tgz

Nervenzusammenbruch:

  • ls -d Listen Sie nur Verzeichnisse auf
  • here_is_a_dir here_is_another_one yet_another_dir die Liste der Verzeichnisse, nach denen gesucht werden soll
  • 2> /dev/null Leite den stderr nach / dev / null, damit wir nur die stdout-Ausgabe erhalten (keine fehlenden Verzeichnisse).
  • | xargs wandelt die Liste der vorhandenen Verzeichnisse aus dem vorherigen Befehl in Argumente um
  • tar -czf archive.tgz Erstellen Sie ein Archiv mit den Argumenten
Inbar Rose
quelle
Beachten Sie, dass xargs versucht herauszufinden, wie viele Argumente es für den angegebenen Befehl liefern kann. Unter ungünstigen Umständen kann dies 1 sein, was dazu führt, dass tar -czf archive.tgzfür jedes Verzeichnis separat gestartet wird. In diesem Fall hätten Sie nur das letzte Verzeichnis im Archiv.
Gerald Schneider
0

Eine, die nur mit POSIX sh ( tar.sh) funktionieren sollte :

#!/bin/sh

first=1
for dir in "$@"; do
        if [ "$first" ]; then  # this is a bit ugly
                set --
                first=
        fi
        if [ -d "$dir" ]; then
                set -- "$@" "$dir"
        fi
done
echo tar -czf archive.tar.gz "$@"    # remove that 'echo'

Prüfung:

$ mkdir here_is_a_dir yet_another_dir
$ ./tar.sh here_is_a_dir here_is_another_one yet_another_dir
tar -czf archive.tar.gz here_is_a_dir yet_another_dir
ilkkachu
quelle
0

Wenn Sie pax haben , können Sie damit filtern, was im Archiv enthalten ist. Pax wird von POSIX als Ersatz für das historisch vielfältige cpio und tar beauftragt, aber viele Unix-Varianten haben sich seiner Einführung widersetzt, und insbesondere BusyBox hat es nicht.

Pax hat eine Funktion zum Ausschließen von Dateien, obwohl diese in der Beschreibung eher versteckt ist. Sie können Dateien mit umbenennen -s. Wenn Sie eine Datei in die leere Zeichenfolge umbenennen, wird sie ausgeschlossen. Bei mehreren -sOptionen gilt für jede die erste Übereinstimmung, und Umbenennungsfilter -swerden übersprungen. Auf diese Weise können Sie eine Reihe von Einschluss- / Ausschlussregeln erstellen, ähnlich wie bei rsync . Eine Einschlussregel in der Pax-Syntax lautet -s'!REGEX!&!'(in sich selbst umbenennen) und eine Ausschlussregel lautet -s'!REGEX!!'(in leer umbenennen).

pax -w -x ustar \
    -s'!^\./here_is_a_dir$!&!' -s'!^\./here_is_a_dir/!&!' \
    -s'!^\./here_is_another_one$!&!' -s'!^\./here_is_another_one/!&!' \
    -s'!^\./yet_another_dir$!&!' -s'!^\./yet_another_dir/!&!' \
    . | gzip >archive.tgz

BSD-Teer hat eine ähnliche -sOption. Auf der anderen Seite haben GNU-Teer und BusyBox-Teer keine Möglichkeit, dies zu tun, einschließlich Gymnastik.

Gilles 'SO - hör auf böse zu sein'
quelle
0

Am einfachsten ist es wahrscheinlich

find adir anotherdir yadir -maxdepth 0 2>&- | tar Tcf - my.tar
jthill
quelle
0

Wenn Ihre Version dies tarunterstützt, erstellen Sie eine leere TAR-Datei und hängen Sie sie bei Bedarf an, indem Sie -rstatt-c

touch foo.tar

for d in here_is_a_dir here_is_another_one yet_another_dir; do
  if [ -d "$d" ]; then
    tar -rf foo.tar "$d"
  fi
done
gzip foo.tar
chepner
quelle