¿Cómo pasar archivos encontrados por find como argumentos?

9

Primero para cortar las respuestas triviales pero inaplicables: no puedo usar ni el truco find+ xargsni sus variantes (como findcon -exec) porque necesito usar pocas expresiones de este tipo por llamada. Volveré a esto al final.


Ahora, para un mejor ejemplo, consideremos:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

¿Cómo paso esos argumentos a program?

Solo hacerlo no hace el truco

$ ./program $(find -L some/dir -name \*.abc | sort)

falla ya que programobtiene los siguientes argumentos:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

Como se puede ver, la ruta con el espacio se dividió y programconsidera que son dos argumentos diferentes.

Cotizar hasta que funcione

Parece que los usuarios novatos como yo, cuando se enfrentan a tales problemas, tienden a agregar citas al azar hasta que finalmente funciona, solo que aquí no parece ayudar ...

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Debido a que las comillas evitan la división de palabras, todos los archivos se pasan como un solo argumento.

Citando caminos individuales

Un enfoque prometedor:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

Las citas están ahí, claro. Pero ya no se interpretan. Son solo parte de las cuerdas. ¡Entonces no solo no impidieron la división de palabras, sino que también entraron en discusiones!

Cambiar IFS

Entonces traté de jugar con ellos IFS. Preferiría findcon -print0y sortcon de -ztodos modos, para que no tengan problemas en las "rutas cableadas". Entonces, ¿por qué no forzar la división de palabras en el nullpersonaje y tenerlo todo?

$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc

Por lo tanto, todavía se divide en el espacio y no se divide en el null.

Traté de colocar la IFStarea tanto en $(…)(como se muestra arriba) como antes ./program. También probé otra sintaxis como \0, \x0, \x00tanto citado con 'y "así como con y sin el $. Ninguno de esos parecía hacer ninguna diferencia ...


Y aquí estoy sin ideas. Intenté algunas cosas más, pero todas parecían tener los mismos problemas enumerados.

¿Qué más puedo hacer? ¿Es factible en absoluto?

Claro, podría hacer que programacepte los patrones y hacer búsquedas por sí mismo. Pero es mucho trabajo doble mientras lo arregla a una sintaxis específica. (¿Qué hay de proporcionar archivos por un greppor ejemplo?).

También podría hacer que programacepte un archivo con una lista de rutas. Entonces puedo volcar fácilmente la findexpresión en algún archivo temporal y proporcionar la ruta solo a ese archivo. Esto podría admitirse a lo largo de rutas directas, de modo que si el usuario solo tiene una ruta simple, se puede proporcionar sin archivo intermedio. Pero esto no parece agradable: uno necesita crear archivos adicionales y cuidarlos, sin mencionar la implementación adicional requerida. (En el lado positivo, sin embargo, podría ser un rescate para los casos en que la cantidad de archivos como argumentos comienzan a causar problemas con la longitud de la línea de comandos ...)


Al final, déjame recordarte nuevamente que los trucos find+ xargs(y similares) no funcionarán en mi caso. Para simplificar la descripción, solo estoy mostrando un argumento. Pero mi verdadero caso se parece más a esto:

$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES

Así que hacer una xargsbúsqueda de uno todavía me deja con la forma de lidiar con el otro ...

Adam Badura
fuente

Respuestas:

13

Usa matrices.

Si no necesita manejar la posibilidad de nuevas líneas en sus nombres de archivo, entonces podría salirse con la suya

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

entonces

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

Si hacer necesidad de nuevas líneas de mango dentro de los nombres de archivo, y tienen fiesta> = 4.4, puede utilizar -print0y -d ''con nulo terminar los nombres durante la construcción de matriz:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(y de manera similar para el XYZ_FILES). Si no tiene el bash más nuevo, entonces podría usar un ciclo de lectura terminado en nulo para agregar nombres de archivo a las matrices, por ejemplo

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
conductor de acero
fuente
¡Excelente! Estaba pensando en las matrices. Pero de alguna manera no encontré nada sobre eso mapfile(o su sinónimo readarray). ¡Pero funciona!
Adam Badura
Sin embargo, podrías mejorarlo un poco. La versión Bash <4.4 (que resulta que tengo ...) con un whilebucle no borra la matriz. Lo que significa que si no se encuentran archivos, la matriz no está definida. Mientras que si ya está definido, se agregarán nuevos archivos (en lugar de reemplazar los antiguos). Parece que agregar declare -a ABC_FILES='()';antes whilehace el truco. (Aunque solo agrega ABC_FILES='()';no.)
Adam Badura
Además, ¿qué < <significa aquí? ¿Es lo mismo que <<? No creo que cambiarlo para <<producir un error de sintaxis ("token inesperado` ('"). Entonces, ¿qué es y cómo funciona?
Adam Badura
Otra mejora (a lo largo de mi uso particular) es construir otra matriz más. Entonces tenemos esos ABC_FILES. Eso está bien. Pero también es útil hacer ABS_ARGScuál es una matriz vacía si ABC_FILESestá vacía o si no es una matriz ('--abc-files' "${ABC_FILES[@]}"). De esta manera más adelante, puedo usarlo así: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"y asegurarme de que funcionará correctamente, independientemente de cuál (si alguno) de los grupos está vacío. O para decirlo de manera diferente: de esta manera --abc-files(y --xyz-files) se proporcionará solo si es seguido por alguna ruta real.
Adam Badura
1
@AdamBadura: while read ... done < <(find blah)es la redirección de shell normal <desde un archivo especial creado por SUSTITUCIÓN DE PROCESO . Esto difiere de la tubería find blah | while read ... doneporque la tubería ejecuta el whilebucle en una subshell, por lo que las var (s) establecidas en ella no se retienen para los comandos posteriores.
dave_thompson_085
3

Puede usar IFS = nueva línea (suponiendo que ningún nombre de archivo contenga nueva línea) pero debe establecerlo en la capa externa ANTES de la sustitución:

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

Con zshpero no bashpuedes usar nulo $'\0'también. Incluso en bashusted podría manejar la nueva línea si hay un personaje suficientemente extraño que nunca se usa como

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

Sin embargo, este enfoque no maneja la solicitud adicional que hizo en los comentarios sobre la respuesta de @ steeldriver para omitir los --afiles si find a está vacío.

dave_thompson_085
fuente
Entonces, como entiendo en Bash, ¿no hay forma de forzar la IFSdivisión null?
Adam Badura
@ AdamBadura: Estoy bastante seguro de que no; bash no permite byte nulo en ninguna variable, incluido IFS. Tenga read -d ''en cuenta que el método utilizado en steeldriver es una cadena vacía que no contiene un byte nulo. (Y una opción de comando no es una var como tal de todos modos.)
dave_thompson_085
También debe deshabilitar globbing ( set -o noglob) antes de usar ese operador split + glob (excepto en zsh).
Stéphane Chazelas
@AdamBadura Sí, en bash, un valor nulo es exactamente igual $'\0'y también como ''.
Isaac
1

No estoy seguro de entender por qué renunciaste xargs.

Así que hacer una xargsbúsqueda de uno todavía me deja con la forma de lidiar con el otro ...

La cadena --xyz-fileses solo uno de los muchos argumentos y no hay razón para considerarla especial antes de que su programa la interprete. Creo que puedes pasarlo xargsentre ambos findresultados:

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files
Kamil Maciorowski
fuente
¡Tienes razón! ¡Esto también funciona! Sin embargo, tenga -print0en cuenta que se perdió en el segundo find. Además, si voy de esta manera, también lo pondría --abc-filescomo echosolo, solo por coherencia.
Adam Badura
Este enfoque parece más simple y algo más de una línea que el enfoque de matriz. Sin embargo, requeriría algo de lógica adicional para cubrir el caso de que si no hay .abcarchivos, entonces tampoco debería haber --abc-files(lo mismo con .xyz). La solución basada en matriz de steeldriver también requiere lógica adicional para ella, pero esa lógica es trivial allí, mientras que podría no ser tan trivial aquí, destruyendo la principal ventaja de esta solución: la simplicidad.
Adam Badura
Además, no estoy muy seguro, pero supongo que xargsnunca va a tratar de dividir argumentos y hacer algunos comandos en lugar de uno, a menos que se explícitamente instruido para hacerlo con -L, --max-lines( -l), --max-args( -n) o --max-chars( -s) argumentos. Estoy en lo cierto? ¿O hay algunos valores predeterminados? Como mi programa no manejaría tal división correctamente y preferiría no llamarlo ...
Adam Badura
1
@AdamBadura Missing -print0- arreglado, gracias. No sé todas las respuestas, pero estoy de acuerdo con que mi solución hace que sea difícil incluir una lógica adicional. Probablemente iría con matrices, ahora que conozco este enfoque. Mi respuesta no fue realmente para ti. Ya había aceptado la otra respuesta y asumí que su problema está resuelto. Solo quería señalar que puede pasar argumentos de múltiples fuentes xargs, lo que no era obvio a primera vista. Puede tratarlo como una prueba de concepto. Ahora todos conocemos algunos enfoques diferentes y podemos elegir conscientemente lo que nos conviene en cada caso particular.
Kamil Maciorowski
Sí, ya implementé la solución basada en matriz y funciona a la perfección. Estoy especialmente orgulloso de cuán limpiamente trata la opcionalidad (si no hay archivos, entonces no --abc-files). Pero tiene razón: ¡es bueno conocer sus alternativas! Especialmente que erróneamente pensé que no es posible.
Adam Badura