Search code examples
shellfish

fish shell: filter $argv (lightweight / dynamic argparse)


I would like to split $argv into two variables. One with opts and one with everything else.

function filter_opts
    set -l filtered_opts
    set -l new_argv

    for opt in $argv
        if string match -r -- '-.*' $opt
            set -a filtered_opts $opt
        else
            set -a new_argv $opt
        end
    end

    set -g argv $new_argv
    echo $filtered_opts
end

function test_filter 
    set -g argv $argv
    echo $argv
    set -l opts (filter_opts $argv)
    echo $opts
    echo $argv

    # prog1 $opts $argv
    # prog2 $argv
end

But the output has the filtered opts duplicated and $argv isn't modified... :-(

$ test_filter Twilight Zone --test -t
Twilight Zone --test -t
--test -t --test -t
Twilight Zone --test -t

Ideally the output would look like this:

$ test_filter Twilight Zone --test -t
Twilight Zone --test -t
--test -t
Twilight Zone

Solution

  • Two improvements:

    1. It's not necessary to loop over $argv, you can just pass multiple arguments to string filter.
    2. A function by default has its own scope, but you can have it modify its caller's scope via --no-scope-shadowing (see function docs)

    Putting those ideas together:

    function filter_opts --no-scope-shadowing
        set options (string match  -- '-*' $argv)
        set arguments (string match --invert -- '-*' $argv)
    end
    
    function test_filter 
        filter_opts $argv
        echo "Options: $options"
        echo "Arguments: $arguments"
    end
    
    test_filter Twilight Zone --test -t
    

    this outputs:

    Options: --test -t
    Arguments: Twilight Zone
    

    also don't miss argparse which is fish's argument parsing command.