
Ich möchte rsync
einige mit einem Array angegebene Dateien übergeben und alle anderen Dateien in einem Verzeichnis löschen.
Der einzige Ansatz, der mir einfällt, besteht darin, andere Dateien mithilfe von find
und rsync
über die Dateien zu entfernen, sodass wir so wenig Dateien wie möglich kopieren.
Im folgenden Beispiel möchte ich alle anderen Dateien in löschen /tmp/tmp/
, außer btrfs_x64.efi
und iso9660_x64.efi
.
$ refind_efi_dir='/tmp/tmp/'
$ drivers=('btrfs_x64.efi' 'iso9660_x64.efi')
$ find ${refind_efi_dir}drivers_x64/ "${drivers[@]/#/! -name }" -type f -exec rm -f {} +
Ich möchte, dass die Erweiterung auf den folgenden Befehl erweitert wird:
$ find /tmp/tmp/drivers_x64/ ! -name btrfs_x64.efi ! -name iso9660_x64.efi -type f -exec rm -f {} +
Stattdessen scheint es den folgenden Befehl auszuführen:
$ find /tmp/tmp/drivers_x64/ "! -name btrfs_x64.efi" "! -name iso9660_x64.efi" -type f -exec rm -f {} +
Gibt es eine Möglichkeit, Ersteres zu erreichen? Im Idealfall funktioniert es auch, wenn einige Array-Einträge Leerzeichen enthalten.
Antwort1
find
Ja, hier müssen Sie für jedes Element Ihres Arrays 3 Argumente generieren . Außerdem nimmt find
's -name
ein Muster an. Damit es also mit exakten Dateinamen übereinstimmt, müssen Sie die find
Platzhalteroperatoren ( *
, ?
, [
und \
) maskieren:
set -o extendedglob # for (#m)
exclusions=()
for name ($drivers) exclusions+=(! -name ${name//(#m)[?*[\\]/\\$MATCH})
find ${refind_efi_dir}drivers_x64/ $exclusions -type f -exec rm -f {} +
"${array[@]/pattern/replacement}"
wird auf so viele Elemente erweitert, wie sich im Array befinden, nachdem für jedes davon eine Ersetzung durchgeführt wurde.
Hier, da -name
dauert eine DateiNameMuster, es sollte nicht enthalten /
, daher könnten Sie jedes Element durch ersetzen !/-name/element
und dann in aufteilen /
:
set -o extendedglob # for (#m)
find ${refind_efi_dir}drivers_x64/ \
${(@s[/])${drivers//(#m)[?*[\\]/\\$MATCH}/#/!\/-name\/} \
-type f -exec rm -f {} +
Oder verwenden Sie $'\0'
anstelle von /
, da es ohnehin nicht als Argument an einen externen Befehl übergeben werden kann:
set -o extendedglob # for (#m)
find ${refind_efi_dir}drivers_x64/ \
${(@0)${drivers//(#m)[?*[\\]/\\$MATCH}/#/!$'\0'-name$'\0'} \
-type f -exec rm -f {} +
Aber das trägt nicht viel zur Lesbarkeit bei...
Hier können Sie auch zsh
den Glob von für alles verwenden:
(cd -P -- $refind_efi_dir && rm -f -- **/^(${(~j[|])drivers})(D.))
Wobei das j[|]
Parametererweiterungsflag die Elemente des $drivers
Arrays mit verbindet |
und ~
bewirkt, dass dies |
als Glob-Operator behandelt wird. Dieses Muster wird mit negiert ^
(wofür Sie die extendedglob
Option benötigen). D
um versteckte Dateien einzuschließen, .
um auf normale Dateien wie Ihre zu beschränken -type f
.
Antwort2
"${drivers[@]/#/! -name }"
fügt ! -name
dasselbe Shell-Wort wie jedes Array-Element ein. "${=drivers[@]/#/! -name }"
würde die Ergebnisse aufteilen, sodass !
und -name
als separate Shell-Wörter enden würden, die Array-Elemente jedoch auch an Leerzeichen getrennt würden.
Eine Lösung besteht darin, die e
und zu missbrauchenP
Glob-Qualifikation:
find … /(e\''reply=($drivers)'\'P\''!'\'P\''-name'\') …
/(…)
erweitert das Glob-Muster /
, das immer genau auf eine Sache passt (das Stammverzeichnis), und wendet die Glob-Qualifizierer darauf an. Der Glob-Qualifizierer e
ersetzt jede Übereinstimmung durch den neuen Wert der Array-Variable reply
, die wir auf die Liste der Wörter setzen, auf die wir die nachfolgenden Glob-Qualifizierer anwenden möchten. Dann fügt der Glob-Qualifizierer P
, zweimal verwendet, den angegebenen Text als separate Wörter vor jeder Übereinstimmung ein.
In diesem konkreten Fallwie Stéphane Chazelas zeigte, Sie können einfach alles mit zsh-Globs machen, ohne find zu verwenden.