====== Ejecución y control de programas desde el shell ====== 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** (**R**ead, **E**xecute, **P**rint, **L**oop). Esto es: el shell espera que ingresemos una orden o comando en la linea de comando, cuando presionamos la tecla '''', 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: * **bash**: Bourne again shell * **sh**: Bourne shell * **ksh**: Korn shell * **csh**: C shell * etc. 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. ===== Modalidad de ejecución de programas ===== 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 '''' programa donde '''' 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: * foreground (primer plano): empleado principalmente para aplicaciones interactivas o ejecución de comandos de linea y/o programas cortos * background (segundo plano): empleado para correr programas no interactivos, que pueden estar un buen rato corriendo 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 ''''. Por ejemplo: programa & 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. ===== Control de los procesos: señales ===== 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. ==== Señales en modalidad foreground ==== 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: * interrumpir: * suspender: donde 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 foreground * ''bg'': 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. ==== Señales en modalidad background ==== Cuando tenemos un proceso corriendo en background, no es posible usar las combinaciones de teclado '''' o '''' 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. === Señales === 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 donde '''' 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 kill -9 kill -2 **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 (el programa suspende la ejecución si estaba activo) kill -19 (el programa retoma la ejecución si estaba suspendido) kill -15 (el programa termina de manera ordenada) kill -9 (el programa termina abruptamente) == NOHUP == 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''. == NICE == 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 :-) == TIME == 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). ===== Control de IO (entrada/salida) ===== 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**. ==== Redireccionamiento ==== 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 outputfile o programa >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: * **>**: modo sobrescritura. Si ''outfile'' existe, se sobrescriben los datos. * **>>**: modo "append". Si ''outfile'' existe, la salida que produce el ''programa'' se agrega al final del archivo ''outfile''. ==== Pipes ==== 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 output1 prog2 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 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. ===== Resumiendo ===== ¿Cómo se corre un programa en Linux desde el shell? Así: nice -15 nohup time programa outputfile & ===== La Yapa ===== 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**