arraysvariableszshexpansion

Remove some arguments from argument string in zsh


I'm trying to remove part of an arguments string using zsh parameter expansion (no external tools like sed please). Here's what for:

The RUBYOPT environment variable contains arguments which are applied whenever the ruby interpreter is used just as if they were given along with the ruby command. One argument controls the warning verbosity, possible settings are for instance -W0 or -W:no-deprecated. My goal is to remove all all -W... from RUBYOPT, say:

My current approach is to split the string to an array and then make a substitution on every member of the array. This works on two lines of code, but I can't make it work on a single line of code:

% RUBYOPT="-W:no-deprecated -X -W1"

% parts=(${(@s: :)RUBYOPT})
% echo ${parts/-W*}
-X

% echo ${(${(@s: :)RUBYOPT})/-W*}
zsh: error in flags

What am I doing wrong here... or is there a different, more elegant way to achieve this?

Thanks for your hints!


Solution

  • ${(... introduces parameter expansion flags (for expample:${(s: :)...}).

    It cannot handle ${(${(@s: :... as a parameter expansion, especially as the parameter expansion flags for the (${(@s... part, so zsh yields an error "zsh: error in flags".

        % RUBYOPT="-W:no-deprecated -X -W1"
        % print -- ${${(s: :)RUBYOPT}/-W*}
        # -X
    

    could rescue.


    update from rowboat's comments: it could be inappropriate for some flags like -abc-Whoops or -foo-Whoo etc:

        % RUBYOPT="-W:no-deprecated -X -W1 -foo-Whoo"
        % parts=(${(s: :)RUBYOPT})
        % print -- ${parts/-W*}
        # -X -foo
        # Note: -foo would be unexpected
        % print -- ${${(s: :)RUBYOPT}/-W*}
        # -X -foo
        # Note: -foo would be unexpected
    

    The s globbing flag (along with the shell option EXTENDED_GLOB) could rescue:

        % RUBYOPT="-W:no-deprecated -X -W1 -foo-Whoo"
        % parts=(${(s: :)RUBYOPT})
        % setopt extendedglob
        # To use `(#s)` flag which is like regex's `^`
        % print -- ${parts/(#s)-W*}
        # -X -foo-Whoo
        % print -- ${${(s: :)RUBYOPT}/(#s)-W*}
        # -X -foo-Whoo
    

    Globbing Flags

    There are various flags which affect any text to their right up to the end of the enclosing group or to the end of the pattern; they require the EXTENDED_GLOB option. All take the form (#X) where X may have one of the following forms:
    ...
    s, e

    Unlike the other flags, these have only a local effect, and each must appear on its own: (#s) and (#e) are the only valid forms. The (#s) flag succeeds only at the start of the test string, and the (#e) flag succeeds only at the end of the test string; they correspond to ^ and $ in standard regular ex‐ pressions.
    ...
    --- zshexpn(1), Expansion, Globbing Flags

    Or ${name#:pattern} syntax described below could rescue, too.

    end update from rowboat's comments


    Use typeset -T feature to manipulate the scalar value by array operators is an option.

        RUBYOPT="-W:no-deprecated -X -W1"
    
        typeset -xT RUBYOPT rubyopt ' '
        rubyopt=(${rubyopt:#-W*})
    
        print -l -- "$RUBYOPT"
        # -X
    

    typeset
    ...
    -T [ SCALAR[=VALUE] ARRAY[=(VALUE ...)] [ SEP ] ]
    ...
    the -T option requires zero, two, or three arguments to be present. With no arguments, the list of parameters created in this fashion is shown. With two or three arguments, the first two are the name of a scalar and of an array parameter (in that order) that will be tied together in the manner of $PATH and $path. The optional third argument is a single-character separator which will be used to join the elements of the array to form the scalar; if absent, a colon is used, as with $PATH. Only the first character of the separator is significant; any remaining characters are ignored. Multibyte characters are not yet supported. ...
    Both the scalar and the array may be manipulated as normal. If one is unset, the other will automatically be unset too. ...

    --- zshbuiltin(1), Shell Bultin Commands, typeset

    And rubyopt=(${rubyopt:#-W*}) to filter the array elements

    ${name:#pattern}

    If the pattern matches the value of name, then substitute the empty string; otherwise, just substitute the value of name. If name is an array the matching array elements are removed (use the (M) flag to remove the non-matched elements).

    --- zshexpn(1), Parameter Expansion , ${name:#pattern}


    Note: It is possible to omit "@" from flags because the empty values are not necessary in this case.

        RUBYOPT="-W:no-deprecated -X -W1"
        parts=(${(s: :)RUBYOPT})
        print -- ${parts/-W*}
        # -X
        print -- ${${(s: :)RUBYOPT}/-W*}
        # -X
    

    Parameter Expansion Flags
    ...
    @
    In double quotes, array elements are put into separate words. E.g., "${(@)foo}" is equivalent to "${foo[@]}" and "${(@)foo[1,2]}" is the same as "$foo[1]" "$foo[2]". This is distinct from field splitting by the f, s or z flags, which still applies within each array element.

    --- zshexpn(1), Parameter Expansion Flags, @

    If we cannot omit the empty value, ${name:#pattern} syntax could rescue.

        RUBYOPT="-W:no-deprecated  -X -W1"
        parts=("${(@s: :)RUBYOPT}")
        # parts=("-W:no-deprecated" ""  "-X" "-W1")
        # Note the empty value are retained
        print -rC1 -- "${(@qqq)parts:#-W*}"
        # ""
        # "-X"
        print -rC1 -- "${(@qqq)${(@s: :)RUBYOPT}:#-W*}"
        # ""
        # "-X"