Preenchimento automático personalizado: lidar com espaços em nomes de arquivos

7

Um pouco de contexto, esta questão é um follow-up deste: Bash autocompletar remoto: altere o diretório" inicial "

De qualquer forma, estou escrevendo meu script personalizado de preenchimento automático: quero que o preenchimento automático funcione exatamente como cd , exceto que desejo obter os nomes de um diretório específico, não necessariamente o atual. Ele funciona muito bem, exceto quando nomes de arquivos têm espaços neles.

Um exemplo rápido. Digamos que o diretório do qual estou obtendo os nomes tenha dois arquivos: a_file e another file (observe o espaço). Isso acontece:

my_command TAB TAB
a_file %código% file

A apresentação não é perfeita, mas a ideia é que eu seja solicitado com 3 opções, another sendo dividido em another file e another . A saída desejada seria: file file_1 . Também gostaria que os espaços fossem escapados automaticamente:

another file TAB
my_command ano

Veja como meu script se parece:

#!/bin/bash

_get_file_list()
{
    dir="/some/path/"
    cd $dir
    find * -maxdepth 0
}

_GetOptMyCommand()
{
    local cur

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}

    case "$cur" in
    -*)
        COMPREPLY=( $( compgen -W "-h -l --help --list --" -- "$cur" ) );;
    *)
        COMPREPLY=( $( compgen -W "$(_get_file_list)" -- "$cur" ) );;
    esac

    return 0
}

complete -F _GetOptMyCommand my_command

Como faço para lidar com espaços em nomes de arquivos e faço meu script de autocompletion como my_command another\ file ?

    
por Pierre Mourlanne 24.05.2013 / 21:30

3 respostas

4

Acredite que pode ser melhor usar compgen em vez de find neste caso.

Você provavelmente já tem um script de conclusão com o sistema. Tente por exemplo.

locate bash_completion

Nas variantes do Debian, isso provavelmente é:

/usr/share/bash-completion/bash_completion

onde você encontra, por exemplo %código%. Então a maneira mais simples seria algo na direção de:

*)
    pushd "/some/path" >/dev/null
    _filedir
    popd >/dev/null

Se isso não for uma opção, isso pode ser um começo:

_comp_by_path()
{
    local opt cur dir
    local IFS=$'\n' x tmp
    local -a tokens

    opt="$1"
    cur="$2"
    dir="$3"

    # Enter target directory
    pushd "$dir" >/dev/null

    # Get directories, filtered against current
    [[ "$opt" != "-f" ]] && \
    x=$( compgen -d -- "$cur" ) &&
    while read -r tmp; do
        tokens+=( "$tmp" )
    done <<< "$x"

    # Get files, filtered against current
    [[ "$opt" != "-d" ]] && \
    x=$( compgen -f -- "$cur" ) &&
    while read -r tmp; do
        tokens+=( "$tmp" )
    done <<< "$x"

    # If anything found
    if [[ ${#tokens[@]} -ne 0 ]]; then
        # Make sure escaping is OK
        compopt -o filenames 2>/dev/null
        COMPREPLY+=( "${tokens[@]}" )
    fi

    # Go back
    popd >/dev/null
}

_GetOptMyCommand()
{
    local cur

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

    case "$cur" in
    -*)
        COMPREPLY=( $( compgen -W "-h -l --help --list --" -- "$cur" ) );;
    *)
        _comp_by_path "any" "$cur" "/some/path"
    esac
}

complete -F _GetOptMyCommand my_command

Uma variante usando _filedir poderia ser algo em direção a isso:

_zaso()
{
    local dir="$1"

    pushd "$dir" >/dev/null
    find * -maxdepth 0 2>/dev/null
    popd >/dev/null
}

_comp_with_find()
{
    local cur dir      
    local IFS=$'\n'

    cur="$1"
    dir="$2"

    compopt -o filenames 2>/dev/null
    COMPREPLY=( $( compgen -W "$(_zaso "$dir")" -- "$cur" ) );
}

Observe também que find no Bash tem uma opção printf . Então, para gerar strings citadas, esta é uma opção para brincar com:

find * -maxdepth 0 2>/dev/null && \
while read -r tmp; do
    printf "%q\n" "$tmp"
done <<< "$x"

Além disso, os nomes de arquivo não podem ter caracteres de nova linha nos quais muito disso será quebrado. Não encontrei uma maneira de usar %q com compgen .

    
por 25.05.2013 / 12:14
3

Eu encontrei o mesmo problema recentemente. Consegui fazer as coisas funcionarem corretamente alterando a variável IFS com local IFS=$'\n' e, em seguida, usando matrizes. Tente usar isso:

_GetOptMyCommand(){
    # Backup old nullglob setting
    local shoptbakup="'shopt -p nullglob'"
    shopt -s nullglob

    local cur opts i opt
    local IFS=$'\n'
    cur="${COMP_WORDS[COMP_CWORD]}"

    case "$cur" in
    -*)
        opts=("-h" "-l" "--help" "--list");;
    *)
        # Get Files
        opts=(${cur}*)
    esac

    COMPREPLY=( $( compgen -W "${opts[*]}" -- "$cur" ) )

    # Restore nullglob setting
    eval "$shoptbakup" 2>/dev/null

    return 0
}
complete -o filenames -o bashdefault -F _GetOptMyCommand my_command
    
por 08.05.2014 / 20:39
0

Tente colocar a saída find em sed 's/ /\ /' .

No entanto, observe que outros caracteres (aspas, $, & ... todos os suspeitos do costume) também podem atrapalhar você.

sed 's/\([ $&!#*()<>|{}[?'"'"'"']\)/\/'

pode ser melhor, pois escapará da maioria dos caracteres "especiais".

(ainda não descobri como escapar corretamente \ embora)

    
por 25.05.2013 / 11:09