【问题标题】:How to autocomplete a bash commandline with file paths from a specific directory without displaying this directory?如何使用来自特定目录的文件路径自动完成 bash 命令行而不显示该目录?
【发布时间】:2021-07-05 03:19:40
【问题描述】:

您可以在这里找到相关问题:How to autocomplete a bash commandline with file paths?

上下文

我正在创建一个 shell 程序,它是一个命令行工具。我想为此工具创建自己的自动完成功能。

对于选项 --unit-test 和 -t,我想自动完成来自特定目录的文件路径,我可以运行 my_app --directory。

例如

运行:

user@computer:~$ my_app --install [TAB][TAB]

会做:

Public/          bin/                 Desktop/              
Documents/       Music/               Downloads/
user@computer:~$ my_app --install 

(显示当前目录)

运行:

user@computer:~$ my_app --unit-tests [TAB][TAB]

会做:

folder/              folder2/             folder3/
.hidden_file         file.extension       file2.extension
user@computer:~$ my_app --unit-tests 

(显示特定目录的建议而不用它完成)

my_app_autocomplete 文件

__my_app_autocomplete()
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="--help -h --install -i --run -r --rebuild -rb --show-running-containers -ps --stop -s --remove -rm --logs -l --bash -b --sass -css --unit-tests -t"
    containers="nginx php mysql mongo node"
    sass="watch"

    # By default, autocomplete with options
    if [[ ${prev} == my_app ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi
    # By default, autocomplete with options
    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi
    # For --install and -i options, autocomplete with folder
    if [ ${prev} == --install ] || [ ${prev} == -i ] ; then
        COMPREPLY=( $(compgen -d -- ${cur}) )
        return 0
    fi
    # For --stop --remove --logs and --bash, autocomplete with containers
    if [ ${prev} == --stop ] || [ ${prev} == -s ] || [ ${prev} == --remove ] || [ ${prev} == -rm ] || [ ${prev} == --logs ] || [ ${prev} == -l ] || [ ${prev} == --bash ] || [ ${prev} == -b ] ; then
        COMPREPLY=( $(compgen -W "${containers}" -- ${cur}) )
        return 0
    fi
    # For --sass and -css, complete with sass options
    if [ ${prev} == --sass ] || [ ${prev} == -css ] ; then
        COMPREPLY=( $(compgen -W "${sass}" -- ${cur}) )
        return 0
    fi
    # For --unit-tests and -t, complete from a specific folder
    if [ ${prev} == --unit-tests ] || [ ${prev} == -t ] ; then
        COMPREPLY=( $(compgen -d -- ${cur}) )
        return 0
    fi
}
complete -o filenames -F __my_app_autocomplete my_app

问题

我找不到办法。你有什么想法吗?

调查

使用包含特定目录的变量

@D'Arcy Nader 建议

my_app_autocomplete开头添加

_directory=/absolute/path/to/the/directory/  

然后替换compgen命令中的变量

# For --unit-tests and -t, complete with relative to my_app folder paths
if [ ${prev} == --unit-tests ] || [ ${prev} == -t ] ; then
    COMPREPLY=( $(compgen -d -- "${_directory}") )
    return 0
fi

行为:

运行

user@computer:~$ my_app --unit-tests [TAB][TAB]

user@computer:~$ my_app --unit-tests /absolute/path/to/the/directory/

它添加了目录的路径。

运行

user@computer:~$ my_app --unit-tests /absolute/path/to/the/directory/file.ext[TAB][TAB]

user@computer:~$ my_app --unit-tests /absolute/path/to/the/directory/

它删除了file.ext 部分。

问题:

  • 我不想在命令行中添加具体路径
  • 它会删除我在特定目录之后添加的内容,而不是自动完成它。

【问题讨论】:

    标签: linux bash autocomplete


    【解决方案1】:

    经过多次尝试和错误,我想我找到了解决您问题的方法(这也是我的问题):

    _complete_specific_path() {
      # declare variables
      local _item _COMPREPLY _old_pwd
    
      # if we already are in the completed directory, skip this part
      if [ "${PWD}" != "$1" ]; then
        _old_pwd="${PWD}"
        # magic here: go the specific directory!
        pushd "$1" &>/dev/null || return
    
        # init completion and run _filedir inside specific directory
        _init_completion -s || return
        _filedir
    
        # iterate on original replies
        for _item in "${COMPREPLY[@]}"; do
          # this check seems complicated, but it handles the case
          # where you have files/dirs of the same name
          # in the current directory and in the completed one:
          # we want only one "/" appended
          if [ -d "${_item}" ] && [[ "${_item}" != */ ]] && [ ! -d "${_old_pwd}/${_item}" ]; then
            # append a slash if directory
            _COMPREPLY+=("${_item}/")
          else
            _COMPREPLY+=("${_item}")
          fi
        done
    
        # popd as early as possible
        popd &>/dev/null
    
        # if only one reply and it is a directory, don't append a space
        # (don't know why we must check for length == 2 though)
        if [ ${#_COMPREPLY[@]} -eq 2 ]; then
          if [[ "${_COMPREPLY}" == */ ]]; then
            compopt -o nospace
          fi
        fi
    
        # set the values in the right COMPREPLY variable
        COMPREPLY=( "${_COMPREPLY[@]}" )
    
        # clean up
        unset _COMPREPLY
        unset _item
      else
        # we already are in the completed directory, easy
        _init_completion -s || return
        _filedir
      fi
    }
    

    我通过查看cat 的自动完成方式找到了这个解决方案。它使用_longopt 函数,该函数又将_filedir 用于不是选项的参数(不以- 开头)。

    现在你可以为你需要的每个目录声明一个完成函数,比如:

    _complete_git_home_path() {
      _complete_specific_path "${GIT_HOME}"
    }
    

    并将其附加到正确的命令:

    complete -F _complete_git_home_path cdrepo lsrepo rmrepo cdwiki pyinst
    

    或者在您自己的完成函数中使用它,以触发特定选项,例如--unit-test

    【讨论】:

      【解决方案2】:

      @pawamoy answer的改进

      调用时:

      _init_completion -s || return
      

      如果 _init_completion 返回一个非 null 值,脚本将在没有执行 popd 命令的情况下退出,这可能会使您留在调用 pushd 时指定的目录中(但它甚至会使我的终端崩溃!)。我建议改为这样做(请参阅 grouping commands 了解 { } 解释)

      _init_completion -s || { popd > /dev/null 2>&1; return; }
      

      另外,如果你的目标是可移植性,&> 重定向是不可移植的,因为它不是官方 POSIX shell 规范 (see this answer) 的一部分,你应该使用

      > /dev/null 2>&1
      

      而不是

      &> /dev/null
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-16
        • 2023-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多