Autocomplete de argumento de bash

2

Estou escrevendo um bash para processar arquivos em um diretório. Eu tenho um caminho básico (bastante longo) para processar. Eu quero especificar via opção e argumentar um sub-caminho e torná-lo autocompletable. Quero dizer algo assim:

#!/bin/bash

dir=~/path/to/base/dir

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

Mas nesta implementação o argumento não é autocompletable e eu tenho que digitar todo o nome de um subdiretório eu mesmo ( Tab não funciona).

Existe uma maneira de conseguir isso em bash?

    
por St.Antario 09.11.2016 / 10:20

1 resposta

1

Sim, é possível implementar a conclusão do caminho a partir de um diretório base personalizado especificado por um sinalizador de opção de linha de comando. Aqui está um pequeno exemplo ilustrando como conseguir isso. Primeiro, vou modificar um pouco seu script de exemplo para tornar a demonstração um pouco mais interessante (ou seja, produzir saída):

#!/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})"

Isto é essencialmente o mesmo que o seu script de exemplo, mas imprime o argumento dado.

Em seguida, precisamos escrever uma função a ser chamada pelo sistema de conclusão programática Bash. Aqui está um script que define essa função:

# 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

Agora vamos criar nosso script de conclusão:

source echo_path_completion.bash

Vamos tornar nosso script executável e movê-lo para algum lugar em nosso PATH:

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

E, finalmente, vamos adicionar um alias ao nosso script que não tenha uma extensão de arquivo:

alias echo_path=echo_path.bash

Agora, se você inserir echo -d e clicar no botão da guia, deverá obter a conclusão do caminho do arquivo a partir do BASE_DIRECTORY. Para testar isso, você pode tentar o seguinte:

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

Ao clicar na guia, você deve obter a seguinte lista de conclusões:

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

Observe que, após a primeira guia, a string é convertida em um caminho absoluto. Você pode mudar isso se quiser, mas acho que esse é o comportamento preferível.

Aqui estão algumas referências que você pode consultar para mais informações.

Para referências oficiais, dê uma olhada nas seguintes seções do manual de Bash:

Veja também o Advanced Bash Scripting Guide do Projeto de Documentação do Linux:

Para uma rápida introdução a alguns recursos de conclusão programática no Bash, veja este post no site "The Geek Stuff":

Também há várias postagens relacionadas ao StackOverflow que você pode achar úteis:

por 05.11.2017 / 22:47