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 diréctamente
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 notar la contrabarra anterior al signo del dolar de la
sustitución del comando. Sin ella, el comando externo se ejecuta
exáctamente una vez: cuando se lee el string 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 bash shell scripts 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 shell 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 -
desafortunadamente, la explicación de funciones en detalle va más allá de
este documento), o como shell 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 sognificante 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, el "\$" es un símbolo de dolar
para un usuario normal y cambia a una "#" si el usuario es el root.
Torben Fjerdingstad me escribió para decirme que a menudo suspende tareas, y después de 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 rodes sus cadenas PS1 con comillas simples. lo que evita que el
bash interprete inmediátamente las contrabarras, así no tiene que
escaparlas como yo había dicho.
NOTA: existe un bug conocid 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ó exáctamente bien.
Posiblemente haya límites prácticos en lo que se puede y debe poner en el
entorno, pero no los conozco, y probablemente se setá hablando de un par
de ordenes de magnitud mayores que 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 corro en mi máquina LiNUX (aparte de Oracle).
Ejecuta top algún tiempo y pulsa 'M' para ordenar por memoria, y
comprueba lo cerca que está bash de la cima de la lista. O sea, que es
¡mayor que sendmail!... Diles que consigan ash o algo así".
Supongo que estaba usando la consola el día que probó eso: ejecutar X y aplicaciones X obtiene muchas cosas mayores que el 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 eficientes que pueden ser. Se parece a un source de un shell
script pero con el ahorro de las operaciones entrada/salida, ya que la
función ya se encuentra 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.