Shell Xargs Fish – Use Input as Command with Xargs

fishshellxargs

I would have assumed that following examples work perfectly fine.

$ which -a python python3 pip | xargs -I {} {} --version
No '{}' such file or folder
$ which -a python python3 pip | xargs -I _ _ --version
No '_' such file or folder
$ which -a python python3 pip | xargs -I py py --version
No 'py' such file or folder

But they don't work at all when I run it interactively it doesn't even substitute the string. I see no note in manual page regarding a special case in first position. Why does this not work?

$ which -a python python3 pip | xargs -I py -p eval py --version
eval ~/.pyenv/shims/python --version?...y
xargs: eval: No such file or directory

This is even more surprising because it does substitute correctly.

How can I use the argument in first place? I don't want to use ... -I py sh -c "py --version" because this will create a new sub-process. I wish to know how to eval command in current env.

Best Answer

In xargs -I replstr utility arguments, POSIX requires that the replstr be only substituted in the arguments, not utility. GNU xargs is compliant in that regard (busybox xargs is not).

To work around it, you can use env as the utility:

which -a ... | xargs -I cmd xargs env cmd --version

(chokes on leading whitespace, backslash, single quote, double quote, newline, possibly sequences of bytes nor forming valid characters, because of the way xargs interprets its input).

Or better:

for cmd in (which -a ...)
  $cmd --version
end

Which would limit problematic characters in file names to newline only.

In any case, you can't and don't want to use eval here. eval is the shell builtin (special builtin in POSIX shells) command to interpret shell code, not to run commands. xargs is an external command to the shell, so it cannot run builtins of your shell or of any shell without starting an interpreter of that shell like with:

which -a ... |
  xargs -rd '\n' sh -c 'for cmd do "$cmd" --version; done' sh

Using sh instead of fish here as AFAIK fish inline scripts can't take arguments. But still not using eval here which wouldn't make sense as we don't want those file names to be interpreted as shell code.

Also using -rd '\n' which is GNU-specific, but doesn't have all the issues of -I, to pass the full contents of all lines as separate arguments to the utility (here sh).

Related Question