Отключить завершение вкладки в Bash для некоторого каталога, который содержит большое количество файлов?

19

Я работаю с большим количеством файлов, которые я храню в каталоге. Каждый раз, когда я захожу в этот каталог и случайно нажимаю Tabдважды, отображение файлов, соответствующих шаблону, занимает слишком много времени (может быть более минуты), и меня это раздражает.

Например, моя структура каталогов:

my-project/
├── docs/
├── data/        <---- contains around 200k files.
└── analyser/

Так как я все еще люблю завершение, есть ли возможность отключить эту функцию только в data/каталоге? Например, установить тайм-аут на 5 секунд или скрипт, который автоматически отключает завершение, когда находится в определенном каталоге?

neizod
источник
Вы бы переключились на Zsh по этому поводу? (Я не знаю, возможно ли это в bash. Я знаю, что это возможно в zsh, но это может быть нелегко, я не проверял.)
Жиль: «Так, перестань быть злым»

Ответы:

9

Это не идеально, но с другой стороны, завершение bash довольно сложно ...

Самый простой способ для каждой команды, он немного более гибкий, чем FIGNOREвы можете сделать:

 complete -f -X "/myproject/data/*" vi

Это инструктирует автозаполнение, что завершение для viфайлов, и удаляет шаблоны, соответствующие -Xфильтру. Недостатком является то, что шаблон не нормализован, поэтому ../dataи варианты не будут совпадать.

Следующая лучшая вещь может быть пользовательской PROMPT_COMMANDфункцией:

# associative arrays of non-autocomplete directories
declare -A noacdirs=([/myproject/data]=1 )

function _myprompt {
    [[ -n "${noacdirs[$PWD]}" ]] && {
       echo autocomplete off
       bind 'set disable-completion on'
    } || {
       echo autocomplete on
       bind 'set disable-completion off'
    }
} 

PROMPT_COMMAND=_myprompt

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

В более общем случае было бы полезно выборочно отключить это для определенных путей, но я считаю, что единственный способ - использовать функцию завершения по умолчанию (bash-4.1 и более поздние версии complete -D) и много возиться.

Это должно работать для вас, но может иметь непредвиденные побочные эффекты (то есть изменения в ожидаемом завершении в некоторых случаях):

declare -A noacdirs=([/myproject/data]=1 )

_xcomplete() {
    local cur=${COMP_WORDS[COMP_CWORD]} # the current token

    name=$(readlink -f "${cur:-./}")  # poor man's path canonify
    dirname=$(dirname "$name/.")

    [[ -n "${noacdirs[$dirname]}" ]] && {
        COMPREPLY=( "" )   # dummy to prevent completion
        return
    }
    # let default kick in
    COMPREPLY=()
}

complete -o bashdefault -o default -F _xcomplete vi

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

Я считаю, что общий подход complete -Dзаключается в динамическом добавлении функций завершения для каждой команды, когда она встречается. Можно также добавить complete -E(завершение имени команды, когда входной буфер пуст).


Обновление Вот гибридная версия решения PROMPT_COMMANDи функции завершения, немного легче понять и взломать, я думаю:

declare -A noacdirs=([/myproject/data]=1 [/project2/bigdata]=1)

_xcomplete() {
    local cmd=${COMP_WORDS[0]}
    local cur=${COMP_WORDS[COMP_CWORD]} # the current token

    [[ -z "$cur" && -n "$nocomplete" ]] && {
        printf "\n(restricted completion for $cmd in $nocomplete)\n"  
        printf "$PS2 $COMP_LINE"
        COMPREPLY=( "" )   # dummy to prevent completion
        return
    }
    COMPREPLY=()       # let default kick in
}

function _myprompt {
    nocomplete=
    # uncomment next line for hard-coded list of directories
    [[ -n "${noacdirs[$PWD]}" ]] && nocomplete=$PWD
    # uncomment next line for per-directory ".noautocomplete"
    # [[ -f ./.noautocomplete ]] && nocomplete=$PWD
    # uncomment next line for size-based guessing of large directories
    # [[ $(stat -c %s .) -gt 512*1024 ]] && nocomplete=$PWD
} 

PROMPT_COMMAND=_myprompt
complete -o bashdefault -o default -F _xcomplete vi cp scp diff

Эта функция подсказки устанавливает nocompleteпеременную при вводе одного из настроенных каталогов. Измененное поведение завершения включается только тогда, когда эта переменная непустая, и только когда вы пытаетесь завершить из пустой строки, что позволяет завершить частичные имена (удалите -z "$cur"условие, чтобы предотвратить полное завершение). Закомментируйте две printfстроки для тихой работы.

Другие варианты включают .noautocompleteфайл флага для каждого каталога, который вы можете touchв каталоге по мере необходимости; и угадывание размера каталога с использованием GNU stat. Вы можете использовать любой или все эти три варианта.

( statМетод является лишь предположением , размер каталога, о котором сообщается, увеличивается с его содержимым, это «верхний предел», который обычно не уменьшается при удалении файлов без какого-либо административного вмешательства. Это дешевле, чем определение реального содержимого потенциально большого размера каталог. Точное поведение и приращение в расчете на файл зависят от базовой файловой системы. Я считаю это надежным показателем, по крайней мере, в системах linux ext2 / 3/4.)

bash добавляет дополнительный пробел, даже когда возвращается пустое завершение (это происходит только при завершении в конце строки). Вы можете добавить -o nospaceв completeкоманду, чтобы предотвратить это.

Остается один недостаток: если вы вернете курсор к началу токена и нажмете на вкладку, то по умолчанию будет снова выполнено завершение. Считайте, что это особенность ;-)

(Или вы можете возиться с этим, ${COMP_LINE:$COMP_POINT-1:1}если вам нравится чрезмерное проектирование, но я обнаружил, что сам bash не может установить переменные завершения надежно, когда вы выполняете резервное копирование и пытаетесь выполнить завершение в середине команды.)

mr.spuratic
источник
6

Если dataкаталог содержит файлы с определенным суффиксом, например .out , вы можете установить bashпеременную FIGNOREв ".out"и они будут игнорироваться. Хотя можно также использовать имена каталогов, это не помогает для текущих имен рабочих каталогов.

Пример:

Создайте тысячи тестовых файлов размером 100 КБ на физическом хосте с жесткими дисками raid 1:

for i in {1..200000}; do dd if=/dev/urandom bs=102400 count=1 of=file$i.json; done

Установите bashпеременную:
$ FIGNORE=".json"

Создайте тестовый файл:
$ touch test.out

Тест на 5000 файлов:

$ ls *.json|wc -l
5587
$ vi test.out

Никакой задержки между ви и синглом tabне test.outпоявляется.

Тест на 50000 файлов:

$ ls *.json|wc -l
51854
$ vi test.out

Одиночная вкладка создает долю секунды до test.outпоявления.

Тест на 200 000 файлов:

$ ls *.json|wc -l
bash: /bin/ls: Argument list too long
$ ls | awk 'BEGIN {count=0} /.*.json/ {count++} END {print count}'
200000
$ vi test.out

Задержка в 1 секунду между ви и синглом tabдо test.outпоявления.

Ссылки:

Выдержка из человека Баш

FIGNORE
              A  colon-separated  list  of  suffixes to ignore when performing filename completion (see READLINE below).  A filename whose suffix matches one of the entries in FIGNORE is
              excluded from the list of matched filenames.  A sample value is ".o:~".
geedoubleya
источник
1
не поможет, попробуйте создать каталог с ~ 100k файлами. тогда я делаю $ vi <tab><tab>, это все равно занимает много времени. : \
neizod
до сих пор не решил проблему, предположим, у меня есть .jsonфайлы 100k , и один текстовый файл с именем foo.txt. включаю FIGNORE='.json', набираю $ vi <tab>и все равно нужно подождать полминуты, чтобы она меня завершила $ vi foo.txt. --- реальный сценарий, который я не собираюсь редактировать или смешивать типы файлов внутри этого каталога, но если я включу FIGNOREи (случайно) нажму вкладку, моя клавиатура будет долго зависать без намека на что-то подобное Display all 188275 possibilities? (y or n), что говорит мне что я могу вернуть свою клавиатуру.
neizod
@neizod - Извините, я пробовал много сценариев, чтобы заставить FIGNORE работать на уровне каталогов, но безрезультатно. Я полагаю, что требуется нестандартное решение, или вы просто отключаете завершение bash на этом хосте, или пробуете решение zsh, подробно описанное на нашем родственном сайте, упомянутом Жилем.
geedoubleya
Извините, это всего 1 секунда в вашей системе, это примерно полминуты в моей системе. Так что, думаю, я бы пошел купить новый компьютер, чтобы это решение заработало.
neizod
0

Угадай, это то, что ты хочешь (позаимствовал код у @ mr.spuratic)

Обратите внимание, что это не мешает вам нажимать клавишу TAB, используя полный путь (например, vim /directory/contains/lots/files<tab><tab>

function cd() {
  builtin cd "$@"
  local NOCOMPLETION=( "/" "/lib" )
  local IS_NOCOMPLETION=0
  for dir in "${NOCOMPLETION[@]}"
  do
    echo $dir
    if [[ $(pwd) == "$dir" ]]
    then
      echo "disable completion"
      bind "set disable-completion on"
      IS_NOCOMPLETION=1
      return
    fi                                                                                                                                                                                              
  done
  if [[ $IS_NOCOMPLETION -eq 0 ]]
  then
    echo "enable completion"
    bind "set disable-completion off"
  fi
}  
yegle
источник
Не забывайте о pushd/ popd. Поскольку для этого используется только обычный массив, а не ассоциативный массив, он будет работать и в bash 3.x.
mr.spuratic