summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--utils/ipset_bash_completion/README.md193
-rw-r--r--utils/ipset_bash_completion/ipset_bash_completion1603
-rw-r--r--utils/ipset_list/README.md4
-rwxr-xr-xutils/ipset_list/ipset_list77
-rw-r--r--utils/ipset_list/ipset_list_bash_completion359
5 files changed, 1734 insertions, 502 deletions
diff --git a/utils/ipset_bash_completion/README.md b/utils/ipset_bash_completion/README.md
index 11df9d5..7a43eaa 100644
--- a/utils/ipset_bash_completion/README.md
+++ b/utils/ipset_bash_completion/README.md
@@ -1,4 +1,195 @@
ipset-bash-completion
=====================
-Programmable completion code (bash) for ipset (netfilter.org)
+Description
+===========
+
+Programmable completion specification (compspec) for the bash shell
+to support the ipset program (netfilter.org).
+
+
+Programmable completion allows the user, while working in an interactive shell,
+to retrieve and auto-complete commands, their options, filenames, etc.
+Pressing [TAB] will complete on the current word, if only one match is found.
+If multiple completions are possible, they will be listed by hitting [TAB] again.
+
+
+Features
+========
+
+This completion specification follows the logic of ipset and
+ will show commands and options only when they are available for the current context
+(combination of commands and/or options).
+Providing some kind of interactive help.
+
+- Show and complete commands and options.
+- Show and complete set names.
+- Show and complete the set types when using the create and help command.
+- Show and complete set elements (members) when using the del command.
+- 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.
+- Complete on filenames if the current option requires it.
+- Complete variable names, command substitution and globbing patterns.
+- Do not complete if an invalid combination of options is used.
+- Do not complete if an invalid value of an option argument is detected.
+
+
+Installation
+============
+
+Put it into ~/.bash_completion or /etc/bash_completion.d/.
+
+Tip:
+To make tab completion more handsome put the following into either
+/etc/inputrc or ~/.inputrc:
+
+ set show-all-if-ambiguous on
+
+This will allow single tab completion as opposed to requiring a double tab.
+
+ set page-completions off
+
+This turns off the use of the internal pager when returning long completion lists.
+
+
+Usage
+=====
+
+Type -[TAB] to start option completion.
+Sets named - (hyphen) or starting with it, are supported,
+excluded are option names (i.e. -exist).
+
+Type [TAB] to complete on anything available at the current context.
+
+Depending on the environment variable **_IPSET_COMPL_OPT_FORMAT**,
+either the long, short or both forms of options are shown for completion.
+By default (empty _IPSET_COMPL_OPT_FORMAT) the long versions of options
+are displayed (_IPSET_COMPL_OPT_FORMAT=long also does the same).
+Setting it to 'short' will cause completion to show only the short form.
+Setting it to any other value, will result in both version being displayed and completed.
+
+---
+
+To complete named port ranges, enter the hypen after the first completed service (port) name,
+hit [TAB] again to start completion on the second named port (the brackets [] for service names
+containing a - (hyphen) are already surrounding the name in the completion list).
+
+---
+
+The environment variable **HOSTFILE** controls how hostname completion is performed.
+Taking the description from the bash man-page:
+
+ Contains the name of a file in the same format as /etc/hosts that
+ should be read when the shell needs to complete a hostname.
+ The list of possible hostname completions may be changed while the shell is running
+ the next time hostname completion is attempted after the value is changed,
+ bash adds the contents of the new file to the existing list.
+ If HOSTFILE is set, but has no value, or does not name a readable file, bash
+ attempts to read /etc/hosts to obtain the list of possible hostname completions.
+ When HOSTFILE is unset, the hostname list is cleared.
+
+
+If the *bash-completion* package is available hostname completion is extended
+the following way (description from bash-completion source):
+
+ Helper function for completing _known_hosts.
+ This function performs host completion based on ssh config and known_hosts
+ files, as well as hostnames reported by avahi-browse if
+ COMP_KNOWN_HOSTS_WITH_AVAHI is set to a non-empty value.
+ Also hosts from HOSTFILE (compgen -A hostname) are added, unless
+ COMP_KNOWN_HOSTS_WITH_HOSTFILE is set to an empty value.
+
+
+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,
+which will be used for hostname completion.
+
+For all *net* type of sets, if hostname completion is attempted,
+networks are retrieved from /etc/networks.
+
+Also a list of ip addresses can be supplied using the environment variable
+**_IPSET_IPLIST_FILE**. Which should point to a file containing an ip address per line.
+They can be ipv4 and/or ipv6. Detection which type should be included
+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**
+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).
+If it is set to any other value both methods are used to generate
+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**
+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).
+If it is set to any other value both methods are used to generate
+the list of possible completions (this is the default).
+
+---
+
+When adding elements to a **bitmap:ip,mac** type of set,
+the environment variable **_IPSET_MACLIST_FILE** will be queried
+for a file containing a list of mac addresses.
+The file should contain one mac address per line.
+Empty lines and comments (also after the address) are supported.
+If the variable is unset mac addresses are fetched from arp cache,
+/etc/ethers and the output of `ip link show`.
+
+---
+
+When adding elements to one of the following set types:
+**hash:ip,port hash:ip,port,ip hash:ip,port,net hash:net,port**
+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.
+This behaviour is because of the many different
+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.
+
+
+
+Compatibility
+=============
+
+Tested with ipset v6.16.1.
+
+Compatibility for future ipset versions cannot be promised, as new options may appear,
+which of course are currently unpredictable.
+
+The bash-completion (v2.0+) package is highly recommended, though not mandatory.
+
+http://bash-completion.alioth.debian.org/
+
+Some things might not be that reliable or feature rich without it.
+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.
+
+
+
+Availability
+============
+
+https://github.com/AllKind/ipset-bash-completion
+
+http://sourceforge.net/projects/ipset-bashcompl/
diff --git a/utils/ipset_bash_completion/ipset_bash_completion b/utils/ipset_bash_completion/ipset_bash_completion
index 124db67..cc7ea7b 100644
--- a/utils/ipset_bash_completion/ipset_bash_completion
+++ b/utils/ipset_bash_completion/ipset_bash_completion
@@ -26,389 +26,1314 @@
# Tested with ipset versions:
# 6.16.1
# -----------------------------------------------------------------
+# Requirements:
+#
+# The bash completion package version 2.0 or greater is recommended.
+# http://bash-completion.alioth.debian.org/
+#
+# If the package is not available, things might not be so reliable.
+# Also the colon (if there) is removed from COMP_WORDBREAKS.
+# This alteration is globally, which might affect other completions,
+# if they don't take care of it themselves.
+#
+# -----------------------------------------------------------------
+# Installation:
#
# Put it into ~/.bash_completion or /etc/bash_completion.d/
#
# -----------------------------------------------------------------
#
-# Version 1.9
+# Version 2.0
#
# -----------------------------------------------------------------
+# -----------------------------------------------------------------
+# 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) )
+ t=${1#??}
+ COMPREPLY=( $(compgen -c -P '$(' $t) )
elif [[ $1 == \$\{* ]]; then # variables with a leading `${'
- t=${1#??}
- COMPREPLY=( $(compgen -v -P '${' -S '}' $t) )
+ 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 # hostname
- t=${1#*@}
- COMPREPLY=( $( compgen -A hostname $t ) )
+ 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
+ COMPREPLY=( $( compgen -G "$1" ) )
+ # ksh-style extended glob pattern - must be complete
elif shopt -q extglob && [[ $1 == *[?*+\!@]\(*\)* ]]; then
- COMPREPLY=( $( compgen -G "$1" ) )
+ 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"
+}
+
+_ipset_is_set() {
+local -i idx
+((${#arr_sets[@]})) || arr_sets=( $(ipset list -n) )
+for idx in ${!arr_sets[@]}; do
+ if [[ ${arr_sets[idx]} = $1 ]]; then
+ return 0
+ fi
+done
+return 1
+}
+
+_ipset_get_set_type() {
+while read n d; do
+ [[ $n = Type: ]] && printf '%s\n' $d && break
+done < <(ipset -t list "$1" 2>/dev/null)
+}
+
+_ipset_set_has_timout() {
+while read -r; do
+ [[ $REPLY = Header:*timeout* ]] && return 0
+done < <(ipset -t list "$1")
+return 1
+}
+
+_ipset_get_supported_types() {
+((${#arr_types[@]})) && return
+local -i i=0
+while read -r; do
+ [[ $REPLY = "Supported set types:"* ]] && ((!i)) && i=1 && continue
+ ((i)) || continue
+ if [[ $REPLY = *:* ]]; then
+ set -- $REPLY
+ arr_types+=("$1")
+ fi
+done < <(ipset help)
+for i in ${!arr_types[@]}; do # remove dupe entries
+ for ((x=i+1; x < ${#arr_types[@]}; x++)); do
+ if [[ ${arr_types[i]} = ${arr_types[x]} ]]; then
+ unset arr_types[x]
+ fi
+ done
+done
+}
+
+_ipset_get_members() {
+local -i in_list=0 no=0
+arr_members=()
+if [[ $1 = --names-only ]]; then no=1
+ shift
+fi
+while read -r; do
+ [[ $REPLY = Members:* ]] && in_list=1 && continue
+ ((in_list)) || continue
+ if ((no)); then
+ arr_members+=("${REPLY%% *}")
+ else
+ arr_members+=("$REPLY")
+ fi
+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")
+}
+
+_ipset_dedupe_cmd_opts() {
+local str_opt
+local -i idx
+for str_opt in "${@}"; do
+ for idx in ${!arr_dupe_cmd_opts[@]}; do
+ [[ $str_opt = ${arr_dupe_cmd_opts[idx]} ]] && continue 2
+ done
+ printf "%s\n" "$str_opt"
+done
+}
+
+_ipset_get_options() {
+local str_list
+local -i idx oidx ridx
+if ((got_action)); then
+ case "$str_action" in
+ rename|e|swap|w|test|flush|destroy|x)
+ str_list='-q -quiet'
+ ;;
+ save)
+ str_list='-f -file -q -quiet'
+ ;;
+ create|n|add|del)
+ str_list='-! -exist -q -quiet'
+ ;;
+ restore)
+ str_list='-! -exist -f -file -q -quiet'
+ ;;
+ list)
+ str_list='-f -file -q -quiet'
+ if ((names_only || headers_only)); then
+ str_list+=' -o -output'
+ elif ((res_sort)); then
+ str_list+=' -o -output -r -resolve -s -sorted'
+ elif ((save_format == 1)); then
+ str_list+=' -r -resolve -s -sorted -t -terse'
+ elif ((save_format == 3)); then
+ str_list+=' -r -resolve -s -sorted'
+ else
+ str_list+=' -n -name -o -output -r -resolve \
+ -s -sorted -t -terse'
+ fi
+ ;;
+ esac
+else
+ str_list='-f -file -q -quiet'
+ if ((names_only || headers_only)) && ((save_format == 1)); then
+ :
+ elif ((names_only || headers_only)); then
+ str_list+=' -o -output'
+ elif ((res_sort)); then
+ str_list+=' -o -output -r -resolve -s -sorted'
+ elif ((save_format == 1)); then
+ str_list+=' -r -resolve -s -sorted -t -terse'
+ elif ((save_format == 3)); then
+ str_list+=' -r -resolve -s -sorted'
+ elif ((ignore_errors)); then
+ :
+ elif ((use_file)); then
+ str_list='-! -exist -n -name -o -output -q -quiet -r \
+ -resolve -s -sorted -t -terse'
+ else
+ str_list='- ${arr_opts[@]}'
+ fi
fi
+COMPREPLY=( $( compgen -W "- $str_list" -- "$cur" ) )
+((${#COMPREPLY[@]})) || return 0
+
+# post process the reply
+if [[ ${_IPSET_COMPL_OPT_FORMAT:=long} = long ]]; then # choose on env var
+ for ridx in ${!COMPREPLY[@]}; do # remove short version of options
+ [[ ${COMPREPLY[ridx]} = -? ]] && unset COMPREPLY[ridx]
+ done
+elif [[ ${_IPSET_COMPL_OPT_FORMAT} = short ]]; then
+ for ridx in ${!COMPREPLY[@]}; do # remove short version of options
+ [[ ${COMPREPLY[ridx]} = -??* ]] && unset COMPREPLY[ridx]
+ done
+fi
+for idx in ${!arr_used_opts[@]}; do
+ # if the user supplied the short form of an option previously,
+ # and now requests the long form, remove the corresponding long option,
+ # vice versa for short options
+ for oidx in ${!arr_opts[@]}; do # cycle through main options
+ set -- ${arr_opts[oidx]} # $1 = short , $2 = long option
+ [[ $1 = $cur ]] && continue
+ [[ ${arr_used_opts[idx]} =~ ^($1|$2)$ ]] || continue
+ for ridx in ${!COMPREPLY[@]}; do # compare with compreply
+ if [[ ${COMPREPLY[ridx]} = ${BASH_REMATCH[1]} ]]; then
+ if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "removing option alias COMPREPLY[$ridx]: %s\n" \
+ "${COMPREPLY[ridx]}"
+ fi
+ unset COMPREPLY[ridx]
+ break 2
+ fi
+ done
+ done
+ for ridx in ${!COMPREPLY[@]}; do # de-dupe options
+ if [[ ${arr_used_opts[idx]} = ${COMPREPLY[ridx]} && \
+ ${COMPREPLY[ridx]} != $cur ]]; then
+ if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "removing dupe option COMPREPLY[$ridx]: %s\n" \
+ "${COMPREPLY[ridx]}"
+ fi
+ unset COMPREPLY[ridx]
+ break
+ fi
+ done
+done
}
+_ipset_get_networks() {
+local foo str_net rest
+[[ -r /etc/networks ]] || return 0
+while read -r foo str_net rest; do
+ [[ $foo = @(""|*([[:blank:]])#*) ]] && continue
+ [[ $str_net = *([[:blank:]])#* ]] && continue
+ printf "%s\n" "$str_net"
+done < /etc/networks
+}
+
+_ipset_get_protocols() {
+local str_name rest
+while read -r str_name rest; do
+ if [[ $str_name = @(""|*([[:blank:]])#*) ]]; then continue
+ elif [[ $str_name = *-* ]]; then str_name="[$str_name]"
+ fi
+ printf "%s\n" "$str_name"
+done < /etc/protocols
+}
+
+_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
+ # second part of range will have offset = first_part_of_range+1
+ str_offset="${2}"
+ shift
+ fi
+ shift
+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
+fi
+while read -r str_name str_num rest; do
+ if [[ $str_name = @(""|*([[:blank:]])#*) ]]; then continue
+ elif [[ $str_p != all && ${str_num#*/} != $str_p ]]; then
+ continue
+ elif [[ $str_offset && $str_num && $str_num = +([[:digit:]])/* ]] && \
+ ((${str_num%/*} <= $str_offset)); then
+ continue
+ elif [[ $str_name = *-* ]]; then str_name="[$str_name]"
+ fi
+ printf "%s\n" "$str_name"
+done < /etc/services
+}
+
+_ipset_get_ifnames() {
+while read -r; do
+ REPLY="${REPLY#*: }"
+ printf "%s\n" ${REPLY%%:*}
+done < <(PATH=${PATH}:/sbin command ip -o link show)
+}
+
+_ipset_complete_iface_spec() {
+if [[ $cur != *,* ]]; then
+ str_prefix=""
+ compopt -o nospace
+ if [[ $cur = *-* ]]; then # range spec
+ str_prefix="${cur%-*}-" cur="${cur#*-}"
+ fi
+ # ip-list from file
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_get_iplist "$(_ipset_get_set_family "$str_setname")")' \
+ -- "$cur" ) )
+ # networks
+ while read; do
+ [[ $REPLY = $cur* ]] && COMPREPLY+=( "$REPLY" )
+ done < <(_ipset_get_networks)
+ # hostnames
+ 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" ) )
+ fi
+ if ((${#COMPREPLY[@]} == 1)); then
+ if [[ ${COMPREPLY[*]} = @(*/*|*-*) ]]; then
+ 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:"
+ else
+ str_var="physdev:"
+ fi
+ COMPREPLY=( $( compgen -P "$str_prefix" -W \
+ '${str_var} $(_ipset_get_ifnames)' -- "$cur" ) )
+ [[ ${COMPREPLY[0]} = *physdev: ]] && compopt -o nospace
+ _ipset_colon_ltrim "$str_prefix"
+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=','
+else str_suffix=''
+fi
+str_regex='^[^,]+,([^,]+)?$'
+if [[ $cur != *,* ]]; then
+ str_prefix=""
+ compopt -o nospace
+ if [[ $str_type = hash:net,port && $str_action = @(add|del) && $cur = *-* ]]
+ then # range spec
+ str_prefix="${cur%-*}-" cur="${cur#*-}"
+ fi
+ # ip-list from file
+ 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"
+ 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" ) )
+ fi
+ if ((${#COMPREPLY[@]} == 1)); then
+ if [[ $str_type = hash:net,port ]]; then
+ if [[ ${COMPREPLY[*]} = @(*/*|*-*) ]]; then
+ COMPREPLY=( ${COMPREPLY[*]}, )
+ fi
+ 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
+ str_regex='.*,(icmp|icmp6|tcp|sctp|udp|udplite):.*' # for compat put regex in var
+ if [[ $cur != *icmp* && \
+ $cur = *,@(?(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_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_var="${str_var#${BASH_REMATCH[1]}:}"
+ str_prefix=${str_prefix%"$cur"} str_var="${str_var%-*}"
+ 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%:*}:"
+ fi
+ _ipset_colon_ltrim "${str_prefix}"
+ elif [[ $cur =~ $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]}:"
+ if [[ $str_var = icmp ]]; then
+ COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" -W \
+ '${arr_icmp_types[@]}' -- "$cur" ) )
+ elif [[ $str_var = icmp6 ]]; then
+ COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" -W \
+ '${arr_icmp6_types[@]}' -- "$cur" ) )
+ elif [[ $str_var = @(tcp|udp|sctp|udplite) ]]; then
+ COMPREPLY=( $( compgen -P "$str_prefix" -W \
+ '$(_ipset_get_services -p $str_var)' -- "$cur" ) )
+ compopt -o nospace
+ fi
+ _ipset_colon_ltrim "$str_prefix"
+ elif [[ $cur = *,* ]]; then # first attempt :/ long list
+ str_prefix="${cur%,*}," cur="${cur#*,}"
+ 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"
+ compopt -o nospace
+ fi
+elif [[ $cur = *,*,* && $str_type = hash:ip,port,@(ip|net) ]]; then
+ str_prefix="${cur}" cur="${cur##*,}"
+ str_prefix="${str_prefix%"$cur"}"
+ # ip-list from file
+ 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" ) )
+ fi
+ _ipset_colon_ltrim "$str_prefix$cur"
+ fi
+ if ((${#COMPREPLY[@]} == 1)); then
+ if [[ $str_type = hash:ip,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
+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}"
+}
+
+# -----------------------------------------------------------------
+# Main
+# -----------------------------------------------------------------
+
_ipset_complete() {
shopt -s extglob
-local cur prev str_action ips_version
-local -i i=x=y=got_action=in_list=0
+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 -i i=x=y=0
+local -i got_bashcompl=got_action=action_index=order_index=set_has_timeout=0
local -i ignore_errors=use_file=names_only=headers_only=save_format=res_sort=0
-local arr_sets=() arr_types=() arr_members=()
+local arr_sets=() arr_types=() arr_members=() arr_unknown_opts=()
+local arr_dupe_cmd_opts=() arr_used_opts=() arr_tmp=()
+local arr_opts=(
+"-! -exist"
+"-o -output"
+"-q -quiet"
+"-r -resolve"
+"-s -sorted"
+"-n -name"
+"-t -terse"
+"-f -file"
+)
+local arr_icmp_types=(
+echo-reply
+pong
+network-unreachable
+host-unreachable
+protocol-unreachable
+port-unreachable
+fragmentation-needed
+source-route-failed
+network-unknown
+host-unknown
+network-prohibited
+host-prohibited
+TOS-network-unreachable
+TOS-host-unreachable
+communication-prohibited
+host-precedence-violation
+precedence-cutoff
+source-quench
+network-redirect
+host-redirect
+TOS-network-redirect
+TOS-host-redirect
+echo-request
+ping
+router-advertisement
+router-solicitation
+ttl-zero-during-transit
+ttl-zero-during-reassembly
+ip-header-bad
+required-option-missing
+timestamp-request
+timestamp-reply
+address-mask-request
+address-mask-reply
+)
+local arr_icmp6_types=(
+no-route
+communication-prohibited
+address-unreachable
+port-unreachable
+packet-too-big
+ttl-zero-during-transit
+ttl-zero-during-reassembly
+bad-header
+unknown-header-type
+unknown-option
+echo-request
+ping
+echo-reply
+pong
+router-solicitation
+router-advertisement
+neighbour-solicitation
+neigbour-solicitation
+neighbour-advertisement
+neigbour-advertisement
+redirect
+)
+
+COMPREPLY=()
# ipset version check 6.x upwards (to v?) is supported
-ips_version="$(ipset --version)"
+ips_version="$(ipset version)"
ips_version="${ips_version#ipset v}"
-ips_version="${ips_version%%.*}"
-[[ $ips_version = +([[:digit:]]) ]] || return 1
-((ips_version < 6)) && return 1
+ips_version="${ips_version%,+([[:blank:]])protocol*}"
+read -a ips_version <<< ${ips_version//./ }
+[[ ${ips_version[0]} = +([[:digit:]]) ]] || return 1
+((ips_version[0] < 6)) && return 1
-COMPREPLY=()
-COMP_WORDBREAKS=$' \t\n"\'><=;|&('
+# ipset -gt v6.17 has counters flag
+if ((ips_version[0] == 6 && ips_version[1] >= 17)) || ((ips_version[0] > 6))
+then
+ str_counters="counters"
+fi
# expecting _get_comp_words_by_ref() to exist from bash_completion
-_get_comp_words_by_ref cur || return
-_get_comp_words_by_ref prev || return
-
-#DEBUG=Y
-if [[ $DEBUG ]]; then
- printf "\nCOMP_WORDBREAKS: <%s>\n" "$COMP_WORDBREAKS"
- printf "COMP_LINE: <%s>\n" "$COMP_LINE"
- printf "COMP_TYPE: <%s>\n" "$COMP_TYPE"
- printf "COMP_POINT: <%s>\n" "$COMP_POINT"
- printf "COMP_KEY: <%s>\n" "$COMP_KEY"
- printf "COMP_CWORD: <%s>\n" "$COMP_CWORD"
- printf "COMP_WORDS:\n"
- printf "<%s>\n" "${COMP_WORDS[@]}"
- printf "cur: <%s> prev: <%s>\n" "$cur" "$prev"
+if declare -f _get_comp_words_by_ref &>/dev/null; then got_bashcompl=1
+ _get_comp_words_by_ref -n : cur prev cword words || return
+else got_bashcompl=0 # not so neat, but a workaround
+ COMP_WORDBREAKS="${COMP_WORDBREAKS//:/}"
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ cword=$COMP_CWORD
+ for i in ${!COMP_WORDS[@]}; do words[i]="${COMP_WORDS[i]}"; done
fi
-for i in ${!COMP_WORDS[@]}; do # check if we already have an action registered
- if [[ ${COMP_WORDS[i]} = @(create|add|del|test|destroy|list|save|restore|flush|rename|swap|help|version) ]]; then
- if [[ ${COMP_WORDS[i]} != save ]]; then
- got_action=1 str_action=${COMP_WORDS[i]}
- break
- else
- if [[ ${COMP_WORDS[i-1]} != -o ]]; then
- got_action=1 str_action=${COMP_WORDS[i]}
- break
- fi
- fi
- fi
-done
+if ((got_bashcompl)); then
+# current bash completion got a bug i reported:
+# https://alioth.debian.org/tracker/index.php?func=detail&aid=314056&group_id=100114&atid=413095
+# putting corrected function here, so things don't break
+__ltrim_colon_completions() {
+ if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
+ # Remove colon-word prefix from COMPREPLY items
+ local colon_word="${1%"${1##*:}"}"
+ local i=${#COMPREPLY[*]}
+ while [[ $((--i)) -ge 0 ]]; do
+ COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
+ done
+ fi
+}
+fi
+
+#_DEBUG_NF_COMPLETION=Y
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "\nCOMP_WORDBREAKS: <%s>\n" "$COMP_WORDBREAKS"
+ printf "COMP_LINE: <%s>\n" "$COMP_LINE"
+ printf "COMP_TYPE: <%s>\n" "$COMP_TYPE"
+ printf "COMP_POINT: <%s>\n" "$COMP_POINT"
+ printf "COMP_KEY: <%s>\n" "$COMP_KEY"
+ printf "COMP_CWORD: <%s>\n" "$COMP_CWORD"
+ printf "cword: <%s>\n" "$cword"
+ printf "cur: <%s> prev: <%s>\n" "$cur" "$prev"
+ printf "words:\n" "<%s>\n" "${words[@]}"
+fi
# collect information about used options
-for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do
- case "${COMP_WORDS[i]}" in
- -\!) ignore_errors=1 ;;
- -f) use_file=1 ;;
- -n) names_only=1 ;;
- -t) headers_only=1 ;;
- -o) save_format=1
- if [[ $prev = -o ]]; then
- save_format=2 # expecting opt-arg
- elif [[ ${COMP_WORDS[i+1]} = save ]]; then
- save_format=3 # no -n/-t with -o save
- fi
- ;;
- -r|-s) res_sort=1 ;;
- esac
+for ((i=1; i < ${#words[@]}-1; i++)); do
+case "${words[i]}" in
+ @(create|n|add|del|test|destroy|x|list|save|restore|flush|rename|e|swap|w|help|version))
+ [[ ${words[i-1]} = @(-f|-file) ]] && continue # there could be a file named like a command
+ if ! ((got_action)); then
+ if [[ ${words[i]} != save ]]; then
+ got_action=1 action_index=$i str_action=${words[i]}
+ elif [[ ${words[i-1]} != @(-o|-output) ]]; then
+ got_action=1 action_index=$i str_action=${words[i]}
+ fi
+ if [[ $str_action = @(create|n|add|del|test|destroy|x|list|save|restore|flush|rename|e|swap|w) ]]
+ then str_setname=${words[i+1]} # register the set name
+ fi
+ fi
+ ;;
+ -\!|-exist)
+ [[ ${words[i-1]} != @(-f|-file) ]] &&\
+ ignore_errors=1 arr_used_opts+=(${words[i]})
+ ;;
+ -f|-file)
+ [[ ${words[i-1]} != @(-f|-file) ]] &&\
+ use_file=1 str_filename="${words[i+1]}" \
+ arr_used_opts+=(${words[i]})
+ ;;
+ -n|-name)
+ [[ ${words[i-1]} != @(-f|-file) ]] &&\
+ names_only=1 arr_used_opts+=(${words[i]})
+ ;;
+ -t|-terse)
+ [[ ${words[i-1]} != @(-f|-file) ]] &&\
+ headers_only=1 arr_used_opts+=(${words[i]})
+ ;;
+ -o|-output)
+ if [[ ${words[i-1]} != @(-f|-file) ]]; then
+ arr_used_opts+=(${words[i]})
+ if [[ $prev = @(-o|-output) ]]; then
+ save_format=2 # expecting opt-arg
+ elif [[ ${words[i+1]} = save ]]; then
+ save_format=3 # no -n/-t with -o save
+ else
+ save_format=1
+ fi
+ fi
+ ;;
+ -r|-resolve|-s|-sorted)
+ [[ ${words[i-1]} != @(-f|-file) ]] &&\
+ res_sort=1 arr_used_opts+=(${words[i]})
+ ;;
+ -q|-quiet)
+ arr_used_opts+=(${words[i]})
+ ;;
+# -?*)
+# if [[ ${words[i]#-} != @(q|quiet) ]]; then
+# # don't include filenames
+# if [[ ${words[i-1]} != @(-f|-file|\>) || ${words[i+1]} != \< ]]; then
+# arr_unknown_opts[${#arr_unknown_opts[@]}]="${words[i]}"
+# fi
+# fi
+# ;;
+ before|after)
+ if ((got_action && ! order_index && i == action_index+3)); then
+ order_index=$i str_order=""
+ fi
+ ;;
+ timeout|range|maxelem|family|hashsize|size|netmask|nomatch|counters)
+ if ((got_action && i > action_index+2)); then
+ str_tmp="$COMP_LINE"
+ [[ $str_setname = ${words[i]} ]] && str_tmp="${str_tmp/${words[i]}/}"
+ [[ $str_filename = ${words[i]} ]] && str_tmp="${str_tmp/${words[i]}/}"
+ [[ $str_tmp = *${words[i]}* ]] && arr_dupe_cmd_opts[${#arr_dupe_cmd_opts[@]}]="${words[i]}"
+ fi
+ ;;
+esac
done
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "\ngot_action: <%s>\n" "$got_action"
+ printf "str_action: <%s>\n" "$str_action"
+ printf "action_index: <%s>\n" "$action_index"
+ printf "order_index: <%s>\n" "$order_index"
+ printf "str_setname: <%s>\n" "$str_setname"
+ printf "str_filename: <%s>\n" "$str_filename"
+ printf "save_format: <%s>\n" "$save_format"
+ printf "ignore_errors: <%s>\n" "$ignore_errors"
+ printf "names_only: <%s>\n" "$names_only"
+ printf "headers_only: <%s>\n" "$headers_only"
+# printf "arr_unknown_opts: <%s>\n" "${arr_unknown_opts[@]}"
+ printf "arr_used_opts: <%s>\n" "${arr_used_opts[@]}"
+ printf "arr_dupe_cmd_opts: <%s>\n" "${arr_dupe_cmd_opts[@]}"
+fi
+
# invalid combination of options
if ((names_only && headers_only)); then
- COMPREPLY=()
- return 0
+ return 0
elif ((names_only || headers_only)); then
- if ((res_sort || ignore_errors)) || ((save_format == 3)); then
- COMPREPLY=()
- return 0
- fi
+ if ((res_sort || ignore_errors)) || ((save_format == 3)); then
+ return 0
+ fi
elif ((ignore_errors)); then
- if ((res_sort || save_format)); then
- COMPREPLY=()
- return 0
- fi
+ if ((res_sort || save_format)); then
+ return 0
+ fi
fi
-# for help or create, find supported set types and save them into an array
-if [[ $str_action = @(help|create) ]]; then i=0
- while read -r; do
- [[ $REPLY = "Supported set types:"* ]] && ((!i)) && i=1 && continue
- ((i)) || continue
- if [[ $REPLY = *:* ]]; then
- set -- $REPLY
- arr_types[${#arr_types[@]}]="$1"
- fi
- done < <(ipset help)
- for i in ${!arr_types[@]}; do # remove dupe entries
- for ((x=i+1; x <= ${#arr_types[@]}; x++)); do
- if [[ ${arr_types[i]} = ${arr_types[x]} ]]; then
- unset arr_types[x]
- fi
- done
- done
-fi
-
-case "$cur" in
- -*) # any option is requested
- if ((save_format == 2)); then
- COMPREPLY=()
- return 0
- fi
- case "$prev" in
- create|add|del|test|rename|swap) # -option not expected
- COMPREPLY=()
- return 0
- ;;
- \<|\>|-f) compopt -o nospace # expecting filenames as completion
- COMPREPLY=( $( compgen -f -- $cur ) )
- return 0
- ;;
- -) COMPREPLY=() # interactive mode
- return 0
- ;;
- esac
- if ((got_action)); then
- case "$str_action" in
- create|add|del)
- COMPREPLY=( -\! -q )
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do
- if [[ $str_action = ${COMP_WORDS[i]} && ${COMP_WORDS[i+2]} = -* ]]; then
- COMPREPLY=() # option not expected, want command value
- break
- fi
- done
- ;;
- destroy|flush) COMPREPLY=( -q ) ;;
- list)
- if ((names_only || headers_only)); then
- COMPREPLY=( -f -o -q )
- elif ((res_sort)); then
- COMPREPLY=( -f -o -q -r -s )
- elif ((save_format == 1)); then
- COMPREPLY=( -f -q -r -s -t )
- elif ((save_format == 3)); then
- COMPREPLY=( -f -q -r -s )
- else
- COMPREPLY=( -f -n -o -q -r -s -t )
- fi
- ;;
- restore) COMPREPLY=( -\! -f -q ) ;;
- save) COMPREPLY=( -f -q ) ;;
- rename|swap|test) COMPREPLY=( -q )
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do
- if [[ $str_action = ${COMP_WORDS[i]} && ${COMP_WORDS[i+2]} = -* ]]; then
- COMPREPLY=() # option not expected, want command value
- break
- fi
- done
- ;;
- help|version) COMPREPLY=()
- return 0
- ;;
- esac
- else COMPREPLY=( - -\! -f -n -o -q -r -s -t )
- if ((names_only || headers_only)) && ((save_format == 1)); then
- COMPREPLY=( -f -q )
- elif ((names_only || headers_only)); then
- COMPREPLY=( -f -o -q )
- elif ((res_sort)); then
- COMPREPLY=( -f -o -q -r -s )
- elif ((save_format == 1)); then
- COMPREPLY=( -f -q -r -s -t )
- elif ((save_format == 3)); then
- COMPREPLY=( -f -q -r -s )
- elif ((ignore_errors)); then
- COMPREPLY=( -f -q )
- elif ((use_file)); then
- COMPREPLY=( -\! -n -o -q -r -s -t )
- fi
- fi
- ;;
- \<|\>) # redirection operator
- compopt -o nospace
- COMPREPLY=( $( compgen -f ) ) # no $cur, so completion starts without space after redirection
- return 0
- ;;
- \$\{*) # variables with a leading `${'
- COMPREPLY=( $(compgen -v -P '${' -S '}' ${cur#??}) )
- return 0
- ;;
- \$*) # variables with a leading `$'
- COMPREPLY=( $(compgen -v -P '$' ${cur#?} ) )
- return 0
- ;;
- *) # not an option
- if ((got_action)); then
- arr_sets=( $(ipset list -n ) )
- else
- COMPREPLY=( $( compgen -W 'create add del test destroy list save restore flush rename swap help version' -- $cur ) )
- fi
- case "$prev" in # depend on previous option
- restore) COMPREPLY=( \< )
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do
- if [[ ${COMP_WORDS[i]} = -f ]]; then
- COMPREPLY=() # don't show redirector if we have option -f
- break
- fi
- done
- return 0
- ;;
- create|version) COMPREPLY=()
- return 0
- ;;
- add|del|destroy|rename|swap|test)
- COMPREPLY=( $( compgen -W '${arr_sets[@]}' -- $cur ) )
- return 0
- ;;
- save)
- if [[ $str_action = save ]]; then
- COMPREPLY=( $( compgen -W '${arr_sets[@]}' -- $cur ) )
- else
- if ((save_format == 3)); then
- COMPREPLY=( $( compgen -W 'list' -- $cur ) )
- fi
- fi
- ;;
- list) COMPREPLY=( $( compgen -W '${arr_sets[@]}' -- $cur ) )
- return 0
- ;;
- help) COMPREPLY=( $( compgen -W '${arr_types[@]}' -- $cur ) )
- return 0
- ;;
- -o)
- if ((names_only || headers_only)); then
- COMPREPLY=( $( compgen -W 'plain xml' -- $cur ) )
- else
- COMPREPLY=( $( compgen -W 'plain save xml' -- $cur ) )
- fi
- return 0
- ;;
- -f) compopt -o nospace
- COMPREPLY=( $( compgen -f -- $cur ) )
- return 0
- ;;
- \<|\>) compopt -o nospace
- COMPREPLY=( $( compgen -f -- $cur ) )
- return 0
- ;;
- -) COMPREPLY=() # interactive mode
- return 0
- ;;
- *)
- if ((got_action)); then
- COMPREPLY=( $( compgen -W '${arr_sets[@]}' -- $cur ) )
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do
- case "${COMP_WORDS[i]}" in
- add)
- for ((x=${COMP_WORDS[i+1]}; x <= ${#COMP_WORDS[@]}; x++)); do
- if [[ ${COMP_WORDS[x]} = $prev ]]; then
- COMPREPLY=() # only list sets after the action command
- break 2
- fi
- done
- break
- ;;
- create) COMPREPLY=()
- if [[ ${COMP_WORDS[i+1]} = $prev ]]; then
- COMPREPLY=( $( compgen -W '${arr_types[@]}' -- $cur ) )
- fi
- break
- ;;
- del) # complete members
- if [[ ${COMP_WORDS[i+1]} = $prev ]]; then
- while read -r; do
- [[ $REPLY = Members:* ]] && in_list=1 && continue
- ((in_list)) || continue
- arr_members[${#arr_members[@]}]="$REPLY"
- done < <(ipset list "$prev" 2>/dev/null)
- COMPREPLY=( $( compgen -W '${arr_members[@]}' -- $cur ) )
- else
- COMPREPLY=()
- fi
- break
- ;;
- help)
- if [[ ${COMP_WORDS[i+1]} ]]; then
- COMPREPLY=() # don't go further than showing the set types
- return 0
- fi
- break
- ;;
- restore) COMPREPLY=() # not a redirecton
- break
- ;;
- swap)
- for x in ${!arr_sets[@]}; do
- if [[ ${arr_sets[x]} = ${COMP_WORDS[i+2]} ]]; then
- COMPREPLY=() # only list two sets
- break 2
- fi
- done
- break
- ;;
- *)
- for ((y=1; y <= ${#COMP_WORDS[@]}; y++)); do
- [[ ${COMP_WORDS[y]} ]] || continue
- for x in ${!arr_sets[@]}; do
- if [[ ${arr_sets[x]} = ${COMP_WORDS[y]} ]]; then
- COMPREPLY=() # list only one set
- break 2
- fi
- done
- done
- ;;
- esac
- done
- else # we don't have the action yet, check options to display appropiate actions
- if ((save_format || names_only || headers_only)); then
- COMPREPLY=( $( compgen -W 'list' -- $cur ) )
- return 0
- elif ((res_sort)); then
- COMPREPLY=( $( compgen -W 'list save' -- $cur ) )
- return 0
- elif ((ignore_errors && use_file)); then
- COMPREPLY=( $( compgen -W 'restore' -- $cur ) )
- return 0
- elif ((ignore_errors)); then
- COMPREPLY=( $( compgen -W 'create add del restore' -- $cur ) )
- return 0
- elif ((use_file)); then
- COMPREPLY=( $( compgen -W 'list save restore' -- $cur ) )
- return 0
- fi
- fi
- ;;
- esac
- ;;
+#case "$cur" in # depend on current
+# \<|\>) # redirection operator
+# compopt -o nospace
+# COMPREPLY=( $( compgen -f ) ) # no $cur, so completion starts without space after redirection
+# return 0
+# ;;
+#esac
+case "$prev" in # depend on previous option
+ -o|-output)
+ # make sure it's not a filename named -o or -output
+ if [[ $str_filename != $prev ]]; then
+ if ((names_only || headers_only)); then
+ COMPREPLY=( $( compgen -W 'plain xml' -- "$cur" ) )
+ else
+ COMPREPLY=( $( compgen -W 'plain save xml' -- "$cur" ) )
+ fi
+ return 0
+ fi
+ ;;
+ -f|-file|\<|\>)
+ if ((got_bashcompl)); then
+ _filedir
+ else
+ compopt -o nospace
+ COMPREPLY=( $( compgen -f -- "$cur" ) )
+ fi
+ return 0
+ ;;
esac
-if ((${#COMPREPLY[@]})); then # post process the reply
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do # remove dupe options
- [[ ${COMP_WORDS[i]} = @(""|-) ]] && continue
- for x in ${!COMPREPLY[@]}; do
- if [[ ${COMP_WORDS[i]} = ${COMPREPLY[x]} ]]; then
- unset COMPREPLY[$x]
- break
- fi
- done
- done
+
+if ((got_action)); then # we got the main action
+# Disallow sets with names of options starting with a hyphen
+if [[ $str_setname = -?* && $cur != -?* && \
+ $str_action = @(create|n|add|del|test|rename|e|swap|w) ]]
+then
+ for x in ${!arr_opts[@]}; do set -- ${arr_opts[x]}
+ [[ $str_setname = @($1|$2) ]] && return 0
+ done
+fi
+if ((cword == action_index+1)) && [[ $str_action = $prev ]]; then
+ # depend on previous option which should be the action
+ case "$str_action" in
+# create|n|version) :
+# ;;
+ help)
+ _ipset_get_supported_types
+ COMPREPLY=( $( compgen -W '${arr_types[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ ;;
+ add|del|rename|e|swap|w|test)
+ COMPREPLY=( $( compgen -W '$(ipset list -n)' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ ;;
+ list|flush|save|destroy|x)
+ # we don't know if its an option request, could also be a set
+ # named `-*', if the latter is true, show sets and options
+ if [[ $cur = -* ]]; then
+ _ipset_get_options
+ if _ipset_is_set "${cur}*"; then
+ COMPREPLY=( $( compgen -W '${arr_sets[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ else
+ COMPREPLY=( $( compgen -W '$(ipset list -n)' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ ;;
+ restore)
+ if [[ $cur = -* ]]; then
+ _ipset_get_options
+ elif ! [[ $str_filename ]]; then
+ # don't show redirector if we have option -f
+ COMPREPLY=( \< )
+ fi
+ ;;
+ esac
+elif ((cword == action_index+2)) && [[ $str_setname = $prev ]]; then
+ case "$str_action" in
+# rename|e) :
+# ;;
+ save|restore|list|flush|destroy|x)
+ if [[ $cur = -* ]]; then
+ _ipset_get_options
+ fi
+ ;;
+ @(create|n))
+ _ipset_get_supported_types
+ COMPREPLY=( $( compgen -W '${arr_types[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ ;;
+ @(swap|w)) # list two sets
+ COMPREPLY=( $( compgen -W '$(ipset list -n)' -- "$cur" ) )
+ for i in ${!COMPREPLY[@]}; do # remove the dupe setname from the list
+ [[ ${COMPREPLY[i]} = $str_setname ]] && unset COMPREPLY[i] && break
+ done
+ _ipset_colon_ltrim "$cur"
+ ;;
+ 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,mac)
+ if [[ $cur = *,* ]]; then
+ str_prefix="$cur" cur="${cur#*,}"
+ str_prefix="${str_prefix%$cur}"
+ str_regex='^([[:xdigit:]]{2})(:[[:xdigit:]]{2}){5}$'
+ x=0 y=0
+ if [[ ${_IPSET_MAC_COMPL_MODE:=both} = both ]]; then
+ x=1 y=1
+ elif [[ $_IPSET_MAC_COMPL_MODE = file ]]; then
+ x=1
+ elif [[ $_IPSET_MAC_COMPL_MODE = system ]]; then
+ y=1
+ fi
+ if ((x)); then
+ if [[ $_IPSET_MACLIST_FILE && -r $_IPSET_MACLIST_FILE ]]
+ then
+ # if a file with mac addresses is in env var, load em
+ str_tmp=$(while read -r mac rest; do
+ [[ $mac = *([[:blank:]])\#* ]] && continue
+ mac="${mac//\#*/}"
+ [[ $mac =~ $str_regex ]] && printf "%s\n" "$mac"
+ done < "${_IPSET_MACLIST_FILE}")
+ COMPREPLY=( $( compgen -P "$str_prefix" \
+ -W "$str_tmp" -- "$cur" ) )
+ _ipset_colon_ltrim "$str_prefix$cur"
+ fi
+ fi
+ if ((y)); then
+ # read arp cache, addresses of local interfaces and /etc/ethers
+ str_tmp=$(while read a b addr rest; do
+ [[ $addr =~ $str_regex ]] && printf "%s\n" "$addr"
+ done < <(PATH=$PATH:/sbin command arp -n 2>/dev/null))
+ str_tmp+=" $(while read -r; do
+ [[ $REPLY = *link/loopback* ]] && continue
+ REPLY=${REPLY#*link/*+([[:blank:]])}
+ REPLY=${REPLY%+([[:blank:]])brd*}
+ [[ $REPLY =~ $str_regex ]] && printf "%s\n" "$REPLY"
+ done < <(PATH=$PATH:/sbin command ip -o link show 2>/dev/null))"
+ if [[ -r /etc/ethers ]]; then
+ str_tmp+=" $(while read -r addr rest; do
+ [[ $addr =~ $str_regex ]] && printf "%s\n" "$addr"
+ done < /etc/ethers)"
+ fi
+ COMPREPLY+=( $( compgen -P "$str_prefix" -W "$str_tmp" \
+ -- "$cur" ) )
+ _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[*]}, )
+ 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" ) )
+ ;;
+ # show sets if the set to add is of type list:set
+ list:*) arr_tmp=() arr_sets=( $(ipset list -n) )
+ _ipset_get_members --names-only "$str_setname"
+ for x in ${!arr_sets[@]}; do
+ [[ ${arr_sets[x]} = $str_setname ]] && continue
+ for y in ${!arr_members[@]}; do
+ [[ ${arr_sets[x]} = ${arr_members[y]} ]] && continue 2
+ done
+ arr_tmp+=("${arr_sets[x]}")
+ done
+ 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)
+ str_type=$(_ipset_get_set_type "$str_setname")
+ if [[ $str_action = del && $str_type = bitmap:@(ip|port) ]]; then
+ # complete members
+ 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
+ fi
+ str_prefix="${cur%-*}-" cur="${cur#*-}"
+ else
+ compopt -o nospace
+ 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) ]]
+ then
+ if [[ ${_IPSET_COMPL_DEL_MODE:=both} = members ]]; then
+ _ipset_get_members --names-only "$str_setname"
+ COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+ _ipset_complete_hostport_spec
+ else
+ _ipset_get_members --names-only "$str_setname"
+ _ipset_complete_hostport_spec
+ COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ elif [[ $str_action = test && $str_type = hash:@(ip,port|ip,port,ip|ip,port,net|net,port) ]]
+ then
+ if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+ _ipset_get_members --names-only "$str_setname"
+ COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+ _ipset_complete_hostport_spec
+ else
+ _ipset_get_members --names-only "$str_setname"
+ _ipset_complete_hostport_spec
+ COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$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" ) )
+ _ipset_colon_ltrim "$cur"
+ elif [[ ${_IPSET_COMPL_DEL_MODE} = spec ]]; then
+ _ipset_complete_iface_spec
+ else
+ _ipset_get_members --names-only "$str_setname"
+ _ipset_complete_iface_spec
+ COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ elif [[ $str_action = test && $str_type = hash:net,iface ]]; then
+ if [[ ${_IPSET_COMPL_TEST_MODE:=both} = members ]]; then
+ _ipset_get_members --names-only "$str_setname"
+ COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ elif [[ ${_IPSET_COMPL_TEST_MODE} = spec ]]; then
+ _ipset_complete_iface_spec
+ else
+ _ipset_get_members --names-only "$str_setname"
+ _ipset_complete_iface_spec
+ COMPREPLY+=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ else
+ _ipset_get_members --names-only "$str_setname"
+ COMPREPLY=( $( compgen -W '${arr_members[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ ;;
+ esac
+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
+ str_timeout=timeout
+ else
+ str_timeout=""
+ fi
+ case "$str_type" in
+ hash:*net*)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts $str_timeout $str_counters nomatch)' \
+ -- "$cur" ) )
+ ;;
+ hash:*!(net)*|bitmap:*)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts $str_timeout $str_counters)' \
+ -- "$cur" ) )
+ ;;
+ list:*)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts $str_order $str_timeout $str_counters)' \
+ -- "$cur" ) )
+ ;;
+ esac
+ ;;
+ create|n)
+ case "$prev" in
+ hash:*)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters)' \
+ -- "$cur" ) )
+ ;;
+ bitmap:ip)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts range netmask timeout $str_counters)' \
+ -- "$cur" ) )
+ ;;
+ bitmap:!(ip)?*)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts range timeout $str_counters)' \
+ -- "$cur" ) )
+ ;;
+ list:*)
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts size timeout $str_counters)' \
+ -- "$cur" ) )
+ ;;
+ esac
+ ;;
+ del|test)
+ str_type=$(_ipset_get_set_type "$str_setname")
+ if [[ $str_type = list:* ]]; then
+ COMPREPLY=( $( compgen -W '$str_order' -- "$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
+ fi
+ case "$str_action" in
+ add)
+ str_type=$(_ipset_get_set_type "$str_setname")
+ if _ipset_set_has_timout "$str_setname"; then
+ str_timeout=timeout
+ else
+ str_timeout=""
+ 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
+ case "$str_type" in
+ hash:*net*)
+ if [[ $prev != timeout ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts $str_timeout $str_counters nomatch)' \
+ -- "$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_colon_ltrim "$cur"
+ elif [[ $prev != timeout ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts $str_order $str_timeout $str_counters)' \
+ -- "$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
+ case "${words[action_index+2]}" in # must be the set type
+ hash:*)
+ if [[ $prev = family ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts inet inet6)' \
+ -- "$cur" ) )
+ elif [[ $prev != @(family|hashsize|timeout|maxelem) ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts family hashsize timeout maxelem $str_counters)' \
+ -- "$cur" ) )
+ fi
+ ;;
+ bitmap:ip)
+ if [[ $prev != @(range|netmask|timeout) ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts range netmask timeout $str_counters)' \
+ -- "$cur" ) )
+ fi
+ ;;
+ bitmap:!(ip)?*)
+ if [[ $prev != @(range|timeout) ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts range timeout $str_counters)' \
+ -- "$cur" ) )
+ fi
+ ;;
+ list:*)
+ if [[ $prev != @(size|timeout) ]]; then
+ COMPREPLY=( $( compgen -W \
+ '$(_ipset_dedupe_cmd_opts size timeout $str_counters)' \
+ -- "$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
+ fi
+ COMPREPLY=( $( compgen -P "$str_prefix" -S "$str_suffix" \
+ -W '$(_ipset_get_services -o $str_var)' -- "$cur" ) )
+ fi
+ ;;
+ del|test)
+ str_type=$(_ipset_get_set_type "$str_setname")
+ case "$str_type" in
+ list:*) arr_tmp=()
+ _ipset_get_members --names-only "$str_setname"
+ if [[ $prev = @(before|after) ]] && ((cword-1 == order_index))
+ then
+ case "$prev" in
+ before)
+ for x in ${!arr_members[@]}; do
+ if [[ ${arr_members[x]} = ${words[action_index+2]} ]]
+ then
+ if [[ ${arr_members[x+1]} ]]; then
+ arr_tmp+=(${arr_members[x+1]})
+ break
+ fi
+ fi
+ done
+ ;;
+ after)
+ for x in ${!arr_members[@]}; do
+ if [[ ${arr_members[x]} = ${words[action_index+2]} ]]
+ then
+ if ((x>0)) && [[ ${arr_members[x-1]} ]]; then
+ arr_tmp+=(${arr_members[x-1]})
+ break
+ fi
+ fi
+ done
+ ;;
+ esac
+ COMPREPLY=( $( compgen -W '${arr_tmp[@]}' -- "$cur" ) )
+ _ipset_colon_ltrim "$cur"
+ fi
+ ;;
+ esac
+ ;;
+ esac
+fi
+else # we don't have the main action yet
+if [[ $prev = - ]] && ((cword == 2)); then
+ return 0 # interactive mode, don't complete on anything further
+fi
+if [[ $cur = -* ]]; then # any option is requested
+ _ipset_get_options
else
- _ipset_bash_default_compl "$cur"
+ # we don't have the action yet, check options to display appropiate actions
+ if ((save_format || names_only || headers_only)); then
+ COMPREPLY=( $( compgen -W 'list' -- "$cur" ) )
+ elif ((res_sort)); then
+ COMPREPLY=( $( compgen -W 'list save' -- "$cur" ) )
+ elif ((ignore_errors && use_file)); then
+ COMPREPLY=( $( compgen -W 'restore' -- "$cur" ) )
+ elif ((ignore_errors)); then
+ COMPREPLY=( $( compgen -W 'create n add del restore' -- "$cur" ) )
+ elif ((use_file)); then
+ COMPREPLY=( $( compgen -W 'list save restore' -- "$cur" ) )
+ else
+ COMPREPLY=( $( compgen -W 'create n add del test destroy x list save \
+ restore flush rename e swap w help version' -- "$cur" ) )
+ fi
+fi
fi
-if [[ $DEBUG ]]; then
- printf "COMPREPLY:\n"
- printf "<%s>\n" "${COMPREPLY[@]}"
+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[@]}"
fi
}
complete -F _ipset_complete ipset
+
diff --git a/utils/ipset_list/README.md b/utils/ipset_list/README.md
index fad617e..4806252 100644
--- a/utils/ipset_list/README.md
+++ b/utils/ipset_list/README.md
@@ -6,7 +6,6 @@ ipset set listing wrapper script
Features:
==========
-(in addition to the native ipset options)
- Calculate sum of set members (and match on that count).
- List only members of a specified set.
@@ -21,6 +20,7 @@ Features:
- 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.
Examples:
@@ -46,9 +46,9 @@ Examples:
- `ipset_list -Xs setA -Xs setB` - show all set names, but exclude setA and setB.
- `ipset_list -Xs "set[AB]"` - show all set names, but exclude setA and setB.
- `ipset_list -Mc 0` - show sets with zero members
-- `ipset_list -Mc '>=100'` - show sets with a member count greater or equal to 100
- `ipset_list -Hr \>=1 -Hv 0 -Hs \>10000` - find sets with at least one reference, revision of 0 and size in memory greater than 10000
- `ipset_list -i -Fr "^210\..*" setA` - show only members of setA matching the regex "^210\\..*"
+- `ipset_list -Mc \>=100 -Mc \<=150` - show sets with a member count greater or equal to 100 and not greater than 150.
- `ipset_list -a -c -Fh "Type:hash:ip" -Fr "^210\..*"` - show all information of sets with type hash:ip, matching the regex "^210\\..*", show match and members sum
- `ipset_list -Fh Type:hash:ip -Fh "Header:family inet *"` - show all set names, which are of type hash:ip and header of ipv4.
- `ipset_list -t -Xh "Revision:*" -Xh "References:*"` - show all sets headers, but exclude Revision and References entries.
diff --git a/utils/ipset_list/ipset_list b/utils/ipset_list/ipset_list
index b5f1dab..18743a5 100755
--- a/utils/ipset_list/ipset_list
+++ b/utils/ipset_list/ipset_list
@@ -59,7 +59,6 @@
# $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 '>=100' - show sets with a member count greater or equal to 100
# $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.
@@ -68,6 +67,8 @@
# $0 -Ht "!(hash:ip)" - show sets which are not of type hash:ip
# $0 -Ht "!(bitmap:*)" - show sets wich are not of any bitmap type
# $0 -i -Fr "^210\..*" setA - show only members of setA matching the regex "^210\..*"
+# $0 -Mc \>=100 -Mc \<=150 - show sets with a member count greater or equal to 100
+#+ and not greater than 150.
# $0 -a -c -Fh "Type:hash:ip" -Fr "^210\..*"
#+ - show all information of sets with type hash:ip,
#+ matching the regex "^210\..*", show match and members sum.
@@ -197,15 +198,16 @@ set +u
# variables
export LC_ALL=C
-readonly version=2.6
+readonly version=2.7
readonly me="${0//*\//}"
readonly oIFS="$IFS"
-declare ips_version="" str_search="" str_match_on_msum="" str_xclude="" opt str_hval str_op
+declare ips_version="" str_search="" str_xclude="" opt str_hval 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 arr_hsearch_int arr_hxclude arr_sxclude
+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=()
# functions
ex_miss_optarg() {
@@ -239,24 +241,25 @@ while (($#)); do
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' "$me"\
- "[-t|-c|-Co|-Cs|-Tm|-Ts] [-Fh header-glob:value-glob] [...]"\
+ "[{-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]"\
+ "[-Mc [!|<|>|<=|>=]value] [...] [-To value]"\
"[-Xh header-glob:value-glob] [...]"\
- "[-Xs setname-glob] [...] [set-name] [...]"
+ "[-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|-Co|-Cs|-Tm|-Ts] [-d char]"\
+ "[-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]"\
+ "[-Mc [!|<|>|<=|>=]value] [...] [-To value]"\
"[-Xh header-glob:value-glob] [...]"\
- "[-Xg|-Xr pattern] [-Xs setname-glob] [...] [set-name] [...]"
+ "[-Xg|-Xr pattern] [-Xs setname-glob] [...] -- [set-name] [...]"
printf 'options:\n'
printf '%-13s%s\n' '-a' 'show all information but with default delim (whitespace).'\
'-c' 'calculate members and match (-Fg|-Fr) sum.'\
@@ -269,6 +272,7 @@ while (($#)); do
'-s' 'print elements sorted (if supported by the set type).'\
'-t' 'show set headers only.'\
'-v' 'version information.'\
+ '-Ca' "shortcut for -c -Cs -Ts -Tm (enable all counters)."\
'-Co' "colorize output (requires \`cl')."\
'-Cs' 'count amount of matching sets.'\
'-Fg pattern' 'match on members using a [ext]glob pattern.'\
@@ -280,8 +284,8 @@ while (($#)); do
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).'\
- '-Mc [!|<|>|<=|>=]value' 'match on member count (value=int).'
+ '-Hv [!|<|>|<=|>=]value' 'match on revision number (value=int).'
+ printf '%-30s%s\n' '-Mc [!|<|>|<=|>=]value [...]' 'match on member count (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.'
@@ -290,6 +294,7 @@ while (($#)); do
printf '%-13s%s\n' '-Xg pattern' 'exclude members matching a [ext]glob pattern.'\
'-Xr pattern' 'exclude members matching a regex pattern.'\
'-Xs pattern' 'exclude sets matching a [ext]glob pattern.'
+ printf '%-13s%s\n' '--' 'stop further option processing.'
exit 0
;;
-a) show_all=1 # like `ipset list', but with $delim as delim
@@ -327,6 +332,10 @@ while (($#)); do
shift 2
fi
;;
+ -Ca) # shortcut for -c -Cs -Ts -Tm
+ show_count=1 count_sets=1 calc_mem=1 sets_total=1
+ shift
+ ;;
-Cs) count_sets=1 # calculate total count of matching sets
shift
;;
@@ -400,7 +409,7 @@ while (($#)); do
-Mc) do_count=1 # match on the count of members
[[ $2 ]] || ex_miss_optarg $1 "value pattern"
if is_compare_str "$2"; then
- str_match_on_msum="$2"
+ arr_match_on_msum[${#arr_match_on_msum[@]}]="$2"
shift 2
else
ex_invalid_usage "invalid format of match on member count value. expecting: \`[!|<|>|<=|>=]value'"
@@ -446,6 +455,8 @@ while (($#)); do
-v) printf "%s version %s\n" "$me" "$version"
exit 0
;;
+ --) shift; break
+ ;;
*) break
esac
done
@@ -456,7 +467,7 @@ declare -i i=x=idx=0
printf "ipset binary \`%s' does not exist, or is not executable. check \`ipset' variable\n" "$ipset" >&2
exit 1
}
-ips_version="$("$ipset" --version)"
+ips_version="$("$ipset" version)"
ips_version="${ips_version#ipset v}"
ips_version="${ips_version%%.*}"
if ! is_int "$ips_version"; then
@@ -518,7 +529,11 @@ if ((glob_xclude_element || regex_xclude_element)); then
fi
if ((colorize)); then
if ! [[ -x ${cl:=/usr/local/bin/cl} ]]; then
- printf "cl program \`%s' does not exist, or is not executable. check \`cl' variable\n" "$cl" >&2
+ printf "\ncl program \`%s' does not exist, or is not executable.\ncheck \`cl' variable.\n\n" "$cl" >&2
+ printf "If you do not have the program, you can download it from:\n"
+ printf "%s\n" "http://sourceforge.net/projects/colorize-shell/" \
+ "https://github.com/AllKind/cl" >&2
+ printf "\n"
exit 1
fi
# set color defaults if unset
@@ -704,6 +719,11 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
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
+ fi
fi
else
if ((glob_xclude_element)); then # exclude matching members
@@ -712,9 +732,14 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
else let xclude_count+=1
fi
elif ((regex_xclude_element)); then # exclude matching members
- if ! [[ $REPLY =~ $str_xclude ]]; then
+ if [[ $REPLY =~ $str_xclude ]]; then
+ let xclude_count+=1
+ else
+ if (($? == 2)); then
+ printf "Invalid regex pattern \`%s'.\n" "$str_xclude"
+ exit 1
+ fi
arr_mcache[i++]="$REPLY"
- else let xclude_count+=1
fi
else
arr_mcache[i++]="$REPLY"
@@ -733,12 +758,14 @@ for idx in "${!arr_sets[@]}"; do found_set=0 arr_hcache=() arr_mcache=()
if ((glob_search || regex_search)) && ((match_count == 0)); then
continue # glob or regex search didn't match
fi
- if [[ $str_match_on_msum ]]; then # match on member sum
- str_op="${str_match_on_msum//[[:digit:]]}"
- [[ ${str_op:===} = \! ]] && str_op='!='
- if ! (($member_count $str_op ${str_match_on_msum//[[:punct:]]})); then
- continue # does not match
- fi
+ if ((${#arr_match_on_msum[@]} > 0)); then # match on member sum
+ for i in ${!arr_match_on_msum[@]}; do
+ str_op="${arr_match_on_msum[i]//[[:digit:]]}"
+ [[ ${str_op:===} = \! ]] && str_op='!='
+ if ! (($member_count $str_op ${arr_match_on_msum[i]//[[:punct:]]})); then
+ continue 2 # does not match
+ fi
+ done
fi
let set_count+=1 # count amount of matching sets
if ((calc_mem)); then
diff --git a/utils/ipset_list/ipset_list_bash_completion b/utils/ipset_list/ipset_list_bash_completion
index 2580009..e4faf7f 100644
--- a/utils/ipset_list/ipset_list_bash_completion
+++ b/utils/ipset_list/ipset_list_bash_completion
@@ -40,164 +40,253 @@ ipset_list=ipset_list
shopt -s extglob
-_remove_reply_entry() {
+# -----------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------
+
+_ipset_list_show_sets() {
+COMPREPLY=( $( compgen -W '${sets[@]}' -- "$cur" ) )
+# dedupe sets listing
+for ((i=set_index; i < ${#words[@]}-1; i++)); do
+ _ipset_list_remove_reply_entry "${words[i]}"
+done
+}
+
+_ipset_list_remove_reply_entry() {
local -i x
while (($#)); do
- for x in ${!COMPREPLY[@]}; do
- if [[ ${COMPREPLY[x]} = $1 ]]; then
- unset COMPREPLY[x]
- break
- fi
- done
- shift
+ for x in ${!COMPREPLY[@]}; do
+ if [[ ${COMPREPLY[x]} = $1 ]]; then
+ if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "removing dupe entry COMPREPLY[$x]: %s\n" \
+ "${COMPREPLY[x]}"
+ fi
+ unset COMPREPLY[x]
+ break
+ fi
+ done
+ shift
done
}
+# -----------------------------------------------------------------
+# Main
+# -----------------------------------------------------------------
+
_ipset_list_complete() {
-local -i i=x=show_all=isolate=show_members=resolve=headers_only=0
-local cur prev
-local sets=()
-sets=( $("$ipset_list" -n ) )
-local opts=(-? -a -c -d -h -i -m -n -r -s -t -v)
-local Copts=(-Cs -Co)
+local -i i=x=got_bashcompl=0
+local -i show_all=isolate=show_members=resolve=headers_only=set_index=0
+local cur prev cword words str_tmp
+local sets=( $("$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 Topts=(-Tm -To -Ts)
local Xopts=(-Xh -Xg -Xr -Xs)
+local arr_types=()
: ${PATH:=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
COMPREPLY=()
-_get_comp_words_by_ref cur || return
-_get_comp_words_by_ref prev || return
-
-#DEBUG=Y
-if [[ $DEBUG ]]; then
- printf "\ncur: <%s> prev: <%s>\n" "$cur" "$prev"
- printf "COMP_WORDS:\n"
- printf "<%s>\n" "${COMP_WORDS[@]}"
+
+# expecting _get_comp_words_by_ref() to exist from bash_completion
+if declare -f _get_comp_words_by_ref &>/dev/null; then got_bashcompl=1
+ _get_comp_words_by_ref -n : cur prev cword words || return
+else got_bashcompl=0 # not so neat, but a workaround
+ COMP_WORDBREAKS="${COMP_WORDBREAKS//:/}"
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ cword=$COMP_CWORD
+ for i in ${!COMP_WORDS[@]}; do words[i]="${COMP_WORDS[i]}"; done
fi
-# dont' allow an option after the set name(s)
-if [[ $cur = -* ]]; then
- for i in ${!sets[@]}; do
- [[ ${sets[i]} = $prev ]] && return 0
- done
+#_DEBUG_NF_COMPLETION=Y
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "\nCOMP_WORDBREAKS: <%s>\n" "$COMP_WORDBREAKS"
+ printf "COMP_LINE: <%s>\n" "$COMP_LINE"
+ printf "COMP_TYPE: <%s>\n" "$COMP_TYPE"
+ printf "COMP_POINT: <%s>\n" "$COMP_POINT"
+ printf "COMP_KEY: <%s>\n" "$COMP_KEY"
+ printf "COMP_CWORD: <%s>\n" "$COMP_CWORD"
+ printf "\ncur: <%s> prev: <%s>\n" "$cur" "$prev"
+ printf "words:\n"
+ printf "<%s>\n" "${words[@]}"
fi
-# some options allow only a subset of other options
-for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do
- case "${COMP_WORDS[i]}" in
- -a) show_all=1 ;;
- -i) isolate=1 ;;
- -m) show_members=1 ;;
- -r) resolve=1 ;;
- -t) headers_only=1 ;;
- -\?|-h|-n|-v)
- return 0
- ;;
- esac
+
+# collect info of cmdline
+for ((i=1; i < ${#words[@]}-1; i++)); do
+ case "${words[i]}" in
+ -a) ((set_index)) && break || show_all=1 ;;
+ -i) ((set_index)) && break || isolate=1 Copts=(-Co) ;;
+ -m) ((set_index)) && break || show_members=1 ;;
+ -r) ((set_index)) && break || resolve=1 ;;
+ -t) ((set_index)) && break || headers_only=1 ;;
+ --) ((set_index)) && break || set_index=$((i+1)) ;;
+ -\?|-h|-n|-v)
+ ((set_index)) || return 0
+ ;;
+ @(-Fh|-Fi|-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))"
+ 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))"
+ if [[ ${words[i]} != $str_tmp ]]; then
+ for x in ${!sets[@]}; do
+ if [[ ${sets[x]} = ${words[i]} ]]; then
+ set_index=$i
+ break
+ fi
+ done
+ fi
+ esac
done
+
# invalid combinations of options
if ((headers_only)); then
- if ((show_all || show_members || isolate || resolve)); then
- return 0
- fi
-elif ((isolate && show_all)); then
- return 0
+ if ((show_all || show_members || isolate || resolve)); then
+ return 0
+ fi
+elif ((isolate)); then
+ if ((show_all || header_operation)); then
+ return 0
+ fi
fi
-case "$cur" in
- -C) COMPREPLY=( ${Copts[@]} $cur )
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do # no set counting on -i
- if [[ ${COMP_WORDS[i]} = -i ]]; then
- COMPREPLY=( -Co )
- break
- fi
- done
- ;;
- -F) COMPREPLY=( ${Fopts[@]} ) ;;
- -H) COMPREPLY=( ${Hopts[@]} ) ;;
- -M) COMPREPLY=( -Mc ) ;;
- -T) COMPREPLY=( ${Topts[@]} ) ;;
- -X) COMPREPLY=( ${Xopts[@]} ) ;;
- -*) # any option is requested
- case "$prev" in # options that exclude any other option, or need a value we can't predict
- @(-@(\?|d|h|n|v|Fg|Fh|Fi|Fr|Ht|Hr|Hs|Hv|Mc|To|Xg|Xh|Xr)))
- return 0
- ;;
- esac
- if ((${#COMP_WORDS[@]} > 2)); then # these options don't allow any other
- opts=("${opts[@]/@(-n|-h|-\?)/}")
- fi
- # some options allow only a subset of other options
- if ((isolate)); then
- COMPREPLY=( -Co -d -r -s $cur )
- elif ((headers_only)); then
- COMPREPLY=( -c ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]} )
- elif ((show_members)); then
- COMPREPLY=( -c -d -r -s ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} )
- elif ((show_all)); then
- COMPREPLY=( -c -d -r -s ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]} )
- elif ((resolve)); then
- COMPREPLY=( -a -c -d -s -m ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]} )
- else
- COMPREPLY=( ${opts[@]} ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]} )
- fi
- ;;
- *) # not an option was requested
- COMPREPLY=( $( compgen -W '${sets[@]}' -- $cur ) )
- case "$prev" in
- -Xh) # retrieve list of headers
- COMPREPLY=()
- while read -r; do
- [[ $REPLY = Name ]] && continue
- COMPREPLY[${#COMPREPLY[@]}]="$REPLY"
- done < <( "$ipset_list" -t "${sets[0]}" | command awk -F: '{ print $1 }' )
- compopt -o nospace
- local IFS=$'\n'
- COMPREPLY=( $( compgen -P '"' -S ':*"' -W '${COMPREPLY[@]}' -- $cur ) )
- ;;
- @(-@(Hr|Hs|Hv|Mc))) # options making use of arithmetic comparison
- compopt -o nospace
- COMPREPLY=( '\!' '\<' '\>' '\<=' '\>=' )
- ;;
- @(-@(\?|d|h|n|v|Fg|Fh|Fi|Fr|Ht|To|Xg|Xr))) COMPREPLY=() ;;
- esac
- if ((isolate)); then # allow only one set with isolate
- for i in ${!sets[@]}; do
- if [[ ${sets[i]} = $prev ]]; then
- COMPREPLY=()
- break
- fi
- done
- fi
-esac
-if ((${#COMPREPLY[@]})); then # post process the reply
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do # mutual exclusive options
- case "${COMP_WORDS[i]}" in
- -Fg) _remove_reply_entry "-Fr" "-Xg" "-Xr" ;;
- -Fr) _remove_reply_entry "-Fg" "-Xg" "-Xr" ;;
- -Xg) _remove_reply_entry "-Fg" "-Fr" "-Xr" ;;
- -Xr) _remove_reply_entry "-Fg" "-Fr" "-Xg" ;;
- esac
- done
- for ((i=1; i <= ${#COMP_WORDS[@]}; i++)); do # remove options that can only be used once
- if [[ ${COMP_WORDS[i]} = @(""|-|-@(Fh|Fi|Xh|Xs)) ]]; then
- continue
- else
- for x in ${!COMPREPLY[@]}; do
- if [[ ${COMP_WORDS[i]} = ${COMPREPLY[x]} ]]; then
- unset COMPREPLY[$x]
- break
- fi
- done
- fi
- done
+# start setting compreply
+# all depends on $set_index
+if ((set_index)); then
+ if ((isolate && cword > set_index)); then
+ return 0 # allow only one set with isolate
+ fi
+ # dont' allow an option after the set name(s)
+ # allows to list sets which start with an hyphen
+ # 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
+ return 0
+elif [[ $prev = -Xs ]]; then
+ # list sets if user does not want to enter a glob
+ _ipset_list_show_sets
+elif [[ $prev = -Ht ]]; then i=0
+ # show supported set types
+ while read -r; do
+ [[ $REPLY = "Supported set types:"* ]] && ((!i)) && \
+ i=1 && continue
+ ((i)) || continue
+ if [[ $REPLY = *:* ]]; then
+ set -- $REPLY
+ arr_types[${#arr_types[@]}]="$1"
+ fi
+ done < <(ipset help)
+ for i in ${!arr_types[@]}; do # remove dupe entries
+ for ((x=i+1; x < ${#arr_types[@]}; x++)); do
+ if [[ ${arr_types[i]} = ${arr_types[x]} ]]; then
+ unset arr_types[x]
+ fi
+ done
+ done
+ COMPREPLY=( $( compgen -W '${arr_types[@]}' -- "$cur" ) )
+elif [[ $prev = @(-Fh|-Xh) ]]; then
+ # retrieve list of headers
+ if ((${#sets[*]} > 0)); then
+ while read -r; do
+ [[ $REPLY = Name ]] && continue
+ COMPREPLY[${#COMPREPLY[@]}]="$REPLY"
+ done < <("$ipset_list" -t "${sets[0]}"|command awk -F: '{print $1}')
+ compopt -o nospace
+ local IFS=$'\n'
+ if [[ $prev = -Xh ]]; then
+ COMPREPLY=( $( compgen -P '"' -S ':*"' \
+ -W '${COMPREPLY[@]}' -- "$cur" ) )
+ elif [[ $prev = -Fh ]]; then
+ COMPREPLY=( $( compgen -P '"' -S ':"' \
+ -W '${COMPREPLY[@]}' -- "$cur" ) )
+ fi
+ fi
+elif [[ $prev = @(-@(Hr|Hs|Hv|Mc)) ]]; then
+ # options making use of arithmetic comparison
+ compopt -o nospace
+ COMPREPLY=( $( compgen -P '\' -W '\! \< \> \<= \>=' -- "$cur" ) )
+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)))
+ # options that exclude any other option,
+ # or need a value we can't predict
+ return 0
+ ;;
+ esac
+ # these options don't allow any other
+ if ((${#words[@]} > 2)); then
+ opts=("${opts[@]/@(-n|-h|-\?)/}")
+ fi
+ # some options allow only a subset of other options
+ if ((isolate)); then
+ COMPREPLY=( $(compgen -W '-- -Co -d -r -s' -- $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[@]}' \
+ -- $cur ) )
+ elif ((show_all)); then
+ COMPREPLY=( $(compgen -W \
+ '-- -c -d -r -s ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]}' \
+ -- $cur ) )
+ elif ((resolve)); then
+ COMPREPLY=( $(compgen -W \
+ '-- -a -c -d -s -m ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]}' \
+ -- $cur ) )
+ elif ((header_operation)); then
+ COMPREPLY=( $(compgen -W \
+ '-- -a -c -d -s -m -t ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]}' \
+ -- $cur ) )
+ else
+ COMPREPLY=( $(compgen -W \
+ '${opts[@]} ${Copts[@]} ${Fopts[@]} ${Hopts[@]} -Mc ${Topts[@]} ${Xopts[@]}' \
+ -- $cur ) )
+ fi
+ # post process the reply
+ if ((${#COMPREPLY[@]})); then
+ x=$((set_index ? set_index : ${#words[*]}-1))
+ # 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" ;;
+ esac
+ # options allowed multiple times
+ if [[ ${words[i]} = @(""|-|-@(Fh|Fi|Mc|Xh|Xs)) ]]; then
+ continue
+ else # remove dupe
+ _ipset_list_remove_reply_entry "${words[i]}"
+ fi
+ done
+ fi
+elif [[ $cur = * ]]; then
+ # non option request
+ # default to sets listing
+ _ipset_list_show_sets
fi
-if [[ $DEBUG ]]; then
- printf "COMPREPLY:\n"
- printf "<%s>\n" "${COMPREPLY[@]}"
+fi
+
+((got_bashcompl)) && __ltrim_colon_completions "$cur"
+
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+ printf "COMPREPLY:\n"
+ printf "<%s>\n" "${COMPREPLY[@]}"
fi
}
-complete -F _ipset_list_complete "$ipset_list"
+complete -F _ipset_list_complete "${ipset_list:-ipset_list}"