Как предотвратить завершение bash от замены символа при завершении табуляции

Я создаю скрипт завершения bash для инструмента, который разделяет семантику загрузки файлов с curl.

С завитком вы можете сделать:

curl -F переменная=@файл

загрузить файл.

Мое приложение имеет аналогичную семантику, и я хочу иметь возможность отображать возможные файлы после нажатия «@». К сожалению, это оказывается трудным:

cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ "$cur" == @* && "$prev" == '=' ]]; then
    COMPREPLY=( $(compgen -f ${cur:1}) )
    return 0
fi 

Итак, если команда (пока) заканчивается:

abc=@

Будут показаны файлы в текущем каталоге.

 var=@/usr/                                                                                                                       
 /usr/bin      /usr/games 

Проблема в том, что если я действительно нажму вкладку для завершения, «@» исчезнет!

 var=/usr/bin

Таким образом, похоже, что bash заменяет все текущее слово на вкладку COMPREPLY.

Единственный способ избежать этого состоял в том, чтобы сделать это:

        COMPREPLY=( $(compgen -f ${cur:1}) )
        for (( i=0; i<${#COMPREPLY[@]}; i++ )); 
        do 
            COMPREPLY[$i]='@'${COMPREPLY[$i]} 
        done                                                                                                                       

Но теперь завершение табуляции выглядит, мягко говоря, странно:

@/usr/bin      @/usr/games

Есть ли способ показать нормальное завершение табуляции файла (без префикса «@»), но сохранить «@» при нажатии на вкладку?


person UsAaR33    schedule 08.05.2012    source источник
comment
Потрясающий вопрос! Я многому научился, отвечая на это.   -  person sanmiguel    schedule 15.05.2012


Ответы (2)


Итак, это заинтриговало меня, поэтому я прочитал исходный код завершения bash (доступный в /etc/bash_completion).

Я наткнулся на эту переменную: ${COMP_WORDBREAKS}, которая, по-видимому, позволяет контролировать, какие символы используются для разделения слов.

Я также столкнулся с этой функцией, _get_cword и дополнительной _get_pword, которые рекомендуются для использования вместо ${COMP_WORDS[COMP_CWORD]} и ${COMP_WORDS[COMP_CWORD-1]} соответственно.

Итак, собрав все это воедино, я провел небольшое тестирование и вот что у меня получилось: похоже, это работает для меня, по крайней мере, надеюсь, что и для вас тоже:

# maintain the old value, so that we only affect ourselves with this
OLD_COMP_WORDBREAKS=${COMP_WORDBREAKS}
COMP_WORDBREAKS="${COMP_WORDBREAKS}@"

cur="$(_get_cword)"
prev="$(_get_pword)"

if [[ "$cur" == '=@' ]]; then
    COMPREPLY=( $(compgen -f ${cur:2}) )

    # restore the old value
    COMP_WORDBREAKS=${OLD_COMP_WORDBREAKS}
    return 0
fi

if [[ "$prev" == '=@' ]]; then
    COMPREPLY=( $(compgen -f ${cur}) )

    # restore the old value
    COMP_WORDBREAKS=${OLD_COMP_WORDBREAKS}
    return 0

fi

Теперь я признаю, что разделенные if ящики немного грязные, определенно есть лучший способ сделать это, но мне нужно больше кофеина.

Кроме того, для чего бы вам это ни стоило, я также обнаружил аргумент -P <prefix> для compgen, что избавит вас от необходимости перебирать массив $COMPREPLY[*]} после вызова compgen, например так

COMPREPLY=( $(compgen -P @ -f ${cur:1}) )

Однако это немного избыточно перед лицом полного решения.

person sanmiguel    schedule 15.05.2012
comment
Отличное исследование. Мне очень нравится, куда вы идете с этим. Судя по сценарию, это сделает завершение табуляции так, как я хочу. Но у меня есть одна большая проблема. Я вижу, что вы сбрасываете COMP_WORDBREAKS после того, как пользователь набрал символы =@. Но что произойдет, если он никогда этого не сделает? Кажется, что в bash навсегда останется модифицированный COMP_WORDBREAKS. Кажется, что все, что нужно этому ответу, - это какой-то гарантированный способ сбросить COMP_WORDBREAKS обратно к исходному значению, когда пользователь закончит ввод строки. - person UsAaR33; 16.05.2012
comment
Вы правы - это оставит COMP_WORDBREAKS в этом состоянии. Я найду решение и обновлю. - person sanmiguel; 16.05.2012
comment
Поскольку это лучший ответ на данный момент, я дам вам награду. Примет этот (или любой будущий ответ), когда решит проблему со сбросом COMP_WODBREAKS. - person UsAaR33; 22.05.2012
comment
Я не забыл об этом! У меня еще не было надлежащего шанса взломать его, но у меня есть некоторые идеи. Спасибо за награду за мой пока еще неполный ответ - как только я найду правильное решение, оно будет здесь. - person sanmiguel; 22.05.2012
comment
Что делает установка COMP_WORDBREAKS внутри полной функции? Не слишком ли поздно устанавливать его здесь, так как readline закончил бы с ним до того, как будет вызвана ваша функция? - person antak; 17.07.2012
comment
Переменная $COMP_WORDBREAKS не используется readline, но является переменной, используемой внутренней работой модуля bash-completion; таким образом, это имеет отношение только к тому, что происходит внутри _get_cword и _get_pword. - person sanmiguel; 24.07.2012
comment
@UsAaR33 UsAaR33: чем больше я изучаю это, тем больше прихожу к выводу, что нет тривиального способа сделать это без полной замены внутренней работы bash-completion. Я копал гораздо глубже, но не могу найти, почему, если вы убедитесь, что $COMP_WORDBREAKS переустанавливается в конце пользовательской функции завершения, она не будет разделена на @. - person sanmiguel; 24.07.2012

Попробуй это ....

function test
{
  COMPREPLY=()
  local cur="${COMP_WORDS[COMP_CWORD]}"
  local opts="Whatever sort of tabbing options you want to have!"
  COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
}
complete -F "test" -o "default" "test"
# complete -F "test" -o "nospace" "sd" ## Use this if you do not want a space after the completed word
person Octávio Filipe Gonçalves    schedule 15.05.2012
comment
Не могли бы вы расширить это? Вы показываете основы сценария завершения bash, но как решить мою основную проблему? - person UsAaR33; 16.05.2012