Tcsh和/或bash目录完成,带有变量隐藏根前缀

Tcsh和/或bash目录完成,带有变量隐藏根前缀,bash,tcsh,tab-completion,bash-completion,Bash,Tcsh,Tab Completion,Bash Completion,我试图在tcsh和/或bash(在我的站点上都使用)中设置目录完成(directory completion):对于一个特定的命令“foo”,我想让completion使用一个自定义函数将第一个/分隔的项与实际的子树节点相匹配,然后对任何后续项执行正常的目录完成。它是cdpath和completion的某种组合,或者我假设是一种目录完成形式,其中起始点由完成脚本控制。其工作如下: $ foo xxx<TAB> (custom completion function produces

我试图在tcsh和/或bash(在我的站点上都使用)中设置目录完成(directory completion):对于一个特定的命令“foo”,我想让completion使用一个自定义函数将第一个/分隔的项与实际的子树节点相匹配,然后对任何后续项执行正常的目录完成。它是cdpath和completion的某种组合,或者我假设是一种目录完成形式,其中起始点由完成脚本控制。其工作如下:

$ 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/...
    complete foo 'C@*@D:/root/sub1/sub2/sub3@'
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

您可以创建一个指向树中第一个感兴趣节点的符号链接。我以前也这样做过,因为我不想麻烦自动完成大型目录树。

这似乎可以满足您的要求:

_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”)
  • 使用
    $()
    而不是反勾号可以提高可读性和功能性
  • 您应该使用
    命令
    来防止运行别名,例如:
    flist=$(命令ls-1-d…。
  • 我使用的是
    find
    而不是
    ls
    ,因为它更合适
  • 您可以使用
    -S/
    compgen
    而不是
    awk
    命令添加斜杠
  • 您可以使用
    $cur
    而不是
    $terms
    ,因为您不必去掉空格,但我使用的是
    $lastword
    $new
    (两个新变量)
  • 没有必要使用
    xargs echo
    ,因为带有换行符的数组可以正常工作
  • 我还没有用带有空格或换行符的目录名测试过这一点

我的解决方案,公认是一个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;
}

基本的
tcsh
解决方案非常容易实现,如下所示:

$ 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/...
    complete foo 'C@*@D:/root/sub1/sub2/sub3@'

不需要
bash
脚本依赖项。当然,在本例中基本目录是硬连线的。

因此,这里有一个tcsh解决方案。添加了完整的$cdpath搜索自动完成目录。complete命令是:

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"

bash的CDPATH环境变量对您有用吗?我查看了CDPATH,但它不适用于完成。您可以使用“cd名称”,但不能使用“cd名称…”我真的需要完成。我认为bash完成(在大多数发行版上,bash附带的额外脚本中充满了有用的东西)包括使用CDPATH更新的cd完成定义。@Jefromi:你说得对,它似乎能够做我认为Jeremy想要做的事情。谢谢你的提示,我会去看看。但是值得一提的是,我需要做的不仅仅是cd;例如,动态设置特定于子树的环境。我正计划使用custom命令,但也许我可以将cd别名为其他内容(这可能太麻烦了)。不幸的是,“第一个感兴趣的节点”可能会有很大差异。由于用户花时间在树的不同部分并在子树上跳跃,因此无法预测他们可能锚定完成尝试的级别。除非我们链接每个级别(看起来很麻烦和笨拙),我不确定链接是否能解决这个问题。谢谢Dennis!这与我想要的非常接近。我可以通过一些调整来使用它,或者甚至可以按原样使用。我将看看是否可以将类似的方法应用于tcsh。我认为我非常孤独,需要复杂的完成脚本:)+1来完成代码工作!