Zsh usa um array em um comando find

Zsh usa um array em um comando find

Gostaria de rsyncexaminar alguns arquivos, especificados com uma matriz, e excluir qualquer outro arquivo em um diretório.

A única abordagem que consigo pensar é remover outros arquivos usando finde rsyncsobre os arquivos, para copiar o mínimo de arquivos possível.

No exemplo a seguir, desejo excluir qualquer outro arquivo /tmp/tmp/, exceto btrfs_x64.efie 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 {} +

Quero que a expansão se expanda para o seguinte comando:

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

Mas em vez disso parece estar executando o seguinte comando:

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

Existe uma maneira de obter o primeiro? Idealmente, também funciona se algumas entradas da matriz contiverem espaços.

Responder1

Sim, aqui você precisa gerar 3 argumentos para findcada elemento do seu array. Também findusa -nameum padrão, portanto, para corresponder aos nomes exatos dos arquivos, você precisa escapar dos findoperadores curinga ( *, ?, [e \):

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}"se expande para quantos elementos houver no array, após a substituição realizada em cada um deles.

Aqui, dado que -nameleva um arquivonomepadrão, ele não deve conter /, então você pode substituir cada elemento por !/-name/elemente depois dividir /:

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

Ou use $'\0'em vez de /, pois não pode ser passado em um argumento para um comando externo de qualquer maneira:

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

Mas isso não ajuda muito na legibilidade ...

Aqui, você também pode usar zsho glob para tudo:

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

Onde o j[|]sinalizador de expansão do parâmetro une os elementos do $driversarray |e ~faz com que ele |seja tratado como um operador glob. Esse padrão é negado com ^(para o qual você precisa da extendedglobopção). Dpara incluir arquivos ocultos, .para restringir arquivos regulares como o seu -type f.

Responder2

"${drivers[@]/#/! -name }"coloca ! -namea mesma palavra shell que cada elemento da matriz. "${=drivers[@]/#/! -name }"dividiria os resultados !e -nameterminaria como palavras de shell separadas, mas os elementos da matriz também seriam divididos em espaços em branco.

Uma solução é abusar do eeP eliminatórias globais:

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

/(…)expande o padrão glob /, que sempre corresponde exatamente a uma coisa (o diretório raiz) e aplica os qualificadores glob a ele. O qualificador glob esubstitui cada correspondência pelo novo valor da variável de matriz reply, que definimos para a lista de palavras às quais queremos aplicar os qualificadores glob subsequentes. Em seguida, o qualificador glob P, usado duas vezes, insere o texto especificado como palavras separadas antes de cada correspondência.

Neste caso específico,como Stéphane Chazelas mostrou, você pode simplesmente fazer tudo com zsh globs, sem usar find.

informação relacionada