zfs/contrib/bash_completion.d/zfs.in

485 lines
15 KiB
Plaintext
Raw Normal View History

# Copyright (c) 2010-2016, Aneurin Price <aneurin.price@gmail.com>
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
__ZFS_CMD="@sbindir@/zfs"
__ZPOOL_CMD="@sbindir@/zpool"
# Disable bash's built-in hostname completion, as this makes it impossible to
# provide completions containing an @-sign, which is necessary for completing
# snapshot names. If bash_completion is in use, this will already be disabled
# and replaced with better completions anyway.
shopt -u hostcomplete
__zfs_get_commands()
{
$__ZFS_CMD 2>&1 | awk '/^\t[a-z]/ {print $1}' | cut -f1 -d '|' | uniq
}
__zfs_get_properties()
{
$__ZFS_CMD get 2>&1 | awk '$2 == "YES" || $2 == "NO" {print $1}'; echo all name space
}
__zfs_get_editable_properties()
{
$__ZFS_CMD get 2>&1 | awk '$2 == "YES" {print $1"="}'
}
__zfs_get_inheritable_properties()
{
$__ZFS_CMD get 2>&1 | awk '$3 == "YES" {print $1}'
}
__zfs_list_datasets()
{
$__ZFS_CMD list -H -o name -s name -t filesystem,volume "$@"
}
__zfs_list_filesystems()
{
$__ZFS_CMD list -H -o name -s name -t filesystem
}
__zfs_match_snapshot()
{
local base_dataset="${cur%@*}"
if [ "$base_dataset" != "$cur" ]
then
$__ZFS_CMD list -H -o name -s name -t snapshot -d 1 "$base_dataset"
else
if [ "$cur" != "" ] && __zfs_list_datasets "$cur" &> /dev/null
then
$__ZFS_CMD list -H -o name -s name -t filesystem -r "$cur" | tail -n +2
# We output the base dataset name even though we might be
# completing a command that can only take a snapshot, because it
# prevents bash from considering the completion finished when it
# ends in the bare @.
echo "$cur"
echo "$cur@"
else
local datasets
datasets="$(__zfs_list_datasets)"
# As above
echo "$datasets"
if [[ "$cur" == */ ]]
then
# If the current command ends with a slash, then the only way
# it can be completed with a single tab press (ie. in this pass)
# is if it has exactly one child, so that's the only time we
# need to offer a suggestion with an @ appended.
local num_children
# This is actually off by one as zfs list includes the named
# dataset in addition to its children
num_children=$(__zfs_list_datasets -d 1 "${cur%/}" 2> /dev/null | wc -l)
if [[ $num_children != 2 ]]
then
return 0
fi
fi
echo "$datasets" | awk '{print $1 "@"}'
fi
fi
}
__zfs_match_snapshot_or_bookmark()
{
local base_dataset="${cur%[#@]*}"
if [ "$base_dataset" != "$cur" ]
then
if [[ $cur == *@* ]]
then
$__ZFS_CMD list -H -o name -s name -t snapshot -d 1 "$base_dataset"
else
$__ZFS_CMD list -H -o name -s name -t bookmark -d 1 "$base_dataset"
fi
else
$__ZFS_CMD list -H -o name -s name -t filesystem,volume
if [ -e "$cur" ] && $__ZFS_CMD list -H -o name -s name -t filesystem,volume "$cur" &> /dev/null
then
echo "$cur@"
echo "$cur#"
fi
fi
}
__zfs_match_multiple_snapshots()
{
local existing_opts
existing_opts="$(expr "$cur" : '\(.*\)[%,]')"
if [ -e "$existing_opts" ]
then
local base_dataset="${cur%@*}"
if [ "$base_dataset" != "$cur" ]
then
local cur="${cur##*,}"
if [[ $cur =~ ^%|%.*% ]]
then
# correct range syntax is start%end
return 1
fi
local range_start
range_start="$(expr "$cur" : '\(.*%\)')"
# shellcheck disable=SC2016
$__ZFS_CMD list -H -o name -s name -t snapshot -d 1 "$base_dataset" | sed 's$.*@$'"$range_start"'$g'
fi
else
__zfs_match_snapshot_or_bookmark
fi
}
__zfs_list_volumes()
{
$__ZFS_CMD list -H -o name -s name -t volume
}
__zfs_argument_chosen()
{
local word property
for word in $(seq $((COMP_CWORD-1)) -1 2)
do
local prev="${COMP_WORDS[$word]}"
if [[ ${COMP_WORDS[$word-1]} != -[tos] ]]
then
if [[ "$prev" == [^,]*,* ]] || [[ "$prev" == *[@:\#]* ]]
then
return 0
fi
for property in "$@"
do
if [[ $prev == "$property"* ]]
then
return 0
fi
done
fi
done
return 1
}
__zfs_complete_ordered_arguments()
{
local list1=$1
local list2=$2
local cur=$3
local extra=$4
# shellcheck disable=SC2086
if __zfs_argument_chosen $list1
then
mapfile -t COMPREPLY < <(compgen -W "$list2 $extra" -- "$cur")
else
mapfile -t COMPREPLY < <(compgen -W "$list1 $extra" -- "$cur")
fi
}
__zfs_complete_multiple_options()
{
local options=$1
local cur=$2
local existing_opts
mapfile -t COMPREPLY < <(compgen -W "$options" -- "${cur##*,}")
existing_opts=$(expr "$cur" : '\(.*,\)')
if [ -n "$existing_opts" ]
then
COMPREPLY=( "${COMPREPLY[@]/#/${existing_opts}}" )
fi
}
__zfs_complete_switch()
{
local options=$1
if [[ ${cur:0:1} == - ]]
then
mapfile -t COMPREPLY < <(compgen -W "-{$options}" -- "$cur")
return 0
else
return 1
fi
}
__zfs_complete_nospace()
{
# Google indicates that there may still be bash versions out there that
# don't have compopt.
if type compopt &> /dev/null
then
compopt -o nospace
fi
}
__zfs_complete()
{
local cur prev cmd cmds
COMPREPLY=()
if type _get_comp_words_by_ref &> /dev/null
then
# Don't split on colon
_get_comp_words_by_ref -n : -c cur -p prev -w COMP_WORDS -i COMP_CWORD
else
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
fi
cmd="${COMP_WORDS[1]}"
if [[ ${prev##*/} == zfs ]]
then
cmds=$(__zfs_get_commands)
mapfile -t COMPREPLY < <(compgen -W "$cmds -?" -- "$cur")
return 0
fi
case "${cmd}" in
bookmark)
if __zfs_argument_chosen
then
mapfile -t COMPREPLY < <(compgen -W "${prev%@*}# ${prev/@/#}" -- "$cur")
else
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
fi
;;
clone)
case "${prev}" in
-o)
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_get_editable_properties)" -- "$cur")
__zfs_complete_nospace
;;
*)
if ! __zfs_complete_switch "o,p"
then
if __zfs_argument_chosen
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_list_datasets)" -- "$cur")
else
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
fi
fi
;;
esac
;;
get)
case "${prev}" in
-d)
mapfile -t COMPREPLY < <(compgen -W "" -- "$cur")
;;
-t)
__zfs_complete_multiple_options "filesystem volume snapshot bookmark all" "$cur"
;;
-s)
__zfs_complete_multiple_options "local default inherited temporary received none" "$cur"
;;
-o)
__zfs_complete_multiple_options "name property value source received all" "$cur"
;;
*)
if ! __zfs_complete_switch "H,r,p,d,o,t,s"
then
# shellcheck disable=SC2046
if __zfs_argument_chosen $(__zfs_get_properties)
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
else
__zfs_complete_multiple_options "$(__zfs_get_properties)" "$cur"
fi
fi
;;
esac
;;
inherit)
if ! __zfs_complete_switch "r"
then
__zfs_complete_ordered_arguments "$(__zfs_get_inheritable_properties)" "$(__zfs_match_snapshot)" "$cur"
fi
;;
list)
case "${prev}" in
-d)
mapfile -t COMPREPLY < <(compgen -W "" -- "$cur")
;;
-t)
__zfs_complete_multiple_options "filesystem volume snapshot bookmark all" "$cur"
;;
-o)
__zfs_complete_multiple_options "$(__zfs_get_properties)" "$cur"
;;
-s|-S)
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_get_properties)" -- "$cur")
;;
*)
if ! __zfs_complete_switch "H,r,d,o,t,s,S"
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
fi
;;
esac
;;
promote)
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_list_filesystems)" -- "$cur")
;;
rollback)
if ! __zfs_complete_switch "r,R,f"
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
fi
;;
send)
if ! __zfs_complete_switch "D,n,P,p,R,v,e,L,i,I"
then
if __zfs_argument_chosen
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
else
if [[ $prev == -*i* ]]
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot_or_bookmark)" -- "$cur")
else
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
fi
fi
fi
;;
snapshot)
case "${prev}" in
-o)
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_get_editable_properties)" -- "$cur")
__zfs_complete_nospace
;;
*)
if ! __zfs_complete_switch "o,r"
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
__zfs_complete_nospace
fi
;;
esac
;;
set)
__zfs_complete_ordered_arguments "$(__zfs_get_editable_properties)" "$(__zfs_match_snapshot)" "$cur"
__zfs_complete_nospace
;;
upgrade)
case "${prev}" in
-a|-V|-v)
mapfile -t COMPREPLY < <(compgen -W "" -- "$cur")
;;
*)
if ! __zfs_complete_switch "a,V,v,r"
then
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_list_filesystems)" -- "$cur")
fi
;;
esac
;;
destroy)
if ! __zfs_complete_switch "d,f,n,p,R,r,v"
then
__zfs_complete_multiple_options "$(__zfs_match_multiple_snapshots)" "$cur"
__zfs_complete_nospace
fi
;;
*)
mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
;;
esac
if type __ltrim_colon_completions &> /dev/null
then
__ltrim_colon_completions "$cur"
fi
return 0
}
__zpool_get_commands()
{
$__ZPOOL_CMD 2>&1 | awk '/^\t[a-z]/ {print $1}' | uniq
}
__zpool_get_properties()
{
$__ZPOOL_CMD get 2>&1 | awk '$2 == "YES" || $2 == "NO" {print $1}'; echo all
}
__zpool_get_editable_properties()
{
$__ZPOOL_CMD get 2>&1 | awk '$2 == "YES" {print $1"="}'
}
__zpool_list_pools()
{
$__ZPOOL_CMD list -H -o name
}
__zpool_complete()
{
local cur prev cmd cmds pools
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cmd="${COMP_WORDS[1]}"
if [[ ${prev##*/} == zpool ]]
then
cmds=$(__zpool_get_commands)
mapfile -t COMPREPLY < <(compgen -W "$cmds" -- "$cur")
return 0
fi
case "${cmd}" in
get)
__zfs_complete_ordered_arguments "$(__zpool_get_properties)" "$(__zpool_list_pools)" "$cur"
return 0
;;
import)
if [[ $prev == -d ]]
then
_filedir -d
else
mapfile -t COMPREPLY < <(compgen -W "$(__zpool_list_pools) -d" -- "$cur")
fi
return 0
;;
set)
__zfs_complete_ordered_arguments "$(__zpool_get_editable_properties)" "$(__zpool_list_pools)" "$cur"
__zfs_complete_nospace
return 0
;;
add|attach|clear|create|detach|offline|online|remove|replace)
pools="$(__zpool_list_pools)"
# shellcheck disable=SC2086
if __zfs_argument_chosen $pools
then
_filedir
else
mapfile -t COMPREPLY < <(compgen -W "$pools" -- "$cur")
fi
return 0
;;
*)
mapfile -t COMPREPLY < <(compgen -W "$(__zpool_list_pools)" -- "$cur")
return 0
;;
esac
}
complete -F __zfs_complete zfs
complete -F __zpool_complete zpool