2009-10-07 14 views
7

Sto cercando di impostare il completamento della directory in tcsh e/o bash (entrambi sono utilizzati nel mio sito) con una leggera svolta: per un comando particolare "foo" , Mi piacerebbe avere il completamento utilizzare una funzione personalizzata per abbinare il primo/-limitato termine a un vero sottostrato nodo, e quindi seguire il normale completamento della directory per eventuali termini successivi. È una specie di combinazione di cdpath e completamento, o suppongo una forma di completamento della directory in cui il punto di partenza è controllato dallo script di completamento. Funzionerebbe come segue:Completamento della directory Tcsh e/o bash con prefisso radice nascosto variabile

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

Vorremmo fare questo perché abbiamo un grande albero di sviluppo della produzione (si tratta di un impianto di produzione CGI), che gli utenti della shell-savvy navigano e saltare in giro per tutto il tempo. La lamentela è che i livelli superiori dell'albero sono ingombranti e ridondanti; hanno solo bisogno di una rapida ricerca sul primo termine per trovare possibili scelte "di testa" e fare il completamento delle directory da lì. Sembra che il completamento programmabile possa offrire un modo per farlo, ma si sta rivelando piuttosto elusivo.

Ho fatto diversi tentativi di completamento personalizzato bash e tcsh per farlo, ma il più vicino che ho ottenuto è una forma di "completamento delle parole" in cui l'utente deve trattare i livelli di directory come parole separate con spazi (ad es. foo scene/shot/element/workspace/...). Potrei continuare a hackerare i miei script correnti, ma mi sono chiesto se c'è qualcosa che non sto capendo - questo è il mio primo tentativo di completamento del programma, e i documenti e gli esempi sono piuttosto sottili nei libri di shell e su internet . Se c'è qualche guru del completamento là fuori che può farmi sulla strada giusta, lo apprezzerei.

FWIW: ecco quello che ho ottenuto finora (in tcsh prima, quindi bash). Si noti che la root statica '/ root/sub1/sub2/sub3' è solo un segnaposto per una funzione di ricerca che troverà corrispondenze diverse in livelli diversi. Se riesco a farlo funzionare, posso inserire successivamente la funzione di ricerca. Ancora, entrambi gli esempi eseguono il completamento delle parole, che richiede all'utente di digitare uno spazio dopo ogni termine corrispondente (devo anche rimuovere gli spazi nella funzione per costruire un percorso effettivo, yuck!)

ESEMPIO TCSH (notare che la funzione è in realtà uno 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 ESEMPIO:

_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 variabile di ambiente CDPATH di bash può funzionare correttamente? – Cascabel

+0

Ho esaminato CDPATH, ma non funziona CON il completamento. Puoi "cd name", ma non "cd name ..." Ho davvero bisogno del completamento. –

+0

Ho pensato che il completamento di bash (lo script extra pieno di chicche utili fornite con la maggior parte delle distro) includesse una definizione aggiornata del completamento del cd usando CDPATH. – Cascabel

risposta

0

si può solo fare un link simbolico al primo nodo interessante nella struttura. Ho fatto questo in passato quando non potevo essere disturbato dal completamento automatico di grandi alberi di directory.

+0

Sfortunatamente il "primo nodo interessante" può variare notevolmente. Poiché gli utenti trascorrono del tempo in diverse parti dell'albero e saltellano attorno ai sottostrutture, non è possibile prevedere quali livelli possano ancorare un tentativo di completamento. A meno che non colleghiamo ogni livello (sembra ingombrante e poco maneggevole), non sono sicuro che i collegamenti lo risolvano. –

4

Questo sembra che potrebbe fare quello che stai cercando:

_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 

Note:

  • Credo che una delle chiavi è l'opzione
  • nospace Mi sento come se' Ho reinventato una ruota da qualche parte nella funzione di cui sopra, probabilmente non usando $COMP_POINT
  • Non sei (ancora, almeno) usando $prev (che mantiene sempre il valore "pippo" nella mia funzione)
  • leggibilità e la funzionalità può essere migliorata utilizzando $() invece di apici inversi
  • Si dovrebbe usare command per evitare gli alias in esecuzione e così via: flist=$(command ls -1 -d...
  • che sto usando find invece di ls perché è più adatto
  • è possibile aggiungere la barra usando -S/ con compgen al posto del tuo comando awk
  • è possibile utilizzare $cur invece di 0.123.dal momento che non c'è bisogno di togliere gli spazi, ma sto usando $lastword e $new (due nuove variabili)
  • Non è necessario usare xargs echo dato un array con a capo funziona bene
  • non ho ancora testato questo con i nomi delle directory con spazi o newline
+0

Grazie Dennis! Questo è molto vicino a quello che sto cercando. Potrei essere in grado di usarlo con alcune modifiche, o forse anche come è. Vedrò se posso applicare anche un metodo simile a tcsh. –

+0

Pensavo di essere abbastanza solo per aver bisogno di complessi script di completamento :) +1 per lo sforzo del codice! – neuro

1

la mia soluzione, che è certamente un martello da 800 libbre, era di scrivere uno script perl per gestire il completamento nel modo in cui lo volevo. in 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 tcsh soluzione di base è molto facile da implementare come segue:

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

Non c'è bash sceneggiatura dipendenza richiesta. Ovviamente la directory di base è cablata in questo esempio.

1

Quindi, ecco una soluzione tcsh. Aggiunge un intero $ cdpath alla ricerca di directory di completamento automatico. Il comando completo è:

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

io sono un po 'di hack tcsh, per cui la manipolazione di stringhe è un po' grezzo. Ma funziona con un overhead trascurabile ... mycdpathcomplete.csh ha questo aspetto:

#!/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" 
Problemi correlati