Search code examples
shellsubstitutionfish

Fish command substitution for arguments -- eval safe?


In fish shell, I want to be able to expand a command substitution or variable as multiple arguments to another command, e.g.:

Without substitution

ls -l -h

Should give the same result as:

ls $(echo '-l -h')

Instead the second one gives an error:

ls: invalid option -- ' '

If I instead do:

eval ls $(echo '-l -h')

It does what I want, it seems.

2 questions then:

  1. Is using eval in this way actually going to do what I want consistently?
  2. Is there some other way I should be doing this instead?

I read this question and answer, which is very similar to mine: Command substitution in fish

But the answer is not applicable in my situation.

The real world use case I have is following the instructions for gstreamer: https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#

Where it says to build the tutorials with:

gcc basic-tutorial-1.c -o basic-tutorial-1 `pkg-config --cflags --libs gstreamer-1.0`

Which, of course, does not work in fish (because backticks). But when converted to $() syntax, it errors with:

gcc: error: unrecognized command-line option ‘-pthread -I/usr/include/gstreamer-1.0 -I/ ....

Because it has interpreted the output of the $() block as a single string -- which makes sense and seems sensible, except that it is blocking this very reasonable (imo) use case, and I'd like to be able to do this comfortably in fish.

Thanks!


Solution

  • This is explicitly called out in the FAQ section of fish's documentation, with pkg-config as an example:

    Unlike other shells, fish splits command substitutions only on newlines, not spaces or tabs or the characters in $IFS.

    However sometimes, especially with pkg-config and related tools, splitting on spaces is needed.

    In these cases use string split -n " " like:

    g++ example_01.cpp (pkg-config --cflags --libs gtk+-2.0 | string split -n " ")
    

    So your command becomes:

    gcc basic-tutorial-1.c -o basic-tutorial-1 $(pkg-config --cflags --libs gstreamer-1.0 | string split -n " ")
    

    Is using eval in this way actually going to do what I want consistently?

    Definitely not. If your command prints something that looks like any sort of expansion it will be executed.

    So if it printed e.g. a * or a $ or a () it would run the actual expansion. In your particular case it is not impossible for the path to contain any of these characters.