Search code examples
bashshvariable-expansion

Expanding to a flag only if value is set


I have some logic like:

if [[ -n "$SSH_CLIENT" ]]
then
    sflag="-s $(echo "$SSH_CLIENT" | awk '{ print $1}')"
else
    sflag=''
fi

iptables -A MY_RULE "$sflag" -p tcp -m tcp --dport 9999 -m conntrack -j ACCEPT

In other words, I want to mimic only passing the -s flag to iptables if SSH_CLIENT is set. What actually happens is that the empty string is inadvertently passed.

I'm interested in whether it is possible, in the interest of not repeating two quite long iptables calls, to expand the flag name and value. E.g. the command above should expand to

  • iptables -A MY_RULE -s 10.10.10.10 -p tcp -m tcp ..., or
  • iptables -A MY_RULE -p tcp -m tcp ...

The problem is that in the second case, the expansion actually becomes:

iptables -A MY_RULE '' -p tcp -m tcp

and there is an extra empty string that is treated as a positional argument. How can I achieve this correctly?


Solution

  • All POSIX shells: Using ${var+ ...expansion...}

    Using ${var+ ...words...} lets you have an arbitrary number of words only if a variable is set:

    iptables -A MY_RULE \
      ${SSH_CLIENT+ -s "${SSH_CLIENT%% *}"} \
      -p tcp -m tcp --dport 9999 -m conntrack -j ACCEPT
    

    Here, if-and-only-if SSH_CLIENT is set, we add -s followed by everything in SSH_CLIENT up to the first space.


    Bash (and other extended shells): Using Arrays

    The more general approach is to use an array whenever you want to represent multiple strings as a single value:

    ssh_client_args=( )
    [[ $SSH_CLIENT ]] && ssh_client_args+=( -s "${SSH_CLIENT%% *}" )
    iptables -A MY_RULE "${ssh_client_args[@]}" -p tcp -m tcp --dport 9999 -m conntrack -j ACCEPT
    

    The syntax "${var%% *} is a parameter expansion which expands to var with the longest possible suffix starting with a space trimmed; thus, leaving the first word. This is much faster than running an external program like awk. See also BashFAQ #100 describing general best practices for native-bash string manipulation.