4

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.

  • 1
    not much use to most people, but it looks like map, my personal replacement for xargs works fine: which -a python python3 | map -1 % --version (I don't have pip) – sitaram Nov 25 '20 at 4:38
  • 1
    @sitaram thx! Always on the lookout for improvements on "old age gran-daddy" tools. – user14492 Nov 29 '20 at 20:42
7

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).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.