PROMPT_COMMAND
bash
proporciona otra variable de entorno llamada
PROMPT_COMMAND
. El contenido de esta variable se ejecuta como un
comando bash
normal justo antes de que bash
muestre el prompt.
[21:55:01][giles@nikola:~] PS1="[\u@\h:\w]\$ "
[giles@nikola:~] PROMPT_COMMAND="date +%H%M"
2155
[giles@nikola:~] d
bin mail
2156
[giles@nikola:~]
Lo que ocurre arriba es que he cambiado PS1
para que no incluya la
secuencia de escape \t
, de tal modo que la hora no forme parte del
prompt. Después he usado date +%H%M
para mostrar la
hora en un formato que me gusta más. Pero aparece en una línea diferente a
la del prompt. Esto se soluciona usando echo -n
... como se muestra
debajo, funciona con bash 2.0+
, pero parece que no lo hace con
bash 1.14.7
: aparentemente el prompt se dibuja de manera diferente,
y el método mostrado a continuación resulta en superposición de texto.
2156
[giles@nikola:~] PROMPT_COMMAND="echo -n [$(date +%H%M)]"
[2156][giles@nikola:~]$
[2156][giles@nikola:~]$ d
bin mail
[2157][giles@nikola:~]$ unset PROMPT_COMMAND
[giles@nikola:~]
echo -n
... controla la salida del comando date
y suprime el
caracter de nueva línea final, permitiendo que el prompt aparezca en una
sola línea. Al final, uso el comando unset
para eliminar la variable
de entorno PROMPT_COMMAND
.
Nótese que uso la convención $(<comando>) para la sustitución de comandos, es decir
$(date +%H%M)
significa «sustituye la salida de date +%H%M
aquí».
Esto funciona en bash 2.0+
. En alguna versión antigua de bash
,
anterior a la 1.14.7
, puede ser necesario el uso de comillas simples
graves (`date +%H%M`
). Estas comillas pueden usarse en
bash 2.0+
, pero es preferible usar $()
, que funciona
mejor en el caso de anidamientos. Voy a usar esta convención a lo largo de
este documento. Si utiliza una versión anterior de bash
, normalmente
podrá sustituir los $()
por las comillas. Si la sustitución de
comandos está escapada (es decir, \$(comando)
), entonces
deberá usar contrabarras para escapar AMBAS comillas (o sea,
\`comando\`
).
También se puede usar la salida de comandos regulares LiNUX directamente
en el prompt. Obviamente, no es deseable insertar muchas cosas, o se
creará un prompt enorme. Además será preferible usar un comando rápido ya
que se va a ejecutar cada vez que el prompt aparezca en pantalla, y
retrasa la aparición de éste lo que puede resultar muy molesto. (A
diferencia del ejemplo anterior al que recuerda, esto funciona con
bash 1.14.7
)
[21:58:33][giles@nikola:~]$ PS1="[\$(date +%H%M)][\u@\h:\w]\$ "
[2159][giles@nikola:~]$ ls
bin mail
[2200][giles@nikola:~]$
Es importante hacer notar la contrabarra anterior al signo de dólar de la
sustitución del comando. Sin ella, el comando externo se ejecuta
exactamente una vez: cuando se lee la cadena almacenada en PS1
del
entorno. Para este prompt, eso significaría que mostraría siempre la misma
hora, sin importar cuanto tiempo se ha usado el prompt. La contrabarra
protege los contenidos de $()
de la interpretación inmediata
del shell, por lo que date
es llamado cada vez que se genera un
prompt.
LiNUX incluye muchas utilidades de pequeño tamaño como date
,
grep
o wc
que permiten la manipulación de datos. Si se encuentra
en la situación de crear una combinación compleja de estos programas
dentro del prompt, podría ser más fácil crear un shell script y
llamarlo desde el prompt. En ocasiones son necesarias secuencias de escape
en los scripts de bash
para asegurar que las variables se expanden en
el momento correcto (como se ha mostrado arriba con el comando date
):
esto llega a niveles mayores con la línea de prompt PS1
, y es una
buena idea evitarlo creando scripts.
Un ejemplo de un pequeño shell script usado dentro de un prompt es el siguiente:
#!/bin/bash
# lsbytesum - suma del número total de bytes de un ls
TotalBytes=0
for Bytes in $(ls -l | grep "^-" | cut -c30-41)
do
let TotalBytes=$TotalBytes+$Bytes
done
TotalMeg=$(echo -e "scale=3 \n$TotalBytes/1048576 \nquit" | bc)
echo -n "$TotalMeg"
A veces he mantenido ambos como funciones (mucho más eficiente, pero
desafortunadamente, la explicación de funciones en detalle va más allá de
este documento), o como scripts en mi directorio /bin
, que
se encuentra en mi variable PATH
. Utilizándolo en un prompt:
[2158][giles@nikola:~]$ PS1="[\u@\h:\w (\$(lsbytesum) Mb)]\$ "
[giles@nikola:~ (0 Mb)]$ cd /bin
[giles@nikola:/bin (4.498 Mb)]$
Se habrá percatado de que yo pongo el nombre de usuario, el nombre de la máquina, la hora y el directorio actual en la mayoría de mis prompts. Con la excepción de la hora, son cosas muy normales de encontrar en un prompt, y la hora es posiblemente la adición más común. Pero lo que incluya cada uno es cosa de gusto personal. Aquí hay ejemplos de personas que conozco que le pueden dar ideas.
El prompt de Dan es mínimo pero muy efectivo, particularmente para su forma de trabajar.
[giles@nikola:~]$ cur_tty=$(tty | sed -e "s/.*tty\(.*\)/\1/")
[giles@nikola:~]$ echo $cur_tty
p4
[giles@nikola:~]$ PS1="\!,$cur_tty,\$?\$ "
1095,p4,0$
A Dan no le gusta que el hecho de tener el directorio actual de trabajo en
el prompt pueda variar el tamaño de éste drásticamente mientras se pasa de
un directorio a otro, así que el mantiene la pista de esto en su cabeza (o
usa pwd
). El aprendió Unix con csh
y tcsh
, así que usa su
histórico de comandos de forma intensiva (cosa que los adictos al
bash
no solemos hacer), así que la primera cosa en el prompt es el
número del histórico. El segundo campo es el caracter significante de la
tty (la salida de tty
es recortada mediante sed), un dato que puede
ser útil para los usuarios de screen
. El tercer campo es el valor de
retorno del último comando/tubería (nótese que se muestra inútil para
cualquier comando que se ejecuta dentro del prompt; se puede solucionar
capturándolo en una variable). Finalmente, «\$
» es un símbolo
de dólar para un usuario normal y cambia a #
si el usuario es el
root.
Torben Fjerdingstad me escribió para decirme que a menudo suspende tareas, y después se le olvidan, así que usa su prompt para servir de recordatorio de las tareas suspendidas:
[giles@nikola:~]$ function jobcount {
> jobs|wc -l| awk '{print $1}'
> }
[giles@nikola:~]$ export PS1='\W[`jobcount`]# '
giles[0]# man ls &
[1] 4150
[1]+ Stopped (tty output) man ls
giles[1]#
Torben usa awk
para evitar el espacio de la salida de wc
,
mientras que yo habría usado sed
o tr
- no porque sean mejor,
sino porque me resultan más familiares. Probablemente existan más formas.
Torben además rodea sus cadenas PS1
con comillas simples. lo que
evita que el bash
interprete inmediatamente las contrabarras, así no
tiene que escaparlas como yo había dicho.
NOTA: existe un bug conocido en bash 2.02
que provoca que
el comando jobs
no retorne nada a una tubería. Si intenta lo de
arriba bajo bash 2.02
, siempre obtendrá un «0
»
independientemente de los trabajos que haya suspendidos. Chet Ramey, uno
de los responsables de bash
me ha dicho que esto se soluciona en la
v2.03
.
bash
y funciones
Como he mencionado antes, PS1
, PS2
, PS3
, PS4
y
PROMPT_COMMAND
se almacenan todas en el entorno del bash
. Para
aquellos que provengan del DOS, la idea de almacenar gran cantidad de
código en el entorno es aterradora, ya que el entorno del DOS era pequeño,
y no creció bien exactamente. Posiblemente haya límites prácticos en lo
que se puede y debe poner en el entorno, pero no los conozco, y
probablemente se está hablando de un par de órdenes de magnitud mayores de
a lo que están acostumbrados los usuarios de DOS. Como dijo Dan:
En mi shell interactivo tengo 62 alias y 25 funciones. Mi regla es que
si necesito algo únicamente para uso interactivo y puedo escribirlo bien
en bash, hago de ello una función de shell (teniendo en cuenta que no
pueda expresarse de manera sencilla como un alias). Si la gente se
preocupa por la memoria no deberían estar usando bash
. bash
es uno de los programas más grandes que ejecuto en mi máquina LiNUX
(aparte de Oracle). Ejecute top
algún tiempo y pulse
«M
» para ordenar por memoria, y compruebe lo cerca que está bash de
la cima de la lista. O sea, que es ¡mayor que sendmail!... Recomienda que
utilicen mejor ash
o algo así.
Supongo que estaba usando la consola el día que probó eso: ejecutando X y aplicaciones X obtendrá muchas cosas mayores que bash. Pero la idea es la misma: el entorno es algo para ser usado, sin preocupación de desbordarlo.
Me arriesgo a la censura de los gurús de unix cuando digo esto (por el
delito de supersimplificación), pero las funciones son básicamente
pequeños shell scripts
que se cargan en el entorno con el propósito
de una mayor eficiencia. Citando a Dan de nuevo: las funciones shell
son lo más eficiente. El procedimiento es similar a un source
de un shell script pero con el ahorro de las operaciones
entrada/salida, ya que la función se encuentra ya en memoria. Las
funciones shell se cargan típicamente del .bashrc o
.bash_profile
dependiendo de si se las quiere en el shell inicial
o en los sucesivos subshells también.
Compárese esto con la ejecución de un shell script: el shell realiza
un fork
, el hijo lleva a cabo un exec
,
potencialmente se busca el path
, el kernel abre el fichero y
examina la cantidad suficiente de bytes para saber cómo ejecutarlo, en el
caso de un shell script debe arrancarse un shell con el nombre del script
como argumento. Comparado con una función shell, cualquier cosa aparte de
la ejecución de las sentencias, puede considerarse una sobrecarga
innecesaria.