如何使用制表符完成替换命令行参数?

时间:2012-05-11 12:39:52

标签: bash command-line-interface

我想知道这在Bash中是否可行,但我想使用制表符完全替换正在扩展的当前参数。  我举个例子: 我想要一个在树中向上移动任意数量级别的函数,所以我可以调用     2 这会打开我2个目录。 但是,我想这样做,如果在数字2,我按Tab键,它会将该数字扩展为路径(相对或绝对,或者很好)。 我有几乎使用完整的内置工作,除了它只会附加文本,所以它将是类似的东西     up 2 / Volumes / Dev /

是否可以替换已完成的符号?

提前致谢:)

更新

非常感谢chepner,因为实际检查我的代码揭示了我的bug所在。我正在与错误的var进行比较,而我的调试代码导致值无法替换。

对于任何有兴趣的人,这里是代码(并且可能有更好的方法来实现这一点):

# Move up N levels of the directory tree
# Or by typing in some dir in the PWD
# eg. Assuming your PWD is "/Volumes/Users/natecavanaugh/Documents/stuff"
#     `up 2` moves up 2 directories to "/Volumes/Users/natecavanaugh"
#     `up 2/` and pressing tab will autocomplete the dirs in "/Volumes/Users/natecavanaugh"
#     `up Users` navigate to "/Volumes/Users"
#     `up us` and pressing tab will autocomplete to "/Volumes/Users"
function up {
    dir="../"
    if [ -n "$1" ]; then
        if [[ $1 =~ ^[0-9]+$ ]]; then
            strpath=$( printf "%${1}s" );
            dir=" ${strpath// /$dir}"
        else
            dir=${PWD%/$1/*}/$1
        fi
    fi

    cd $dir
}

function _get_up {
    local cur
    local dir
    local results
    COMPREPLY=()
    #Variable to hold the current word
    cur="${COMP_WORDS[COMP_CWORD]}"

    local lower_cur=`echo ${cur##*/} | tr [:upper:] [:lower:]`

    # Is the arg a number or number followed by a slash
    if [[ $cur =~ ^[0-9]+/? ]]; then
        dir="../"
        strpath=$( printf "%${cur%%/*}s" );
        dir=" ${strpath// /$dir}"

        # Is the arg just a number?
        if [[ $cur =~ ^[0-9]+$ ]]; then
            COMPREPLY=($(compgen -W "${dir}"))
        else
            if [[ $cur =~ /.*$ ]]; then
                cur="${cur##*/}"
            fi

            results=$(for t in `cd $dir && ls -d */`; do if [[ `echo $t | tr [:upper:] [:lower:]` == "$lower_cur"* ]]; then echo "${t}"; fi done)

            COMPREPLY=($(compgen -P "$dir" -W "${results}"))
        fi
    else
        # Is the arg a word that we can look for in the PWD
        results=$(for t in `echo $PWD | tr "/" "\n"`; do if [[ `echo $t | tr [:upper:] [:lower:]` == "$lower_cur"* ]]; then echo "${t}"; fi; done)

        COMPREPLY=($(compgen -W "${results}"))
    fi  
}

#Assign the auto-completion function _get for our command get.
complete -F _get_up up

2 个答案:

答案 0 :(得分:2)

以下内容基于问题中OP自己的代码构建:

  • 提高健壮性:处理需要\的目录名称 - 正确转义 - 例如,带有嵌入空格的名称 - 正确;如果指定了无效的目录名(未完成),则报告错误
  • 修改命令完成方法:在命令完成时,级别编号/名称前缀扩展为相应的绝对路径终止{{1} } ,以便根据需要立即执行基于子目录的进一步完成。

修改后的例子是:

/

这是完整的代码(注意语法着色表示格式错误的代码,但事实并非如此):

# Assume that the working directory is '/Users/jdoe/Documents/Projects/stuff'.
#  `up 2` moves 2 levels up to '/Users/jdoe/Documents'
#  `up 2<tab>` completes to `up /Users/jdoe/Documents/`
#     Hit enter to change to that path or [type additional characters and]
#     press tab again to complete based on subdirectories.
#  `up Documents` or `up documents` changes to '/Users/jdoe/Documents'
#  `up Doc<tab>` or `up doc<tab>` completes to `up /Users/jdoe/Documents/`
#     Hit enter to change to that path or [type additional characters and]
#     press tab again to complete based on subdirectories.
#     Note: Case-insensitive completion is only performed if it is turned on
#     globally via the completion-ignore-case Readline option
#     (configured, for instance, via ~/.inputrc or /etc/inputrc).

答案 1 :(得分:2)

可以用一个新单词完全替换当前单词。用我的bash 4.2.29,我可以这样做:

_xxx() { COMPREPLY=( foo ); }
complete -F _xxx x
x bar # pressing tab turns this into x foo

但是,如果有多个可能的完成,并且您希望部分完成公共前缀,则会遇到问题。然后我的实验表明bash会尝试将可用的完成次数与您输入的前缀相匹配。

所以一般来说,如果某些内容是唯一定义的,你应该只用一些完全不同的东西替换当前的参数。否则,您应该生成与当前前缀匹配的完成,以便用户从中选择。在您的情况下,您可以用以下内容替换COMPREPLY=($(compgen -P "$dir" -W "${results}"))

local IFS=$'\n'
COMPREPLY=( $(find "${dir}" -maxdepth 1 -type d -iname "${cur#*/}*" -printf "%P\n") )
if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
    COMPREPLY=( "${dir}${COMPREPLY[0]}" )
fi

但是,在这种特定情况下,最好只用适当的路径替换前缀数字,并将其他所有内容保留为默认的bash完成:

_up_prefix() {
    local dir cur="${COMP_WORDS[COMP_CWORD]}"
    COMPREPLY=()

    if [[ ${cur} =~ ^[0-9]+/? ]]; then
        # Starting with a number, possibly followed by a slash
        dir=$( printf "%${cur%%/*}s" );
        dir="${dir// /../}"
        if [[ ${cur} == */* ]]; then
            dir="${dir}${cur#*/}"
        fi
        COMPREPLY=( "${dir}" "${dir}." ) # hack to suppress trailing space
    elif [[ ${cur} != */* ]]; then
        # Not a digit, and no slash either, so search parent directories
        COMPREPLY=( $(IFS='/'; compgen -W "${PWD}" "${cur}") )
        if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
            dir="${PWD%${COMPREPLY[0]}/*}${COMPREPLY[0]}/"
            COMPREPLY=( "${dir}" "${dir}." ) # hack as above
        fi
    fi
}

complete -F _up_prefix -o dirnames up

代码变得更容易阅读和维护,而且启动效率更高。唯一的缺点是,在某些情况下,您必须再次按Tab键一次:一次替换前缀,再多两次以实际查看可能的完成列表。您的选择是否可以接受。

还有一件事:完成将把参数转换为常规路径,但是你的up函数不接受它们。所以也许您应该使用[[ -d $1 ]]检查启动该功能,如果存在则只需cd到该目录。否则,您的完成将生成对被调用函数不可接受的参数。