8

As far as I understand, the asterisk character * has a special meaning in Bash and in order to print the literal character we need to either escape it (\*) or quote it ("*" or '*'.

However, when I have tried the following:

$ seq 3 |  xargs -I *  echo *
1
2
3

Why doesn't Bash interpret * in the example above? But in the following example it did interpret it as a wildcard character (it matches the file in the current directory):

$ seq 3 |  xargs -I*  echo * # no space between -I and *
main.py
main.py
main.py

Could you please explain the logic behind this?

Thank you

19

Bash did expand the star in your first example.

Assuming that the current directory contains only one file, main.py, here is what happened:

$ seq 3 | xargs -I *  echo *
# Expands to:
$ seq 3 | xargs -I main.py echo main.py

This asks to do echo main.py on each input, replacing main.py with that input (because of -I main.py). And this is what you observed: the three templates turn from echo main.py to echo 1, echo 2, echo 3.

In the second case, the star being just next to -I prevented that word from expanding (since no files match the pattern -I*):

$ seq 3 | xargs -I*  echo *
# Expands to:
$ seq 3 | xargs -I* echo main.py

This asks xargs to replace instances of * (a literal star) in the template echo main.py with the input. Since the template does not include the placeholder * at all, it prints main.py each time.

(With failglob enabled in Bash, or in zsh with the default settings, the glob matching nothing would produce an error and the command would not run.)

If your command had protected both occurrences of *, then you would have obtained the same output as in your first attempt:

$ seq 3 | xargs -I '*' echo '*'
1
2
3

If you happened to have more than one file in the directory, then the unquoted * might create problems more visibly. E.g. if there the two files main.py and other.py, then:

$ seq 3 | xargs -I *  echo *
# Expands to:
$ seq 3 | xargs -I main.py other.py echo main.py other.py

and xargs will try to run other.py echo main.py other.py as the command (with main.py replaced by the current input string). That will likely give a "command not found" error from xargs. Or, if the second filename in sort order would match a runnable program in PATH, then xargs would run that program, and not echo.


A useful trick to get some amount of visibility into which Bash expansions happen is to run set -o xtrace. With the flag set, you can see the expansions on lines starting with +:

$ set -o xtrace
$ seq 3 | xargs -I * echo *
+ seq 3
+ xargs -I main.py echo main.py
1
2
3

$ seq 3 | xargs -I* echo *
+ seq 3
+ xargs '-I*' echo main.py
main.py
main.py
main.py

$ seq 3 | xargs -I '*' echo '*'
+ seq 3
+ xargs -I '*' echo '*'
1
2
3
2
  • You may also ask xargs to show the commands it's executing, using its -t option.
    – Kusalananda
    Sep 22 at 17:35
  • @Kusalananda: Yes, but in this case it will not tell what's going on. Instead set -x will show that. Sep 22 at 17:41

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.