Tcsh和/或bash目录完成,带有变量隐藏根前缀
我试图在tcsh和/或bash(在我的站点上都使用)中设置目录完成(directory completion):对于一个特定的命令“foo”,我想让completion使用一个自定义函数将第一个/分隔的项与实际的子树节点相匹配,然后对任何后续项执行正常的目录完成。它是cdpath和completion的某种组合,或者我假设是一种目录完成形式,其中起始点由完成脚本控制。其工作如下: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
$ 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
- 您没有(至少)使用
(它在我的函数中始终保持值“foo”)$prev
- 使用
而不是反勾号可以提高可读性和功能性$()
- 您应该使用
来防止运行别名,例如:命令
flist=$(命令ls-1-d…。
- 我使用的是
而不是find
,因为它更合适ls
- 您可以使用
和-S/
而不是compgen
命令添加斜杠awk
- 您可以使用
而不是$cur
,因为您不必去掉空格,但我使用的是$terms
和$lastword
(两个新变量)$new
- 没有必要使用
,因为带有换行符的数组可以正常工作xargs echo
- 我还没有用带有空格或换行符的目录名测试过这一点
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 { "e_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来完成代码工作!