2009-10-07 8 views
7

Estoy tratando de configurar la finalización del directorio en tcsh y/o bash (ambos se usan en mi sitio) con un ligero giro: para un comando en particular "foo" , Me gustaría que complete la función personalizada para que coincida con el primer/-delimitado término a un nodo de subárbol real, y luego siga la finalización de directorio normal para cualquier término sucesivo. Es una especie de combinación de cdpath y terminación, o supongo que una forma de finalización de directorio donde el punto de inicio está controlado por el script de finalización. Que funcionaría de la siguiente manera:Finalización del directorio Tcsh y/o bash con el prefijo de raíz oculta 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/... 

Nos gustaría hacer esto porque tenemos un gran árbol de desarrollo de la producción (se trata de una instalación de producción de CGI), que los usuarios de shell-comprensión están navegando y saltando en todo el hora. La queja es que los niveles superiores del árbol son engorrosos y redundantes; solo necesitan una búsqueda rápida en el primer término para encontrar posibles opciones de "encabezado" y completar el directorio desde allí. Parece que la finalización programable podría ofrecer una forma de hacerlo, pero se está volviendo bastante elusivo.

He realizado varios intentos de finalización de bash y tcsh para hacerlo, pero lo más cercano que he obtenido es una forma de "finalización de palabra" donde el usuario debe tratar los niveles de directorio como palabras separadas con espacios (por ej. foo scene/shot/element/workspace/...). Podría seguir pirateando mis scripts actuales, pero me he estado preguntando si hay algo que no entiendo: este es mi primer intento de programar la finalización, y los documentos y ejemplos son bastante escasos en los libros de shell y en Internet. . Si hay algún gurú de la finalización que pueda llevarme por el buen camino, lo agradecería.

FWIW: esto es lo que tengo hasta ahora (en tcsh primero, luego bash). Tenga en cuenta que la raíz estática '/ root/sub1/sub2/sub3' es solo un marcador de posición para una función de búsqueda que encontraría diferentes coincidencias en diferentes niveles. Si puedo conseguir que funcione, puedo subir a la función de búsqueda más tarde. De nuevo, ambos ejemplos completan palabras, lo que requiere que el usuario escriba un espacio después de cada término coincidente (también tengo que eliminar los espacios en la función para construir una ruta real, ¡asco!)

TCSH EJEMPLO (observe que la función es en realidad 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 Ejemplo:

_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 de entorno CDPATH de bash funcionará para usted? – Cascabel

+0

Miré en CDPATH, pero no funciona CON la finalización. Puede "cd name", pero no "cd name ..." Realmente necesito completarlo. –

+0

Pensé que la finalización de bash (el guión adicional lleno de objetos útiles que se incluye con bash en la mayoría de las distribuciones) incluía una definición actualizada de la finalización del CD con CDPATH. – Cascabel

Respuesta

0

Se podía hacer un enlace simbólico al primer nodo interesante en el árbol. He hecho esto en el pasado cuando no me molestaba autocompletar grandes árboles de directorios.

+0

Desafortunadamente, el "primer nodo interesante" puede variar mucho. A medida que los usuarios pasan tiempo en diferentes partes del árbol y saltan alrededor de los subárboles, no se puede predecir en qué niveles pueden anclar un intento de finalización. A menos que vinculemos todos los niveles (parece engorroso e inmanejable), no estoy seguro de que los enlaces lo resuelvan. –

4

Esto parece que podría hacer lo que está buscando:

_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 

Notas:

  • Creo que una de las claves es la opción
  • nospace Siento que' he reinventado una rueda en algún lugar de la función anterior, posiblemente al no usar $COMP_POINT
  • No está (todavía, al menos) usando $prev (que Siempre mantiene el valor "foo" en mi función)
  • legibilidad y funcionalidad se pueden mejorar mediante el uso de $() en lugar de acentos abiertos
  • Debe utilizar command alias para evitar que se ejecutan y tal: flist=$(command ls -1 -d...
  • que estoy usando en lugar find de ls porque es más adecuado
  • Usted puede agregar la barra utilizando -S/ con compgen en lugar de su comando awk
  • Usted puede utilizar en lugar de $cur$terms ya que no tienen que se deben eliminar espacios, pero yo estoy usando $lastword y $new (dos nuevas variables)
  • No es necesario el uso de xargs echo desde una matriz con saltos de línea funciona bien
  • No he probado esto con nombres de directorio que tienen espacios o líneas nuevas en ellos
+0

Gracias Dennis! Eso está muy cerca de lo que estoy buscando. Es posible que pueda usarlo con algunos ajustes, o tal vez como sea. Veré si puedo aplicar un método similar a tcsh también. –

+0

Pensé que estaba bastante solo para necesitar scripts de finalización complejos :) +1 para el esfuerzo de código! – neuro

1

mi solución, que es sin duda un martillo de 800 lb, fue escribir una secuencia de comandos perl para manejar la finalización de la manera que yo quería. en tsch ...

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 solución básica tcsh es muy fácil de implementar de la siguiente manera:

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

No hay bash escritura de la dependencia requiere. Por supuesto, el directorio base está cableado en este ejemplo.

1

Entonces, aquí hay una solución tcsh. Agrega $ cdpath completo para buscar directorios de autocompletado. El comando completo es:

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

Soy un poco de un truco tcsh, por lo que la manipulación de cadenas es un poco crudo. Pero funciona con una sobrecarga insignificante ... mycdpathcomplete.csh se ve así:

#!/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" 
Cuestiones relacionadas