
Я хотел бы 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 {} +
Но это не сильно улучшает читаемость...
Здесь вы также можете использовать zsh
glob для всего:
(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.