Завершение каталога Tcsh и / или bash с переменным скрытым корневым префиксом - PullRequest
7 голосов
/ 07 октября 2009

Я пытаюсь настроить завершение каталогов в tcsh и / или bash (оба используются на моем сайте) с небольшим поворотом: для конкретной команды "foo" я хотел бы, чтобы завершение использовало пользовательскую функцию сопоставить первый / -ограниченный термин с фактическим узлом поддерева, а затем следовать обычному завершению каталога для любых последующих терминов. Это своего рода комбинация cdpath и завершения, или я предполагаю форму завершения каталога, где начальная точка контролируется сценарием завершения. Это будет работать следующим образом:

$ foo xxx<TAB>
(custom completion function produces choices it finds at arbitrary levels in the dir tree)
xxxYYY xxxZZZ xxxBLAH ...
foo xxxYYY/<TAB>
(normal directory completion proceeds from this point on, to produce something like:)
foo scene/shot/element/workspace/user/...

Мы хотели бы сделать это, потому что у нас есть большое дерево разработки производства (это производственная база CGI), в котором находчивые в оболочке пользователи постоянно перемещаются и прыгают. Жалоба на то, что верхние уровни дерева громоздки и избыточны; им просто нужен быстрый поиск по первому члену, чтобы найти возможные варианты "заголовка" и выполнить оттуда завершение каталога. Кажется, что программируемое завершение может предложить способ сделать это, но оно оказывается довольно неуловимым.

Я сделал несколько попыток пользовательского завершения bash и tcsh, чтобы сделать это, но ближе всего я получил форму «завершения слова», где пользователь должен обрабатывать уровни каталога как отдельные слова с пробелами (например, foo сцена / выстрел / элемент / рабочее пространство / ...). Я мог бы продолжать взламывать мои текущие сценарии - но мне было интересно, есть ли что-то, чего я не понимаю - это моя первая попытка завершить программирование, а документы и примеры довольно тонкие в книгах по оболочкам и в Интернете , Если есть какой-нибудь гуру-завершитель, который поможет мне выбрать верный путь, я буду признателен.

FWIW: вот что у меня есть (сначала в tcsh, затем в bash). Обратите внимание, что статический корень '/ root / sub1 / sub2 / sub3' является просто заполнителем для функции поиска, которая будет находить разные совпадения на разных уровнях. Если я смогу заставить это работать, я могу добавить в функцию поиска позже. Опять же, оба примера выполняют завершение слов, что требует от пользователя вводить пробел после каждого соответствующего термина (я также должен удалить пробелы в функции, чтобы создать фактический путь, чёрт!)

TCSH EXAMPLE (обратите внимание, что функция на самом деле является скриптом bash):

complete complete_p2 'C@*@`./complete.p2.list.bash $:1 $:2 $:3 $:4 $:5 $:6 $:7 $:8 $:9`@@'

#!/bin/bash --norc

# complete.p2.list.bash - Completion prototype "p2" for shotc command

# Remove spaces from input arguments
ppath=`echo $@ | sed -e 's/ //g'`

# Print basenames (with trailing slashes) of matching dirs for completion
ls -1 -d /root/sub1/sub2/sub3/$ppath* 2>/dev/null | sed -e 's#^.*/##' | awk '{print $1 "/"}'

ПРИМЕР BASH:

_foo() 
{
    local cur prev opts flist
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    # Get all command words so far (omit command [0] element itself), remove spaces
    terms=`echo ${COMP_WORDS[@]:1} | sed -e 's/ //g'`

    # Get basenames (with trailing slashes) of matching dirs for completion
    flist=`ls -1 -d /root/sub1/sub2/sub3/${terms}* 2>/dev/null | sed -e 's#^.*/##' | awk '{print $1 "/"}' | xargs echo`

    COMPREPLY=( $(compgen -W "${flist}" ${cur}) )
    return 0
}
complete -F _foo foo

Ответы [ 5 ]

4 голосов
/ 08 октября 2009

Кажется, что он может делать то, что вы ищете:

_foo()
{
    local cur prev opts flist lastword new
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    lastword="${COMP_WORDS[@]: -1}"

    if [[ $lastword =~ / ]]
    then
        new="${lastword##*/}"      # get the part after the slash
        lastword="${lastword%/*}"  # and the part before it
    else
        new="${lastword}"
        lastword=""
    fi

    flist=$( command find /root/sub1/sub2/sub3/$lastword \
      -maxdepth 1 -mindepth 1 -type d -name "${new}*" \
      -printf "%f\n" 2>/dev/null )

    # if we've built up a path, prefix it to 
    #   the proposed completions: ${var:+val}
    COMPREPLY=( $(compgen ${lastword:+-P"${lastword}/"} \
      -S/ -W "${flist}" -- ${cur##*/}) )
    return 0
}
complete -F _foo -o nospace foo

Примечания:

  • Я думаю, что один из ключей - это опция nospace
  • Мне кажется, что я заново изобрел колесо где-то в приведенной выше функции, возможно, не используя $COMP_POINT
  • Вы (пока, по крайней мере) не используете $prev (что всегда поддерживает значение "foo" в моей функции)
  • Удобочитаемость и функциональность могут быть улучшены при использовании $() вместо обратных галочек
  • Вы должны использовать command, чтобы запретить запуск псевдонимов и такие: flist=$(command ls -1 -d...
  • Я использую find вместо ls, потому что он лучше подходит
  • Вы можете добавить косую черту, используя -S/ с compgen вместо вашей awk команды
  • Вы можете использовать $cur вместо $terms, поскольку вам не нужно удалять пробелы, но я использую $lastword и $new (две новые переменные)
  • Нет необходимости использовать xargs echo, так как массив с символами новой строки работает нормально
  • Я не проверял это с именами каталогов с пробелами или символами новой строки
1 голос
/ 08 июня 2017

Итак, вот решение tcsh. Добавляет полный $ cdpath поиск для автозаполнения каталогов. Полная команда:

complete cd 'C@/@d@''p@1@`source $HOME/bin/mycdpathcomplete.csh $:1`@/@'

Я немного взломал tcsh, поэтому манипулирование строками немного грубовато. Но это работает с незначительными издержками ... mycdpathcomplete.csh выглядит так:

#!/bin/tcsh -f

set psuf=""
set tail=""

if ( $1 !~ "*/*" ) then
    set tail="/$1"
else
    set argsplit=(`echo "$1" | sed -r "s@(.*)(/[^/]*\')@\1 \2@"`)
    set psuf=$argsplit[1]
    set tail=$argsplit[2]
endif

set mycdpath=(. $cdpath)
set mod_cdpath=($mycdpath)

if ($psuf !~ "") then
    foreach i (`seq 1 1 $#mycdpath`)
        set mod_cdpath[$i]="$mycdpath[$i]/$psuf"
        if ( ! -d $mod_cdpath[$i] ) then
            set mod_cdpath[$i]=""
        endif
    end
endif

# debug statements
#echo "mycdpath = $mycdpath"
#echo "mod_cdpath = $mod_cdpath"
#echo "tail = $tail"
#echo "psuf = $psuf"

set matches=(`find -L $mod_cdpath -maxdepth 1 -type d -regex ".*${tail}[^/]*" | sed -r "s@.*(/?${psuf}${tail}[^/]*)\'@\1@" | sort -u`)

# prune self matches
if ($psuf =~ "") then
    foreach match (`seq 1 1 $#matches`)
        set found=0
        foreach cdp ($mod_cdpath)
            if ( -e "${cdp}${matches[$match]}" ) then
                set found=1;
                break;
            endif
        end
        if ( $found == 0 ) then
            set matches[$match]=""
        else
            set matches[$match]=`echo "$matches[$match]" | sed -r "s@^/@@"`
        endif
    end
endif

echo "$matches"
1 голос
/ 11 февраля 2010

Мое решение, которое по общему признанию является молотком на 800 фунтов, состояло в том, чтобы написать сценарий perl для обработки завершения так, как я хотел. в цхш ...

complete cd 'p|1|`complete_dirs.pl $:1 $cdpath`|/'

#!/usr/bin/perl

my $pattern = shift @ARGV;
my @path = @ARGV;
my @list;

if ($pattern =~ m!^(/.+/|/)(.*)!) {
  @list = &search_dir($1,$2,undef);
} elsif ($pattern =~ m!(.+/|)(.*)!) {
  my $dir; foreach $dir ('.',@path) {
    push(@list,&search_dir("$dir/$1",$2,$1));
  }
}
if (@list) {
  @list = map { &quote_path($_) } @list;
  print join(' ',@list), "\n";
}

sub search_dir {
  my ($dir,$pattern,$prefix) = @_;
  my @list;

  if (opendir(D,$dir)) {
    my $node; while ($node = readdir(D)) {
      next     if ($node =~ /^\./);
      next unless ($node =~ /^$pattern/);
      next unless (-d "$dir$node");

      my $actual; if (defined $prefix) {
        $actual = "$prefix$node";
      } elsif ($dir =~ m!/$!) {
        $actual = "$dir$node";
      } else {
        $actual = "$dir/$node";
      }
      push(@list,$actual);
    }
    closedir(D);
  }
  return @list;
}
sub quote_path {
  my ($string) = @_;

  $string =~ s!(\s)!\\$1!g;
  return $string;
}
0 голосов
/ 18 мая 2012

Базовое решение tcsh очень легко реализовать следующим образом:

    complete foo 'C@*@D:/root/sub1/sub2/sub3@'

Не требуется bash зависимость от сценария. Конечно, базовый каталог в этом примере аппаратно зашит.

0 голосов
/ 07 октября 2009

Вы можете просто создать символическую ссылку на первый интересный узел в дереве. Я делал это в прошлом, когда меня не беспокоило автоматическое заполнение больших деревьев каталогов.

...