Linux lhjmq-records 5.15.0-118-generic #128-Ubuntu SMP Fri Jul 5 09:28:59 UTC 2024 x86_64
Your IP : 3.21.44.115
# bash completion for GNU tar -*- shell-script -*-
#
# General info
# ============
#
# The "old" style arguments
# -------------------------
#
# We don't "advice" the old tar option format by default for GNU tar, example:
#
# 'tar czfT /tmp/archive.tar patterns.txt'
#
# We rather advice the 'tar -czf /tmp/archive.tar -T patterns.txt' format of
# arguments. Though, if user starts the 'first' tar argument without leading
# dash, we treat the command line apropriately.
#
#
# long/short options origin
# -------------------------
#
# For GNU tar, everything is parsed from `tar --help` output so not so much
# per-distribution work should be needed. The _parse_help does not seem to be
# good enough so parsed here directly.
#
#
# FIXME: --starting-file (-K) (should be matched for extraction only)
# FIXME: handle already used (at least short) options
# FIXME: Test-cases for make check.
# - check for no global variable pollution
# FIXME: why PS4='$BASH_SOURCE:$LINENO: ' shows sometimes negative lines?
# FIXME: timeout on tarball listing
# FIXME: cache 'tar --help' parsing results into global variables
# FIXME: at least 'tar -<tab>' should show some helping text (apart from just
# pure option advices)
# FIXME: short option completion should be more intuitive
# - verbose mode option should be advised multiple times
# - mode option should be advised only once
# - format option should be advised only once
# ...
__gtar_parse_help_opt()
{
local opttype arg opt separator optvar
opttype=long
arg="$2"
opt="$1"
separator=" "
case "$opt" in
--*) ;;
-\?)
return
;;
-*)
opttype=short
opt=${opt##-}
separator=
;;
*)
echo "bash_completion: $FUNCNAME: unknown option $opt" >&2
return 1
;;
esac
# Remove arguments.
opt=${opt//\[*/}
opt=${opt//=*/=}
# Basic sanity.
opt=${opt//\"*/}
opt=${opt//\'*/}
opt=${opt//\;*/}
optvar=$opttype'_arg_'$arg
eval "$optvar=\"\$$optvar$separator\"\"$opt\""
}
__gtar_parse_help_line()
{
local i
for i in $1; do
case "$i" in
# regular options
--* | -*)
__gtar_parse_help_opt "$i" "$2"
;;
# end once there is single non-option word
*)
break
;;
esac
done
}
__gnu_tar_parse_help()
{
local str line arg
while IFS= read line; do
# Ok, this requires some comment probably. The GNU help output prints
# options on lines beginning with spaces. After that, there is one
# or more options separated by ', ' separator string. We are matching
# like this then: ^<spaces>(<separator>?<option>)+<whatever>$
if [[ $line =~ \
^[[:blank:]]{1,10}(((,[[:blank:]])?(--?([\]\[a-zA-Z0-9?=-]+))(,[[:space:]])?)+).*$ ]]; then
line=${BASH_REMATCH[1]}
str="${line//,/ }"
# Detect that all options on this line accept arguments (and whether
# the arguments are required or not). Note that only long option
# description in GNU help output mentions arguments. So the $line
# variable may contain e.g. '-X, --XXX[=NAME], -XXX2[=NAME]'.
arg=none
if [[ $line =~ --[A-Za-z0-9-]+(\[?)= ]]; then
[[ -n ${BASH_REMATCH[1]} ]] && arg=opt || arg=req
fi
__gtar_parse_help_line "$str" "$arg"
fi
done <<<"$(tar --help)"
long_opts="\
$long_arg_none $long_arg_opt $long_arg_req"
short_opts="$short_arg_none$short_arg_opt$short_arg_req"
}
# Hack: parse --warning keywords from tar's error output
__gtar_parse_warnings()
{
local line
LC_ALL=C tar --warning= 2>&1 | while IFS= read line; do
if [[ $line =~ ^[[:blank:]]*-[[:blank:]]*[\`\']([a-zA-Z0-9-]+)\'$ ]]; then
echo "${BASH_REMATCH[1]} no-${BASH_REMATCH[1]}"
fi
done
}
# Helper to obtain last character of string.
__tar_last_char()
{
echo "${1:$((${#1} - 1))}"
}
__tar_parse_old_opt()
{
local first_word char
# current word is the first word
[[ $cword -eq 1 && -n $cur && ${cur:0:1} != '-' ]] &&
old_opt_progress=1
# check that first argument does not begin with "-"
first_word=${words[1]}
[[ -n $first_word && ${first_word:0:1} != "-" ]] &&
old_opt_used=1
# parse the old option (if present) contents to allow later code expect
# corresponding arguments
if ((old_opt_used == 1)); then
char=${first_word:0:1}
while [[ -n $char ]]; do
if __tar_is_argreq "$char"; then
old_opt_parsed+=("$char")
fi
first_word=${first_word##$char}
char=${first_word:0:1}
done
fi
}
# Make the analysis of whole command line.
__tar_preparse_cmdline()
{
local first_arg i modes="ctxurdA"
shift # progname
__tar_parse_old_opt
first_arg=1
for i in "$@"; do
case "$i" in
--delete | --test-label)
tar_mode=${i:2:100}
tar_mode_arg=$i
break
;;
--*)
# skip
;;
-*[$modes]*)
tar_mode=${i//[^$modes]/}
tar_mode=${tar_mode:0:1}
tar_mode_arg=$i
break
;;
*[$modes]*)
# Only the first arg may be "MODE" without leading dash
if ((first_arg == 1)); then
tar_mode=${i//[^$modes]/}
tar_mode=${tar_mode:0:1}
tar_mode_arg=$i
fi
;;
esac
first_arg=0
done
}
# Generate completions for -f/--file.
__tar_file_option()
{
local ext="$1"
case "$tar_mode" in
c)
# no need to advise user to re-write existing tarball
_filedir -d
;;
*)
_filedir "$ext"
;;
esac
}
# Returns truth if option requires argument. No equal sign must be pasted.
# Accepts option in format: 'c', '-c', '--create'
__tar_is_argreq()
{
local opt
opt=$1
case "$opt" in
-[A-Za-z0-9?])
[[ $short_arg_req =~ ${opt##-} ]] && return 0
;;
[A-Za-z0-9?])
[[ $short_arg_req =~ ${opt} ]] && return 0
;;
--*)
[[ $long_arg_req =~ [[:blank:]]$opt=[[:blank:]] ]] && return 0
;;
esac
return 1
}
# Called only for short parameter
__tar_complete_mode()
{
local short_modes rawopt generated \
allshort_raw_unused allshort_raw \
filler i
short_modes="ctx"
[[ ! -v basic_tar ]] && short_modes="ctxurdA"
# Remove prefix when needed
rawopt=${cur#-}
# -c -z -x ... => czx
allshort_raw=${short_opts//[- ]/}
# init the 'mode' option if no option is in ${cur}
if [[ $tar_mode == none ]]; then
# when user passed something like 'tar cf' do not put the '-' before
filler=
if [[ -z $cur && ! -v basic_tar ]]; then
filler=-
fi
generated=""
for ((i = 0; 1; i++)); do
local c="${short_modes:i:1}"
[[ -z $c ]] && break
generated+=" $filler$cur$c"
done
COMPREPLY=($(compgen -W "$generated"))
return 0
fi
# The last short option requires argument, like '-cf<TAB>'. Cut the
# completion here to enforce argument processing.
if ((old_opt_progress == 0)) &&
__tar_is_argreq "$(__tar_last_char "$cur")"; then
COMPREPLY=("$cur") && return 0
fi
allshort_raw_unused=${allshort_raw//[$rawopt]/}
if [[ $tar_mode != none ]]; then
allshort_raw_unused=${allshort_raw_unused//[$short_modes]/}
fi
generated=
for ((i = 0; 1; i++)); do
local c="${allshort_raw_unused:i:1}"
[[ -z $c ]] && break
generated+=" $cur$c"
done
COMPREPLY=($(compgen -W "$generated"))
return 0
}
__gtar_complete_lopts()
{
local rv
COMPREPLY=($(compgen -W "$long_opts" -- "$cur"))
rv=$?
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
return $rv
}
__gtar_complete_sopts()
{
local generated short_mode_opts i c
short_mode_opts="ctxurdA"
generated=${short_opts//[$short_mode_opts]/}
for ((i = 0; 1; i++)); do
c="${allshort_raw_unused:i:1}"
[[ -z $c ]] && break
generated+=" $cur$c"
done
COMPREPLY=($(compgen -W "$generated" -- "$cur"))
}
__tar_try_mode()
{
case "$cur" in
--*)
# posix tar does not support long opts
[[ -v basic_tar ]] && return 0
__gtar_complete_lopts
return $?
;;
-*)
# posix tar does not support short optios
[[ -v basic_tar ]] && return 0
__tar_complete_mode && return 0
;;
*)
if [[ $cword -eq 1 || $tar_mode == none ]]; then
__tar_complete_mode && return 0
fi
;;
esac
return 1
}
__tar_adjust_PREV_from_old_option()
{
# deal with old style arguments here
# $ tar cfTC # expects this sequence of arguments:
# $ tar cfTC ARCHIVE_FILE PATTERNS_FILE CHANGE_DIR
if ((old_opt_used == 1 && cword > 1 && \
cword < ${#old_opt_parsed[@]} + 2)); then
# make e.g. 'C' option from 'cffCT'
prev="-${old_opt_parsed[cword - 2]}"
fi
}
__tar_extract_like_mode()
{
local i
for i in x d t delete; do
[[ $tar_mode == "$i" ]] && return 0
done
return 1
}
__tar_try_list_archive()
{
local tarball tarbin untar i
__tar_extract_like_mode || return 1
# This all is just to approach directory completion from "virtual"
# directory structure in tarball (for which the _filedir is unusable)
set -- "${words[@]}"
tarbin=$1
untar="tf"
shift
for i in "$@"; do
if [[ $i == *.$ext ]]; then
tarball=$i
break
fi
done
if [[ -n $tarball ]]; then
local IFS=$'\n'
COMPREPLY=($(compgen -o filenames -W "$(
$tarbin $untar "$tarball" 2>/dev/null |
while read line; do
printf "%q\n" "$(printf %q"\n" "$line")"
done
)" -- "$(printf "%q\n" "$cur")"))
return 0
fi
}
__tar_cleanup_prev()
{
if [[ $prev =~ ^-[a-zA-Z0-9?]*$ ]]; then
# transform '-caf' ~> '-f'
prev="-$(__tar_last_char "$prev")"
fi
}
__tar_detect_ext()
{
local tars='@(@(tar|gem|spkg)?(.@(Z|[bgx]z|bz2|lz?(ma|o)|zst))|t@([abglx]z|b?(z)2|zst))'
ext="$tars"
case "$tar_mode_arg" in
--*)
# Should never happen?
;;
?(-)*[cr]*f)
ext='@(tar|gem|spkg)'
case ${words[1]} in
*a*) ext="$tars" ;;
*z*) ext='t?(ar.)gz' ;;
*Z*) ext='ta@(r.Z|z)' ;;
*[jy]*) ext='t@(?(ar.)bz?(2)|b2)' ;;
*J*) ext='t?(ar.)xz' ;;
esac
;;
+([^ZzJjy])f)
# Pass through using defaults above
;;
*[Zz]*f)
ext='@(@(t?(ar.)|gem.|spkg.)@(gz|Z)|taz)'
;;
*[jy]*f)
ext='@(@(t?(ar.)|gem.)bz?(2)|spkg|tb2)'
;;
*[J]*f)
ext='@(@(tar|gem|spkg).@(lzma|xz)|t[lx]z)'
;;
esac
}
_gtar()
{
local long_opts short_opts \
long_arg_none="" long_arg_opt="" long_arg_req="" \
short_arg_none="" short_arg_opt="" short_arg_req="" \
tar_mode tar_mode_arg old_opt_progress=0 \
old_opt_used=0 old_opt_parsed=()
# Main mode, e.g. -x or -c (extract/creation)
local tar_mode=none
# The mode argument, e.g. -cpf or -c
# FIXME: handle long options
local tar_mode_arg=
if [[ -v BASHCOMP_TAR_OPT_DEBUG ]]; then
set -x
PS4='$BASH_SOURCE:$LINENO: '
fi
local cur prev words cword split
_init_completion -s || return
# Fill the {long,short}_{opts,arg*}
__gnu_tar_parse_help
__tar_preparse_cmdline "${words[@]}"
local ext
__tar_detect_ext
while true; do # just-for-easy-break while, not looping
__tar_adjust_PREV_from_old_option
__tar_posix_prev_handle && break
__tar_cleanup_prev
# Handle all options *REQUIRING* argument. Optional arguments are up to
# user (TODO: is there any sane way to deal with this?). This case
# statement successes only if there already is PREV.
case $prev in
--directory | -!(-*)C)
_filedir -d
break
;;
--atime-preserve)
COMPREPLY=($(compgen -W 'replace system' -- "$cur"))
break
;;
--group)
COMPREPLY=($(compgen -g -- "$cur"))
break
;;
--owner)
COMPREPLY=($(compgen -u -- "$cur"))
break
;;
--info-script | --new-volume-script | --rmt-command | --rsh-command | \
--use-compress-program | -!(-*)[FI])
compopt -o filenames
COMPREPLY=($(compgen -c -- "$cur"))
break
;;
--volno-file | --add-file | --files-from | --exclude-from | \
--index-file | --listed-incremental | -!(-*)[TXg])
_filedir
break
;;
--format | -!(-*)H)
COMPREPLY=($(compgen -W 'gnu oldgnu pax posix ustar v7' \
-- "$cur"))
break
;;
--quoting-style)
COMPREPLY=($(compgen -W 'literal shell shell-always c c-maybe
escape locale clocale' -- "$cur"))
break
;;
--totals)
COMPREPLY=($(compgen -W 'SIGHUP SIGQUIT SIGINT SIGUSR1 SIGUSR2' \
-- "$cur"))
break
;;
--warning)
COMPREPLY=($(compgen -W "$(__gtar_parse_warnings)" -- "$cur"))
break
;;
--file | -!(-*)f)
__tar_file_option "$ext"
break
;;
--*)
# parameter with required argument but no completion yet
[[ " $long_arg_req " =~ \ $prev=\ ]] && break
# parameter with optional argument passed with =, something like
# --occurrence=*<TAB> which is not handled above
[[ " $long_arg_opt " =~ \ $prev\ ]] && break
# if there is some unknown option with '=', for example
# (literally) user does --nonexistent=<TAB>, we do not want
# continue also
$split && break
# Most probably, when code goes here, the PREV variable contains
# some string from "$long_arg_none" and we want continue.
;;
-!(-*)[a-zA-Z0-9?])
# argument required but no completion yet
[[ $short_arg_req =~ ${prev##-} ]] && break
;;
esac
# safety belts
case "$cur" in
-[a-zA-Z0-9]=*)
# e.g. 'tar -c -f=sth' does not what user could expect
break
;;
esac
# Handle the main operational mode of tar. We should do it as soon as
# possible.
__tar_try_mode && break
# handle others
case "$cur" in
--*)
__gtar_complete_lopts
break
;;
-*)
# called only if it is *not* first parameter
__gtar_complete_sopts
break
;;
esac
# the first argument must be "mode" argument or --param, if any of those
# was truth - the 'break' statement would have been already called
((cword == 1)) && break
__tar_try_list_archive && break
# file completion on relevant files
if [[ $tar_mode != none ]]; then
_filedir
fi
break
done # just-for-easy-break while
if [[ -v BASHCOMP_TAR_OPT_DEBUG ]]; then
set +x
unset PS4
fi
}
__tar_posix_prev_handle()
{
case "$prev" in
-f)
__tar_file_option "$ext"
return 0
;;
-b)
return 0
;;
esac
return 1
}
_posix_tar()
{
local long_opts short_opts basic_tar \
long_arg_none="" long_arg_opt long_arg_req="" \
short_arg_none short_arg_opt short_arg_req \
tar_mode tar_mode_arg old_opt_progress=0 \
old_opt_used=1 old_opt_parsed=()
# Main mode, e.g. -x or -c (extract/creation)
local tar_mode=none
# The mode argument, e.g. -cpf or -c
local tar_mode_arg=
local cur prev words cword split
_init_completion -s || return
basic_tar=yes
tar_mode=none
# relatively compatible modes are {c,t,x}
# relatively compatible options {b,f,m,v,w}
short_arg_req="fb"
short_arg_none="wmv"
short_opts="$short_arg_req$short_arg_none"
__tar_preparse_cmdline "${words[@]}"
local ext
__tar_detect_ext
__tar_adjust_PREV_from_old_option
__tar_posix_prev_handle && return
__tar_try_mode && return
__tar_try_list_archive && return
# file completion on relevant files
_filedir
}
_tar()
{
local cmd=${COMP_WORDS[0]} func line
line="$($cmd --version 2>/dev/null)"
case "$line" in
*GNU*)
func=_gtar
;;
*)
func=_posix_tar
;;
esac
$func "$@"
# Install real completion for subsequent completions
if [[ ${COMP_TAR_INTERNAL_PATHS-} ]]; then
complete -F $func -o dirnames tar
else
complete -F $func tar
fi
unset -f _tar
}
if [[ ${COMP_TAR_INTERNAL_PATHS-} ]]; then
complete -F _tar -o dirnames tar
complete -F _gtar -o dirnames gtar
complete -F _posix_tar -o dirnames bsdtar
complete -F _posix_tar -o dirnames star
else
complete -F _tar tar
complete -F _gtar gtar
complete -F _posix_tar bsdtar
complete -F _posix_tar star
fi
# ex: filetype=sh
|