]> git.proxmox.com Git - mirror_zfs.git/blob - contrib/bash_completion.d/zfs.in
72e1092a039bd13ea5a39ba30220914b7ddb2b7f
[mirror_zfs.git] / contrib / bash_completion.d / zfs.in
1 # Copyright (c) 2010-2016, Aneurin Price <aneurin.price@gmail.com>
2
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation
5 # files (the "Software"), to deal in the Software without
6 # restriction, including without limitation the rights to use,
7 # copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the
9 # Software is furnished to do so, subject to the following
10 # conditions:
11
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 # OTHER DEALINGS IN THE SOFTWARE.
23
24 __ZFS_CMD="@sbindir@/zfs"
25 __ZPOOL_CMD="@sbindir@/zpool"
26
27 # Disable bash's built-in hostname completion, as this makes it impossible to
28 # provide completions containing an @-sign, which is necessary for completing
29 # snapshot names. If bash_completion is in use, this will already be disabled
30 # and replaced with better completions anyway.
31 shopt -u hostcomplete
32
33 __zfs_get_commands()
34 {
35 $__ZFS_CMD 2>&1 | awk '/^\t[a-z]/ {print $1}' | cut -f1 -d '|' | uniq
36 }
37
38 __zfs_get_properties()
39 {
40 $__ZFS_CMD get 2>&1 | awk '$2 == "YES" || $2 == "NO" {print $1}'; echo all name space
41 }
42
43 __zfs_get_editable_properties()
44 {
45 $__ZFS_CMD get 2>&1 | awk '$2 == "YES" {print $1"="}'
46 }
47
48 __zfs_get_inheritable_properties()
49 {
50 $__ZFS_CMD get 2>&1 | awk '$3 == "YES" {print $1}'
51 }
52
53 __zfs_list_datasets()
54 {
55 $__ZFS_CMD list -H -o name -s name -t filesystem,volume "$@"
56 }
57
58 __zfs_list_filesystems()
59 {
60 $__ZFS_CMD list -H -o name -s name -t filesystem
61 }
62
63 __zfs_match_snapshot()
64 {
65 local base_dataset="${cur%@*}"
66 if [ "$base_dataset" != "$cur" ]
67 then
68 $__ZFS_CMD list -H -o name -s name -t snapshot -d 1 "$base_dataset"
69 else
70 if [ "$cur" != "" ] && __zfs_list_datasets "$cur" &> /dev/null
71 then
72 $__ZFS_CMD list -H -o name -s name -t filesystem,volume -r "$cur" | tail -n +2
73 # We output the base dataset name even though we might be
74 # completing a command that can only take a snapshot, because it
75 # prevents bash from considering the completion finished when it
76 # ends in the bare @.
77 echo "$cur"
78 echo "$cur@"
79 else
80 local datasets
81 datasets="$(__zfs_list_datasets)"
82 # As above
83 echo "$datasets"
84 if [[ "$cur" == */ ]]
85 then
86 # If the current command ends with a slash, then the only way
87 # it can be completed with a single tab press (ie. in this pass)
88 # is if it has exactly one child, so that's the only time we
89 # need to offer a suggestion with an @ appended.
90 local num_children
91 # This is actually off by one as zfs list includes the named
92 # dataset in addition to its children
93 num_children=$(__zfs_list_datasets -d 1 "${cur%/}" 2> /dev/null | wc -l)
94 if [[ $num_children != 2 ]]
95 then
96 return 0
97 fi
98 fi
99 echo "$datasets" | awk '{print $1 "@"}'
100 fi
101 fi
102 }
103
104 __zfs_match_snapshot_or_bookmark()
105 {
106 local base_dataset="${cur%[#@]*}"
107 if [ "$base_dataset" != "$cur" ]
108 then
109 if [[ $cur == *@* ]]
110 then
111 $__ZFS_CMD list -H -o name -s name -t snapshot -d 1 "$base_dataset"
112 else
113 $__ZFS_CMD list -H -o name -s name -t bookmark -d 1 "$base_dataset"
114 fi
115 else
116 $__ZFS_CMD list -H -o name -s name -t filesystem,volume
117 if [ -e "$cur" ] && $__ZFS_CMD list -H -o name -s name -t filesystem,volume "$cur" &> /dev/null
118 then
119 echo "$cur@"
120 echo "$cur#"
121 fi
122 fi
123 }
124
125 __zfs_match_multiple_snapshots()
126 {
127 local existing_opts
128 existing_opts="$(expr "$cur" : '\(.*\)[%,]')"
129 if [ -e "$existing_opts" ]
130 then
131 local base_dataset="${cur%@*}"
132 if [ "$base_dataset" != "$cur" ]
133 then
134 local cur="${cur##*,}"
135 if [[ $cur =~ ^%|%.*% ]]
136 then
137 # correct range syntax is start%end
138 return 1
139 fi
140 local range_start
141 range_start="$(expr "$cur" : '\(.*%\)')"
142 # shellcheck disable=SC2016
143 $__ZFS_CMD list -H -o name -s name -t snapshot -d 1 "$base_dataset" | sed 's$.*@$'"$range_start"'$g'
144 fi
145 else
146 __zfs_match_snapshot_or_bookmark
147 fi
148 }
149
150 __zfs_list_volumes()
151 {
152 $__ZFS_CMD list -H -o name -s name -t volume
153 }
154
155 __zfs_argument_chosen()
156 {
157 local word property
158 for word in $(seq $((COMP_CWORD-1)) -1 2)
159 do
160 local prev="${COMP_WORDS[$word]}"
161 if [[ ${COMP_WORDS[$word-1]} != -[tos] ]]
162 then
163 if [[ "$prev" == [^,]*,* ]] || [[ "$prev" == *[@:\#]* ]]
164 then
165 return 0
166 fi
167 for property in "$@"
168 do
169 if [[ $prev == "$property"* ]]
170 then
171 return 0
172 fi
173 done
174 fi
175 done
176 return 1
177 }
178
179 __zfs_complete_ordered_arguments()
180 {
181 local list1=$1
182 local list2=$2
183 local cur=$3
184 local extra=$4
185 # shellcheck disable=SC2086
186 if __zfs_argument_chosen $list1
187 then
188 mapfile -t COMPREPLY < <(compgen -W "$list2 $extra" -- "$cur")
189 else
190 mapfile -t COMPREPLY < <(compgen -W "$list1 $extra" -- "$cur")
191 fi
192 }
193
194 __zfs_complete_multiple_options()
195 {
196 local options=$1
197 local cur=$2
198 local existing_opts
199
200 mapfile -t COMPREPLY < <(compgen -W "$options" -- "${cur##*,}")
201 existing_opts=$(expr "$cur" : '\(.*,\)')
202 if [ -n "$existing_opts" ]
203 then
204 COMPREPLY=( "${COMPREPLY[@]/#/${existing_opts}}" )
205 fi
206 }
207
208 __zfs_complete_switch()
209 {
210 local options=$1
211 if [[ ${cur:0:1} == - ]]
212 then
213 mapfile -t COMPREPLY < <(compgen -W "-{$options}" -- "$cur")
214 return 0
215 else
216 return 1
217 fi
218 }
219
220 __zfs_complete_nospace()
221 {
222 # Google indicates that there may still be bash versions out there that
223 # don't have compopt.
224 if type compopt &> /dev/null
225 then
226 compopt -o nospace
227 fi
228 }
229
230 __zfs_complete()
231 {
232 local cur prev cmd cmds
233 COMPREPLY=()
234 if type _get_comp_words_by_ref &> /dev/null
235 then
236 # Don't split on colon
237 _get_comp_words_by_ref -n : -c cur -p prev -w COMP_WORDS -i COMP_CWORD
238 else
239 cur="${COMP_WORDS[COMP_CWORD]}"
240 prev="${COMP_WORDS[COMP_CWORD-1]}"
241 fi
242 cmd="${COMP_WORDS[1]}"
243
244 if [[ ${prev##*/} == zfs ]]
245 then
246 cmds=$(__zfs_get_commands)
247 mapfile -t COMPREPLY < <(compgen -W "$cmds -?" -- "$cur")
248 return 0
249 fi
250
251 case "${cmd}" in
252 bookmark)
253 if __zfs_argument_chosen
254 then
255 mapfile -t COMPREPLY < <(compgen -W "${prev%@*}# ${prev/@/#}" -- "$cur")
256 else
257 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
258 fi
259 ;;
260 clone)
261 case "${prev}" in
262 -o)
263 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_get_editable_properties)" -- "$cur")
264 __zfs_complete_nospace
265 ;;
266 *)
267 if ! __zfs_complete_switch "o,p"
268 then
269 if __zfs_argument_chosen
270 then
271 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_list_datasets)" -- "$cur")
272 else
273 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
274 fi
275 fi
276 ;;
277 esac
278 ;;
279 get)
280 case "${prev}" in
281 -d)
282 mapfile -t COMPREPLY < <(compgen -W "" -- "$cur")
283 ;;
284 -t)
285 __zfs_complete_multiple_options "filesystem volume snapshot bookmark all" "$cur"
286 ;;
287 -s)
288 __zfs_complete_multiple_options "local default inherited temporary received none" "$cur"
289 ;;
290 -o)
291 __zfs_complete_multiple_options "name property value source received all" "$cur"
292 ;;
293 *)
294 if ! __zfs_complete_switch "H,r,p,d,o,t,s"
295 then
296 # shellcheck disable=SC2046
297 if __zfs_argument_chosen $(__zfs_get_properties)
298 then
299 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
300 else
301 __zfs_complete_multiple_options "$(__zfs_get_properties)" "$cur"
302 fi
303 fi
304 ;;
305 esac
306 ;;
307 inherit)
308 if ! __zfs_complete_switch "r"
309 then
310 __zfs_complete_ordered_arguments "$(__zfs_get_inheritable_properties)" "$(__zfs_match_snapshot)" "$cur"
311 fi
312 ;;
313 list)
314 case "${prev}" in
315 -d)
316 mapfile -t COMPREPLY < <(compgen -W "" -- "$cur")
317 ;;
318 -t)
319 __zfs_complete_multiple_options "filesystem volume snapshot bookmark all" "$cur"
320 ;;
321 -o)
322 __zfs_complete_multiple_options "$(__zfs_get_properties)" "$cur"
323 ;;
324 -s|-S)
325 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_get_properties)" -- "$cur")
326 ;;
327 *)
328 if ! __zfs_complete_switch "H,r,d,o,t,s,S"
329 then
330 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
331 fi
332 ;;
333 esac
334 ;;
335 promote)
336 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_list_filesystems)" -- "$cur")
337 ;;
338 rollback)
339 if ! __zfs_complete_switch "r,R,f"
340 then
341 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
342 fi
343 ;;
344 send)
345 if ! __zfs_complete_switch "D,n,P,p,R,v,e,L,i,I"
346 then
347 if __zfs_argument_chosen
348 then
349 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
350 else
351 if [[ $prev == -*i* ]]
352 then
353 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot_or_bookmark)" -- "$cur")
354 else
355 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
356 fi
357 fi
358 fi
359 ;;
360 snapshot)
361 case "${prev}" in
362 -o)
363 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_get_editable_properties)" -- "$cur")
364 __zfs_complete_nospace
365 ;;
366 *)
367 if ! __zfs_complete_switch "o,r"
368 then
369 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
370 __zfs_complete_nospace
371 fi
372 ;;
373 esac
374 ;;
375 set)
376 __zfs_complete_ordered_arguments "$(__zfs_get_editable_properties)" "$(__zfs_match_snapshot)" "$cur"
377 __zfs_complete_nospace
378 ;;
379 upgrade)
380 case "${prev}" in
381 -a|-V|-v)
382 mapfile -t COMPREPLY < <(compgen -W "" -- "$cur")
383 ;;
384 *)
385 if ! __zfs_complete_switch "a,V,v,r"
386 then
387 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_list_filesystems)" -- "$cur")
388 fi
389 ;;
390 esac
391 ;;
392 destroy)
393 if ! __zfs_complete_switch "d,f,n,p,R,r,v"
394 then
395 __zfs_complete_multiple_options "$(__zfs_match_multiple_snapshots)" "$cur"
396 __zfs_complete_nospace
397 fi
398 ;;
399 *)
400 mapfile -t COMPREPLY < <(compgen -W "$(__zfs_match_snapshot)" -- "$cur")
401 ;;
402 esac
403 if type __ltrim_colon_completions &> /dev/null
404 then
405 __ltrim_colon_completions "$cur"
406 fi
407 return 0
408 }
409
410 __zpool_get_commands()
411 {
412 $__ZPOOL_CMD 2>&1 | awk '/^\t[a-z]/ {print $1}' | uniq
413 }
414
415 __zpool_get_properties()
416 {
417 $__ZPOOL_CMD get 2>&1 | awk '$2 == "YES" || $2 == "NO" {print $1}'; echo all
418 }
419
420 __zpool_get_editable_properties()
421 {
422 $__ZPOOL_CMD get 2>&1 | awk '$2 == "YES" {print $1"="}'
423 }
424
425 __zpool_list_pools()
426 {
427 $__ZPOOL_CMD list -H -o name
428 }
429
430 __zpool_complete()
431 {
432 local cur prev cmd cmds pools
433 COMPREPLY=()
434 cur="${COMP_WORDS[COMP_CWORD]}"
435 prev="${COMP_WORDS[COMP_CWORD-1]}"
436 cmd="${COMP_WORDS[1]}"
437
438 if [[ ${prev##*/} == zpool ]]
439 then
440 cmds=$(__zpool_get_commands)
441 mapfile -t COMPREPLY < <(compgen -W "$cmds" -- "$cur")
442 return 0
443 fi
444
445 case "${cmd}" in
446 get)
447 __zfs_complete_ordered_arguments "$(__zpool_get_properties)" "$(__zpool_list_pools)" "$cur"
448 return 0
449 ;;
450 import)
451 if [[ $prev == -d ]]
452 then
453 _filedir -d
454 else
455 mapfile -t COMPREPLY < <(compgen -W "$(__zpool_list_pools) -d" -- "$cur")
456 fi
457 return 0
458 ;;
459 set)
460 __zfs_complete_ordered_arguments "$(__zpool_get_editable_properties)" "$(__zpool_list_pools)" "$cur"
461 __zfs_complete_nospace
462 return 0
463 ;;
464 add|attach|clear|create|detach|offline|online|remove|replace)
465 pools="$(__zpool_list_pools)"
466 # shellcheck disable=SC2086
467 if __zfs_argument_chosen $pools
468 then
469 _filedir
470 else
471 mapfile -t COMPREPLY < <(compgen -W "$pools" -- "$cur")
472 fi
473 return 0
474 ;;
475 *)
476 mapfile -t COMPREPLY < <(compgen -W "$(__zpool_list_pools)" -- "$cur")
477 return 0
478 ;;
479 esac
480
481 }
482
483 complete -F __zfs_complete zfs
484 complete -F __zpool_complete zpool