First to cut off trivial but inapplicable answers: I can use neither the find
+xargs
trick nor its variants (like find
with -exec
) because I need to use few such expressions per call. I will get back to this at the end.
Now for a better example let's consider:
$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
How do I pass those as arguments to program
?
Just doing it doesn't do the trick
$ ./program $(find -L some/dir -name \*.abc | sort)
fails since program
gets following arguments:
[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc
As can be seen, the path with space was split and program
considers it to be two different arguments.
Quote until it works
It seems novice users such as myself, when faced with such problems, tend to randomly add quotes until it finally works - only here it doesn't seem to help…
"$(…)"
$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
Because the quotes prevent word-splitting, all the files are passed as a single argument.
Quoting individual paths
A promising approach:
$ ./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"
The quotes are there, sure. But they are no longer interpreted. They are just part of the strings. So not only they did not prevent word splitting, but also they got into arguments!
Change IFS
Then I tried playing around with IFS
. I would prefer find
with -print0
and sort
with -z
anyway - so that they will have no issues on "wired paths" themselves. So why not force word splitting on the null
character and have it all?
$ ./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
So it still splits on space and does not split on the null
.
I tried to place the IFS
assignment both in $(…)
(as shown above) and before ./program
. Also I tried other syntax like \0
, \x0
, \x00
both quoted with '
and "
as well as with and without the $
. None of those seemed to make any difference…
And here I'm out of ideas. I tried few more things but all seemed to run down to the same problems as listed.
What else could I do? Is it doable at all?
Sure, I could make the program
accept the patterns and do searches itself. But it is a lot of double work while fixing it to a specific syntax. (What about providing files by a grep
for example?).
Also I could make the program
accept a file with a list of paths. Then I can easily dump find
expression to some temp file and provide the path to that file only. This could supported be along direct paths so that if user has just a simple path it can be provided without intermediate file. But this doesn't seem nice - one needs to create extra files and take care of them, not to mention extra implementation required. (On the plus side, however, it could be a rescue for cases in which the number of files as arguments start to cause issues with command line length…)
At the end, let me remind you again that find
+xargs
(and alike) tricks will not work in my case. For description simplicity I'm showing only one argument. But my true case looks more like this:
$ 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
So doing an xargs
from one search still leaves me with how to deal with the other one…
mapfile
(or its synonymreadarray
). But it does work! – Adam Badura Oct 24 '16 at 05:22while
loop doesn't clear the array. Which means that if no files are found the array is undefined. While if it is already defined new files will be appended (instead of replacing the old ones). It seems that addingdeclare -a ABC_FILES='()';
beforewhile
does the trick. (While just addingABC_FILES='()';
does not.) – Adam Badura Oct 24 '16 at 05:27< <
mean here? Is it the same as<<
? I don't think so as changing it to<<
yields syntax error ("unexpected token `('"). So what it is and how does it work? – Adam Badura Oct 24 '16 at 05:39ABC_FILES
. That is fine. But it is useful to also makeABS_ARGS
which is an empty array ifABC_FILES
is empty or else it is an array('--abc-files' "${ABC_FILES[@]}")
. This way later on I can use it like this:./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"
and be sure that it will work correctly regardless of which (if any) of the groups is empty. Or to state it differently: this way--abc-files
(and--xyz-files
) will be provided only if it is followed by some actual path. – Adam Badura Oct 24 '16 at 05:57while read ... done < <(find blah)
is normal shell redirection<
from a special file created by PROCESS SUBSTITUTION. This differs from pipingfind blah | while read ... done
because the pipeline runs thewhile
loop in a subshell so the var(s) set in it aren't retained for subsequent commands. – dave_thompson_085 Oct 24 '16 at 06:40<(find ...)
expression failed or not? In some scenarios it will by my own program and if it exists with non-0
status I would rather interrupt the whole procedure with an error message. While it seems to me that with this approach I will end up with an empty array (as my program doesn't generate any output in such cases). – Adam Badura Oct 24 '16 at 07:06