Next Previous Contents

3. Comandos Externos

3.1 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\` ).

3.2 Comandos externos en el prompt

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)]$

3.3 Qué poner en el prompt

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.

3.4 Entorno 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.


Next Previous Contents