]> git.ipfire.org Git - thirdparty/ipset.git/commitdiff
The bash utilities are updated
authorJozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Fri, 7 Mar 2014 12:10:01 +0000 (13:10 +0100)
committerJozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Fri, 7 Mar 2014 12:10:01 +0000 (13:10 +0100)
Makefile.am
utils/ipset_bash_completion/README.md
utils/ipset_bash_completion/ipset_bash_completion
utils/ipset_list/README.md
utils/ipset_list/ipset_list
utils/ipset_list/ipset_list_bash_completion

index 3fc9cb99521686184953dbc19978d536e74aaf8c..f085ca56e1810478eb7ef7429be7bfbee037505f 100644 (file)
@@ -81,10 +81,10 @@ update_includes:
        done
 
 update_utils:
-       wget -O /tmp/ipset-bash-completion.tar.gz http://sourceforge.net/projects/ipset-bashcompl/files/latest/download
+       wget -4 -O /tmp/ipset-bash-completion.tar.gz http://sourceforge.net/projects/ipset-bashcompl/files/latest/download
        cd utils/ipset_bash_completion; tar xz --strip-components=1 -f /tmp/ipset-bash-completion.tar.gz
        rm -f /tmp/ipset-bash-completion.tar.gz
-       wget -O /tmp/ipset-list.tar.gz http://sourceforge.net/projects/ipset-list/files/latest/download
+       wget -4 -O /tmp/ipset-list.tar.gz http://sourceforge.net/projects/ipset-list/files/latest/download
        cd utils/ipset_list; tar xz --strip-components=1 -f /tmp/ipset-list.tar.gz
        rm -f /tmp/ipset-list.tar.gz
 
index 7a43eaa003c1e81b33cb8af1247a084e3b5eb3d9..ce5c47e0a5cc00a660faf4d18e5f147419e408d7 100644 (file)
@@ -29,11 +29,12 @@ Providing some kind of interactive help.
 - Show and complete services (also named port ranges), protocols,
 icmp[6] types and interface names when adding, deleting or testing elements.
 - Show and complete hostnames, when adding, deleting or testing elements.
-- Show and complete mac addresses.
+- Show and complete ip and mac addresses (dynamically and from file).
 - Complete on filenames if the current option requires it.
-- Complete variable names, command substitution and globbing patterns.
+- Complete variable names and command substitution.
 - Do not complete if an invalid combination of options is used.
 - Do not complete if an invalid value of an option argument is detected.
+- Environment variables allow to modify completion behaviour.
 
 
 Installation
@@ -90,6 +91,9 @@ Taking the description from the bash man-page:
        attempts to read /etc/hosts to obtain the list of possible hostname completions.
        When HOSTFILE is unset, the hostname list is cleared.
 
+As it is impossible to distinguish between IPv4 and IPv6 hostnames without resolving
+them, it is considered best practice to seperate IPv4 hosts from IPv6 hosts
+in different files.
 
 If the *bash-completion* package is available hostname completion is extended
 the following way (description from bash-completion source):
@@ -103,10 +107,11 @@ the following way (description from bash-completion source):
 
 
 Also the environment variable **_IPSET_SSH_CONFIGS** controls which files are taken
-as ssh_config files, in order to retrieve the globl and user known_host files,
+as ssh_config files, in order to retrieve the global and user known_host files,
 which will be used for hostname completion.
 
-For all *net* type of sets, if hostname completion is attempted,
+For all *net* type of sets and the hash:ip,mark set type, if hostname completion is attempted,
+if the environment variable **_IPSET_COMP_NETWORKS** is set to a non-empty value,
 networks are retrieved from /etc/networks.
 
 Also a list of ip addresses can be supplied using the environment variable
@@ -116,8 +121,7 @@ is done automatically based on the set header.
 
 ---
 
-When deleting elements from one of the following set types:
-**hash:ip,port hash:ip,port,ip hash:ip,port,net hash:net,port hash:net,iface**
+When deleting elements from any but list:set set types:
 the environment variable **_IPSET_COMPL_DEL_MODE** is queried to decide how to complete.
 If it is set to 'members' it will list the members of the set.
 If it is set to 'spec' it will follow the format of a port specification ([proto:]port).
@@ -126,8 +130,7 @@ the list of possible completions (this is the default).
 
 ---
 
-When testing elements from one of the following set types:
-**hash:ip,port hash:ip,port,ip hash:ip,port,net hash:net,port hash:net,iface**
+When testing elements from any but list:set set types:
 the environment variable **_IPSET_COMPL_TEST_MODE** is queried to decide how to complete.
 If it is set to 'members' it will list the members of the set.
 If it is set to 'spec' it will follow the format of a port specification ([proto:]port).
@@ -147,7 +150,7 @@ If the variable is unset mac addresses are fetched from arp cache,
 ---
 
 When adding elements to one of the following set types:
-**hash:ip,port hash:ip,port,ip hash:ip,port,net hash:net,port**
+**hash:ip,port hash:ip,port,ip hash:ip,port,net hash:net,port hash:net,port,net**
 and completion is attempted on the port specification,
 the list of possible completions may become quite long.
 Especially if no characters are given to match on.
@@ -158,15 +161,31 @@ values such a port specification can possibly have.
 ---
 
 At any time completion on variable names (starting with '$' or '${'),
-command substitution (starting with '$(') and file name [ext] globbing
-patterns is available.
+or command substitution (starting with '$(') is available.
+Using this with values validated by input validation, will stop further completion.
+In that case it is recommended to disable input validation (see below).
+
+
+---
+
+If the environment variable **_IPSET_VALIDATE_INPUT** is set to a non empty value
+validation of users input is disabled.
+
+---
+
+If the environment variable **_DEBUG_NF_COMPLETION** is defined (any value)
+debugging information is displayed.
 
 
 
 Compatibility
 =============
 
-Tested with ipset v6.16.1.
+Compatible with ipset versions 6+.
+
+Tested with ipset v6.20.1.
+
+bash v4+ is required.
 
 Compatibility for future ipset versions cannot be promised, as new options may appear, 
 which of course are currently unpredictable.
@@ -180,9 +199,6 @@ Also the colon (if there) is removed from COMP_WORDBREAKS.
 This alteration is globally, which might affect other completions,
 if they do not take care of it themselves.
 
-If the bash-completion package is available bash v4+ is required.
-Otherwise bash v3.2 and upwards are supported.
-
 The iproute program (ip) is needed to display information about the local system.
 
 
@@ -193,3 +209,12 @@ Availability
 https://github.com/AllKind/ipset-bash-completion
 
 http://sourceforge.net/projects/ipset-bashcompl/
+
+
+
+Bugs
+============
+
+Please report bugs!
+
+
index cc7ea7beaf5d42337e50ab17df2d33e21978ac48..25f8db20d3bb4f9f6a92a3ab5fb12a080b7498bd 100644 (file)
@@ -7,7 +7,7 @@
 # https://sourceforge.net/projects/ipset-bashcompl
 # -----------------------------------------------------------------
 
-# Copyright (C) 2013 AllKind (AllKind@fastest.cc)
+# Copyright (C) 2013-2014 AllKind (AllKind@fastest.cc)
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 # -----------------------------------------------------------------
+# Compatible with ipset versions: 6+
 # Tested with ipset versions:
-# 6.16.1
+# 6.20.1
 # -----------------------------------------------------------------
 # Requirements:
 #
+# bash v4 or greater.
+#
 # The bash completion package version 2.0 or greater is recommended.
 # http://bash-completion.alioth.debian.org/
 #
 #
 # -----------------------------------------------------------------
 #
-# Version 2.0
+# Version 2.5
 #
 # -----------------------------------------------------------------
 
+shopt -s extglob
+
 # -----------------------------------------------------------------
 # Functions
 # -----------------------------------------------------------------
 
-_ipset_bash_default_compl() { # taken from examples - modified by me
-# call with the word to be completed as $1
-local t
-if [[ $1 == \$\(* ]]; then # command substitution
-    t=${1#??}
-    COMPREPLY=( $(compgen -c -P '$(' $t) )
-elif [[ $1 == \$\{* ]]; then # variables with a leading `${'
-    t=${1#??}
-    COMPREPLY=( $(compgen -v -P '${' -S '}' $t) )
-elif [[ $1 == \$* ]]; then # variables with a leading `$'
-    t=${1#?}
-    COMPREPLY=( $(compgen -v -P '$' $t ) )
-elif [[ $1 == *[*?[]* ]]; then # sh-style glob pattern
-    COMPREPLY=( $( compgen -G "$1" ) )
-    # ksh-style extended glob pattern - must be complete
-elif shopt -q extglob && [[ $1 == *[?*+\!@]\(*\)* ]]; then
-    COMPREPLY=( $( compgen -G "$1" ) )
-else # last fallback is filename completion
-    if ((got_bashcompl)); then
-        _filedir
-    else
-        compopt -o nospace
-        COMPREPLY=( $( compgen -f -- "$cur" ) )
-    fi
-fi
-}
-
 _ipset_colon_ltrim() {
 ((got_bashcompl)) || return 0
 __ltrim_colon_completions "$1"
@@ -95,15 +73,16 @@ return 1
 }
 
 _ipset_get_set_type() {
+local n d
 while read n d; do
     [[ $n = Type: ]] && printf '%s\n' $d && break
 done < <(ipset -t list "$1" 2>/dev/null)
 }
 
-_ipset_set_has_timout() {
+_ipset_set_has_option() {
 while read -r; do
-    [[ $REPLY = Header:*timeout* ]] && return 0
-done < <(ipset -t list "$1")
+    [[ $REPLY = Header:*$1* ]] && return 0
+done < <(ipset -t list "$2")
 return 1
 }
 
@@ -144,18 +123,10 @@ while read -r; do
 done < <(ipset list "$1" 2>/dev/null)
 }
 
-_ipset_set_has_timeout() {
-while read -r; do
-    [[ $REPLY = Header:*timeout* ]] && return 0
-done < <(ipset -t list "$1")
-return 1
-}
-
 _ipset_get_set_family() {
 while read -r; do
     [[ $REPLY = Header:*"family inet6"* ]] && printf "v6\n" && return
     [[ $REPLY = Header:*"family inet "* ]] && printf "v4\n" && return
-    [[ $REPLY = Header:*"range "*:*:* ]] && printf "v6\n" && return
     [[ $REPLY = Header:*"range "*.*.*.* ]] && printf "v4\n" && return
 done < <(ipset -t list "$1")
 }
@@ -163,9 +134,15 @@ done < <(ipset -t list "$1")
 _ipset_dedupe_cmd_opts() {
 local str_opt
 local -i idx
-for str_opt in "${@}"; do
+for str_opt; do
     for idx in ${!arr_dupe_cmd_opts[@]}; do
-        [[ $str_opt = ${arr_dupe_cmd_opts[idx]} ]] && continue 2
+        if [[ $str_opt = ${arr_dupe_cmd_opts[idx]} ]]; then
+            if [[ $_DEBUG_NF_COMPLETION ]]; then
+                printf "removing dupe option str_opt: %s\n" \
+                    "${arr_dupe_cmd_opts[idx]}"
+            fi
+            continue 2
+        fi
     done
     printf "%s\n" "$str_opt"
 done
@@ -225,7 +202,7 @@ else
         str_list='- ${arr_opts[@]}'
     fi
 fi
-COMPREPLY=( $( compgen -W "$str_list" -- "$cur" ) )
+COMPREPLY=( $( compgen -W "$str_list" -- "$cur" ) )
 ((${#COMPREPLY[@]})) || return 0
 
 # post process the reply
@@ -274,6 +251,7 @@ done
 _ipset_get_networks() {
 local foo str_net rest
 [[ -r /etc/networks ]] || return 0
+[[ ${_IPSET_COMP_NETWORKS-1} ]] || return 0
 while read -r foo str_net rest; do
     [[ $foo = @(""|*([[:blank:]])#*) ]] && continue
     [[ $str_net = *([[:blank:]])#* ]] && continue
@@ -291,13 +269,42 @@ while read -r str_name rest; do
 done < /etc/protocols
 }
 
+_ipset_get_svnum() {
+# find service num to set offset
+local str_name str_num str_p=all rest
+local _str_p _str_o=""
+while (($#)); do
+    if [[ $1 = -p ]]; then
+        _str_p="${2:-all}"
+        shift
+    elif [[ $1 = -o && ${2:-"-no"} != -* ]]; then
+        # second part of range will have offset = first_part_of_range+1
+        _str_o="$2"
+        shift
+    fi
+    shift
+done
+if [[ $_str_o && $_str_o != +([[:digit:]]) ]]; then
+    while read str_name str_num rest; do
+        if [[ $str_name = *([[:blank:]])#* ]]; then continue
+        elif [[ $_str_p != all && ${str_num#*/} != $_str_p ]]; then
+            continue
+        fi
+        [[ $str_name = $_str_o ]] && printf "%s\n" ${str_num%/*} && return
+
+    done < /etc/services
+else
+    printf "%s\n" "$_str_o"
+fi
+}
+
 _ipset_get_services() {
 local str_offset="" str_name str_num str_p=all rest
 while (($#)); do
     if [[ $1 = -p ]]; then
         str_p="${2:-all}"
         shift
-    elif [[ $1 = -o && $2 ]]; then
+    elif [[ $1 = -o && ${2:-"-no"} != -* ]]; then
         # second part of range will have offset = first_part_of_range+1
         str_offset="${2}"
         shift
@@ -306,15 +313,10 @@ while (($#)); do
 done
 # find service num to set offset
 if [[ $str_offset && $str_offset != +([[:digit:]]) ]]; then
-    while read str_name str_num rest; do
-        if [[ $str_name = *([[:blank:]])#* ]]; then continue
-        elif [[ $str_p != all && ${str_num#*/} != $str_p ]]; then
-            continue
-        fi
-        [[ $str_name = $str_offset ]] && str_offset=${str_num%/*} && break
-    done < /etc/services
-    [[ $str_offset = +([[:digit:]]) ]] || return 0
+    str_offset=$(_ipset_get_svnum -p "$str_p" -o "$str_offset")
+    [[ $str_offset = +([[:digit:]]) ]] || str_offset="" # we failed
 fi
+# identify and print the services
 while read -r str_name str_num rest; do
     if [[ $str_name = @(""|*([[:blank:]])#*) ]]; then continue
     elif [[ $str_p != all && ${str_num#*/} != $str_p ]]; then
@@ -335,201 +337,432 @@ while read -r; do
 done < <(PATH=${PATH}:/sbin command ip -o link show)
 }
 
+_ipset_get_iplist() {
+# if a file with ip addresses is in env var, load em
+local str_ip rest
+if [[ $1 = v4 ]]; then
+str_regex='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/([3][0-2]|[1-2]?[0-9]))?$'
+elif [[ $1 = v6 ]]; then
+str_regex='^([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}|([0-9a-fA-F]{1,4}:){1}(:[0-9a-fA-F]{1,4}){1,6}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1})(/([1][0-2][0-9]|[1-9]?[0-9]))?$'
+else return 0
+fi
+[[ $_IPSET_IPLIST_FILE && -r $_IPSET_IPLIST_FILE ]] || return 0
+while read -r str_ip rest; do
+    [[ $str_ip = *([[:blank:]])\#* ]] && continue
+    str_ip="${str_ip//\#*/}"
+    [[ $str_ip =~ $str_regex ]] && printf "%s\n" "$str_ip"
+done < "${_IPSET_IPLIST_FILE}"
+}
+
+_ipset_complete_elements() {
+local lcur="$1" str_lprefix=""
+local -i no_range=0
+shift
+while (($#)); do
+    [[ $1 = --no-range ]] && no_range=1
+    shift
+done
+if [[ $lcur = *-[[]+([!]]) ]]; then # incomplete range - host/port with dash
+    str_lprefix="${lcur%\-[*}-"
+    lcur="${lcur#"$str_lprefix"}"
+elif [[ $lcur = \[*\]-* ]]; then # first part of host/portname range
+    str_lprefix="${lcur%\]-*}]-" lcur="${lcur##*\]-}"
+elif [[ $lcur = *-* ]]; then
+    str_lprefix="${lcur%-*}-" lcur="${lcur##*-}"
+elif [[ $lcur =~ (tcp|udp):.* ]]; then # proto:port (bitmap:port)
+    str_lprefix="${BASH_REMATCH[1]}:" lcur="${lcur#*:}"
+    no_range=0  # workaround
+fi
+if [[ $str_lprefix ]]; then
+    [[ $str_lprefix = */* ]] && return 1
+    ((no_range)) && return 1
+    _ipset_get_members --names-only "$str_setname"
+    COMPREPLY+=( $( compgen -P "$str_lprefix" -W '${arr_members[@]/*\/*/}' -- "$lcur" ) )
+else
+    _ipset_get_members --names-only "$str_setname"
+    COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$lcur" ) )
+fi
+}
+
+_ipset_complete_portrange() {
+# complete port ranges
+local lcur="$1"
+local str_lprefix="$lcur" str_p=""
+str_var=0 str_glob='[^[]*-*'
+if [[ $lcur = *:* ]]; then # look for `proto:'
+    ((got_bp_proto)) || return 0 # supported since ipset v6.20
+    # only tcp/udp is valid as PROTO spec
+    [[ ${lcur%%:*} = @(tcp|udp) ]] || return 0
+    str_p=${lcur%%:*}
+    lcur="${lcur#$str_p:}"
+fi
+if [[ $lcur = \[*-*\]-* ]]; then # spec with bracket
+    str_var="${lcur#\[}"
+    str_var="${str_var%%\]*}"
+    lcur="${lcur#*\]-}"
+    str_lprefix=${str_lprefix%"$lcur"}
+elif [[ $lcur = $str_glob ]]; then # spec without bracket
+    str_var="${lcur%%-*}"
+    lcur="${lcur#*-}"
+    str_lprefix=${str_lprefix%"$lcur"}
+else # no prefix
+    str_lprefix=""
+    compopt -o nospace
+fi
+if [[ $str_p ]]; then # we have a proto spec
+    COMPREPLY=( $(compgen -P "$str_p:$str_lprefix" \
+        -W '$(_ipset_get_services -o $str_var -p $str_p)' -- "$lcur" ) )
+    _ipset_colon_ltrim "$str_p:$str_lprefix$lcur"
+else
+    if [[ $str_lprefix ]]; then
+        COMPREPLY=( $(compgen -P "$str_lprefix" \
+            -W '$(_ipset_get_services -o $str_var)' -- "$lcur" ) )
+    else
+        if ((got_bp_proto)); then # supported since ipset v6.20
+            COMPREPLY=( $(compgen \
+                -W 'tcp: udp: $(_ipset_get_services)' -- "$lcur" ) )
+        else # only tcp services prior to ipset v6.20
+            COMPREPLY=( $(compgen \
+                -W '$(_ipset_get_services -p tcp)' -- "$lcur" ) )
+        fi
+    fi
+fi
+}
+
+_ipset_complete_hostnames() {
+local -i idx got_bracket=0
+local lcur="${1//@(\[|\])}"
+[[ $lcur = $1 ]] || got_bracket=1
+if ((got_bashcompl)); then
+    _ipset_known_hosts -F "$_IPSET_SSH_CONFIGS" -- "$lcur"
+else
+    if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
+        COMPREPLY+=( $( compgen -A hostname -- "$lcur" ) )
+    fi
+fi
+for idx in ${!COMPREPLY[@]}; do
+    if [[ ${COMPREPLY[idx]} = *-* ]]; then
+        COMPREPLY[idx]="[${COMPREPLY[idx]}]"
+    else
+        ((got_bracket)) && unset COMPREPLY[idx]
+    fi
+done
+#_ipset_colon_ltrim "$lcur"
+}
+
 _ipset_complete_iface_spec() {
-if [[ $cur != *,* ]]; then
-    str_prefix=""
+local lcur="$1" str_lprefix=""
+if [[ $lcur != *,* ]]; then
+    str_lprefix="" str_glob='+([![])-*'
     compopt -o nospace
-    if [[ $cur = *-* ]]; then # range spec
-        str_prefix="${cur%-*}-" cur="${cur#*-}"
+    if [[ x$str_action != xtest ]]; then
+        if [[ $lcur = \[*-*\]-* ]]; then # hostrange spec
+            str_lprefix="${lcur%\]-*}]-" lcur="${lcur#*\]-}"
+        elif [[ $lcur = *-[[]+([!]]) ]]; then # incomplete range - host with dash
+            str_lprefix="${lcur%-\[*}-"
+            lcur="${lcur#"$str_lprefix"}"
+        elif [[ $lcur = $str_glob ]]; then # range spec
+            str_lprefix="${lcur%-*}-" lcur="${lcur#*-}"
+        fi
     fi
     # ip-list from file
-    COMPREPLY=( $( compgen -W \
+    COMPREPLY+=( $( compgen -W \
         '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
-        -- "$cur" ) )
+        -- "$lcur" ) )
     # networks
-    while read; do
-        [[ $REPLY = $cur* ]] && COMPREPLY+=( "$REPLY" )
-    done < <(_ipset_get_networks)
+    COMPREPLY+=( $( compgen -W '$(_ipset_get_networks)' -- "$lcur" ) )
     # hostnames
-    if ((got_bashcompl)); then
-        _known_hosts_real -F "$_IPSET_SSH_CONFIGS" -- "$cur"
+    _ipset_complete_hostnames "$lcur"
+    if [[ $str_lprefix ]]; then # range spec
+        COMPREPLY=( $( compgen -P "$str_lprefix" -W '${COMPREPLY[@]//*\/*/}' \
+            -- "$lcur" ) )
     else
-        if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
-            COMPREPLY+=( $( compgen -A hostname "$cur" ) )
-        fi
-        _ipset_colon_ltrim "$cur"
-    fi
-    if [[ $str_prefix ]]; then # range spec
-        COMPREPLY=( $( compgen -P "$str_prefix" -W '${COMPREPLY[@]}' -- "$cur" ) )
+        COMPREPLY=( $( compgen -W '${COMPREPLY[@]}' -- "$lcur" ) )
     fi
     if ((${#COMPREPLY[@]} == 1)); then
-        if [[ ${COMPREPLY[*]} = @(*/*|*-*) ]]; then
+        if [[ $str_lprefix || x$str_action = xtest ]]; then
            COMPREPLY=( ${COMPREPLY[*]}, )
-        fi
+       else
+           COMPREPLY=( ${COMPREPLY[*]}, ${COMPREPLY[*]}- )
+       fi
     fi
-elif [[ $cur = *,* ]]; then
-    str_prefix="${cur}" cur="${cur#*,}" str_var=""
-    str_prefix="${str_prefix%"$cur"}"
-    if [[ $cur = physdev:* ]]; then
-        cur="${cur#physdev:}"
-        str_prefix="${str_prefix}physdev:"
+    _ipset_colon_ltrim "$str_lprefix$lcur"
+elif [[ $lcur = *,* ]]; then
+    str_lprefix="${lcur}" lcur="${lcur#*,}" str_var=""
+    str_lprefix="${str_lprefix%"$lcur"}"
+    if [[ $lcur = physdev:* ]]; then
+        lcur="${lcur#physdev:}"
+        str_lprefix="${str_lprefix}physdev:"
     else
         str_var="physdev:"
     fi
-    COMPREPLY=( $( compgen -P "$str_prefix" -W \
-        '${str_var} $(_ipset_get_ifnames)' -- "$cur" ) )
+    COMPREPLY+=( $( compgen -P "$str_lprefix" -W \
+        '${str_var} $(_ipset_get_ifnames)' -- "$lcur" ) )
     [[ ${COMPREPLY[0]} = *physdev: ]] && compopt -o nospace
-    _ipset_colon_ltrim "$str_prefix"
+    _ipset_colon_ltrim "$str_lprefix"
+fi
+}
+
+_ipset_complete_host_spec() {
+local lcur="$1" str_lprefix="" str_lsuffix=""
+local -i no_range=0 v4_only=0
+if [[ $lcur = *,* && $str_type = hash:ip,mark ]]; then
+    return 0
+fi
+shift
+compopt -o nospace
+while (($#)); do
+    [[ $1 = --no-range ]] && no_range=1
+    [[ $1 = --v4 ]] && v4_only=1
+    shift
+done
+# range spec
+if [[ $lcur = @(""|+([[:word:]])) ]]; then # empty or [:word:]
+    str_lsuffix="-"
+elif [[ $lcur = [[]*-*[]] ]]; then # host with hyphen
+    str_lsuffix="-"
+elif [[ $lcur = [[]*([!]]) ]]; then # incomplete host with dash
+    str_lsuffix="-"
+elif [[ $lcur = *-[[]+([!]]) ]]; then # incomplete range - host with dash
+    str_lprefix="${lcur%\-[*}-"
+    lcur="${lcur#"$str_lprefix"}"
+elif [[ $lcur = \[*\]-* ]]; then # first part of hostname range
+    str_lprefix="${lcur%\]-*}]-" lcur="${lcur##*\]-}"
+elif [[ $lcur != *-* ]]; then # no hypen
+    str_lsuffix="-"
+else # ip-range
+    str_lprefix="${lcur%-*}-" lcur="${lcur##*-}"
+fi
+if [[ $str_lprefix ]]; then
+    # range not valid
+       ((no_range)) && return 1
+    # range not valid if first part is ip/cidr
+    [[ $str_lprefix = */* ]] && return 1
+fi
+# ip-list from file
+if [[ $str_lprefix ]]; then
+    # only ipv4 range supported
+    COMPREPLY+=( $( compgen -W '$(_ipset_get_iplist v4)' -- "$lcur" ) )
+elif ((v4_only)); then
+    # this type only supports ipv4
+    COMPREPLY+=( $( compgen -W '$(_ipset_get_iplist v4)' -- "$lcur" ) )
+else
+    # we gather the family type
+    COMPREPLY+=( $( compgen -W \
+        '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
+        -- "$lcur" ) )
+    _ipset_colon_ltrim "$lcur"
+fi
+_ipset_complete_hostnames "$lcur"
+if [[ $str_lprefix ]]; then
+    # if the prefix is defined add it to compreply
+    COMPREPLY=( $( compgen -P "$str_lprefix" -W '${COMPREPLY[@]}' -- "$lcur" ) )
+else
+    # add networks for hash:net?(,net), hash:ip,mark, or bitmap:ip for add/del action
+    if [[ $str_type = hash:@(net?(,net)|ip,mark) ]] || \
+        [[ $str_type = bitmap:*  && $str_action = @(add|create|del) ]]
+    then
+        COMPREPLY+=( $( compgen -W '$(_ipset_get_networks)' -- "$lcur" ) )
+    fi
+fi
+if ((${#COMPREPLY[@]} == 1)); then
+    if [[ $str_lprefix ]]; then
+        # we can add a space, if it's a range (not with hash:net,net or hash:ip,mark)
+        if [[ $str_type != hash:@(net,net|ip,mark) ]]; then
+            compopt +o nospace
+        fi
+    fi
 fi
 }
 
 _ipset_complete_hostport_spec() {
 # complete on host,proto:port[,host] spec
-local str_proto str_glob2
-if [[ $str_type = hash:ip,port,@(ip|net) ]]; then str_suffix=','
+local str_proto str_glob2 str_lprefix lcur str_lprefix2 lcur2
+local lcur="$1"
+if [[ $str_type = hash:@(ip|net),port,@(ip|net) ]]; then str_suffix=','
 else str_suffix=''
 fi
 str_regex='^[^,]+,([^,]+)?$'
-if [[ $cur != *,* ]]; then
-    str_prefix=""
+if [[ $lcur != *,* ]]; then
+    str_lprefix="" str_suffix=""
     compopt -o nospace
-    if [[ $str_type = hash:net,port && $str_action = @(add|del) && $cur = *-* ]]
-    then # range spec
-        str_prefix="${cur%-*}-" cur="${cur#*-}"
+    if [[ $str_type = hash:@(ip,port|net,port|ip,port,ip|ip,port,net|net,port,net) && \
+        $str_action = @(add|del|test) ]]
+    then
+        # range spec
+        if [[ $lcur = @(""|+([[:word:]])) ]]; then # empty or [:word:]
+            str_suffix="-"
+        elif [[ $lcur = [[]*-*[]] ]]; then # host with hyphen
+            str_suffix="-"
+        elif [[ $lcur = [[]*([!]]) ]]; then # incomplete host with dash
+            str_suffix="-"
+        elif [[ $lcur = *-[[]+([!]]) ]]; then # incomplete range - host with dash
+            str_lprefix="${lcur%\-[*}-"
+            lcur="${lcur#"$str_lprefix"}"
+        elif [[ $lcur = \[*\]-* ]]; then # first part of hostname range
+            str_lprefix="${lcur%\]-*}]-" lcur="${lcur##*\]-}"
+        elif [[ $lcur != *-* ]]; then # no hypen
+            str_suffix="-"
+        else # ip-range
+            str_lprefix="${lcur%-*}-" lcur="${lcur##*-}"
+        fi
     fi
     # ip-list from file
-    COMPREPLY=( $( compgen -W \
+    COMPREPLY+=( $( compgen -W \
         '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
-        -- "$cur" ) )
-    if [[ $str_type = hash:net,port ]]; then
-        COMPREPLY+=( $( compgen -W '$(_ipset_get_networks)' -- "$cur" ) )
-        _ipset_colon_ltrim "$cur"
+        -- "$lcur" ) )
+    if [[ $str_type = hash:net,port?(,net) ]]; then
+        COMPREPLY+=( $( compgen -W '$(_ipset_get_networks)' -- "$lcur" ) )
+        _ipset_colon_ltrim "$lcur"
     fi
-    if ((got_bashcompl)); then
-        _known_hosts_real -F "$_IPSET_SSH_CONFIGS" -- "$cur"
-    else
-        if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
-            COMPREPLY+=( $( compgen -A hostname "$cur" ) )
-        fi
-        _ipset_colon_ltrim "$cur"
-    fi
-    if [[ $str_prefix ]]; then # range spec
-        COMPREPLY=( $( compgen -P "$str_prefix" -W '${COMPREPLY[@]}' -- "$cur" ) )
+    _ipset_complete_hostnames "$lcur"
+    if [[ $str_lprefix ]]; then # range spec
+        COMPREPLY=( $( compgen -P "$str_lprefix" -W '${COMPREPLY[@]}' -- "$lcur" ) )
     fi
     if ((${#COMPREPLY[@]} == 1)); then
-        if [[ $str_type = hash:net,port ]]; then
-            if [[ ${COMPREPLY[*]} = @(*/*|*-*) ]]; then
-               COMPREPLY=( ${COMPREPLY[*]}, )
-            fi
+        if [[ $str_suffix = - ]]; then
+            COMPREPLY=( $( compgen -W '${COMPREPLY[*]}, ${COMPREPLY[*]}-' -- "$lcur" ) )
         else
             COMPREPLY=( ${COMPREPLY[*]}, )
         fi
     fi
-elif [[ $cur =~ $str_regex ]]; then
-    (( $(IFS=,; set -- $str_type; printf "%d\n" $#) == 3 )) && compopt -o nospace
-    str_glob='[^\[]*-' # otherwise messes up my vim syntax highlightning
+    _ipset_colon_ltrim "$str_lprefix$lcur"
+elif [[ $lcur =~ $str_regex ]]; then
+    compopt -o nospace
+    str_glob='[^[]*-' # otherwise messes up my vim syntax highlightning
     str_regex='.*,(icmp|icmp6|tcp|sctp|udp|udplite):.*' # for compat put regex in var
-    if [[ $cur != *icmp* && \
-        $cur = *,@(?(tcp:|sctp:|udp:|udplite:)@(+([[:word:]])-|\[*-*\]-)|\[*-*\]-)* ]]
+    if [[ $lcur != *icmp* && \
+        $lcur = *,@(?(tcp:|sctp:|udp:|udplite:)@(+([[:word:]])-|\[*-*\]-)|\[*-*\]-)* ]]
     then # range spec
-        str_prefix="$cur" str_glob='*[[]*' str_glob2='*-\[*' str_proto="tcp" str_var=""
-        [[ $cur =~ .*(tcp|sctp|udp|udplite):.* ]] && str_proto=${BASH_REMATCH[1]}
-        if [[ $cur = *\[*-*\]-* ]]; then
-            str_var="${cur#*,*[}" cur="${cur#*\]-}"
-            str_prefix=${str_prefix%"$cur"} str_var="${str_var%\]*}"
-        elif [[ $cur = $str_glob2 ]]; then
-            str_var="${cur#*,}" cur="${cur#*-}"
+        str_lprefix="$lcur" str_glob='*[[]*' str_glob2='*,*-\[*' str_proto="tcp" str_var=""
+        [[ $lcur =~ .*(tcp|sctp|udp|udplite):.* ]] && str_proto=${BASH_REMATCH[1]}
+        if [[ $lcur = *,*\[*-*\]-* ]]; then
+            str_var="${lcur#*,*[}" lcur="${lcur##*\]-}"
+            str_lprefix=${str_lprefix%"$lcur"} str_var="${str_var%\]*}"
+        elif [[ $lcur = $str_glob2 ]]; then
+            str_var="${lcur#*,}" lcur="${lcur##*-}"
             str_var="${str_var#${BASH_REMATCH[1]}:}"
-            str_prefix=${str_prefix%"$cur"} str_var="${str_var%%-*}"
-        elif [[ $cur != $str_glob ]]; then
-            str_var="${cur#*,}" cur="${cur##*-}"
+            str_lprefix=${str_lprefix%"$lcur"} str_var="${str_var%%-*}"
+        else
+            str_var="${lcur#*,}" lcur="${lcur##*-}"
             str_var="${str_var#${BASH_REMATCH[1]}:}"
-            str_prefix=${str_prefix%"$cur"} str_var="${str_var%-*}"
+            str_lprefix=${str_lprefix%"$lcur"} str_var="${str_var%-*}"
+        fi
+        COMPREPLY+=( $( compgen -P "$str_lprefix" -S "$str_suffix" -W \
+            '$(_ipset_get_services -p "$str_proto" -o "$str_var")' -- "$lcur") )
+        if [[ $str_lprefix = *:* ]]; then
+            str_lprefix="${str_lprefix%:*}:"
         fi
-        COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" -W \
-            '$(_ipset_get_services -p "$str_proto" -o "$str_var")' -- "$cur") )
-        if [[ $str_prefix = *:* ]]; then
-            str_prefix="${str_prefix%:*}:"
+        _ipset_colon_ltrim "${str_lprefix}"
+        if ((${#COMPREPLY[@]} == 1)); then
+            if [[ $str_lprefix && $str_type != hash:@(ip|net),port,@(ip|net) ]]; then
+                compopt +o nospace
+            fi
         fi
-        _ipset_colon_ltrim "${str_prefix}"
-    elif [[ $cur =~ $str_regex ]]; then
+    elif [[ $lcur =~ $str_regex ]]; then
         # icmp[6] and services with (tcp|udp|sctp|udplite): prefix
         str_var=${BASH_REMATCH[1]}
-        str_prefix="${cur}" cur="${cur#*,}"
-        str_prefix="${str_prefix%"$cur"}"
-        cur="${cur#${BASH_REMATCH[1]}:}"
-        str_prefix="${str_prefix}${BASH_REMATCH[1]}:"
+        str_lprefix="${lcur}" lcur="${lcur#*,}"
+        str_lprefix="${str_lprefix%"$lcur"}"
+        lcur="${lcur#${BASH_REMATCH[1]}:}"
+        str_lprefix="${str_lprefix}${BASH_REMATCH[1]}:"
         if [[ $str_var = icmp ]]; then
-            COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" -W \
-                '${arr_icmp_types[@]}' -- "$cur" ) )
+            COMPREPLY+=( $( compgen -P "$str_lprefix" -S "$str_suffix" -W \
+                '${arr_icmp_types[@]}' -- "$lcur" ) )
         elif [[ $str_var = icmp6 ]]; then
-            COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" -W \
-                '${arr_icmp6_types[@]}' -- "$cur" ) )
+            COMPREPLY+=( $( compgen -P "$str_lprefix" -S "$str_suffix" -W \
+                '${arr_icmp6_types[@]}' -- "$lcur" ) )
         elif [[ $str_var = @(tcp|udp|sctp|udplite) ]]; then
-            COMPREPLY=( $( compgen -P "$str_prefix" -W \
-                '$(_ipset_get_services -p $str_var)' -- "$cur" ) )
-            compopt -o nospace
+            COMPREPLY+=( $( compgen -P "$str_lprefix" -W \
+                '$(_ipset_get_services -p $str_var)' -- "$lcur" ) )
         fi
-        _ipset_colon_ltrim "$str_prefix"
-    elif [[ $cur = *,* ]]; then # first attempt :/ long list
-        str_prefix="${cur%,*}," cur="${cur#*,}"
+        _ipset_colon_ltrim "$str_lprefix"
+    elif [[ $lcur = *,* ]]; then # first attempt :/ long list
+        str_lprefix="${lcur%,*}," lcur="${lcur#*,}"
         str_var="tcp: udp: sctp: udplite: icmp: icmp6:"
         # add the services
-        COMPREPLY=( $( compgen -P "$str_prefix" -W \
-            '$str_var $(_ipset_get_services -p tcp)' -- "$cur" ) )
-        for str_var in $( compgen -P "$str_prefix" -S ":0$str_suffix" -W \
-            '$(_ipset_get_protocols)' -- "$cur" )
-        do
-            COMPREPLY+=( "$str_var" ) # add the protocols
-        done
-        _ipset_colon_ltrim "$str_prefix$cur"
+        COMPREPLY+=( $( compgen -P "$str_lprefix" -W \
+            '$str_var $(_ipset_get_services)' -- "$lcur" ) )
+        # add the protocols
+        COMPREPLY+=( $( compgen -P "$str_lprefix" -S ":0$str_suffix" -W \
+            '$(_ipset_get_protocols)' -- "$lcur" ) )
+        _ipset_colon_ltrim "$str_lprefix$lcur"
         compopt -o nospace
     fi
-elif [[ $cur = *,*,* && $str_type = hash:ip,port,@(ip|net) ]]; then
-    str_prefix="${cur}" cur="${cur##*,}"
-    str_prefix="${str_prefix%"$cur"}"
+elif [[ $lcur = *,*,* && $str_type = hash:@(ip,port,@(ip|net)|net,port,net) ]]; then
+    str_lprefix2="${lcur}" lcur2="${lcur##*,}"
+    str_lprefix2="${str_lprefix2%"$lcur2"}"
+    lcur="$lcur2"
     # ip-list from file
-    COMPREPLY=( $( compgen -W \
+    COMPREPLY+=( $( compgen -W \
         '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
-        -- "$cur" ) )
-    if [[ $str_type = hash:ip,port,net ]]; then
-        while read; do
-            [[ $REPLY = $cur* ]] && COMPREPLY+=( "$str_prefix$REPLY" )
-        done < <(_ipset_get_networks)
-        _ipset_colon_ltrim "$str_prefix$cur"
-    fi
-    if ((got_bashcompl)); then
-        _known_hosts_real -p "$str_prefix" -F "$_IPSET_SSH_CONFIGS" -- "$cur"
-    else
-        if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
-            COMPREPLY+=( $( compgen -P "$str_prefix" -A hostname "$cur" ) )
+        -- "$lcur2" ) )
+    if [[ $str_type = hash:@(ip|net),port,net && x$str_action != xtest ]]; then
+        # range spec
+        if [[ $lcur = @(""|+([[:word:]])) ]]; then # empty or [:word:]
+            str_suffix="-"
+        elif [[ $lcur = [[]*-*[]] ]]; then # host with hyphen
+            str_suffix="-"
+        elif [[ $lcur = [[]+([!]]) ]]; then # incomplete host with dash
+            str_suffix="-"
+        elif [[ $lcur = *-[[]+([!]]) ]]; then # incomplete range - host with dash
+            str_lprefix="${lcur%\-[*}-"
+            lcur="${lcur#"$str_lprefix"}"
+        elif [[ $lcur = \[*\]-* ]]; then # first part of hostname range
+            str_lprefix="${lcur%\]-*}]-" lcur="${lcur##*\]-}"
+        elif [[ $lcur != *-* ]]; then # no hypen
+            str_suffix="-"
+        else # ip-range
+            str_lprefix="${lcur%-*}-" lcur="${lcur##*-}"
         fi
-        _ipset_colon_ltrim "$str_prefix$cur"
+        # networks
+        COMPREPLY+=( $( compgen -W '$(_ipset_get_networks)' -- "$lcur" ) )
+    fi
+    _ipset_complete_hostnames "$lcur"
+    if [[ $str_lprefix ]]; then
+        COMPREPLY=( $( compgen -P "$str_lprefix" \
+            -W '${COMPREPLY[@]}' -- "$lcur" ) )
     fi
+    if [[ $str_lprefix2 ]]; then
+        COMPREPLY=( $( compgen -P "$str_lprefix2" \
+            -W '${COMPREPLY[@]}' -- "$lcur2" ) )
+    fi
+    _ipset_colon_ltrim "$str_lprefix2$lcur2"
     if ((${#COMPREPLY[@]} == 1)); then
-        if [[ $str_type = hash:ip,port,net && ${COMPREPLY[*]} != */* ]]; then
+        if [[ $str_type = hash:@(ip|net),port,net && \
+            ${COMPREPLY[*]##*,} != */* ]]
+        then
             compopt -o nospace
-            COMPREPLY=( ${COMPREPLY[*]}/ )
         fi
     fi
 fi
 }
 
-_ipset_get_iplist() {
-# if a file with ip addresses is in env var, load em
-local str_ip rest
-if [[ $1 = v4 ]]; then
-str_regex='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/([3][0-2]|[1-2]?[0-9]))?$'
-elif [[ $1 = v6 ]]; then
-str_regex='^([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}|([0-9a-fA-F]{1,4}:){1}(:[0-9a-fA-F]{1,4}){1,6}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1})(/([1][0-2][0-9]|[1-9]?[0-9]))?$'
-else return 0
+_ipset_complete_netnet_spec() {
+# complete hash:net,net sets
+local lcur="$1"
+if [[ $lcur = *,* ]]; then
+    str_lprefix="$lcur" lcur="${lcur#*,}"
+    str_lprefix="${str_lprefix%,*},"
+    _ipset_complete_host_spec "$lcur"
+    compopt -o nospace
+    COMPREPLY+=( $( compgen -P "$str_lprefix" -W '${COMPREPLY[@]}' -- "$lcur" ) )
+    if ((${#COMPREPLY[@]} == 1 )); then
+        str_glob='@(*/*|\[*\]-*|+([![])-*)'
+        [[ ${COMPREPLY[0]#*,} = $str_glob ]] && compopt +o nospace
+    fi
+else
+    _ipset_complete_host_spec "$lcur"
+    compopt -o nospace
+    if ((${#COMPREPLY[@]} == 1 )); then
+        str_glob='@(*/*|\[*\]-\[*\]|+([![])-+([![])|\[*\]-+([![])|+([![])-\[*\])'
+        if [[ ${COMPREPLY[0]} = $str_glob ]]; then
+            COMPREPLY=( ${COMPREPLY[*]}, )
+        else
+            COMPREPLY=( ${COMPREPLY[*]}- ${COMPREPLY[*]}, )
+        fi
+    fi
 fi
-[[ $_IPSET_IPLIST_FILE && -r $_IPSET_IPLIST_FILE ]] || return 0
-while read -r str_ip rest; do
-    [[ $str_ip = *([[:blank:]])\#* ]] && continue
-    str_ip="${str_ip//\#*/}"
-    [[ $str_ip =~ $str_regex ]] && printf "%s\n" "$str_ip"
-done < "${_IPSET_IPLIST_FILE}"
 }
 
 # -----------------------------------------------------------------
@@ -537,14 +770,15 @@ done < "${_IPSET_IPLIST_FILE}"
 # -----------------------------------------------------------------
 
 _ipset_complete() {
-shopt -s extglob
 local cur prev cword words ips_version
 local str_action str_setname str_type str_filename
 local str_glob str_regex str_prefix str_suffix
 local str_tmp="" str_var=""
-local str_timeout="timeout" str_order="before after" str_counters=""
+local str_timeout="timeout" str_order="before after" str_forceadd=""
+local str_counters="" str_bp_counters="" str_comment="" str_markmask=""
 local -i i=x=y=0
 local -i got_bashcompl=got_action=action_index=order_index=set_has_timeout=0
+local -i got_bp_proto=0
 local -i ignore_errors=use_file=names_only=headers_only=save_format=res_sort=0
 local arr_sets=() arr_types=() arr_members=() arr_unknown_opts=()
 local arr_dupe_cmd_opts=() arr_used_opts=() arr_tmp=()
@@ -618,6 +852,9 @@ neigbour-advertisement
 redirect
 )
 
+# at least bash 4 is required
+((${BASH_VERSINFO[0]} < 4)) && return 0
+
 COMPREPLY=()
 
 # ipset version check 6.x upwards (to v?) is supported
@@ -628,10 +865,30 @@ read -a ips_version <<< ${ips_version//./ }
 [[ ${ips_version[0]} = +([[:digit:]]) ]] || return 1
 ((ips_version[0] < 6)) && return 1
 
-# ipset -gt v6.17 has counters flag
-if ((ips_version[0] == 6 && ips_version[1] >= 17)) || ((ips_version[0] > 6))
-then
+# ipset -ge v6.19 has counters flag
+# ipset -ge v6.20 has comment flag
+# ipset -ge v6.2? has hash:ip,mark markmask flag
+if ((ips_version[0] > 6)); then
     str_counters="counters"
+    str_bp_counters="bytes packets"
+    str_comment="comment"
+    str_markmask="markmask"
+    got_bp_proto=1
+else
+    if ((ips_version[0] == 6 && ips_version[1] >= 19)); then
+        str_counters="counters"
+        str_bp_counters="bytes packets"
+    fi
+    if ((ips_version[0] == 6 && ips_version[1] >= 20)); then
+        str_comment="comment"
+        got_bp_proto=1
+    fi
+    if ((ips_version[0] == 6 && ips_version[1] >= 21)); then
+        str_comment="comment"
+        str_markmask="markmask"
+        str_forceadd="forceadd"
+        got_bp_proto=1
+    fi
 fi
 
 # expecting _get_comp_words_by_ref() to exist from bash_completion
@@ -659,6 +916,14 @@ __ltrim_colon_completions() {
         done
     fi
 }
+
+# construct own known_hosts function from origin
+# just remove the __ltrim_colon_completions call
+# to avoid unwanted ltrim if we need to work with the list of hosts
+# ugly hack - gimme better ;p
+if ! declare -F _ipset_known_hosts &>/dev/null; then
+eval '_ipset_known_hosts() { '$(declare -f _known_hosts_real | grep -v __ltrim_colon_completions | grep -Ev "^_known_hosts_real.*$" | grep -Ev "^(\{|\})")'; }'
+fi
 fi
 
 #_DEBUG_NF_COMPLETION=Y
@@ -739,7 +1004,7 @@ case "${words[i]}" in
             order_index=$i str_order=""
         fi
     ;;
-    timeout|range|maxelem|family|hashsize|size|netmask|nomatch|counters)
+    timeout|range|maxelem|family|hashsize|size|netmask|nomatch|counters|bytes|packets|comment|markmask|forceadd)
         if ((got_action && i > action_index+2)); then
             str_tmp="$COMP_LINE"
             [[ $str_setname = ${words[i]} ]] && str_tmp="${str_tmp/${words[i]}/}"
@@ -786,6 +1051,18 @@ fi
 #        return 0
 #    ;;
 #esac
+# catch variables and command substitution
+if [[ $cur == \$\(* ]]; then # command substitution
+    COMPREPLY=( $(compgen -c -P '$(' ${cur#??}) )
+    return 0
+elif [[ $cur == \$\{* ]]; then # variables with a leading `${'
+    COMPREPLY=( $(compgen -v -P '${' -S '}' ${cur#??}) )
+    return 0
+elif [[ $cur == \$* ]]; then # variables with a leading `$'
+    COMPREPLY=( $(compgen -v -P '$' ${cur#?} ) )
+    return 0
+fi
+
 case "$prev" in # depend on previous option
     -o|-output)
         # make sure it's not a filename named -o or -output
@@ -879,32 +1156,23 @@ elif ((cword == action_index+2)) && [[ $str_setname = $prev ]]; then
         add)
             str_type=$(_ipset_get_set_type "$str_setname")
             case "$str_type" in
-                hash:ip|bitmap:ip|hash:net)
-                    # ip-list from file
-                    COMPREPLY=( $( compgen -W \
-                        '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
-                        -- "$cur" ) )
-                    if ((got_bashcompl)); then
-                        _known_hosts_real -F "$_IPSET_SSH_CONFIGS" -- "$cur"
-                    else
-                        if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
-                            COMPREPLY=( $( compgen -A hostname "$cur" ) )
-                        fi
-                        _ipset_colon_ltrim "$cur"
-                    fi
-                    if [[ $str_type = bitmap:ip,mac ]]; then
-                        compopt -o nospace
-                        ((${#COMPREPLY[@]} == 1)) && COMPREPLY=( ${COMPREPLY[*]}, )
-                    elif [[ $str_type = hash:net ]]; then
-                        COMPREPLY+=( $( compgen -W '$(_ipset_get_networks)' -- "$cur" ) )
-                        if ((${#COMPREPLY[@]} == 1)); then
-                            if [[ ${COMPREPLY[*]} != */* ]]; then
-                                compopt -o nospace
-                                COMPREPLY=( ${COMPREPLY[*]}/ )
-                            fi
-                        fi
-                        _ipset_colon_ltrim "$cur"
-                    fi
+                bitmap:ip)
+                    _ipset_complete_host_spec "$cur" --v4
+                    _ipset_colon_ltrim "$cur"
+                ;;
+                hash:ip|hash:net|hash:ip,mark)
+                    _ipset_complete_host_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
+                ;;
+                hash:net,iface)
+                    _ipset_complete_iface_spec "$cur"
+                ;;
+                hash:@(ip,port|ip,port,ip|ip,port,net|net,port|net,port,net))
+                    _ipset_complete_hostport_spec "$cur"
+                ;;
+                hash:net,net)
+                    _ipset_complete_netnet_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
                 ;;
                 bitmap:ip,mac)
                     if [[ $cur = *,* ]]; then
@@ -954,38 +1222,17 @@ elif ((cword == action_index+2)) && [[ $str_setname = $prev ]]; then
                             _ipset_colon_ltrim "$str_prefix$cur"
                         fi
                     else
-                        # ip-list from file
-                        COMPREPLY=( $( compgen -W \
-                            '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
-                            -- "$cur" ) )
-                        if ((got_bashcompl)); then
-                            _known_hosts_real -F "$_IPSET_SSH_CONFIGS" -- "$cur"
-                        else
-                            if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
-                                COMPREPLY=( $( compgen -A hostname "$cur" ) )
-                            fi
-                            _ipset_colon_ltrim "$cur"
-                        fi
                         compopt -o nospace
-                        ((${#COMPREPLY[@]} == 1)) && COMPREPLY=( ${COMPREPLY[*]}, )
+                        _ipset_complete_host_spec "$cur" --v4 --no-range
+                        _ipset_colon_ltrim "$cur"
+                        if ((${#COMPREPLY[@]} == 1)); then
+                            COMPREPLY=( ${COMPREPLY[*]}, )
+                        fi
                     fi
                 ;;
                 bitmap:port)
                     # complete port [range]
-                    str_tmp="$cur" str_var=""
-                    str_glob='[^\[]*-*'
-                    if [[ $cur = \[*-*\]-* ]]; then str_var="${cur#\[}"
-                        str_var="${str_var%%\]*}"
-                        cur="${cur#*\]-}"
-                        str_tmp=${str_tmp%"$cur"}
-                    elif [[ $cur = $str_glob ]]; then str_var="${cur%%-*}"
-                        cur="${cur#*-}"
-                        str_tmp=${str_tmp%"$cur"}
-                    else str_tmp=""
-                        compopt -o nospace
-                    fi
-                    COMPREPLY=( $( compgen -P "$str_tmp" \
-                        -W '$(_ipset_get_services -o $str_var)' -- "$cur" ) )
+                    _ipset_complete_portrange "$cur"
                 ;;
                 # show sets if the set to add is of type list:set
                 list:*) arr_tmp=() arr_sets=( $(ipset list -n) )
@@ -1000,89 +1247,220 @@ elif ((cword == action_index+2)) && [[ $str_setname = $prev ]]; then
                     COMPREPLY=( $( compgen -W '${arr_tmp[@]}' -- "$cur" ) )
                     _ipset_colon_ltrim "$cur"
                 ;;
-                hash:@(ip,port|ip,port,ip|ip,port,net|net,port))
-                    _ipset_complete_hostport_spec
-                ;;
-                hash:net,iface)
-                    _ipset_complete_iface_spec
-                ;;
             esac
         ;;
-        del|test)
+        del)
             str_type=$(_ipset_get_set_type "$str_setname")
-            if [[ $str_action = del && $str_type = bitmap:@(ip|port) ]]; then
-                # complete members
+            if [[ $str_type = bitmap:ip ]]; then
                 str_prefix=""
-                _ipset_get_members --names-only "$str_setname"
-                if [[ $cur = *-* ]]; then
-                    if [[ $str_type = bitmap:port ]]; then
-                        for i in ${!arr_members[@]}; do
-                            ((${arr_members[i]} <= ${cur%%-*})) && \
-                                unset arr_members[i] || break
-                        done
+                if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur"
+                elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+                    _ipset_complete_host_spec "$cur" --v4
+                    _ipset_colon_ltrim "$cur"
+                else
+                    _ipset_complete_host_spec "$cur" --v4
+                    _ipset_complete_elements "$cur"
+                    _ipset_colon_ltrim "$cur"
+                fi
+            elif [[ $str_type = bitmap:port ]]; then
+                str_prefix=""
+                if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur"
+                elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+                    _ipset_complete_portrange "$cur"
+                else
+                    _ipset_complete_portrange "$cur"
+                    _ipset_get_members --names-only "$str_setname"
+                    str_glob='?(tcp:|udp:)@([![]*-*|\[?*\]-*)'
+                    if [[ $cur = $str_glob ]]; then
+                        str_var="${cur#?(tcp:|udp:)}" # offset
+                        str_tmp="${cur%"$str_var"}" # proto
+                        # identify service number by name, to find the offset
+                        if [[ $str_var != +([[:digit:]]) ]]; then
+                            if [[ $str_var = \[+([![])\]-* ]]; then
+                                str_var="${str_var%%\]*}"
+                                str_var="${str_var#\[}"
+                            elif  [[ $str_var = *-* ]]; then
+                                str_var="${str_var%%-*}"
+                            fi
+                            str_var=$(_ipset_get_svnum -p "${str_tmp:-all}" -o "$str_var")
+                        fi
+                        if [[ $str_var = +([[:digit:]]) ]]; then
+                            for i in ${!arr_members[@]}; do
+                                ((${arr_members[i]} <= $str_var)) && \
+                                    unset arr_members[i] || break
+                            done
+                        fi
+                        str_prefix="${cur%-*}-" cur="${cur##*-}"
                     fi
-                    str_prefix="${cur%-*}-" cur="${cur#*-}"
+                    COMPREPLY+=( $( compgen -P "$str_prefix" -W '${arr_members[@]}' -- "$cur" ) )
+                fi
+            elif [[ $str_type = bitmap:ip,mac ]]; then
+                str_prefix=""
+                if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_colon_ltrim "$cur"
+                elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+                    _ipset_complete_host_spec "$cur" --v4 --no-range
+                    _ipset_colon_ltrim "$cur"
                 else
-                    compopt -o nospace
+                    _ipset_complete_host_spec "$cur" --v4 --no-range
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_colon_ltrim "$cur"
                 fi
-                COMPREPLY=( $( compgen -P "$str_prefix" -W '${arr_members[@]}' -- "$cur" ) )
-            elif [[ $str_action = del && $str_type = hash:@(ip,port|ip,port,ip|ip,port,net|net,port) ]]
+            elif [[ $str_type = hash:@(ip?(,mark)|net) ]]; then
+                str_prefix=""
+                if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur"
+                    _ipset_colon_ltrim "$cur"
+                elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+                    _ipset_complete_host_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
+                else
+                    _ipset_complete_host_spec "$cur"
+                    _ipset_complete_elements "$cur"
+                    _ipset_colon_ltrim "$cur"
+                fi
+            elif [[ $str_type = hash:net,net ]]; then
+                if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur"
+                    _ipset_colon_ltrim "$cur"
+                elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+                    _ipset_complete_netnet_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
+                else
+                    _ipset_complete_netnet_spec "$cur"
+                    _ipset_complete_elements "$cur"
+                    _ipset_colon_ltrim "$cur"
+                fi
+            elif [[ $str_type = hash:@(ip,port|ip,port,ip|ip,port,net|net,port|net,port,net) ]]
             then
                 if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
-                    _ipset_get_members --names-only "$str_setname"
-                    COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    _ipset_complete_elements "$cur" --no-range
                     _ipset_colon_ltrim "$cur"
                 elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
-                    _ipset_complete_hostport_spec
+                    _ipset_complete_hostport_spec "$cur"
                 else
-                    _ipset_get_members --names-only "$str_setname"
-                    _ipset_complete_hostport_spec
-                    COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_complete_hostport_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
+                fi
+            elif [[  $str_type = hash:net,iface ]]; then
+                if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_colon_ltrim "$cur"
+                elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+                    _ipset_complete_iface_spec "$cur"
+                else
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_complete_iface_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
+                fi
+            else
+                _ipset_complete_elements "$cur" --no-range
+                _ipset_colon_ltrim "$cur"
+            fi
+        ;;
+        test)
+            str_type=$(_ipset_get_set_type "$str_setname")
+            if [[ $str_type = bitmap:ip ]]; then
+                if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur" --no-range
+                elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+                    _ipset_complete_host_spec "$cur" --no-range --v4
+                else
+                    _ipset_complete_elements "$cur" --no-range
+                    if ! _ipset_complete_host_spec "$cur" --no-range --v4; then
+                        COMPREPLY=()
+                    fi
+                fi
+            elif [[ $str_type = hash:ip?(,mark) ]]; then
+                if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_colon_ltrim "$cur"
+                elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+                    _ipset_complete_host_spec "$cur" --no-range
+                    _ipset_colon_ltrim "$cur"
+                else
+                    _ipset_complete_elements "$cur" --no-range
+                    if ! _ipset_complete_host_spec "$cur" --no-range; then
+                        COMPREPLY=()
+                    fi
+                    _ipset_colon_ltrim "$cur"
+                fi
+            elif [[ $str_type = hash:net ]]; then
+                if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_colon_ltrim "$cur"
+                elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+                    _ipset_complete_host_spec "$cur"
+                    _ipset_colon_ltrim "$cur"
+                else
+                    _ipset_complete_elements "$cur" --no-range
+                    if ! _ipset_complete_host_spec "$cur"; then
+                        COMPREPLY=()
+                    fi
                     _ipset_colon_ltrim "$cur"
                 fi
-            elif [[ $str_action = test && $str_type = hash:@(ip,port|ip,port,ip|ip,port,net|net,port) ]]
+            elif [[ $str_type = hash:@(ip,port|ip,port,ip|ip,port,net|net,port|net,port,net) ]]
             then
                 if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
-                    _ipset_get_members --names-only "$str_setname"
-                    COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    _ipset_complete_elements "$cur" --no-range
                     _ipset_colon_ltrim "$cur"
                 elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
-                    _ipset_complete_hostport_spec
+                    _ipset_complete_hostport_spec "$cur"
                 else
-                    _ipset_get_members --names-only "$str_setname"
-                    _ipset_complete_hostport_spec
-                    COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_complete_hostport_spec "$cur"
                     _ipset_colon_ltrim "$cur"
                 fi
-            elif [[ $str_action = del && $str_type = hash:net,iface ]]; then
-                if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
-                    _ipset_get_members --names-only "$str_setname"
-                    COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+            elif [[ $str_type = hash:net,iface ]]; then
+                if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+                    _ipset_complete_elements "$cur" --no-range
                     _ipset_colon_ltrim "$cur"
-                elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
-                    _ipset_complete_iface_spec
+                elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+                    _ipset_complete_iface_spec "$cur"
                 else
-                    _ipset_get_members --names-only "$str_setname"
-                    _ipset_complete_iface_spec
-                    COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    _ipset_complete_elements "$cur" --no-range
+                    _ipset_complete_iface_spec "$cur"
                     _ipset_colon_ltrim "$cur"
                 fi
-            elif [[ $str_action = test && $str_type = hash:net,iface ]]; then
+            elif [[ $str_type = bitmap:port ]]; then
+                str_prefix="" str_tmp="$cur"
+                if [[ $cur = @(tcp|udp):* ]]; then
+                    ((got_bp_proto)) || return 0 # supported since ipset v6.20
+                    str_prefix="${cur%:*}"
+                    str_tmp="${str_tmp#${str_prefix}:}"
+                fi
                 if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
-                    _ipset_get_members --names-only "$str_setname"
-                    COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    _ipset_complete_elements "$cur" --no-range
                     _ipset_colon_ltrim "$cur"
                 elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
-                    _ipset_complete_iface_spec
+                    if ((got_bp_proto)); then # supported since ipset v6.20
+                        COMPREPLY=( $(compgen -W \
+                            'tcp: udp: $(_ipset_get_services -p "$str_prefix")' -- "$str_tmp" ) )
+                        [[ ${COMPREPLY[*]} = @(tcp|udp): ]] && compopt -o nospace
+                        _ipset_colon_ltrim "$cur"
+                    else # only tcp services prior to ipset v6.20
+                        COMPREPLY=( $(compgen \
+                            -W '$(_ipset_get_services -p tcp)' -- "$cur" ) )
+                    fi
                 else
-                    _ipset_get_members --names-only "$str_setname"
-                    _ipset_complete_iface_spec
-                    COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                    if ((got_bp_proto)); then # supported since ipset v6.20
+                        COMPREPLY=( $(compgen -W \
+                            'tcp: udp: $(_ipset_get_services -p "$str_prefix")' -- "$str_tmp" ) )
+                        [[ ${COMPREPLY[*]} = @(tcp|udp): ]] && compopt -o nospace
+                        _ipset_colon_ltrim "$cur"
+                    else # only tcp services prior to ipset v6.20
+                        COMPREPLY=( $(compgen \
+                            -W '$(_ipset_get_services -p tcp)' -- "$cur" ) )
+                    fi
+                    _ipset_complete_elements "$cur" --no-range
                     _ipset_colon_ltrim "$cur"
                 fi
             else
-                _ipset_get_members --names-only "$str_setname"
-                COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                _ipset_complete_elements "$cur" --no-range
                 _ipset_colon_ltrim "$cur"
             fi
         ;;
@@ -1091,49 +1469,60 @@ elif ((cword == action_index+3)) && [[ $cur != -* ]]; then
     case "$str_action" in
         add)
             str_type=$(_ipset_get_set_type "$str_setname")
-            if _ipset_set_has_timout "$str_setname"; then
+            if _ipset_set_has_option timeout "$str_setname"; then
                 str_timeout=timeout
             else
                 str_timeout=""
             fi
+            if ! _ipset_set_has_option counters "$str_setname"; then
+                str_bp_counters=""
+            fi
+            if ! _ipset_set_has_option comment "$str_setname"; then
+                str_comment=""
+            fi
             case "$str_type" in
                 hash:*net*)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts $str_timeout $str_counters nomatch)' \
+                        '$(_ipset_dedupe_cmd_opts $str_timeout $str_bp_counters $str_comment nomatch)' \
                         -- "$cur" ) )
                 ;;
                 hash:*!(net)*|bitmap:*)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts $str_timeout $str_counters)' \
+                        '$(_ipset_dedupe_cmd_opts $str_timeout $str_bp_counters $str_comment)' \
                         -- "$cur" ) )
                 ;;
                 list:*)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts $str_order $str_timeout $str_counters)' \
+                        '$(_ipset_dedupe_cmd_opts $str_order $str_timeout $str_bp_counters $str_comment)' \
                         -- "$cur" ) )
                 ;;
             esac
         ;;
         create|n)
             case "$prev" in
+                hash:ip,mark)
+                    COMPREPLY=( $( compgen -W \
+                        '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters $str_markmask $str_comment $str_forceadd)' \
+                        -- "$cur" ) )
+                ;;
                 hash:*)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters)' \
+                        '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters $str_comment $str_forceadd)' \
                         -- "$cur" ) )
                 ;;
                 bitmap:ip)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts range netmask timeout $str_counters)' \
+                        '$(_ipset_dedupe_cmd_opts range netmask timeout $str_counters $str_comment)' \
                     -- "$cur" ) )
                 ;;
                 bitmap:!(ip)?*)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts range timeout $str_counters)' \
+                        '$(_ipset_dedupe_cmd_opts range timeout $str_counters $str_comment)' \
                         -- "$cur" ) )
                 ;;
                 list:*)
                     COMPREPLY=( $( compgen -W \
-                        '$(_ipset_dedupe_cmd_opts size timeout $str_counters)' \
+                        '$(_ipset_dedupe_cmd_opts size timeout $str_counters $str_comment)' \
                         -- "$cur" ) )
                 ;;
             esac
@@ -1142,126 +1531,165 @@ elif ((cword == action_index+3)) && [[ $cur != -* ]]; then
             str_type=$(_ipset_get_set_type "$str_setname")
             if [[ $str_type = list:* ]]; then
                 COMPREPLY=( $( compgen -W '$str_order' -- "$cur" ) )
+            elif [[ $str_action = test && $str_type = hash:*net* ]]; then
+                COMPREPLY=( $( compgen -W 'nomatch' -- "$cur" ) )
             fi
         ;;
     esac
 elif ((cword == action_index+3)) && [[ $cur = -* ]]; then
     _ipset_get_options
-elif ((cword >= action_index+4)); then # add options following
-    if [[ $cur = -* && \
-        $prev != @(timeout|hashsize|size|family|maxelem|range|netmask|before|after|counters) ]]
-    then _ipset_get_options
-        return 0
+elif ((cword >= action_index+4)) && [[ $cur = -* ]]; then # add all following hyphen options
+    if [[ $prev != @(timeout|hashsize|size|family|maxelem|range|netmask|before|after|bytes|packets|comment|markmask) ]]
+    then
+        _ipset_get_options
     fi
+elif ((cword >= action_index+4)); then # add all following non-hyphen options
     case "$str_action" in
         add)
             str_type=$(_ipset_get_set_type "$str_setname")
-            if _ipset_set_has_timout "$str_setname"; then
+            if _ipset_set_has_option timeout "$str_setname"; then
                 str_timeout=timeout
             else
                 str_timeout=""
             fi
+            if ! _ipset_set_has_option counters "$str_setname"; then
+                str_bp_counters=""
+            fi
+            if ! _ipset_set_has_option comment "$str_setname"; then
+                str_comment=""
+            fi
             # validate option argument values
-            for ((x=$action_index+3; x < ${#words[@]}; x++)); do
-                [[ ${words[x]} = timeout && \
-                    ${words[x+1]} != @(+([[:digit:]])|0x+([[:xdigit:]])) ]] && return 0
-            done
+            if [[ ${_IPSET_VALIDATE_INPUT-1} ]]; then
+                for ((x=$action_index+3; x < ${#words[@]}; x++)); do
+                    [[ ${words[x]} = @(timeout|bytes|packets) && \
+                        ${words[x+1]} != @(+([[:digit:]])|0[xX]+([[:xdigit:]])) ]] && return 0
+                done
+            fi
             case "$str_type" in
                 hash:*net*)
-                    if [[ $prev != timeout ]]; then
+                    if [[ $prev != @(timeout|bytes|packets|comment) ]]; then
+                        COMPREPLY=( $( compgen -W \
+                            '$(_ipset_dedupe_cmd_opts $str_timeout $str_bp_counters $str_comment nomatch)' \
+                            -- "$cur" ) )
+                    fi
+                ;;
+                hash:*!(net)*|bitmap:*)
+                    if [[ $prev != @(timeout|bytes|packets|comment) ]]; then
                         COMPREPLY=( $( compgen -W \
-                            '$(_ipset_dedupe_cmd_opts $str_timeout $str_counters nomatch)' \
+                            '$(_ipset_dedupe_cmd_opts $str_timeout $str_bp_counters $str_comment)' \
                             -- "$cur" ) )
                     fi
                 ;;
                 list:*)
                     if [[ $prev = @(before|after) ]] && ((cword-1 == order_index)); then
-                        _ipset_get_members --names-only "$str_setname"
-                        COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+                        _ipset_complete_elements "$cur"
                         _ipset_colon_ltrim "$cur"
-                    elif [[ $prev != timeout ]]; then
+                    elif [[ $prev != @(timeout|bytes|packets) ]]; then
                         COMPREPLY=( $( compgen -W \
-                            '$(_ipset_dedupe_cmd_opts $str_order $str_timeout $str_counters)' \
+                            '$(_ipset_dedupe_cmd_opts $str_order $str_timeout $str_bp_counters $str_comment)' \
                             -- "$cur" ) )
                     fi
                 ;;
             esac
         ;;
         create|n)
-            for ((x=$action_index+3; x < ${#words[@]}; x++)); do
-                if [[ ${words[x+1]} && ${words[x+1]} != $cur ]]; then
-                    case "${words[x]}" in # validate option argument values
-                        @(hashsize|timeout|size|maxelem))
-                            [[ ${words[x+1]} != @(+([[:digit:]])|0x+([[:xdigit:]])) ]] && return 0
-                        ;;
-                        family)
-                            [[ ${words[x+1]} != inet?(6) ]] && return 0
-                        ;;
-                        range)
-                            case "$str_type" in
-                                bitmap:port)
-                                    [[ ${words[x+1]} != *-* ]] && return 0
-                                ;;
-                                *)
-                                    [[ ${words[x+1]} != @(*-*|*/+([[:digit:]])) ]] && return 0
-                                ;;
-                            esac
-                        ;;
-                    esac
-                fi
-            done
+            # validate option argument values
+            if [[ ${_IPSET_VALIDATE_INPUT-1} ]]; then
+                for ((x=$action_index+3; x < ${#words[@]}; x++)); do
+                    if [[ ${words[x+1]} && ${words[x+1]} != $cur ]]; then
+                        case "${words[x]}" in
+                            @(hashsize|timeout|size|maxelem|markmask))
+                                [[ ${words[x+1]} != @(+([[:digit:]])|0[xX]+([[:xdigit:]])) ]] && return 0
+                            ;;
+                            family)
+                                [[ ${words[x+1]} != inet?(6) ]] && return 0
+                            ;;
+                            range)
+                                case "$str_type" in
+                                    bitmap:port)
+                                        [[ ${words[x+1]} != *-* ]] && return 0
+                                    ;;
+                                    *)
+                                        [[ ${words[x+1]} != @(*-*|*/+([[:digit:]])) ]] && return 0
+                                    ;;
+                                esac
+                            ;;
+                        esac
+                    fi
+                done
+            fi
             case "${words[action_index+2]}" in # must be the set type
+                hash:ip,mark)
+                    if [[ $prev = family ]]; then
+                        COMPREPLY=( $( compgen -W \
+                            '$(_ipset_dedupe_cmd_opts inet inet6)' \
+                            -- "$cur" ) )
+                    elif [[ $prev != @(hashsize|timeout|maxelem|markmask) ]]; then
+                        COMPREPLY=( $( compgen -W \
+                            '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters $str_markmask $str_comment $str_forceadd)' \
+                            -- "$cur" ) )
+                    fi
+                ;;
                 hash:*)
                     if [[ $prev = family ]]; then
                         COMPREPLY=( $( compgen -W \
                             '$(_ipset_dedupe_cmd_opts inet inet6)' \
                             -- "$cur" ) )
-                    elif [[ $prev != @(family|hashsize|timeout|maxelem) ]]; then
+                    elif [[ $prev != @(hashsize|timeout|maxelem) ]]; then
                         COMPREPLY=( $( compgen -W \
-                            '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters)' \
+                            '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters $str_comment $str_forceadd)' \
                             -- "$cur" ) )
                     fi
                 ;;
                 bitmap:ip)
                     if [[ $prev != @(range|netmask|timeout) ]]; then
                         COMPREPLY=( $( compgen -W \
-                            '$(_ipset_dedupe_cmd_opts range netmask timeout $str_counters)' \
+                            '$(_ipset_dedupe_cmd_opts range netmask timeout $str_counters $str_comment)' \
                             -- "$cur" ) )
                     fi
                 ;;
                 bitmap:!(ip)?*)
                     if [[ $prev != @(range|timeout) ]]; then
                         COMPREPLY=( $( compgen -W \
-                            '$(_ipset_dedupe_cmd_opts range timeout $str_counters)' \
+                            '$(_ipset_dedupe_cmd_opts range timeout $str_counters $str_comment)' \
                             -- "$cur" ) )
                     fi
                 ;;
                 list:*)
                     if [[ $prev != @(size|timeout) ]]; then
                         COMPREPLY=( $( compgen -W \
-                            '$(_ipset_dedupe_cmd_opts size timeout $str_counters)' \
+                            '$(_ipset_dedupe_cmd_opts size timeout $str_counters $str_comment)' \
                             -- "$cur" ) )
                     fi
                 ;;
             esac
             if [[ ${words[action_index+2]} = bitmap:port && $prev = range ]]; then
                 # complete port ranges
-                str_prefix="$cur" str_suffix="" str_var="" str_glob='[^\[]*-*'
-                if [[ $cur = \[*-*\]-* ]]; then
-                    str_var="${cur#\[}"
-                    str_var="${str_var%%\]*}"
-                    cur="${cur#*\]-}"
-                    str_prefix=${str_prefix%"$cur"}
-                elif [[ $cur = $str_glob ]]; then
-                    str_var="${cur%%-*}"
-                    cur="${cur#*-}"
-                    str_prefix=${str_prefix%"$cur"}
-                else
-                    str_prefix="" str_suffix=-
-                    compopt -o nospace
+                _ipset_complete_portrange "$cur"
+            elif [[ ${words[action_index+2]} = bitmap:* && $prev = range ]]; then
+                str_prefix=""
+                if [[ $cur = @(""|+([[:word:]])) ]]; then # empty or [:word:]
+                    :
+                elif [[ $cur = [\[]*-*[\]] ]]; then # host with hyphen
+                    :
+                elif [[ $cur = [[]*([!]]) ]]; then # incomplete host with dash
+                    :
+                elif [[ $cur = *-[[]+([!]]) ]]; then # incomplete range - host with dash
+                    str_prefix="${cur%\-[*}-"
+                elif [[ $cur = \[*\]-* ]]; then # first part of hostname range
+                    str_prefix="${cur%\]-*}]-"
+                elif [[ $cur != *-* ]]; then # no hypen
+                    :
+                else # ip-range
+                    str_prefix="${cur%-*}-"
+                fi
+                _ipset_complete_host_spec "$cur" --v4
+                if ((${#COMPREPLY[@]} == 1)); then
+                    if [[ -z $str_prefix  && ${COMPREPLY[*]} != */* ]]; then
+                        compopt -o nospace
+                        COMPREPLY=( $( compgen -W '${COMPREPLY[*]}/ ${COMPREPLY[*]}-' -- "$cur" ) )
+                    fi
                 fi
-                COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" \
-                    -W '$(_ipset_get_services -o $str_var)' -- "$cur" ) )
             fi
         ;;
         del|test)
@@ -1327,9 +1755,6 @@ else
     fi
 fi
 fi
-if ! ((${#COMPREPLY[@]})); then # last exit brooklyn
-    [[ $cur ]] && _ipset_bash_default_compl "$cur"
-fi
 if [[ $_DEBUG_NF_COMPLETION ]]; then
     printf "COMPREPLY:\n"
     printf "<%s>\n" "${COMPREPLY[@]}"
index 480625275a2df2bdfd79c0360c9fd2dcd3ffa81e..579d3bf9cf5daa41d6cd85a7d036642d8b922d28 100644 (file)
@@ -1,7 +1,8 @@
 ipset_list
 ==========
 
-ipset set listing wrapper script
+ipset set listing wrapper script written for the bash shell.
+It allows you to match and display sets, headers and elements in various ways.
 
 
 Features:
@@ -12,10 +13,13 @@ Features:
 - Choose a delimiter character for separating members.
 - Show only sets containing a specific (glob matching) header.
 - Arithmetic comparison on headers with an integer value.
+- Arithmetic comparison on flags of the headers 'Header' field.
+- Arithmetic comparison on member options with an integer value.
 - Match members using a globbing or regex pattern.
 - Suppress listing of (glob matching) sets.
 - Suppress listing of (glob matching) headers.
 - Suppress listing of members matching a glob or regex pattern.
+- Suppress listing of members options.
 - Calculate the total size in memory of all matching sets.
 - Calculate the amount of matching, excluded and traversed sets.
 - Colorize the output.
@@ -62,5 +66,10 @@ Examples:
 - `ipset_list -c -t -Cs -Ts -Xh "@(Size*|Re*|Header):*" -Ht "!(bitmap:*)"` - find all sets not of any bitmap type, count their members sum, display only the 'Type' header, count amount of matching and traversed sets.
 - `ipset_list -Co -c -Ts -Tm`  - show all set names, count their members, count total amount of sets, show total memory usage of all sets, colorize the output
 - `ipset_list -m -r -To 0`     - show members of all sets, try to resolve hosts, set the timeout to 0 (effectivly disabling it).
+- `ipset_list -m -Xo setA`     - show members of setA, but suppress displaying of the elements options.
+- `ipset_list -m -Oi packets:0`     - show members of all sets which have a packet count of 0.
+- `ipset_list -m -Oi "packets:>100" -Oi "bytes:>1024"`     - show members of all sets which have a packet count greater than 100 and a byte count greater than 1024.
+- `ipset_list -n -Ca "foo*"`    - show only set names matching the glob "foo*" and enable all counters.
+- `ipset_list -Hi "markmask:>=0x0000beef" -Hi timeout:\!10000`    - show only sets with the header 'Header' fields containing a markmask greater or equal to 0x0000beef and a timeout which is not 10000.
 
 
index 18743a5e70484e212b5a1fed3ee8ce0d9343fdce..6db57aef177767c977e3e38128bd9becb7a40e63 100755 (executable)
@@ -7,7 +7,7 @@
 # https://sourceforge.net/projects/ipset-list/
 # -----------------------------------------------------------------
 
-# Copyright (C) 2013 AllKind (AllKind@fastest.cc)
+# Copyright (C) 2013-2014 AllKind (AllKind@fastest.cc)
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -23,8 +23,9 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 # -----------------------------------------------------------------
+# Compatible with ipset version 6+
 # Tested with ipset versions:
-# 6.16.1
+# 6.16.1, 6.20.1
 # -----------------------------------------------------------------
 
 # -----------------------------------------------------------------
@@ -34,6 +35,8 @@
 # - Choose a delimiter character for separating members.
 # - Show only sets containing a specific (glob matching) header.
 # - Arithmetic comparison on headers with an integer value.
+# - Arithmetic comparison on flags of the headers 'Header' field.
+# - Arithmetic comparison on member options with an integer value.
 # - Match members using a globbing or regex pattern.
 # - Suppress listing of (glob matching) sets.
 # - Suppress listing of (glob matching) headers.
@@ -42,6 +45,7 @@
 # - Calculate the amount of matching, excluded and traversed sets.
 # - Colorize the output.
 # - Operate on a single, selected, or all sets.
+# - Programmable completion is included to make usage easier and faster.
 # -----------------------------------------------------------------
 
 # -----------------------------------------------------------------
@@ -58,7 +62,7 @@
 # $0 -m -r -s setA     - show members of setA resolved and sorted
 # $0 -Ts               - show all set names and total count of sets.
 # $0 -Tm               - calculate total size in memory of all sets.
-# $0 -Mc 0             - show sets with zero members 
+# $0 -Mc 0             - show sets with zero members
 # $0 -Fi References:0  - show all sets with 0 references
 # $0 -Hr 0             - shortcut for `-Fi References:0'
 # $0 -Xs setA -Xs setB - show all set names, but exclude setA and setB.
@@ -91,7 +95,7 @@
 # $0 -c -m -Xg "210.*" setA - show members of setA, but suppress listing of entries
 #+ matching the glob pattern "210.*", show count of excluded and total members.
 #
-# $0 -t -Tm -Xh "@(Type|Re*|Header):*" 
+# $0 -t -Tm -Xh "@(Type|Re*|Header):*"
 #+ show all sets headers, but suppress all but name and memsize entry,
 #+ calculate the total memory size of all sets.
 #
 #
 # $0 -m -r -To 0       - show members of all sets, try to resolve hosts,
 # set the timeout to 0 (effectivly disabling it).
+#
+# $0 -m -Xo setA       - show members of setA,
+# + but suppress displaying of the elements options.
+#
+# $0 -m -Oi packets:0
+# + show members of all sets which have a packet count of 0.
+#
+# $0 -m -Oi "packets:>100" -Oi "bytes:>1024"
+# + show members of all sets which have a
+# + packet count greater than 100 and a byte count greater than 1024.
+#
+# $0 -n -Ca "foo*"
+# + show only set names matching the glob "foo*" and enable all counters.
+#
+# $0 -Hi "markmask:>=0x0000beef" -Hi timeout:\!10000`
+# + show only sets with the header 'Header' fields containing a markmask
+# + greater or equal to 0x0000beef and a timeout which is not 10000.
 # -----------------------------------------------------------------
 
 # -----------------------------------------------------------------
 # Modify here
 # -----------------------------------------------------------------
 
-# path to ipset. defaults to `/sbin/ipset' if unset.
-ipset="/sbin/ipset"
+# modify your PATH variable
+# by default the path is only set if the PATH variable is not already set in the environment
+# PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+: ${PATH:=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
+
+# path to ipset.
+# defaults to `/sbin/ipset' if unset.
+#ipset="/sbin/ipset"
+# find in path if not declared in parent environment
+: ${ipset:=$(command -v ipset)}
 
 # default delimiter character for set members (elements).
 # defaults to whitespace if unset.
@@ -136,6 +165,7 @@ TMOUT=30
 colorize=0
 
 # path to cl (to colorize the output).
+# http://sourceforge.net/projects/colorize-shell/ or
 # https://github.com/AllKind/cl
 # defaults to `/usr/local/bin/cl' if unset.
 cl="/usr/local/bin/cl"
@@ -198,18 +228,21 @@ set +u
 
 # variables
 export LC_ALL=C
-readonly version=2.7
+readonly version=3.1
 readonly me="${0//*\//}"
 readonly oIFS="$IFS"
-declare ips_version="" str_search="" str_xclude="" opt str_hval str_op
+declare ips_version="" str_search="" str_xclude="" opt str_name str_val str_op
 declare -i show_all=show_count=show_members=headers_only=names_only=isolate=calc_mem=count_sets=sets_total=0
-declare -i match_on_header=glob_search=regex_search=member_count=match_count=do_count=0
-declare -i exclude_header=glob_xclude_element=glob_xclude_element=exclude_set=0
-declare -i in_header=found_set=found_hxclude=found_sxclude=xclude_count=mem_total=mem_tmp=set_count=sets_sum=i=x=idx=0
-declare -a arr_sets=() arr_par=() arr_hcache=() arr_mcache=() arr_hsearch=()
-declare -a arr_hsearch_int=() arr_hxclude=() arr_sxclude=() arr_match_on_msum=()
+declare -i match_on_header=glob_search=regex_search=member_count=match_count=do_count=opt_int_search=0
+declare -i exclude_header=glob_xclude_element=glob_xclude_element=exclude_set=xclude_member_opts=0
+declare -i in_header=found_set=found_member_opt=found_hxclude=found_sxclude=xclude_count=mem_total=mem_tmp=set_count=sets_sum=i=x=y=idx=0
+declare -a arr_sets=() arr_par=() arr_hcache=() arr_mcache=() arr_hsearch=() arr_tmp=()
+declare -a arr_hsearch_int=() arr_hsearch_xint=() arr_hxclude=() arr_sxclude=() arr_match_on_msum=() arr_opt_int_search=()
 
+# -----------------------------------------------------------------
 # functions
+# -----------------------------------------------------------------
+
 ex_miss_optarg() {
 printf "%s of option \`%s' is missing\n" "$2" "$1" >&2
 exit 2
@@ -224,9 +257,68 @@ is_int() {
 [[ $1 = +([[:digit:]]) ]]
 }
 
+is_digit_or_xigit() {
+[[ $1 = @(+([[:digit:]])|0x+([[:xdigit:]])) ]]
+}
+
 is_compare_str() {
 [[ $1 = ?(\!|<|>|<=|>=)+([[:digit:]]) ]]
 }
+
+add_search_to_member_cache() {
+if ((show_members || show_all || isolate)); then
+       arr_mcache[i++]="$REPLY"
+fi
+}
+
+arith_elem_opt_search() {
+found_member_opt=0
+for y in ${!arr_opt_int_search[@]}; do
+       str_val="${arr_opt_int_search[y]#*:}"
+       str_op="${str_val//[[:digit:]]}" # compare operator defaults to `=='
+       [[ ${str_op:===} = \! ]] && str_op='!='
+       set -- $REPLY
+       shift
+       while (($# > 1)); do
+               if [[ $1 = ${arr_opt_int_search[y]%:*} ]]; then
+                       if is_int "${str_val//[[:punct:]]}"; then
+                               if (($2 $str_op ${str_val//[[:punct:]]})); then
+                                       let found_member_opt+=1
+                                       shift
+                               fi
+                       fi
+               fi
+               shift
+       done
+done
+if ((opt_int_search == found_member_opt)); then
+       let match_count+=1
+       add_search_to_member_cache
+fi
+}
+
+xclude_elem_search() {
+if ((glob_xclude_element)); then # exclude matching members
+       if [[ $REPLY = $str_xclude ]]; then
+               let xclude_count+=1
+               return 0
+       fi
+elif ((regex_xclude_element)); then # exclude matching members
+       if [[ $REPLY =~ $str_xclude ]]; then
+               let xclude_count+=1
+               return 0
+       else
+               if (($? == 2)); then
+                       printf "Invalid regex pattern \`%s'.\n" "$str_xclude" >&2
+                       exit 1
+               fi
+       fi
+fi
+return 1
+}
+
+# -----------------------------------------------------------------
+# main
 # -----------------------------------------------------------------
 
 # validate value of colorize
@@ -238,36 +330,47 @@ fi
 while (($#)); do
        case "$1" in
                -\?|-h) printf "\n\tipset set listing wrapper script\n\n"
-                       printf '%s [option [opt-arg]] [set-name] [...]\n\n' "$me"
-                       printf '%s %s\n' "$me" "{-?|-h} | -n"
-                       printf '%s %s\n\t%s\n' "$me" "[-i|-r|-s|-Co] [-d char] [-To value]"\
-                               "[{-Fg|-Fr}|{-Xg|-Xr} pattern] -- set-name"
-                       printf '%s %s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n' "$me"\
-                               "[-t|-c|-Ca|-Co|-Cs|-Tm|-Ts]"\
-                               "[-Fh header-glob:value-glob] [...]"\
-                               "[-Fi header-glob:[!|<|>|<=|>=]value] [...]"\
-                               "[-Fg|-Fr pattern] [-Ht type-glob]"\
-                               "[-Hr|-Hs|-Hv [!|<|>|<=|>=]value]"\
-                               "[-Mc [!|<|>|<=|>=]value] [...] [-To value]"\
-                               "[-Xh header-glob:value-glob] [...]"\
-                               "[-Xs setname-glob] [...] -- [set-name] [...]"
-                       printf '%s %s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n' "$me"\
-                               "[-a|-c|-m|-r|-s|-Ca|-Co|-Cs|-Tm|-Ts] [-d char]"\
-                               "[-Fh header-glob:value-glob] [...]"\
-                               "[-Fi header-glob:[!|<|>|<=|>=]value] [...]"\
-                               "[-Fg|-Fr pattern] [-Ht type-glob]"\
-                               "[-Hr|-Hs|-Hv [!|<|>|<=|>=]value]"\
-                               "[-Mc [!|<|>|<=|>=]value] [...] [-To value]"\
+                       printf '%s [option [opt-arg]] [set-name-glob] [...]\n\n' "$me"
+                       printf '%s %s\n' "$me" "{-?|-h} | -v"
+                       printf '%s %s\n\t%s\n\t%s\n' "$me"\
+                               "[-i|-r|-s|-Co|-Xo] [-d char] [-To value]"\
+                               "[-Fg|-Fr pattern] [-Xg|-Xr pattern]"\
+                               "[-Oi option-glob:[!|<|>|<=|>=]value] [...] -- set-name"
+                       printf '%s %s\n\t%s\n\t%s\n\t%s\n' "$me"\
+                               "[-n|-c|-Ca|-Co|-Cs|-Tm|-Ts|-Xs] [-To value]"\
+                               "[-Fh header-glob:value-glob] [...] [-Fg|-Fr pattern]"\
+                               "[-Hi glob:[!|<|>|<=|>=]value] [...]"\
+                               "[-Oi option-glob:[!|<|>|<=|>=]value] [...] -- [set-name-glob] [...]"
+                       printf '%s %s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n' "$me"\
+                               "[-t|-c|-Ca|-Co|-Cs|-Tm|-Ts]"\
+                               "[-Fh header-glob:value-glob] [...]"\
+                               "[-Fi header-glob:[!|<|>|<=|>=]value] [...]"\
+                               "[-Fg|-Fr pattern] [-Ht type-glob]"\
+                               "[-Hi glob:[!|<|>|<=|>=]value] [...]"\
+                               "[-Hr|-Hs|-Hv [!|<|>|<=|>=]value]"\
+                               "[-Mc [!|<|>|<=|>=]value] [...] [-To value]"\
+                               "[-Oi option-glob:[!|<|>|<=|>=]value] [...]"\
                                "[-Xh header-glob:value-glob] [...]"\
-                               "[-Xg|-Xr pattern] [-Xs setname-glob] [...] -- [set-name] [...]"
+                               "[-Xs set-name-glob] [...] -- [set-name-glob] [...]"
+                       printf '%s %s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n' "$me"\
+                               "[-a|-c|-m|-r|-s|-Ca|-Co|-Cs|-Tm|-Ts|-Xo] [-d char]"\
+                               "[-Fh header-glob:value-glob] [...]"\
+                               "[-Fi header-glob:[!|<|>|<=|>=]value] [...]"\
+                               "[-Fg|-Fr pattern] [-Ht type-glob]"\
+                               "[-Hi glob:[!|<|>|<=|>=]value] [...]"\
+                               "[-Hr|-Hs|-Hv [!|<|>|<=|>=]value]"\
+                               "[-Mc [!|<|>|<=|>=]value] [...]"\
+                               "[-Oi option-glob:[!|<|>|<=|>=]value] [...]"\
+                               "[-To value] [-Xh header-glob:value-glob] [...]"\
+                               "[-Xg|-Xr pattern] [-Xs set-name-glob] [...] -- [set-name-glob] [...]"
                        printf 'options:\n'
                        printf '%-13s%s\n' '-a' 'show all information but with default delim (whitespace).'\
-                               '-c' 'calculate members and match (-Fg|-Fr) sum.'\
+                               '-c' 'calculate members and match sum.'\
                                '-d delim' 'delimiter character for separating member entries.'\
                                '-h|-?' 'show this help text.'\
                                '-i' 'show only the members of a single set.'\
                                '-m' 'show set members.'\
-                               '-n' "show set names only (raw \`ipset list -n' output)."\
+                               '-n' "show set names only."\
                                '-r' 'try to resolve ip addresses in the output (slow!).'\
                                '-s' 'print elements sorted (if supported by the set type).'\
                                '-t' 'show set headers only.'\
@@ -281,14 +384,18 @@ while (($#)); do
                                'show sets containing one or more [ext]glob matching headers.'
                        printf '%s\n\t%s\n' '-Fi header-glob:[!|<|>|<=|>=]value [...]'\
                                'show sets matching one or more integer valued header entries.'
+                       printf '%s\n\t%s\n' '-Hi header-glob:[!|<|>|<=|>=]value [...]'\
+                               "match one or more integer valued headers \`Header' entries."
                        printf '%-24s%s\n' '-Ht set-type-glob' 'match on set type.'\
                                '-Hr [!|<|>|<=|>=]value' 'match on number of references (value=int).'\
                                '-Hs [!|<|>|<=|>=]value' 'match on size in memory (value=int).'\
                                '-Hv [!|<|>|<=|>=]value' 'match on revision number (value=int).'
                        printf '%-30s%s\n' '-Mc [!|<|>|<=|>=]value [...]' 'match on member count (value=int).'
+                       printf '%s\n\t%s\n' '-Oi option-glob:[!|<|>|<=|>=]value [...]' 'match on member options (value=int).'
                        printf '%-13s%s\n' '-Tm' 'calculate total memory usage of all matching sets.'\
                                '-To' 'set timeout value (int) for read (listing sets).'\
-                               '-Ts' 'count amount of traversed sets.'
+                               '-Ts' 'count amount of traversed sets.'\
+                               '-Xo' 'suppress display of member options.'
                        printf '%s\n\t%s\n' '-Xh header-glob:value-glob [...]'\
                                'exclude one or more [ext]glob matching header entries.'
                        printf '%-13s%s\n' '-Xg pattern' 'exclude members matching a [ext]glob pattern.'\
@@ -352,6 +459,15 @@ while (($#)); do
                        str_search="$2"
                        shift 2
                ;;
+               -Oi) let opt_int_search+=1
+                       [[ $2 ]] || ex_miss_optarg $1 "pattern"
+                       if [[ $2 = *:* ]] && is_compare_str "${2#*:}"; then
+                               arr_opt_int_search[y++]="$2"
+                               shift 2
+                       else
+                               ex_invalid_usage "invalid format of header descriptor. expecting: \`glob:[!|<|>|<=|>=]value'"
+                       fi
+               ;;
                -Fh) let match_on_header+=1 # show only sets, which contain a matching header entry
                        [[ $2 ]] || ex_miss_optarg $1 "header pattern"
                        if [[ $2 = *:* ]]; then
@@ -363,13 +479,22 @@ while (($#)); do
                ;;
                -Fi) let match_on_header+=1 # show only sets, containing a matching (int compare) header entry
                        [[ $2 ]] || ex_miss_optarg $1 "header pattern"
-                       if is_compare_str "$2"; then
+                       if [[ $2 = *:* ]] && is_compare_str "${2#*:}"; then
                                arr_hsearch_int[idx++]="$2"
                                shift 2
                        else
                                ex_invalid_usage "invalid format of header descriptor. expecting: \`name:[!|<|>|<=|>=]value'"
                        fi
                ;;
+               -Hi) let match_on_header+=1 # match on name + integer (digit & xdigit) inside of headers 'Header' flag
+                       [[ $2 ]] || ex_miss_optarg $1 "header pattern"
+                       if [[ $2 = *:?(\!|<|>|<=|>=)@(+([[:digit:]])|0x+([[:xdigit:]])) ]]; then
+                               arr_hsearch_xint[${#arr_hsearch_xint[@]}]="$2"
+                               shift 2
+                       else
+                               ex_invalid_usage "invalid format of headers \'Header' flag descriptor. expecting: \`name:[!|<|>|<=|>=]value'"
+                       fi
+               ;;
                -Hr) let match_on_header+=1 # shortcut for -Fi References:...
                        [[ $2 ]] || ex_miss_optarg $1 "header pattern"
                        if is_compare_str "$2"; then
@@ -445,6 +570,9 @@ while (($#)); do
                        str_xclude="$2"
                        shift 2
                ;;
+               -Xo) xclude_member_opts=1 # don't show elements options
+                       shift
+               ;;
                -Xs) exclude_set=1 # don't show certain sets
                        [[ $2 ]] || ex_miss_optarg $1 "set name ([ext]glob pattern)"
                        arr_sxclude[${#arr_sxclude[@]}]="$2"
@@ -460,7 +588,7 @@ while (($#)); do
                *) break
        esac
 done
-declare -i i=x=idx=0
+declare -i i=x=y=idx=0
 
 # check for ipset program and version
 [[ -x ${ipset:=/sbin/ipset} ]] || {
@@ -486,24 +614,25 @@ fi
 
 # option logic
 if ((names_only)); then
-       if ((headers_only||show_count||show_members||show_all||isolate||\
-               match_on_header||do_count||glob_search||regex_search||calc_mem||\
-               glob_xclude_element||regex_xclude_element||count_sets||sets_total||exclude_set))
+       if ((headers_only||show_members||show_all||isolate||\
+               glob_xclude_element||regex_xclude_element||xclude_member_opts))
        then
-               ex_invalid_usage "option -n does not allow another option"
+               ex_invalid_usage "option -n does not allow this combination of options"
        fi
-       # raw ipset output
-       "$ipset" list -n
-       exit $?
 fi
 if ((headers_only)); then
        if ((show_members || show_all || isolate)); then
                ex_invalid_usage "options -t and -a|-i|-m are mutually exclusive"
        fi
 fi
+if ((headers_only)); then
+       if ((xclude_member_opts||glob_xclude_element||regex_xclude_element)); then
+               ex_invalid_usage "options -t and -Xg|-Xr|-Xo are mutually exclusive"
+       fi
+fi
 if ((isolate)); then
        if ((show_count||show_all||calc_mem||count_sets||sets_total||exclude_set)); then
-               ex_invalid_usage "options -i and -a|-c|-Cs|-Tm|-Ts|-Xs are mutually exclusive"
+               ex_invalid_usage "options -i and -a|-c|-Ca|-Cs|-Tm|-Ts|-Xs are mutually exclusive"
        fi
        if ((match_on_header)); then
                ex_invalid_usage "option -i does not allow matching on header entries"
@@ -513,9 +642,6 @@ if ((glob_search || regex_search)); then
        if ((glob_search && regex_search)); then
                ex_invalid_usage "options -Fg and -Fr are mutually exclusive"
        fi
-       if ((glob_xclude_element || regex_xclude_element)); then
-               ex_invalid_usage "options -Fg|-Fr and -Xg|-Xr are mutually exclusive"
-       fi
 fi
 if ((exclude_header)); then
        if ! ((headers_only || show_all)); then
@@ -524,7 +650,7 @@ if ((exclude_header)); then
 fi
 if ((glob_xclude_element || regex_xclude_element)); then
        if ! ((show_members || show_all || isolate)); then
-               ex_invalid_usage "options -Fg|-Fr require any of -a|-i|-m"
+               ex_invalid_usage "options -Xg|-Xr require any of -a|-i|-m"
        fi
 fi
 if ((colorize)); then
@@ -551,7 +677,7 @@ if ((colorize)); then
        for opt in col_fg col_bg col_headers col_members col_match col_memsize \
                col_set_count col_set_total col_highlight
        do
-               ($cl ${!opt}) || ex_invalid_usage "variable \`$opt' has an invalid color value: \`${!opt}'"
+               ("$cl" ${!opt}) || ex_invalid_usage "variable \`$opt' has an invalid color value: \`${!opt}'"
        done
        [[ -t 1 ]] || colorize=0 # output is not a terminal
 fi
@@ -565,10 +691,13 @@ if ! ((${#arr_sets[@]})); then
        exit 1
 fi
 if [[ $1 ]]; then # there are remaining arg(s)
-       for opt in "$@"; do found_set=0 # check if the sets exist
+       for opt; do found_set=0 # check if the sets exist
                for idx in ${!arr_sets[@]}; do
-                       if [[ $opt = ${arr_sets[idx]} ]]; then found_set=1
-                               break
+                       if [[ ${arr_sets[idx]} = $opt ]]; then found_set=1
+                               # match could be a glob, thus multiple matches possible
+                               # save to temp array
+                               arr_tmp[${#arr_tmp[@]}]="${arr_sets[idx]}"
+                               unset arr_sets[idx]
                        fi
                done
                if ! ((found_set)); then
@@ -580,7 +709,10 @@ if [[ $1 ]]; then # there are remaining arg(s)
                        ex_invalid_usage "option -i is only valid for a single set"
                fi
        fi
-       arr_sets=("$@") # reassign remaining args
+       arr_sets=("${arr_tmp[@]}") # reassign matched sets
+       if ((isolate && ${#arr_sets[@]} > 1)); then
+               ex_invalid_usage "option -i is only valid for a single set"
+       fi
 else
        if ((isolate)); then
                ex_invalid_usage "option -i is only valid for a single set"
@@ -589,7 +721,7 @@ fi
 
 # read sets
 for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
-       while read -r || { 
+       while read -r || {
                (($? > 128)) && \
                        printf "timeout reached or signal received, while reading set \`%s'.\n" \
                        "${arr_sets[idx]}" >&2 && continue 2;
@@ -603,26 +735,32 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                fi
                                let sets_sum+=1
                                if ((exclude_set)); then # don't show certain sets
-                                       for x in ${!arr_sxclude[@]}; do
-                                               if [[ ${arr_sets[idx]} = ${arr_sxclude[x]} ]]; then let found_sxclude+=1
+                                       for y in ${!arr_sxclude[@]}; do
+                                               if [[ ${arr_sets[idx]} = ${arr_sxclude[y]} ]]; then let found_sxclude+=1
                                                        continue 3 # don't unset, as user could list sets multiple times
                                                fi
                                        done
                                fi
-                               in_header=1 found_set=1 found_header=0 member_count=0 match_count=0 xclude_count=0 mem_tmp=0 i=0 x=0 
+                               in_header=1 found_set=1 found_header=0 member_count=0 match_count=0 xclude_count=0 mem_tmp=0 i=0 x=0
                                if ! ((isolate)); then # if showing members only, continue without saving any header data
-                                       if ! ((headers_only||show_members||show_all||show_count||match_on_header||do_count||calc_mem||glob_search||regex_search))
+                                       if ((names_only)); then
+                                               if ((colorize)); then
+                                                       arr_hcache[x++]="$("$cl" bold $col_headers)${REPLY#*:+([[:blank:]])}$("$cl" normal $col_fg $col_bg)"
+                                               else
+                                                       arr_hcache[x++]="${REPLY#*:+([[:blank:]])}"
+                                               fi
+                                       elif ! ((headers_only||show_members||show_all||show_count||match_on_header||do_count||calc_mem||glob_search||regex_search||opt_int_search))
                                        then
-                                               in_header=0 
+                                               in_header=0
                                                if ((colorize)); then
-                                                       arr_hcache[x++]="$($cl bold $col_headers)${REPLY}$($cl normal $col_fg $col_bg)"
+                                                       arr_hcache[x++]="$("$cl" bold $col_headers)${REPLY}$("$cl" normal $col_fg $col_bg)"
                                                else
                                                        arr_hcache[x++]="$REPLY"
                                                fi
                                                break # nothing to show but the names
                                        else
                                                if ((colorize)); then
-                                                       arr_hcache[x++]=$'\n'"$($cl bold $col_headers)${REPLY}$($cl normal $col_fg $col_bg)"
+                                                       arr_hcache[x++]=$'\n'"$("$cl" bold $col_headers)${REPLY}$("$cl" normal $col_fg $col_bg)"
                                                else
                                                        arr_hcache[x++]=$'\n'"$REPLY"
                                                fi
@@ -641,8 +779,8 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                        fi
                                fi
                                if ((exclude_header)); then # don't show certain headers
-                                       for idx in ${!arr_hxclude[@]}; do
-                                               if [[ ${REPLY%%:*} = ${arr_hxclude[idx]%%:*} && ${REPLY#*: } = ${arr_hxclude[idx]#*:} ]]
+                                       for y in ${!arr_hxclude[@]}; do
+                                               if [[ ${REPLY%%:*} = ${arr_hxclude[y]%%:*} && ${REPLY#*: } = ${arr_hxclude[y]#*:} ]]
                                                then found_hxclude=1
                                                        break
                                                fi
@@ -650,7 +788,7 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                fi
                                if ((show_all && ! found_hxclude)); then
                                        if ((colorize)); then
-                                               arr_hcache[x++]="$($cl bold $col_headers)${REPLY}$($cl normal $col_fg $col_bg)"
+                                               arr_hcache[x++]="$("$cl" bold $col_headers)${REPLY}$("$cl" normal $col_fg $col_bg)"
                                        else
                                                arr_hcache[x++]="$REPLY"
                                        fi
@@ -663,25 +801,56 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                fi
                                if ((in_header)); then # we should be in the header
                                        if ((match_on_header && found_header < match_on_header)); then # match on an header entry
-                                               for idx in ${!arr_hsearch[@]}; do # string compare
-                                                       if [[ ${REPLY%%:*} = ${arr_hsearch[idx]%%:*} && ${REPLY#*: } = ${arr_hsearch[idx]#*:} ]]
+                                               for y in ${!arr_hsearch[@]}; do # string compare
+                                                       if [[ ${REPLY%%:*} = ${arr_hsearch[y]%%:*} && ${REPLY#*: } = ${arr_hsearch[y]#*:} ]]
                                                        then let found_header+=1
                                                        fi
                                                done
-                                               for idx in ${!arr_hsearch_int[@]}; do # int compare
-                                                       if [[ ${REPLY%%:*} = ${arr_hsearch_int[idx]%%:*} ]]; then # header name matches
+                                               for y in ${!arr_hsearch_int[@]}; do # int compare
+                                                       if [[ ${REPLY%%:*} = ${arr_hsearch_int[y]%%:*} ]]; then # header name matches
                                                                if ! is_int "${REPLY#*: }"; then
                                                                        printf "header value \`%s' is not an integer.\n" "${REPLY#*: }" >&2
                                                                        exit 1
                                                                fi
-                                                               str_hval="${arr_hsearch_int[idx]#*:}"
-                                                               str_op="${str_hval//[[:digit:]]}" # compare operator defaults to `=='
+                                                               str_val="${arr_hsearch_int[y]#*:}"
+                                                               str_op="${str_val//[[:digit:]]}" # compare operator defaults to `=='
                                                                [[ ${str_op:===} = \! ]] && str_op='!='
-                                                               if ((${REPLY#*: } $str_op ${str_hval//[[:punct:]]})); then
+                                                               if ((${REPLY#*: } $str_op ${str_val//[[:punct:]]})); then
                                                                        let found_header+=1
                                                                fi
                                                        fi
                                                done
+                                               # search and arithmetic compare values of the headers 'Header' flag
+                                               if ((${#arr_hsearch_xint[@]})) && [[ ${REPLY%%:*} = Header ]]; then
+                                                       set -- ${REPLY#*:}
+                                                       while (($#)); do
+                                                               if is_digit_or_xigit "$1"; then
+                                                                       shift
+                                                                       continue
+                                                               fi
+                                                               for y in ${!arr_hsearch_xint[@]}; do
+                                                                       str_name="${arr_hsearch_xint[y]%%:*}"
+                                                                       str_val="${arr_hsearch_xint[y]#*:}"
+                                                                       if [[ $str_val = ??0x+([[:xdigit:]]) ]]; then
+                                                                               str_op="${str_val%0x*}"
+                                                                       elif [[ $str_val = ??+([[:digit:]]) ]]; then
+                                                                               str_op="${str_val//[[:digit:]]}"
+                                                                       fi
+                                                                       str_val="${str_val#"${str_op}"}"
+                                                                       [[ ${str_op:===} = \! ]] && str_op='!='
+                                                                       if [[ $1 = $str_name ]]; then
+                                                                               if is_digit_or_xigit "$2"; then
+                                                                                       if (($2 $str_op $str_val)); then
+                                                                                               let found_header+=1
+                                                                                               shift
+                                                                                               break
+                                                                                       fi
+                                                                               fi
+                                                                       fi
+                                                               done
+                                                               shift
+                                                       done
+                                               fi
                                        fi
                                        if ((calc_mem)); then
                                                if [[ ${REPLY%%:*} = "Size in memory" ]]; then
@@ -695,8 +864,8 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                        fi
                                        if ((headers_only || show_all)); then found_hxclude=0
                                                if ((exclude_header)); then # don't show certain headers
-                                                       for idx in ${!arr_hxclude[@]}; do
-                                                               if [[ ${REPLY%%:*} = ${arr_hxclude[idx]%%:*} && ${REPLY#*: } = ${arr_hxclude[idx]#*:} ]]
+                                                       for y in ${!arr_hxclude[@]}; do
+                                                               if [[ ${REPLY%%:*} = ${arr_hxclude[y]%%:*} && ${REPLY#*: } = ${arr_hxclude[y]#*:} ]]
                                                                then found_hxclude=1
                                                                        break
                                                                fi
@@ -707,28 +876,42 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                                fi
                                        fi
                                else # this should be a member entry
-                                       if ((show_members || show_all || isolate || glob_search || regex_search)); then
-                                               if ((glob_search)); then # show sets with matching members
-                                                       if [[ $REPLY = $str_search ]]; then let match_count+=1
-                                                               if ((show_members || show_all || isolate)); then
-                                                                       arr_mcache[i++]="$REPLY"
+                                       if ((show_members || show_all || isolate || glob_search || regex_search || opt_int_search)); then
+                                               if ((glob_search)); then # show sets with glob pattern matching members
+                                                       if ! xclude_elem_search; then
+                                                               if [[ $REPLY = $str_search ]]; then
+                                                                       if ((opt_int_search)); then
+                                                                               arith_elem_opt_search
+                                                                       else
+                                                                               let match_count+=1
+                                                                               add_search_to_member_cache
+                                                                       fi
                                                                fi
                                                        fi
-                                               elif ((regex_search)); then # show sets with matching members
-                                                       if [[ $REPLY =~ $str_search ]]; then let match_count+=1
-                                                               if ((show_members || show_all || isolate)); then
-                                                                       arr_mcache[i++]="$REPLY"
-                                                               fi
-                                                       else
-                                                               if (($? == 2)); then
-                                                                       printf "Invalid regex pattern \`%s'.\n" "$str_search"
-                                                                       exit 1
+                                               elif ((regex_search)); then # show sets with regex pattern matching members
+                                                       if ! xclude_elem_search; then
+                                                               if [[ $REPLY =~ $str_search ]]; then
+                                                                       if ((opt_int_search)); then
+                                                                               arith_elem_opt_search
+                                                                       else
+                                                                               let match_count+=1
+                                                                               add_search_to_member_cache
+                                                                       fi
+                                                               else
+                                                                       if (($? == 2)); then
+                                                                               printf "Invalid regex pattern \`%s'.\n" "$str_search" >&2
+                                                                               exit 1
+                                                                       fi
                                                                fi
                                                        fi
+                                               elif ((opt_int_search)); then # show sets with matching member options
+                                                       if ! xclude_elem_search; then
+                                                               arith_elem_opt_search
+                                                       fi
                                                else
                                                        if ((glob_xclude_element)); then # exclude matching members
                                                                if ! [[ $REPLY = $str_xclude ]]; then
-                                                                       arr_mcache[i++]="$REPLY"
+                                                                       add_search_to_member_cache
                                                                else let xclude_count+=1
                                                                fi
                                                        elif ((regex_xclude_element)); then # exclude matching members
@@ -736,10 +919,10 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                                                                        let xclude_count+=1
                                                                else
                                                                        if (($? == 2)); then
-                                                                               printf "Invalid regex pattern \`%s'.\n" "$str_xclude"
+                                                                               printf "Invalid regex pattern \`%s'.\n" "$str_xclude" >&2
                                                                                exit 1
                                                                        fi
-                                                                       arr_mcache[i++]="$REPLY"
+                                                                       add_search_to_member_cache
                                                                fi
                                                        else
                                                                arr_mcache[i++]="$REPLY"
@@ -755,10 +938,10 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                esac
        done < <("$ipset" list "${arr_sets[idx]}" "${arr_par[@]}")
        if ((found_set)); then # print gathered information
-               if ((glob_search || regex_search)) && ((match_count == 0)); then
-                       continue # glob or regex search didn't match
+               if ((glob_search || regex_search || opt_int_search)) && ((match_count == 0)); then
+                       continue # glob, regex or option-integer search didn't match
                fi
-               if ((${#arr_match_on_msum[@]} > 0)); then # match on member sum
+               if ((${#arr_match_on_msum[@]} > 0)); then # match on member sum (do_count=1)
                        for i in ${!arr_match_on_msum[@]}; do
                                str_op="${arr_match_on_msum[i]//[[:digit:]]}"
                                [[ ${str_op:===} = \! ]] && str_op='!='
@@ -771,64 +954,69 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
                if ((calc_mem)); then
                        let mem_total+=$mem_tmp
                fi
-               if ((${#arr_hcache[@]})); then
+               if ((${#arr_hcache[@]})); then # print header
                        if ((colorize)); then
-                               printf "$($cl $col_headers)%b$($cl normal $col_fg $col_bg)\n" "${arr_hcache[@]}"
+                               printf "$("$cl" $col_headers)%b$("$cl" normal $col_fg $col_bg)\n" "${arr_hcache[@]}"
                        else
                                printf "%s\n" "${arr_hcache[@]}"
                        fi
                fi
-               if ((${#arr_mcache[@]})); then
+               if ((${#arr_mcache[@]})); then # print members
+                       if ((xclude_member_opts)); then
+                               arr_mcache=( "${arr_mcache[@]%% *}" )
+                       fi
                        IFS="${delim:= }"
                        if ((colorize)); then
-                               printf "$($cl $col_members)%s$($cl normal $col_fg $col_bg)" "${arr_mcache[*]}"
+                               printf "$("$cl" $col_members)%s$("$cl" normal $col_fg $col_bg)" "${arr_mcache[*]}"
                        else
                                printf "%s" "${arr_mcache[*]}"
                        fi
                        IFS="$oIFS"
                        printf "\n"
                fi
-               if ((show_count)); then
-                       if ((glob_search || regex_search)); then
+               if ((show_count)); then # print counters
+                       if ((glob_search || regex_search || opt_int_search)); then
                                if ((colorize)); then
-                                       printf "$($cl $col_match)Match count$($cl normal $col_fg $col_bg):\
- $($cl bold $col_match)%d$($cl normal $col_fg $col_bg)\n" $match_count
+                                       printf "$("$cl" $col_match)Match count$("$cl" normal $col_fg $col_bg):\
+ $("$cl" bold $col_match)%d$("$cl" normal $col_fg $col_bg)\n" $match_count
                                else
                                        printf "Match count: %d\n" $match_count
                                fi
                        fi
                        if ((glob_xclude_element || regex_xclude_element)); then
                                if ((colorize)); then
-                                       printf "$($cl $col_match)Exclude count$($cl normal $col_fg $col_bg):\
- $($cl bold $col_match)%d$($cl normal $col_fg $col_bg)\n" $xclude_count
+                                       printf "$("$cl" $col_match)Exclude count$("$cl" normal $col_fg $col_bg):\
+ $("$cl" bold $col_match)%d$("$cl" normal $col_fg $col_bg)\n" $xclude_count
                                else
                                        printf "Exclude count: %d\n" $xclude_count
                                fi
                        fi
                        if ((colorize)); then
-                               printf "$($cl bold $col_highlight)Member count$($cl normal $col_fg $col_bg):\
- $($cl bold $col_members)%d$($cl normal $col_fg $col_bg)\n" $member_count
+                               printf "$("$cl" bold $col_highlight)Member count$("$cl" normal $col_fg $col_bg):\
+ $("$cl" bold $col_members)%d$("$cl" normal $col_fg $col_bg)\n" $member_count
                        else
                                printf "Member count: %d\n" $member_count
                        fi
                fi
        fi
 done
+
+# print global counters
 if ((count_sets || calc_mem || sets_total || exclude_set)); then
        printf "\n"
        if ((count_sets)); then
                if ((colorize)); then
-                       printf "$($cl bold $col_highlight)Count$($cl normal $col_fg $col_bg) of all\
- $($cl bold $col_set_count)matched sets$($cl normal $col_fg $col_bg):\
- $($cl bold $col_set_count)%d$($cl normal $col_fg $col_bg)\n" $set_count
+                       printf "$("$cl" bold $col_highlight)Count$("$cl" normal $col_fg $col_bg) of all\
+ $("$cl" bold $col_set_count)matched sets$("$cl" normal $col_fg $col_bg):\
+ $("$cl" bold $col_set_count)%d$("$cl" normal $col_fg $col_bg)\n" $set_count
                else
                        printf "Count of all matched sets: %d\n" $set_count
                fi
                if ((exclude_set)); then
                        if ((colorize)); then
-                               printf "$($cl bold $col_highlight)Count$($cl normal $col_fg $col_bg) of all\
- $($cl bold $col_match)excluded sets$($cl normal $col_fg $col_bg):\
- $($cl bold $col_match)%d$($cl normal $col_fg $col_bg)\n" $found_sxclude
+                               printf "$("$cl" bold $col_highlight)Count$("$cl" normal $col_fg $col_bg) of all\
+ $("$cl" bold $col_match)excluded sets$("$cl" normal $col_fg $col_bg):\
+ $("$cl" bold $col_match)%d$("$cl" normal $col_fg $col_bg)\n" $found_sxclude
                        else
                                printf "Count of all excluded sets: %d\n" $found_sxclude
                        fi
@@ -836,17 +1024,17 @@ if ((count_sets || calc_mem || sets_total || exclude_set)); then
        fi
        if ((sets_total)); then
                if ((colorize)); then
-                       printf "$($cl bold $col_highlight)Count$($cl normal $col_fg $col_bg) of all\
- $($cl bold $col_set_total)traversed sets$($cl normal $col_fg $col_bg):\
- $($cl bold $col_set_total)%d$($cl normal $col_fg $col_bg)\n" $sets_sum
+                       printf "$("$cl" bold $col_highlight)Count$("$cl" normal $col_fg $col_bg) of all\
+ $("$cl" bold $col_set_total)traversed sets$("$cl" normal $col_fg $col_bg):\
+ $("$cl" bold $col_set_total)%d$("$cl" normal $col_fg $col_bg)\n" $sets_sum
                else
                        printf "Count of all traversed sets: %d\n" $sets_sum
                fi
        fi
        if ((calc_mem)); then
                if ((colorize)); then
-                       printf "$($cl bold $col_memsize)Total memory size$($cl normal $col_fg $col_bg)\
- of all matched sets: $($cl bold $col_memsize)%d$($cl normal $col_fg $col_bg)\n" $mem_total
+                       printf "$("$cl" bold $col_memsize)Total memory size$("$cl" normal $col_fg $col_bg)\
+ of all matched sets: $("$cl" bold $col_memsize)%d$("$cl" normal $col_fg $col_bg)\n" $mem_total
                else
                        printf "Total memory size of all matched sets: %d\n" $mem_total
                fi
index e4faf7fe50864edd8eaab15b78feec65b9ad0e9c..f92c7ab5d75a5488d9a73b72faaf0ba6bca6521d 100644 (file)
@@ -7,7 +7,7 @@
 # https://sourceforge.net/projects/ipset-list/
 # -----------------------------------------------------------------
 
-# Copyright (C) 2013 AllKind (AllKind@fastest.cc)
+# Copyright (C) 2013-2014 AllKind (AllKind@fastest.cc)
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -75,15 +75,16 @@ done
 
 _ipset_list_complete() {
 local -i i=x=got_bashcompl=0
-local -i show_all=isolate=show_members=resolve=headers_only=set_index=0
+local -i show_all=isolate=show_members=resolve=headers_only=names_only=0
+local -i header_operation=set_index=0
 local cur prev cword words str_tmp
-local sets=( $("$ipset_list" -n ) )
+local sets=( $("${ipset_list:-ipset_list}" -n) )
 local opts=(-- -? -a -c -d -h -i -m -n -r -s -t -v)
 local Copts=(-Ca -Cs -Co)
-local Fopts=(-Fh -Fi -Fg -Fr)
-local Hopts=(-Hr -Hs -Ht -Hv)
+local Fopts=(-Fh -Fi -Fg -Fr -Oi)
+local Hopts=(-Hi -Hr -Hs -Ht -Hv)
 local Topts=(-Tm -To -Ts)
-local Xopts=(-Xh -Xg -Xr -Xs)
+local Xopts=(-Xh -Xg -Xr -Xs -Xo)
 local arr_types=()
 
 : ${PATH:=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
@@ -120,22 +121,23 @@ for ((i=1; i < ${#words[@]}-1; i++)); do
         -a) ((set_index)) && break || show_all=1 ;;
         -i) ((set_index)) && break || isolate=1 Copts=(-Co) ;;
         -m) ((set_index)) && break || show_members=1 ;;
+        -n) ((set_index)) && break || names_only=1 ;;
         -r) ((set_index)) && break || resolve=1 ;;
-        -t) ((set_index)) && break || headers_only=1 ;;
+        -t) ((set_index)) && break || headers_only=1 Xopts=(-Xh -Xs) Fopts=(${Fopts[*]/-Oi/}) ;;
         --) ((set_index)) && break || set_index=$((i+1)) ;;
-        -\?|-h|-n|-v)
+        -\?|-h|-v)
             ((set_index)) || return 0
         ;;
-        @(-Fh|-Fi|-Xh)) ((set_index)) && break || header_operation=1 ;;
+        @(-Fh|-Fi|-Hi|-Xh)) ((set_index)) && break || header_operation=1 ;;
         *)
         ((set_index)) && break
         # options expecting an opt arg
-        str_tmp="@(-@(d|Fg|Fh|Fi|Fr|Ht|Hr|Hs|Hv|Mc|To|Xg|Xh|Xr|Xs))"
+        str_tmp="@(-@(d|Fg|Fh|Fi|Fr|Hi|Ht|Hr|Hs|Hv|Mc|Oi|To|Xg|Xh|Xr|Xs))"
         if [[ ${words[i-1]} = $str_tmp ]]; then
             continue
         fi
         # if not an option, register set index
-        str_tmp="@(-@(-|?|a|c|d|h|i|m|n|r|s|t|v|Ca|Cs|Co|Fg|Fh|Fi|Fr|Ht|Hr|Hs|Hv|Mc|To|Tm|Ts|Xg|Xh|Xr))"
+        str_tmp="@(-@(-|?|a|c|d|h|i|m|n|r|s|t|v|Ca|Cs|Co|Fg|Fh|Fi|Fr|Hi|Ht|Hr|Hs|Hv|Mc|Oi|To|Tm|Ts|Xg|Xh|Xo|Xr))"
         if [[ ${words[i]} != $str_tmp ]]; then
             for x in ${!sets[@]}; do
                 if [[ ${sets[x]} = ${words[i]} ]]; then
@@ -148,7 +150,12 @@ for ((i=1; i < ${#words[@]}-1; i++)); do
 done
 
 # invalid combinations of options
-if ((headers_only)); then
+if ((names_only)); then
+    if ((headers_only)); then
+        return 0
+    fi
+fi
+if ((headers_only||names_only)); then
     if ((show_all || show_members || isolate || resolve)); then
         return 0
     fi
@@ -169,7 +176,7 @@ if ((set_index)); then
     # and also handles those who have the name of ipset_list options
     _ipset_list_show_sets
 else
-if [[ $prev = @(-@(\?|d|h|n|v|Fg|Fi|Fr|Ht|To|Xg|Xr)) ]]; then
+if [[ $prev = @(-@(\?|d|h|v|Fg|Fi|Fr|Hi|Ht|Oi|To|Xg|Xr)) ]]; then
     return 0
 elif [[ $prev = -Xs ]]; then
     # list sets if user does not want to enter a glob
@@ -217,7 +224,7 @@ elif [[ $prev = @(-@(Hr|Hs|Hv|Mc)) ]]; then
 elif [[ $cur = -* ]]; then
     # any option is requested
     case "$prev" in
-        @(-@(\?|d|h|n|v|Fg|Fh|Fi|Fr|Ht|Hr|Hs|Hv|Mc|To|Xg|Xh|Xr)))
+        @(-@(\?|d|h|v|Fg|Fh|Fi|Fr|Hi|Ht|Hr|Hs|Hv|Mc|Oi|To|Xg|Xh|Xr)))
         # options that exclude any other option,
         # or need a value we can't predict
             return 0
@@ -225,18 +232,22 @@ elif [[ $cur = -* ]]; then
     esac
     # these options don't allow any other
     if ((${#words[@]} > 2)); then
-        opts=("${opts[@]/@(-n|-h|-\?)/}")
+        opts=("${opts[@]/@(-v|-h|-\?)/}")
     fi
     # some options allow only a subset of other options
     if ((isolate)); then
-        COMPREPLY=( $(compgen -W '-- -Co -d -r -s' -- $cur ) )
+        COMPREPLY=( $(compgen -W '-- -Co -d -r -s -Fg -Fr -Oi -To -Xg -Xo -Xr' -- $cur ) )
+    elif ((names_only)); then
+        COMPREPLY=( $(compgen -W \
+            '-- -c ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} -Xs' \
+            -- $cur ) )
     elif ((headers_only)); then
         COMPREPLY=( $(compgen -W \
             '-- -c ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]}' \
             -- $cur ) )
     elif ((show_members)); then
         COMPREPLY=( $(compgen -W \
-            '-- -c -d -r -s ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]}' \
+            '-- -c -d -r -s ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} -Xg -Xr -Xo' \
             -- $cur ) )
     elif ((show_all)); then
         COMPREPLY=( $(compgen -W \
@@ -261,13 +272,13 @@ elif [[ $cur = -* ]]; then
         # mutual exclusive options
         for ((i=1; i < x; i++)); do
             case "${words[i]}" in
-                -Fg) _ipset_list_remove_reply_entry "-Fr" "-Xg" "-Xr" ;;
-                -Fr) _ipset_list_remove_reply_entry "-Fg" "-Xg" "-Xr" ;;
-                -Xg) _ipset_list_remove_reply_entry "-Fg" "-Fr" "-Xr" ;;
-                -Xr) _ipset_list_remove_reply_entry "-Fg" "-Fr" "-Xg" ;;
+                -Fg) _ipset_list_remove_reply_entry "-Fr" ;;
+                -Fr) _ipset_list_remove_reply_entry "-Fg" ;;
+                -Xg) _ipset_list_remove_reply_entry "-Xr" ;;
+                -Xr) _ipset_list_remove_reply_entry "-Xg" ;;
             esac
             # options allowed multiple times
-            if [[ ${words[i]} = @(""|-|-@(Fh|Fi|Mc|Xh|Xs)) ]]; then
+            if [[ ${words[i]} = @(""|-|-@(Fh|Fi|Hi|Mc|Oi|Xh|Xs)) ]]; then
                 continue
             else # remove dupe
                 _ipset_list_remove_reply_entry "${words[i]}"