
Primero, para eliminar respuestas triviales pero inaplicables: no puedo usar el truco find
+ xargs
ni sus variantes (como find
con -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 program
obtiene 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 program
considera 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 find
con -print0
y sort
con -z
de 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 null
personaje 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 IFS
tarea tanto en $(…)
(como se muestra arriba) como antes ./program
. También probé otra sintaxis como \0
, \x0
, \x00
ambas 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 program
acepte 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, grep
por ejemplo?).
También podría hacer que program
aceptara un archivo con una lista de rutas. Luego puedo volcar fácilmente find
la 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 xargs
bú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 -print0
y -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 zsh
pero no también bash
puedes usar null . $'\0'
Incluso bash
podrí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
xargs
búsqueda todavía me deja sin saber cómo lidiar con la otra...
La cadena --xyz-files
es 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 xargs
entre ambos find
resultados:
{ 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