Zsh verwendet ein Array in einem Suchbefehl

Zsh verwendet ein Array in einem Suchbefehl

Ich möchte rsynceinige 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 findund 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.efiund 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

findJa, hier müssen Sie für jedes Element Ihres Arrays 3 Argumente generieren . Außerdem nimmt find's -nameein Muster an. Damit es also mit exakten Dateinamen übereinstimmt, müssen Sie die findPlatzhalteroperatoren ( *, ?, [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 -namedauert eine DateiNameMuster, es sollte nicht enthalten /, daher könnten Sie jedes Element durch ersetzen !/-name/elementund 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 zshden Glob von für alles verwenden:

(cd -P -- $refind_efi_dir && rm -f -- **/^(${(~j[|])drivers})(D.))

Wobei das j[|]Parametererweiterungsflag die Elemente des $driversArrays mit verbindet |und ~bewirkt, dass dies |als Glob-Operator behandelt wird. Dieses Muster wird mit negiert ^(wofür Sie die extendedglobOption benötigen). Dum versteckte Dateien einzuschließen, .um auf normale Dateien wie Ihre zu beschränken -type f.

Antwort2

"${drivers[@]/#/! -name }"fügt ! -namedasselbe Shell-Wort wie jedes Array-Element ein. "${=drivers[@]/#/! -name }"würde die Ergebnisse aufteilen, sodass !und -nameals separate Shell-Wörter enden würden, die Array-Elemente jedoch auch an Leerzeichen getrennt würden.

Eine Lösung besteht darin, die eund 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 eersetzt 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.

verwandte Informationen