bash - una - permisos terminal ubuntu
Obtener compgen para incluir barras en los directorios al buscar archivos (5)
Me gustaría obtener el siguiente comportamiento de mi finalización personalizada
Dado
$ mkdir foo
$ touch foo faz/bar faz/baz
Me gustaría obtener esto
$ foo -u <tab><tab> =>
foo faz/
$ foo -u fa<tab><tab> =>
foo -u faz/
$ foo -u faz/<tab><tab> =>
bar baz
Supuse que compgen -ff
generaría foo faz/
, pero produce foo faz
lo que no me ayuda mucho.
¿Necesito postprocesar el resultado o hay alguna combinación mágica de opciones para compgen que funcione?
Tuve el mismo problema. Aquí está la solución que estoy usando:
- Registre la función de finalización con
-o default
, por ejemplo,complete -o default -F _my_completion
. - Cuando desee completar un nombre de archivo, simplemente configure
COMPREPLY=()
y deje que Readline se haga cargo (eso es lo que hace el-o default
).
Existe un problema potencial con esto: es posible que esté utilizando COMPREPLY=()
para denegar la finalización donde no sea apropiado, y eso ya no funcionará. En Bash 4.0 y compopt
superiores, puedes compopt
esto usando el compopt
siguiente manera:
- En la parte superior de su función de finalización, siempre ejecute
compopt +o default
. Esto desactiva la finalización del nombre de archivo de Readline cuandoCOMPREPLY
está vacío. - Cuando desee completar un nombre de archivo, use
compopt -o default; COMPREPLY=()
compopt -o default; COMPREPLY=()
. En otras palabras, habilite la finalización del nombre de archivo de Readline solo cuando lo necesite.
No he encontrado una solución completa para Bash anterior a 4.0, pero tengo algo que funciona lo suficientemente bien para mí. Puedo describir esto si a alguien realmente le importa, pero espero que estas versiones antiguas de Bash pronto se salgan del uso común de todos modos.
Esto funcionó para mí:
_my_completion() {
compopt -o nospace
COMPREPLY=( $(compgen -S "/" -d "${COMP_WORDS[$COMP_CWORD]}") )
}
complete -F _my_completion foo
Si desea poder continuar usando la pestaña después de que se completó el directorio; esta es la solución completa:
cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -S "/" -d -- ${cur}) );
compopt -o nospace;
primero agregue una barra inclinada a la respuesta. Luego ejecute el compopt -o nospace
para eliminar el espacio final. Ahora, si solo tiene foo/bar
en su directorio, ¡dos pestañas bastarán para escribirlas!
Las respuestas anteriores requieren bash 4 o no se pueden componer con otras terminaciones basadas en compgen para el mismo comando. La siguiente solución no requiere componentes (por lo que funciona con bash 3.2) y es composable (por ejemplo, puede agregar filtros adicionales para que coincidan con ciertas extensiones de archivos). Salte al final si es impaciente.
Puede probar compgen ejecutándolo directamente en la línea de comando:
compgen -f -- $cur
donde $cur
es la palabra que habría tipeado hasta ahora durante la finalización de tabulación interactiva.
Si estoy en un directorio que tiene un archivo afile.py
y un subdirectorio adir
, y cur=a
, el comando anterior muestra lo siguiente:
$ cur=a
$ compgen -f -- $cur
adir
afile
Tenga en cuenta que compgen -f
muestra archivos y directorios. compgen -d
muestra solo directorios:
$ compgen -d -- $cur
adir
Agregar -S /
agregará una barra inclinada a cada resultado:
$ compgen -d -S / -- $cur
adir/
Ahora, podemos intentar enumerar todos los archivos y todos los directorios:
$ compgen -f -- $cur; compgen -d -S / -- $cur
adir
afile.py
adir/
Tenga en cuenta que no hay nada que le impida llamar a compgen más de una vez. En tu script de finalización lo usarías así:
cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -f -- "$cur"; compgen -d -S / -- "$cur") )
Desafortunadamente, esto todavía no nos da el comportamiento que queremos, porque si escribe ad<TAB>
tiene dos terminaciones posibles: adir
y adir/
. Por lo tanto, ad<TAB>
se completará en adir
, en cuyo punto aún tendrá que escribir el /
para eliminar la ambigüedad.
Lo que necesitamos ahora es una función que devolverá todos los archivos pero no directorios. Aquí está:
$ grep -v -F -f <(compgen -d -P ''^'' -S ''$'' -- "$cur") /
> <(compgen -f -P ''^'' -S ''$'' -- "$cur") |
> sed -e ''s/^/^//'' -e ''s//$$//''
afile.py
Vamos a descomponerlo:
-
grep -f file1 file2
significa que muestra las líneas en el archivo2 que coinciden con cualquiera de los patrones del archivo1. -
-F
significa que los patrones en el archivo1 deben coincidir exactamente (como una subcadena); no son expresiones regulares. -
-v
significa invierta la coincidencia: solo muestra las líneas que no están en el archivo1. -
<(...)
es la sustitución del proceso bash. Le permite ejecutar cualquier comando en los lugares donde se espera un archivo.
Entonces le estamos diciendo a grep: aquí hay una lista de archivos: elimine todos los que coincidan con esta lista de directorios.
$ grep -v -F -f <(compgen -d -P ''^'' -S ''$'' -- "$cur") /
> <(compgen -f -P ''^'' -S ''$'' -- "$cur")
^afile.py$
He añadido marcadores de inicio y final con comp de -P ^
y -S ''$''
porque grep''s -F
hace coincidir las subcadenas y no queremos eliminar un nombre de archivo como a-file-with-adir-in-the-middle
solo porque su parte central coincidía con el nombre de un directorio. Una vez que tenemos la lista de archivos, eliminamos esos marcadores con sed.
Ahora podemos escribir una función que hace lo que queremos:
# Returns filenames and directories, appending a slash to directory names.
_mycmd_compgen_filenames() {
local cur="$1"
# Files, excluding directories:
grep -v -F -f <(compgen -d -P ^ -S ''$'' -- "$cur") /
<(compgen -f -P ^ -S ''$'' -- "$cur") |
sed -e ''s/^/^//'' -e ''s//$$/ /''
# Directories:
compgen -d -S / -- "$cur"
}
Lo usas así:
_mycmd_complete() {
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(_mycmd_compgen_filenames "$cur") )
}
complete -o nospace -F _mycmd_complete mycmd
Tenga en cuenta que -o nospace
significa que no obtiene un espacio después de /
. Para archivos normales agregamos un espacio al final con sed.
Una cosa buena de tener esto en una función separada es que es fácil de probar. Por ejemplo, aquí hay una prueba automatizada:
diff -u <(printf "afile.py /nadir//n") <(_mycmd_compgen_filenames "a") /
|| { echo "error: unexpected completions"; exit 1; }
Este comportamiento está influenciado por los mark-directories
variable readline (y los mark-directories
mark-symlinked-directories
relacionados). Creo que la variable debe configurarse para complete
para imprimir una barra inclinada en los nombres de los directorios (el valor predeterminado en bash v3.00.16). Aparentemente, el comportamiento relacionado de compgen
no compgen
una barra oblicua a los nombres de directorio: - /
Establezca el valor de mark-directories
alternativamente en on
y off
y luego vuelva a intentar su prueba:
bind ''set mark-directories on''
bind ''set mark-directories off''
Para que el cambio sea permanente para futuras invocaciones de bash
, agregue lo siguiente a su archivo INPUTRC, comúnmente ~/.inputrc
: -
$if Bash
# append ''/'' to dirnames
set mark-directories on
$endif
La sugerencia para establecer las variables de lectura en el shell actual se encontró aquí: https://unix.stackexchange.com/a/27545 . No determiné cómo probar el valor actual de una variable de lectura.
Ideas adicionales
Quizás solo interés académico ...
Cree una lista de nombres de directorio solamente y añada una barra inclinada: -
compgen -d -S / f