El shell constituye la interfaz de usuario (UI) de consola. Es un intérprete de linea de comando (CLI: command line interface) que funciona en modalidad REPL (Read, Execute, Print, Loop). Esto es: el shell espera que ingresemos una orden o comando en la linea de comando, cuando presionamos la tecla <Enter>
, el shell interpreta y ejecuta la orden o comando que acabamos de ingresar, el resultado de esa ejecución puede aparecer en la consola (print) y una vez que finaliza el shell vuelve a quedar esperando que ingresemos una nueva orden o comando.
Linux dispone de una gran variedad de shells:
En la mayoría de las distribuciones de Linux hoy en día, el shell por defecto es bash, por lo que vamos a concentrarnos en la descripción y uso de ese shell.
Para correr un programa o ejecutar un comando en la linea de comandos de bash, alcanza con escribir el nombre del programa y presionar la tecla <Enter>
programa <Enter>
donde <Enter>
representa la tecla Enter o Intro del teclado. Cuando corremos un programa de esta manera, el shell espera a que el programa termine antes de volver a atendernos. Si el programa demora unos segundos en ejecutar, eso se nota en que el prompt
del shell no aparece hasta que el programa termina.
Desde bash hay dos modalidades de ejecución de programas:
Si el programa es interactivo, vale decir, requiere la intervención de usuario, inevitablemente debe usarse la modalidad foreground, que es la que ejemplificamos más arriba. En esta modalidad, el shell no vuelve a atendernos hasta que el programa termine.
Para indicarle al shell que pretendemos correr un programa en background, hay que agregar un &
al final de la linea de comandos, antes del <Enter>
. Por ejemplo:
programa & <Enter>
En esta modalidad, el programa se desliga del shell, queda ejecutando en segundo plano, reaparece el prompt del shell y no necesitamos esperar a que el programa temine para seguir trabajando en el shell.
Al lanzar un proceso en background el shell nos devuelve dos datos: el índice de proceso del shell, que aparece entre paréntesis rectos, y el PID (Process ID) del proceso. Todo proceso que está corriendo en el sistema tiene un número de proceso o PID.
Por ejemplo:
programa & [1] 15435
[1]
es el índice de proceso y 15435
es su correspondiente PID.
Obviamente que podemos lanzar tantos programas como queramos en background y mientras corren podemos seguir trabajando en el shell. Por eso la modalidad background es tan cómoda y es la preferida para correr programas no interactivos que van a correr por un buen rato.
Para saber cuantas tareas están corriendo en background, podemos correr el comando
jobs
que lista todas las tareas que aún están ejecutando. Ojo: sólo lista las tareas que dependan de ese sesión de shell. Podrían haber otras tares corriendo, pero que fueron lanzadas por otras sesiones de shell y no aparecen con el comando jobs
. Para poder ver todos los procesos que están corriendo en el sistema, debemos emplear el comando ps
ps -l ps aux
Sugerimos consultar el manual para ver todas las opciones del comando ps
man ps
El comando pstree
nos muestra el árbol de procesos que están ejecutando en el sistema.
Linux dispone de una gran variedad de mecanismos de comunicación entre procesos (IPC: Inter Process communication). Un mecanismo básico es el de las señales. La manera de interrumpir, suspender, terminar o matar un proceso es enviándole una señal apropiada.
Cuando tenemos un programa que está ejecutando en foreground podemos enviarle señales para interrumpirlo o suspenderlo. La manera de enviarle esa señal es con una combinación de teclas:
donde <Ctrl C> coresponde a mantener presionada la tecla Ctrl
y presionar la tecla C
. Lo mismo para el caso de la suspensión.
¿Qué pasa cuando interrumpimos un programa que está corriendo en foreground? Lo que habitualmente ocurre es que el programa termina, cancela, lo que queda de manifiesto porque reaparece el prompt del shell.
¿Qué pasa cuando lo suspendemos? El programa no cancela, pero queda en suspenso, sin correr y reaparece el prompt.
Si corremos el comando jobs
podemos comprobar que no está corriendo.
Podemos volver a activar un programa que fue suspendido, usando uno de dos comandos:
fg
: el programa se reactiva y vuelve a ejecutar en foregroundbg
: el programa se reactiva, pero queda corriendo en background
Con jobs
podemos verificar que el programa que había sido suspendido, ahora está corriendo en background.
Si queremos traer a foreground uno de los procesos que aparece en el listado que nos da el comando jobs
, podemos usar el comando fg
fg %i
donde el valor de i
corresponde al índice de tarea que nos lista el comando jobs
y que aparece entre paréntesis rectos al principio de la linea.
Cuando tenemos un proceso corriendo en background, no es posible usar las combinaciones de teclado <Ctrl C>
o <Ctrl Z>
para interrumpirlo con suspenderlo. Así que ahí recurrimos a otros comandos
Si el proceso sobre el que queremos actuar aparece en el listado de jobs
(es decir, es un proceso que fue lanzado desde esa sesión de shell), alcanza con ejecutar el comando kill y el índice de proceso:
kill %i
Eso le manda la señal de terminación y el proceso cancela la ejecución.
Si el proceso no aparece en el listado de jobs
, eso quiere decir que es un proceso que fue lanzado desde otro shell, pero de todas maneras le podemos enviar señales empleando el comando kill, usando el PID (Process ID) del proceso.
Para conocer el PID de un proceso, podemos listarlo usando el comando ps
. Por ejemplo, si queremos ver todos los procesos que tenemos corriendo bajo nuestro usuario, podemos correr el comando:
ps -u
Ver man ps
para más opciones de este comando.
Para saber qué señales se pueden enviar con el comando kill, sugerimos consultar el manual de signal(7)
man 7 signal
Acá mencionaremos únicamente las señales más empleadas, incluyendo la acción que debería ocurrir con el programa:
Signal Value Action Keyboard Comment ────────────────────────────────────────────────────────────────────── SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process SIGINT 2 Term Ctrl-C Interrupt from keyboard SIGKILL 9 Term Kill signal SIGTERM 15 Term Termination signal SIGSTOP 17,19,23 Stop Ctrl-Z Stop process
Por defecto, cuando corremos el comando:
kill <PID>
donde <PID>
representa el PID del proceso.
Lo que estamos enviando es la señal 15 (SIGTERM) y por defecto el programa debería terminar.
Si queremos mandar otra señal, debemos explicitarla en el propio comando kill
:
kill -15 <PID> kill -9 <PID> kill -2 <PID>
kill no es el único comando que puede usarse para enviar señales. Sugerimos que consulten el manual de: killall, pkill, killall5, o corran el comando apropos signal
o apropos kill
para listar toda la documentación sobre este tema.
Algunas señales son “atrapables” por el programa que está corriendo y se puede programar el comportamiento que el programa vaya a seguir en caso de recibir esa señal. Por ejemplo, las señales 1, 2 y 15 son atrapables por el programa, que podría estar programado para ignorarlas o hacer otra cosa diferente a lo que se espera sea el comportamiento por defecto.
En particular es muy útil que la señal 15 sea atrapable, pues se asume que un programa que reciba esa señal debe terminar. La utilidad de poder atrapar una señal permite al programa realizar tareas de cierre o hosekeeping y terminar de manera ordenada. La señal 15 no mata al proceso, sino que la indica que debe terminar y el programa ocuparse de cerrar archivos, salvar datos a disco o terminar de procesar los datos que tiene en ese momento, antes de cancelar la ejecución.
Entonces el procedimiento previsto para terminar la ejecución de un programa es mandar primero la señal 15, esperar unos segundos y verificar si el programa terminó o sigue corriendo. Si el programa no cancela la ejecución luego de haber recibido la señal 15, pero necesitamos que termine, entonces podemos mandarle la señal 9 (SIGKILL), que no puede ser ingorada y el proceso termina abruptamente. Como eso podría tener consecuencias (archivos de datos mal cerrados, pérdida de información, etc.), es que se recomienda primero mandar la señal 15 y, si no hay más remedio, la señal 9.
La señal SIGSTOP (que corresponde a los números 17, 19 y 23) lo que hace es supender la ejecución del programa (el programa deja de correr, pero no termina, queda esperando por otra señal que lo reactive o cancele). Para reactivar la ejecución del programa, le mandamos la misma señal.
Ejemplos:
kill -19 <PID> (el programa suspende la ejecución si estaba activo) kill -19 <PID> (el programa retoma la ejecución si estaba suspendido) kill -15 <PID> (el programa termina de manera ordenada) kill -9 <PID> (el programa termina abruptamente)
Si uno deja un programa corriendo y cierra el shell (termina la sesión), dependiendo de bajo qué condiciones ocurra esto, puede pasar que el programa que dejamos corriendo reciba una señal 1 (SIGHUP) y cancele, sin que nosotros nos hayamos enterado. Más tarde, cuando volvemos a abrir un shell en el equipo, nos encontramos con que el programa no continuó corriendo y que proceso quedó trunco.
Para evitar este tipo se situaciones conviene que nuestro programa ignore ese tipo de señales y continúe ejecutando.
Para eso es conveniente lanzar los programas anteponiéndoles el comando nohup
:
nohup programa &
El programa nohup
se encarga de establecer que la señal SIGHUP sea ignorada. Cómo el programa
que mandamos correr es hijo del programa nohup
, éste también ignora esa señal. Esa es una de las consecuencias de la manera que los procesos se generan el Linux. El comando nohup también se encarga de redirigir stdout
hacia el archivo nohup.out
. Si no redirijimos explícitamente el stdout
de nuestro programa
, la salida que genere irá a parar a nohup.out
.
Los procesos que corren el el sistema pueden tener diferentes propridades. Cuanto mayor sea su prioridad, más tiempo le dedica el sistema a correr ese proceso. Por defecto los procesos de los usuarios tienen priridad 10, mientras que los procesos del sistema tiene mayor prioridad. Eso está pensado de esa manera para que el sistema pueda encargarse de atender todos los procesos y hacer un uso eficiente de sus recursos.
La mejor política es nunca meterse a tocar o cambiar la prioridad de los procesos que se están ejecutando. Pero de todas maneras existen dos comandos: nice
y renice
, con los cuales podemos elegir la prioridad inicial al ejecutar un programa (nice) o cambiar la prioridad de un proceso que ya está corriendo (renice).
El rango de valores a elegir puede ir desde -19 (máxima prioridad) a 20 (mínima prioridad).
Lo que se recomienda es que uno use el comando nice
para bajar la prioridad inicial de un proceso (es decir ser “bueno” (nice) con el sistema.
Por ejemplo:
nice -15 programa
baja a 15 puntos la prioridad del programa que vayamos a ejecutar. No se recomienda subir la prioridad de un programa a ejecutar, pues podemos correr el riesgo de competir con los procesos del propio sistema operativo. Es mejor dejar que sea el propio sistema operativo el que maneje esas cosas. En todo caso, se puede usar el comando renice
para bajar la prioridad de un programa que ya está ejecutando. Nunca para subirla… pero si quieren subirla… pueden… la libertad es libre
Si queremos saber el tiempo que le lleva correr a un programa, podemos anteponerle el comando time
:
time programa
Al finalizar el programa
, aparece en stdout
3 datos: el tiempo transcurrido (real time), el tiempo de CPU empleado por el usuario (user) y el tiempo de CPU empleado ejecutando llamadas del sistema (sys).
En Linux, por defecto, todo comando o programa que es ejecutado desde el shell tiene al menos 3 unidades de entrada/salida:
Unidad | Nombre | Tipo | Dispositivo por defecto |
---|---|---|---|
0 | stdin | Standard Input | teclado |
1 | stdout | Standard Output | pantalla |
2 | stderr | Standard Error | pantalla |
Para los programas que se ejecutan desde el shell, stdin
corresponde a lectura del teclado, mientras que stdout
y stderr
corresponden a escritura a pantalla.
El problema es que no siempre resulta práctico leer datos desde el teclado o escribir resultados a la pantalla. Muchos programas requieren leer o escribir grandes cantidades de datos y no tiene sentido ingresar los datos por el teclado o ver los resultados sólo por la pantalla.
El shell de Linux prevé dos mecanismos para controlar la lectura o escritura de datos de las unidades de IO: el redireccionamiento y los pipes.
Para cambiar el origen o destino de los datos que un programa lea o escriba, se pueden usar los redireccionamientos n<, n> y n», donde n es la unidad de IO que queramos redireccionar.
Si queremos redireccionar las unidades 0 (stdin) o 1 (stdout), no es obligatorio escribir 0< o 1>, alcanza con poner < o >. Para cualquier otra unidad de IO, es necesario poner el valor numérico de esa unidad.
Así entonces podemos escribir lo siguiente:
programa <inputfile >outputfile
o
programa <inputfile >>outputfile
donde inputfile
es un archivo desde donde el programa
va a leer los datos que necesita y outfile
es el nombre del archivo adonde el programa va a escribir los resultados.
En el caso de escritura de datos, vimos que hay dos opciones:
outfile
existe, se sobrescriben los datos.outfile
existe, la salida que produce el programa
se agrega al final del archivo outfile
.Puede darse la necesidad de procesar datos con un programa y luego usar otro programa para procesar los datos que generó el primero.
Una posibilidad es escribir los datos del primer programa a un archivo y luego usar ese archivo como datos de entrada para el segundo programa:
prog1 <input1 >output1 prog2 <output1 >output2
Pero Linux ofrece una alternativa mucho más interesante y flexible, que implica conectar la salida estándar (stdout) de un programa con la entrada estándar (stdin) de otro, mediante el uso de un pipe: |
prog1 <input1 | prog2 >output2
De hecho se pueden encadenar varios programas usando pipes:
prog1 | prog2 | prog3
Los pipes ofrecen mucha versatilidad, pues permiten encadenar programas sencillos, que hacen una transformación concreta a los datos que reciben y le pasan el resultado de esa transformación a otro programa sencillo que hace otra transformación.
Este mecanismo encierra una idea muy poderosa, pues permite concentrar el esfuerzo en escribir programas sencillos que, encadenados, pueden hacer procesamiento de datos mucho más sofisticados que lo que cada programa puede hacer por separado.
El mecanismo de pipes es muy usado en la programación de scripts, justamente porque es simple y versátil, permitiendo implementar soluciones no previstas.
¿Cómo se corre un programa en Linux desde el shell?
Así:
nice -15 nohup time programa <inputfile >outputfile &
Desde un shell se pueden ejecutar varios comandos seguidos, simplemente separando cada comando del siguiente con un punto y coma:
prog1 ; prog2; prog3
Los programas también se pueden ejecuta de manera condicional:
prog1 && prog2
prog2
se ejecuta si prog1
termina exitosamente.
¿Cómo sabemos si un programa terminó exitosamente?
Cuando un programa termina, devuelve un exit code. Usualmente un programa que termina bien, devuelve un exit code igual a cero. Si el programa termina con algún error, el exit code será mayor que cero.
Hay que tener en cuenta que en informática un valor igual a cero suele asociarse al valor lógico TRUE
, mientras que un valor distinto de cero es considerado FALSE
.
El operador &&
actúa como el AND
lógico. Por eso si el primero programa devuelve un código de error distinto de cero, se asume que equivale a un FALSE
y sabemos que FALSE AND
cualquier otro valor siempre da FALSE
y por lo tanto el segundo programa no ejecuta.
El otro operador condicional es el ||
, que equivale a un OR
:
prog1 || prog2
En este caso prog2
se ejecuta si y sólo si prog1
devuelve un exit code distinto de cero. Así que es un mecanismo que nos permite tomar alguna acción en caso que el primer programa falla.
Vamos a ver más de este tipo de herramientas cuando veamos en más detalle programación shell