Zsh usa una matriz en un comando de búsqueda

Zsh usa una matriz en un comando de búsqueda

Me gustaría rsyncrevisar algunos archivos, especificados con una matriz, y eliminar cualquier otro archivo en un directorio.

El único enfoque que se me ocurre es eliminar otros archivos usando findy rsyncsobre los archivos, de modo que copiemos la menor cantidad de archivos posible.

En el siguiente ejemplo, quiero eliminar cualquier otro archivo en /tmp/tmp/, excepto btrfs_x64.efiy 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 {} +

Quiero que la expansión se expanda al siguiente comando:

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

Pero en lugar de eso parece estar ejecutando el siguiente comando:

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

¿Hay alguna manera de conseguir lo primero? Idealmente, también funciona si algunas entradas de la matriz tienen espacios.

Respuesta1

Sí, aquí necesitas generar 3 argumentos findpara cada elemento de tu matriz. También findtoma un patrón, por lo que -namepara que coincida con los nombres de archivo exactos, deberá omitir los findoperadores comodín ( *, ?y ):[\

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 a tantos elementos como haya en la matriz, después de realizar la sustitución en cada uno de ellos.

Aquí, dado que -namelleva un archivo.nombrepatrón, no debe contener /, por lo que puede reemplazar cada elemento !/-name/elementy luego dividirlo en /:

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

O utilícelo $'\0'en lugar de, /ya que de todos modos no se puede pasar como argumento a un comando externo:

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

Pero eso no ayuda mucho con la legibilidad...

Aquí también puedes usar zsh's glob para todo:

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

Donde el j[|]indicador de expansión de parámetros une los elementos de la $driversmatriz |y ~hace que |se trate como un operador global. Ese patrón se niega con ^(para lo cual necesita la extendedglobopción). Dpara incluir archivos ocultos, .para restringir a archivos normales como su -type f.

Respuesta2

"${drivers[@]/#/! -name }"coloca ! -namela misma palabra de shell que cada elemento de la matriz. "${=drivers[@]/#/! -name }"dividiría los resultados, por lo que !y -nameterminaría como palabras de shell separadas, pero los elementos de la matriz también se dividirían en espacios en blanco.

Una solución es abusar del eyP clasificatorios globales:

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

/(…)expande el patrón global /, que siempre coincide exactamente con una cosa (el directorio raíz), y le aplica los calificadores globales. El calificador global ereemplaza cada coincidencia por el nuevo valor de la variable de matriz reply, que configuramos en la lista de palabras a las que queremos aplicar los calificadores globales posteriores. Luego, el calificador global P, usado dos veces, inserta el texto especificado como palabras separadas antes de cada coincidencia.

En este caso específico,como demostró Stéphane Chazelas, simplemente puedes hacer todo con zsh globs, sin usar find.

información relacionada