Bash 参数自动完成

Bash 参数自动完成

我正在编写一个 bash 来处理目录中的文件。我有一些基本(相当长)的路径来处理。我想通过选项和参数指定子路径并使其可自动完成。我的意思是这样的:

#!/bin/bash

dir=~/path/to/base/dir

while getopts ":d:" opt; do
    case $opt in
      d)
         dir=$dir/$OPTARG
         ;;
      #invalid input handling
    esac
done

但在这个实现中,参数不能自动完成,我必须自己输入子目录的所有名称(Tab不起作用)。

有没有办法在 bash 中实现这一目标?

答案1

是的,可以从命令行选项标志指定的自定义基目录实现路径完成。这是一个小例子,说明如何实现这一点。首先,我将稍微修改您的示例脚本,以使演示稍微更有趣(即产生输出):

#!/bin/bash

# echo_path.sh
#
#    Echoes a path.
#

# Parse command-line options
while getopts ":d:" opt; do

    # Use '-d' to specify relative path
    case "${opt}" in
    d)
        directory="${OPTARG}"
        ;;
    esac
done

# Print the full path
echo "$(readlink -f ${directory})"

这本质上与您的示例脚本相同,但它打印出给定的参数。

接下来我们需要编写一个由 Bash 编程完成系统调用的函数。这是定义这样一个函数的脚本:

# echo_path_completion.bash

# Programmatic path completion for user specified file paths.

# Define a base directory for relative paths.
export BASE_DIRECTORY=/tmp/basedir

# Define the completion function
function _echo_path_completion() {

    # Define local variables to store adjacent pairs of arguments
    local prev_arg;
    local curr_arg;

    # If there are at least two arguments then we have a candidate
    # for path-completion, i.e. we need the option flag '-d' and
    # the path string that follows it.
    if [[ ${#COMP_WORDS[@]} -ge 2 ]]; then

        # Get the current and previous arguments from the command-line
        prev_arg="${COMP_WORDS[COMP_CWORD-1]}";
        curr_arg="${COMP_WORDS[COMP_CWORD]}";

        # We only want to do our custom path-completion if the previous
        # argument is the '-d' option flag
        if [[ "${prev_arg}" = "-d" ]]; then

            # We only want to do our custom path-completion if a base
            # directory is defined and the argument is a relative path
            if [[ -n "${BASE_DIRECTORY}" && ${curr_arg} != /* ]]; then

                # Generate the list of path-completions starting from BASE_DIRECTORY
                COMPREPLY=( $(compgen -d -o default -- "${BASE_DIRECTORY}/${curr_arg}") );

                # Don't append a space after the command-completion
                # This is so we can continue to apply completion to subdirectories
                compopt -o nospace;

                # Return immediately
                return 0;
            fi
        fi
    fi

    # If no '-d' flag is given or no base directory is defined then apply default command-completion
    COMPREPLY=( $(compgen -o default -- "${curr_arg}") );
    return 0;
}

# Activate the completion function
complete -F _echo_path_completion echo_path

现在让我们获取完成脚本:

source echo_path_completion.bash

让我们使脚本可执行并将其移动到路径中的某个位置:

chmod +x echo_path.bash
mv -i echo_path.bash /usr/local/bin

最后,让我们为没有文件扩展名的脚本添加一个别名:

alias echo_path=echo_path.bash

现在,如果您输入echo -d并点击选项卡按钮,那么您应该获得从 BASE_DIRECTORY 开始的文件路径补全。要对此进行测试,您可以尝试以下操作:

mkdir -p ${BASE_DIRECTORY}
mkdir -p "${BASE_DIRECTORY}/file"{1..5}

当您点击 Tab 时,您应该会得到以下完成列表:

user@host:~$ echo_path -d
user@host:~$ echo_path -d /tmp/basedir/file
/tmp/basedir/file1  /tmp/basedir/file2  /tmp/basedir/file3  /tmp/basedir/file4  /tmp/basedir/file5

请注意,在第一个选项卡之后,字符串将转换为绝对路径。如果您愿意,您可以更改此设置,但我认为这可能是更好的行为。

以下是一些参考资料,您可以查阅以获取更多信息。

如需官方参考,请查看 Bash 手册的以下部分:

另请参阅 Linux 文档项目中的高级 Bash 脚本指南:

有关 Bash 中编程完成的一些功能的快速介绍,请参阅“The Geek Stuff”网站上的这篇文章:

还有一些相关的 StackOverflow 帖子可能对您有用:

相关内容