2009-10-07 26 views
7

J'essaie de configurer l'achèvement du répertoire dans tcsh et/ou bash (les deux sont utilisés sur mon site) avec une légère rotation: pour une commande particulière "foo" , J'aimerais que l'achèvement utilise une fonction personnalisée pour faire correspondre le premier terme/-delimited à un nœud de sous-arbre réel, puis suivre l'achèvement normal du répertoire pour tous les termes successifs. C'est en quelque sorte une combinaison de cdpath et d'achèvement, ou je suppose que c'est une forme d'achèvement de répertoire où le point de départ est contrôlé par le script d'achèvement. Il fonctionnerait comme suit:Achèvement du répertoire Tcsh et/ou bash avec préfixe racine caché variable

$ 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/... 

Nous aimerions faire cela parce que nous avons un grand arbre de développement de la production (ce qui est une installation de production de CGI), que les utilisateurs shell avertis naviguent et sauter dans tous les temps. La plainte est que les niveaux supérieurs de l'arbre sont encombrants et redondants; ils ont juste besoin d'une recherche rapide sur le premier terme pour trouver des choix de tête possibles et faire l'achèvement du répertoire à partir de là. Il semble que l'achèvement programmable pourrait offrir un moyen de le faire, mais il s'avère être assez difficile à atteindre. J'ai fait plusieurs tentatives de complétion personnalisée de bash et de tcsh pour cela, mais le plus proche est une forme de "complétion de mot" où l'utilisateur doit traiter les niveaux de répertoire comme des mots séparés avec des espaces (par ex. foo scène/tir/élément/espace de travail/...). Je pourrais continuer à pirater mes scripts actuels - mais je me demandais s'il y avait quelque chose que je ne comprends pas - c'est ma première tentative d'achèvement du programme, et les docs et exemples sont assez minces dans les livres shell et sur internet . S'il y a un gourou de l'achèvement qui peut me mettre sur la bonne voie, j'apprécierais. FWIW: voici ce que j'ai jusqu'ici (dans tcsh d'abord, puis bash). Notez que la racine statique '/ root/sub1/sub2/sub3' est juste un espace réservé pour une fonction de recherche qui trouverait différentes correspondances dans différents niveaux. Si je peux obtenir que cela fonctionne, je peux sous la fonction de recherche plus tard. Encore une fois, les deux exemples font l'achèvement du mot, ce qui oblige l'utilisateur à taper un espace après chaque terme correspondant (je dois également supprimer les espaces dans la fonction pour construire un chemin réel, beurk!)

TCSH EXEMPLE (notez que la fonction est en fait un script bash):

complete complete_p2 '[email protected]*@`./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 [email protected] | 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 Exemple:

_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 
+0

La variable d'environnement CDPATH de bash fonctionnerait-elle pour vous? – Cascabel

+0

J'ai regardé dans CDPATH, mais cela ne fonctionne pas avec l'achèvement. Vous pouvez "cd name", mais pas "cd name ..." J'ai vraiment besoin de l'achèvement. –

+0

Je pensais que l'achèvement de bash (le script supplémentaire plein de goodies utiles qui sont livrés avec bash sur la plupart des distributions) incluait une définition mise à jour de l'achèvement de CD en utilisant CDPATH. – Cascabel

Répondre

0

Vous pouvez tout simplement faire un lien symbolique au premier noeud intéressant dans l'arbre. Je l'ai fait dans le passé quand je ne pouvais pas être dérangé auto-complétant de grands arbres de répertoires.

+0

Malheureusement, le "premier noeud intéressant" peut varier considérablement. Comme les utilisateurs passent du temps dans différentes parties de l'arbre et sautent autour des sous-arbres, il est impossible de prédire à quels niveaux ils peuvent ancrer une tentative d'achèvement. À moins que nous n'associons tous les niveaux (ce qui semble lourd et lourd), je ne suis pas sûr que les liens le résoudront. –

4

Cela semble que cela pourrait faire ce que vous cherchez:

_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 

Notes:

  • Je pense que l'une des clés est l'option nospace
  • Je me sens comme je Nous avons réinventé une roue quelque part dans la fonction ci-dessus, éventuellement en n'utilisant pas $COMP_POINT
  • Vous n'êtes pas (encore, au moins) en utilisant $prev (qui maintient toujours la valeur « foo » dans ma fonction)
  • Lisibilité et fonctionnalité peuvent être améliorées à l'aide $() au lieu de
  • accents graves Vous devez utiliser command pour empêcher les alias en cours d'exécution et autres: flist=$(command ls -1 -d...
  • J'utilise find à la place de ls parce qu'il est mieux
  • Vous pouvez ajouter la barre oblique à l'aide -S/ avec compgen au lieu de votre commande awk
  • Vous pouvez utiliser $cur au lieu de 0.123.puisque vous n'avez pas dépouiller les espaces, mais je suis en utilisant $lastword et $new (deux nouvelles variables)
  • Il est pas nécessaire d'utiliser xargs echo depuis un tableau avec des sauts de ligne fonctionne très bien
  • Je n'ai pas testé avec les noms des répertoires ayant des espaces ou des retours à la ligne
+0

Merci Dennis! C'est très proche de ce que je cherche. Je peux être en mesure de l'utiliser avec quelques réglages, ou peut-être même tel quel. Je vais voir si je peux aussi appliquer une méthode similaire à tcsh. –

+0

Je pensais que j'étais assez seul pour avoir besoin de scripts de complétion complexes :) +1 pour le code effort! – neuro

1

ma solution, qui est un marteau de 800 livres, était d'écrire un script perl pour gérer l'achèvement comme je le voulais. tcsh ...

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

La solution tcsh de base est très facile à mettre en œuvre comme suit:

complete foo '[email protected]*@D:/root/sub1/sub2/[email protected]' 

Il n'y a pas de dépendance bash script requis. Bien sûr, le répertoire de base est câblé dans cet exemple.

1

Donc, voici une solution tcsh. Ajoute $ cdpath complet à la recherche de répertoires à fin de saisie automatique. La commande complète est:

complete cd '[email protected]/@[email protected]''[email protected]@`source $HOME/bin/mycdpathcomplete.csh $:1`@/@' 

Je suis un peu un hack tcsh, de sorte que la manipulation de chaînes est un peu brut. Mais cela fonctionne avec un surcoût négligeable ... mycdpathcomplete.csh ressemble à ceci:

#!/bin/tcsh -f 

set psuf="" 
set tail="" 

if ($1 !~ "*/*") then 
    set tail="/$1" 
else 
    set argsplit=(`echo "$1" | sed -r "[email protected](.*)(/[^/]*\')@\1 \[email protected]"`) 
    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 "[email protected]*(/?${psuf}${tail}[^/]*)\'@\[email protected]" | 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 "[email protected]^/@@"` 
     endif 
    end 
endif 

echo "$matches"