
Ich habe einen Stammordner Products
und darin eine Reihe von Unterordnern. Jeder dieser Unterordner enthält derzeit eine Reihe von Dateien. Nur der Einfachheit halber habe ich mir die Namen der Unterordner folder{number}
und der Dateien ausgedacht, files{number}.json
aber im Allgemeinen haben sie unterschiedliche Namen.
Im Allgemeinen habe ich 20 verschiedene Unterordner im Stammordner und jeder Unterordner hat maximal etwa 30 Dateien.
(Abbildung 1)
Products
├── folder1
│ ├── files1.json
│ ├── files2.json
│ └── files3.json
├── folder2
│ ├── files4.json
│ ├── files5.json
│ └── files6.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
Jetzt komprimiere ich das alles in eine tar.gz
Datei, indem ich den folgenden Befehl ausführe -
tar cvzf ./products.tgz Products
Frage:-
Ich habe ein neues Design, wie unten gezeigt, bei dem jeder Unterordner im Products
Stammordner drei Umgebungsordner enthält – dev
, stage
und prod
.
(Figur 2)
Products
├── folder1
│ ├── dev
│ │ └── files1.json
│ ├── files1.json
│ ├── files2.json
│ ├── files3.json
│ ├── prod
│ │ └── files1.json
│ └── stage
│ └── files1.json
├── folder2
│ ├── dev
│ │ └── files5.json
│ ├── files4.json
│ ├── files5.json
│ ├── files6.json
│ ├── prod
│ │ └── files5.json
│ └── stage
│ └── files5.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
Beispiel: Innerhalb folder1
eines Unterordners gibt es drei weitere Unterordner dev
und stage
genau prod
dasselbe gilt für die anderen Unterordner folder2
und folder3
. Jeder dieser Unterordner dev
und stage
jeder prod
Unterordner innerhalb folder{number}
eines Unterordners enthält Dateien, die für sie überschrieben werden.
Ich muss tar.gz
jetzt drei verschiedene Dateien generieren – eine für jede dev
und stage
aus prod
der obigen Struktur.
- Welche Dateien ich auch immer darin habe
dev
,stage
sieprod
überschreiben ihre Unterordnerdateien, wenn sie auch in deren Unterordner (Ordner1, Ordner2 oder Ordner3) vorhanden sind. - Wenn also im Unterordner
files1.json
vorhanden ist und dieselbe Datei auch in einem der Ordner vorhanden ist , muss ich beim Verpacken alles verwenden, was in deren Umgebungsordner vorhanden ist, und die Unterordnerdateien überschreiben, andernfalls verwende ich einfach das, was in deren Unterordnern vorhanden ist.folder1
dev
stage
prod
Am Ende werde ich drei verschiedene Strukturen wie diese haben – eine für dev
, eine für stage
und eine weitere für , prod
wobei Ordner1 (oder 2 und 3) entsprechend dem, was ich in ihrer Umgebung als erste Präferenz habe, da sie überschrieben werden, und andere Dateien, die nicht überschrieben werden, Dateien enthalten werden.
(Figur 3)
Products
├── folder1
│ ├── files1.json
│ ├── files2.json
│ └── files3.json
├── folder2
│ ├── files4.json
│ ├── files5.json
│ └── files6.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
Und ich muss generieren products-dev.gz
, products-stage.gz
und products-prod.gz
daraus figure 2
werden Daten wie diese figure 3
, aber spezifisch für jede Umgebung, stammen. Der einzige Unterschied besteht darin, dass jeder Unterordner Ordner1 (2 oder 3) Dateien enthält, die für sie als erste Präferenz aus ihrem jeweiligen Umgebungsordner überschrieben werden, und der Rest wird nur aus seinem Unterordner verwendet.
Ist dies mit einigen Linux-Befehlen möglich? Ich bin mir nur nicht sicher, wie ich bestimmte Umgebungsdateien in einem bestimmten Unterordner überschreiben und dann drei verschiedene tar.gz
Dateien darin generieren kann.
Aktualisieren:
Bedenken Sie auch Fälle wie die folgenden:
Products
├── folder1
│ ├── dev
│ │ ├── files1.json
│ │ └── files5.json
│ ├── files1.json
│ ├── files2.json
│ ├── files3.json
│ ├── prod
│ │ ├── files10.json
│ │ └── files1.json
│ └── stage
│ └── files1.json
├── folder2
│ ├── dev
│ ├── prod
│ └── stage
└── folder3
├── dev
├── prod
└── stage
Wie Sie sehen folder2
, folder3
gibt es Ordner, die die Umgebung überschreiben, aber sie enthalten keine Dateien. In diesem Fall möchte ich leere folder2
und folder3
auch in jeder Umgebung spezifische tar.gz
Dateien generieren.
Antwort1
Es gibt viele Möglichkeiten, doch alle erfordern eine gewisse Komplexität, um den Override-Fall behandeln zu können.
Als Einzeiler, wenn auch etwas lang, könnten Sie es für eine Iteration, also ein „Umgebungen“-Verzeichnis, wie folgt machen:
(r=Products; e=stage; (find -- "$r" -regextype posix-extended -maxdepth 2 \( -regex '^[^/]+(/[^/]+)?' -o ! -type d \) -print0; find -- "$r" -mindepth 1 -path "$r/*/$e/*" -print0) | tar --null --no-recursion -czf "$r-$e.tgz" -T- --transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%')
zur besseren Übersicht aufgeschlüsselt:
(
r=Products; e=stage
(
find -- "$r" -regextype posix-extended -maxdepth 2 \( -regex '^[^/]+(/[^/]+)?' -o ! -type d \) -print0
find -- "$r" -mindepth 1 -path "$r/*/$e/*" -print0
) \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'
)
Zu beachten:
- es zeigt die Syntax der GNU-Tools. Für BSD
find
müssen Sie-regextype posix-extended
durch just ersetzen-E
und für BSDtar
müssen Sie--no-recursion
durch just ersetzen-n
, ebenso wie--transform=s
(<- beachten Sie das letztes
) durch just-s
- der Einfachheit halber wird davon ausgegangen, dass das Snippet aus dem Verzeichnis ausgeführt wird, das enthält
Products
, und verwendet die benutzerdefinierte$e
Variable für den Namen des zu archivierenden Verzeichnisses „environments“, während$r
nur eine Hilfsvariable mit Kurznamen ist, die denProducts
Namen enthält - es ist in Klammern eingeschlossen, was es zu einer Subshell macht, nur um Ihre Shell nicht zu verunreinigen,
$r
und$e
wenn Sie es von der Kommandozeile aus ausführen - es kopiert nicht die Originaldateien und stellt auch keine Verknüpfungen/Referenzen zu ihnen her, es verarbeitet alle gültigen Dateinamen, hat keine Speicherbeschränkungen und kann mit einer beliebigen Anzahl von Namen umgehen; die einzige Annahme betrifft die ersten beiden Ebenen der Verzeichnishierarchie, da alle Verzeichnisse direkt unter der ersten Ebene als „Umgebungsverzeichnis“ betrachtet und somit ignoriert werden (mit Ausnahme des in angegebenen Verzeichnisses
$e
).
Sie können diesen Code-Schnipsel einfach in eine for e in dev prod stage; do ...; done
Shell-Schleife einschließen und loslegen. (Evtl. die äußersten Klammern entfernen und stattdessen die gesamte for
Schleife einschließen.)
Der Vorteil ist, dass es ziemlich kurz und letztendlich relativ einfach ist.
Der Nachteil ist, dass immer auch archiviert wirdalleDieüberschriebenDateien (also die Basisdateien), der Trick besteht lediglich darin, dass die doppelten find
Befehle tar
zuerst die zu überschreibenden Dateien einspeisen und diese daher während der Extraktion von den überschreibenden Dateien (also den „umgebungsspezifischen“) überschrieben werden. Dies führt dazu, dass ein größeres Archiv sowohl bei der Erstellung als auch bei der Extraktion mehr Zeit benötigt, und kann unerwünscht sein, je nachdem, ob dieser „Overhead“ vernachlässigbar ist oder nicht.
Die in Prosa beschriebene Pipeline lautet:
- (außer den äußersten Klammern und den Hilfsvariablen)
- Der erste
find
Befehl erzeugt nur die Liste der nichtspezifischen Dateien (und der führenden Verzeichnisse gemäß Ihrem Update), während der zweitefind
nur die Liste aller umgebungsspezifischen Dateien erzeugt - die beiden
find
Befehle stehen in Klammern, so dass ihre Ausgänge nacheinander die Pipetar
versorgen tar
liest diese Pipe, um die Namen der Dateien zu erhalten, und legt diese Dateien im Archiv ab, während--transform
ihre Namen gleichzeitig mit -ing versehen werden, indem die Komponente „environs“ (falls vorhanden) aus dem Pfadnamen jeder Datei entfernt wird- die beiden
find
Befehle sind getrennt, anstatt nur einer zu sein, und sie werden nacheinander ausgeführt, so dass die nichtspezifischen Dateien (zumtar
Verwenden) vor den umgebungsspezifischen Dateien erstellt werden, was den Trick ermöglicht, den ich zuvor beschrieben habe
Um den Mehraufwand beim Einbinden zu vermeidenimmer alleBei den Dateien benötigen wir zusätzliche Komplexität, um die überschriebenen Dateien wirklich zu löschen. Eine Möglichkeit könnte wie folgt aussehen:
# still a pipeline, but this time I won't even pretend it to be a one-liner
(
r=Products; e=stage; LC_ALL=C
find -- "$r" -regextype posix-extended \( -path "$r/*/$e/*" -o \( -regex '^([^/]+/){2}[^/]+' ! -type d \) -o -regex '^[^/]+(/[^/]+)?' \) -print0 \
| sed -zE '\%^(([^/]+/){2})([^/]+/)%s%%0/\3\1%;t;s%^%1//%' \
| sort -zt/ -k 3 -k 1,1n \
| sort -zut/ -k 3 \
| sed -zE 's%^[01]/(([^/]+/)|/)(([^/]+/?){2})%\3\2%' \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'
)
Einige Dinge sind zu beachten:
- alles, was wir zuvor über GNU- und BSD-Syntaxen gesagt haben, gilt
find
auchtar
hier - wie die vorherige Lösung hat sie keinerlei Einschränkungen außer der Annahme über die ersten beiden Ebenen der Verzeichnishierarchie
- Ich verwende
sed
hier GNU, um mit durch Nullen getrennten E/A-Vorgängen umzugehen (Option-z
), aber Sie können diese beidensed
Befehle problemlos ersetzen durch z. B. einewhile read ...
Shell-Schleife (Bash Version 3 oder höher wäre erforderlich) oder eine andere Sprache, mit der Sie sich sicher fühlen. Die einzige Empfehlung ist, dass das von Ihnen verwendete Tool in der Lage ist, mit durch Nullen getrennten E/A-Vorgängen umzugehen (z. B. können GNUsgawk
dies); siehe unten für einen Ersatz durch Bash-Schleifen - Ich verwende
find
hier nur eine einzige, da ich mich nicht auf implizites Verhalten vontar
- Die
sed
Befehle manipulieren die Liste der Namen und ebnen den Weg für diesort
Befehle - Insbesondere verschiebt die erste
sed
den Namen der „Umgebungen“ an den Anfang des Pfads und stellt ihm außerdem eine Hilfsnummer voran,0
damit er vor den Dateien sortiert wird, die keine Umgebungen sind, da ich diesen letzteren1
zum Zweck der Sortierung ein führendes voranstelle - eine solche Vorbereitung normalisiert die Liste der Namen in den "Augen" der
sort
Befehle, so dass alle Namen ohne den Namen "Umgebungen" sind und alle die gleiche Anzahl von durch Schrägstriche getrennten Feldern am Anfang haben, was fürsort
die Schlüsseldefinitionen von wichtig ist - Die erste
sort
Sortierung basiert zunächst auf den Dateinamen, wobei gleiche Namen nebeneinander angeordnet werden und dann nach dem numerischen Wert0
oder1
wie zuvor durch den Befehl markiertsed
. Dadurch wird sichergestellt, dass jede „umgebungsspezifische“ Datei, sofern vorhanden, vor ihrem nichtspezifischen Gegenstück steht. - Die zweite Option fügt die Dateinamen
sort
zusammen (Option ), so dass nur der erste der doppelten Namen übrig bleibt, der aufgrund der vorherigen Neuordnung immer eine „umgebungsspezifische“ Datei ist, wenn vorhanden-u
- Schließlich macht ein zweiter Schritt
sed
das rückgängig, was der erste getan hat, und formt so die Dateinamen fürtar
das Archiv neu.
Wenn Sie neugierig sind, die Zwischenstücke einer so langen Pipeline zu erkunden, denken Sie daran, dass sie alle mitNull-getrennte Namen und werden daher auf dem Bildschirm nicht gut angezeigt. Sie können jede der Zwischenausgaben (also zumindest das entfernen tar
) an eine Hilfsquelle weiterleiten tr '\0' '\n'
, um eine benutzerfreundliche Ausgabe anzuzeigen. Denken Sie jedoch daran, dass Dateinamen mit Zeilenumbrüchen auf dem Bildschirm zwei Zeilen umfassen.
Es könnten einige Verbesserungen vorgenommen werden, sicherlich indem man es zu einer vollständig parametrisierten Funktion/einem vollständig parametrisierten Skript macht oder beispielsweise jeden beliebigen Namen für „Umgebungs“-Verzeichnisse automatisch erkennt, wie unten:
Wichtig: Achten Sie auf die Kommentare, da diese von einer interaktiven Shell möglicherweise nicht gut angenommen werden
(
export r=Products LC_ALL=C
cd -- "$r/.." || exit
# make arguments out of all directories lying at the second level of the hierarchy
set -- "$r"/*/*/
# then expand all such paths found, take their basenames only, uniquify them, and pass them along xargs down to a Bash pipeline the same as above
printf %s\\0 "${@#*/*/}" \
| sort -zu \
| xargs -0I{} sh -c '
e="${1%/}"
echo --- "$e" ---
find -- "$r" -regextype posix-extended \( -path "$r/*/$e/*" -o \( -regex '\''^([^/]+/){2}[^/]+'\'' ! -type d \) -o -regex '\''^[^/]+(/[^/]+)?'\'' \) -print0 \
| sed -zE '\''\%^(([^/]+/){2})([^/]+/)%s%%0/\3\1%;t;s%^%1//%'\'' \
| sort -zt/ -k 3 -k 1,1n \
| sort -zut/ -k 3 \
| sed -zE '\''s%^[01]/(([^/]+/)|/)(([^/]+/?){2})%\3\2%'\'' \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'\''%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'\''
' packetizer {}
)
Beispielhafter Ersatz für den ersten sed
Befehl durch eine Bash-Schleife:
(IFS=/; while read -ra parts -d $'\0'; do
if [ "${#parts[@]}" -gt 3 ]; then
env="${parts[2]}"; unset parts[2]
printf 0/%s/%s\\0 "$env" "${parts[*]}"
else
printf 1//%s\\0 "${parts[*]}"
fi
done)
Für den zweiten sed
Befehl:
(IFS=/; while read -ra parts -d $'\0'; do
printf %s "${parts[*]:2:2}" "/${parts[1]:+${parts[1]}/}" "${parts[*]:4}"
printf \\0
done)
Beide Snippets erfordern die umgebenden Klammern, um als Drop-In-Ersatz für die jeweiligen sed
Befehle in der obigen Pipeline zu dienen, und natürlich muss der sh -c
Teil danach xargs
in umgewandelt werden bash -c
.
Antwort2
Allgemeine Lösung
- Erstellen Sie eine Kopie des Verzeichnisbaums. Verknüpfen Sie die Dateien mit einem Hardlink, um Platz zu sparen.
- Ändern Sie die Kopie. (Bei Hardlinks müssen Sie wissen, was Sie sicher tun können. Siehe unten.)
- Archivieren Sie die Kopie.
- Entfernen Sie die Kopie.
- Bei Bedarf wiederholen (mit unterschiedlichen Änderungen).
Beispiel
Einschränkungen:
- Dieses Beispiel verwendet Nicht-POSIX-Optionen (getestet unter Debian 10),
- es macht einige Annahmen über den Verzeichnisbaum,
- es kann fehlschlagen, wenn zu viele Dateien vorhanden sind.
Betrachten Sie es als Proof of Concept und passen Sie es an Ihre Bedürfnisse an.
Erstellen einer Kopie
cd
zum übergeordneten Verzeichnis vonProducts
. Dieses VerzeichnisProducts
und alles darin sollte zu einem einzigen Dateisystem gehören. Erstellen Sie ein temporäres Verzeichnis und erstellen SieProducts
dort Folgendes neu:mkdir -p tmp cp -la Products/ tmp/
Ändern der Kopie
Dateien in den beiden Verzeichnisbäumen sind fest verknüpft. Wenn Sie derenInhaltdann ändern Sie die Originaldaten. Operationen, die Informationen in Verzeichnissen ändern, sind sicher, sie ändern die Originaldaten nicht, wenn sie im anderen Baum ausgeführt werden. Diese sind:
- Dateien entfernen,
- Umbenennen von Dateien,
- Verschieben von Dateien (hierzu gehört auch das Verschieben einer Datei über eine andere Datei mit
mv
), - Erstellen völlig unabhängiger Dateien.
dev
Verschieben Sie in Ihrem Fall den Inhalt jedes Verzeichnisses mit der richtigen Tiefe um eine Ebene nach oben:cd tmp/Products dname=dev find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
Anmerkungen:
mv -- * ../
Ist anfällig fürargument list too long
,- stimmt standardmäßig
*
nicht mit Dotfiles überein.
Entfernen Sie dann Verzeichnisse:
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
Beachten Sie, dass hierdurch das jetzt leere
dev
und nicht mehr benötigteprod
,stage
; entfernt wird.Undjedes andere Verzeichnis in dieser Tiefe.Archivieren der Kopie
# still in tmp/Products because of the previous step cd .. tar cvzf "products-$dname.tgz" Products
Entfernen der Kopie
# now in tmp because of the previous step rm -rf Products
Wiederholen
Gehen Sie zurück in das richtige Verzeichnis und beginnen Sie erneut, diesmal mit
dname=stage
; und so weiter.
Beispielskript (schnell und schmutzig)
#!/bin/bash
dir=Products
[ -d "$dir" ] || exit 1
mkdir -p tmp
for dname in dev prod stage; do
(
cp -la "$dir" tmp/
cd "tmp/$dir"
[ "$?" -eq 0 ] || exit 1
find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
cd ..
[ "$?" -eq 0 ] || exit 1
tar cvzf "${dir,,}-$dname.tgz" "$dir"
rm -rf "$dir" || exit 1
) || exit "$?"
done
Antwort3
Ich habe das etwas allgemeiner gemacht und arbeite an nicht-trivialen Dateinamen, ohne die Quellverzeichnisse tatsächlich zu ändern
Products
wird als Argument angegeben. Schlüsselwörter dev prod stage
sind im Skript fest codiert (können aber leicht geändert werden)
Hinweis: Dies ist GNU-spezifisch --transform
und -print0
-z
eine Erweiterung
Führen Sie das Skript aus
./script Products
#!/bin/sh
# environment
subdirs="dev prod stage"
# script requires arguments
[ -n "$1" ] || exit 1
# remove trailing /
while [ ${i:-0} -le $# ]
do
i=$((i+1))
dir="$1"
while [ "${dir#"${dir%?}"}" = "/" ]
do
dir="${dir%/}"
done
set -- "$@" "$dir"
shift
done
# search string
for sub in $subdirs
do
[ -n "$search" ] && search="$search -o -name $sub" || search="( -name $sub"
done
search="$search )"
# GNU specific zero terminated handling for non-trivial directory names
excludes="$excludes $(find -L "$@" -type d $search -print0 | sed -z 's,[^/]*/,*/,g' | sort -z | uniq -z | xargs -0 printf '--exclude=%s\n')"
# for each argument
for dir in "$@"
do
# for each environment
[ -e "$dir" ] || continue
for sub in $subdirs
do
# exclude other subdirs
exclude=$(echo "$excludes" | grep -v "$sub")
# # exclude files that exist in subdir (at least stable against newlines and spaces in file names)
# include=$(echo "$excludes" | grep "$sub" | cut -d= -f2)
# [ -n "$include" ] && files=$(find $include -mindepth 1 -maxdepth 1 -print0 | tr '\n[[:space:]]' '?' | sed -z "s,/$sub/,/," | xargs -0 printf '--exclude=%s\n')
# exclude="$exclude $files"
# create tarball archive
archive="${dir##*/}-${sub}.tgz"
[ -f "$archive" ] && echo "WARNING: '$archive' is overwritten"
tar --transform "s,/$sub$,," --transform "s,/$sub/,/," $exclude -czhf "$archive" "$dir"
done
done
Möglicherweise bemerken Sie Duplikate im Archiv. tar
wird rekursiv Verzeichnisse absteigen, bei der Wiederherstellung werden die tieferen DateienüberschreibenDateien im übergeordneten Verzeichnis
Dies muss jedoch noch weiter auf konsistentes Verhalten getestet werden (da bin ich mir nicht sicher). Der richtige Weg wäre exlude files1.json
+ funktioniert files5.json
leider nicht mit-X
--null
Wenn Sie diesem Verhalten nicht vertrauen oder keine doppelten Dateien in Archiven möchten, können Sie einige Ausschlüsse für einfache Dateinamen hinzufügen.Kommentar entfernender obige Code tar
. Zeilenumbrüche und Leerzeichen sind in Dateinamen zulässig, werden aber mit Platzhaltern ?
im Ausschlussmuster ausgeschlossen, was theoretisch mehr Dateien als erwartet ausschließen könnte (wenn es ähnliche Dateien gibt, die diesem Muster entsprechen).
Sie können ein echo
vor setzen tar
und Sie werden sehen, dass das Skript die folgenden Befehle generiert
tar --transform 's,/dev$,,' --transform 's,/dev/,/,' --exclude=*/*/prod --exclude=*/*/stage -czhf Products-dev.tgz Products
tar --transform 's,/prod$,,' --transform 's,/prod/,/,' --exclude=*/*/dev --exclude=*/*/stage -czhf Products-prod.tgz Products
tar --transform 's,/stage$,,' --transform 's,/stage/,/,' --exclude=*/*/dev --exclude=*/*/prod -czhf Products-stage.tgz Products