Zsh использует массив в команде find

Zsh использует массив в команде find

Я хотел бы rsyncперебрать несколько файлов, указанных с помощью массива, и удалить все остальные файлы в каталоге.

Единственный подход, который я могу придумать, — это удалить другие файлы с помощью find, и rsyncповерх файлов, чтобы скопировать как можно меньше файлов.

В следующем примере я хочу удалить все остальные файлы в /tmp/tmp/, за исключением btrfs_x64.efiи 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 {} +

Я хочу, чтобы расширение было расширено до следующей команды:

$ find /tmp/tmp/drivers_x64/ ! -name btrfs_x64.efi ! -name iso9660_x64.efi -type f -exec rm -f {} +

Но вместо этого, похоже, выполняется следующая команда:

$ find /tmp/tmp/drivers_x64/ "! -name btrfs_x64.efi" "! -name iso9660_x64.efi" -type f -exec rm -f {} +

Есть ли способ получить первое? В идеале это также работает, если в некоторых записях массива есть пробелы.

решение1

Да, здесь вам нужно сгенерировать 3 аргумента для findкаждого элемента вашего массива. Также find's -nameпринимает шаблон, поэтому для того, чтобы он соответствовал точным именам файлов, вам нужно экранировать findподстановочные операторы ( *, ?, [и \):

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}"расширяется до стольких элементов, сколько их в массиве, после подстановки, выполненной над каждым из них.

Здесь, учитывая, что -nameберет файлимяшаблон, он не должен содержать /, поэтому вы можете заменить каждый элемент на !/-name/element, а затем разделить на /:

set -o extendedglob # for (#m)
find ${refind_efi_dir}drivers_x64/ \
  ${(@s[/])${drivers//(#m)[?*[\\]/\\$MATCH}/#/!\/-name\/} \
  -type f -exec rm -f {} +

Или используйте $'\0'вместо /, поскольку его все равно нельзя передать в качестве аргумента внешней команде:

set -o extendedglob # for (#m)
find ${refind_efi_dir}drivers_x64/ \
  ${(@0)${drivers//(#m)[?*[\\]/\\$MATCH}/#/!$'\0'-name$'\0'} \
  -type f -exec rm -f {} +

Но это не сильно улучшает читаемость...

Здесь вы также можете использовать zshglob для всего:

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

Где j[|]флаг расширения параметра объединяет элементы массива $driversс |и ~заставляет его |обрабатываться как оператор glob. Этот шаблон инвертируется с помощью ^(для чего вам нужна опция extendedglob). Dдля включения скрытых файлов, .для ограничения обычными файлами, такими как ваш -type f.

решение2

"${drivers[@]/#/! -name }"помещает ! -nameто же самое слово оболочки, что и каждый элемент массива. "${=drivers[@]/#/! -name }"разделит результаты, поэтому !и -nameбудут в итоге отдельными словами оболочки, но элементы массива также будут разделены пробелами.

Одним из решений является злоупотребление eиP квалификаторы glob:

find … /(e\''reply=($drivers)'\'P\''!'\'P\''-name'\') …

/(…)расширяет шаблон glob /, который всегда соответствует только одному объекту (корневому каталогу), и применяет к нему квалификаторы glob. Квалификатор glob eзаменяет каждое совпадение новым значением переменной массива reply, которую мы устанавливаем в список слов, к которым мы хотим применить последующие квалификаторы glob. Затем квалификатор glob P, используемый дважды, вставляет указанный текст в виде отдельных слов перед каждым совпадением.

В этом конкретном случае,как показал Стефан Шазелас, вы можете просто делать все с помощью zsh-глобалов, не используя find.

Связанный контент