#!/bin/bash # ----------------------------------------------------------------- # Programmable completion code for ipset (netfilter.org) # # https://github.com/AllKind/ipset-bash-completion # https://sourceforge.net/projects/ipset-bashcompl # ----------------------------------------------------------------- # Copyright (C) 2013 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 # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ----------------------------------------------------------------- # Tested with ipset versions: # 6.16.1 # ----------------------------------------------------------------- # # Put it into ~/.bash_completion or /etc/bash_completion.d/ # # ----------------------------------------------------------------- # # Version 1.9 # # ----------------------------------------------------------------- _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 # hostname t=${1#*@} COMPREPLY=( $( compgen -A hostname $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" ) ) fi } _ipset_complete() { shopt -s extglob local cur prev str_action ips_version local -i i=x=y=got_action=in_list=0 local -i ignore_errors=use_file=names_only=headers_only=save_format=res_sort=0 local arr_sets=() arr_types=() arr_members=() # ipset version check 6.x upwards (to v?) is supported ips_version="$(ipset --version)" ips_version="${ips_version#ipset v}" ips_version="${ips_version%%.*}" [[ $ips_version = +([[:digit:]]) ]] || return 1 ((ips_version < 6)) && return 1 COMPREPLY=() COMP_WORDBREAKS=$' \t\n"\'><=;|&(' # 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" 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 # 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 done # invalid combination of options if ((names_only && headers_only)); then COMPREPLY=() return 0 elif ((names_only || headers_only)); then if ((res_sort || ignore_errors)) || ((save_format == 3)); then COMPREPLY=() return 0 fi elif ((ignore_errors)); then if ((res_sort || save_format)); then COMPREPLY=() 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 ;; 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 else _ipset_bash_default_compl "$cur" fi if [[ $DEBUG ]]; then printf "COMPREPLY:\n" printf "<%s>\n" "${COMPREPLY[@]}" fi } complete -F _ipset_complete ipset