Simplemente hacerlo no funciona

Simplemente hacerlo no funciona

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


Ahora, para ver 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 se los paso como argumentos program?

Simplemente hacerlo no funciona

$ ./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 observar, el camino con el espacio se dividió y programconsidera que se trata de dos argumentos diferentes.

Cotiza hasta que funcione

Parece que los usuarios novatos como yo, cuando nos enfrentamos a este tipo de problemas, tendemos a añadir comillas aleatoriamente hasta que finalmente funciona, sólo 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

Como las comillas evitan la división de palabras, todos los archivos se pasan como un único 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 sólo parte de las cuerdas. ¡Así que no sólo no impidieron la división de palabras, sino que además se metieron en discusiones!

Cambiar IFS

Luego intenté jugar con IFS. Preferiría findcon -print0y sortcon -zde todos modos, para que ellos mismos no tengan problemas con 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 en el null.

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


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

¿Qué más podría hacer? ¿Es factible en absoluto?

Claro, podría hacer que programacepte los patrones y realice búsquedas por sí mismo. Pero supone mucho trabajo doble fijarlo en una sintaxis específica. (¿Qué tal si proporcionamos archivos mediante a, greppor ejemplo?).

También podría hacer que programaceptara un archivo con una lista de rutas. Luego puedo volcar fácilmente findla expresión en algún archivo temporal y proporcionar la ruta a ese archivo únicamente. Esto podría realizarse a lo largo de rutas directas, de modo que si el usuario tiene solo una ruta simple, se pueda proporcionar sin un archivo intermedio. Pero esto no parece agradable: es necesario crear archivos adicionales y cuidarlos, sin mencionar la implementación adicional que se requiere. (Sin embargo, en el lado positivo, podría ser un rescate para los casos en los que la cantidad de archivos como argumentos comienza a causar problemas con la longitud de la línea de comando...)


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

$ 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

Entonces, hacer una xargsbúsqueda todavía me deja sin saber cómo lidiar con la otra...

Respuesta1

Utilice matrices.

Si no necesita manejar la posibilidad de nuevas líneas en sus nombres de archivos, 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 ustedhacernecesita manejar nuevas líneas dentro de los nombres de archivos y tener bash >= 4.4, puede usar -print0y -d ''para terminar en nulo los nombres durante la construcción de la matriz:

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

(y lo mismo para el XYZ_FILES). Si ustednotiene el bash más nuevo, entonces podría usar un bucle de lectura terminado en nulo para agregar nombres de archivos 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)

Respuesta2

Puede usar IFS=newline (suponiendo que ningún nombre de archivo contenga una nueva línea) pero debe configurarlo en el shell externo 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 también bashpuedes usar null . $'\0'Incluso bashpodrías manejar una nueva línea si hay un carácter 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 realizó en los comentarios sobre la respuesta de @steeldriver de omitir --afiles si find a está vacío.

Respuesta3

No estoy seguro de entender por qué te diste por vencido xargs.

Entonces, hacer una xargsbúsqueda todavía me deja sin saber cómo lidiar con la otra...

La cadena --xyz-fileses sólo uno de 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

información relacionada