Tema 105: Shells y scripts

Objetivos del tema 105

  • 105.1 Personalizar y usar el entorno de shell

  • 105.2 Personalización y escritura de scripts sencillos

105.1 Personalizar y usar el entorno de shell

Importancia

4

Descripción

El candidato debe ser capaz de personalizar el entorno de shell para adaptarlo a las necesidades de los usuarios así como de modificar perfiles globales y de usuario.

Áreas de conocimiento clave:

  • Establecer variables de entorno (e.g. PATH) al inicio de sesión o al generar un nuevo shell.

  • Escribir funciones en Bash para secuencias de comandos usadas con frecuencia.

  • Mantener el esqueleto de directorios para nuevas cuentas de usuario.

  • Establecer el directorio adecuado en la ruta de búsqueda de comandos.

Contenidos

El shell es posiblemente la herramienta más poderosa en un sistema operativo Linux y puede definirse como una interfaz entre usuario y kernel, tiene la función de Interpretar los comandos introducidos por el usuario, por lo tanto, los administradores de sistemas deben ser hábiles en su uso.

El Bourne Again Shell (Bash) es el shell de facto de la gran mayoría de las distribuciones de Linux. En el momento que el sistema operativo inicia, lo primero que el Bash (o cualquier otro shell) realiza, es ejecutar una serie de scripts de inicio que personalizan el entorno de sesión.

Existen varios scripts para todo el sistema operativo, así como también para usuarios específicos, en estos scripts podemos elegir las preferencias o configuraciones que mejor se adapten a las necesidades de nuestros usuarios en forma de variables, alias y funciones y la serie exacta de estos depende de un parámetro muy importante: El tipo de shell.

Iniciando una terminal

En un entorno de escritorio, podemos abrir una terminal o cambiar a una de las consolas del sistema fácilmente. Aquí tenemos que un nuevo shell es un pts cuando se abre desde un emulador de terminal en el GUI o una tty cuando se ejecuta desde una consola de sistema.

tty significa "teletypewritter"; pts significa "pseudo terminal slave". Para más información: man tty y man pts.

Como parte de las sesiones gráficas, los emuladores de terminales como gnome-terminal o konsole son muy amplios en características y fáciles de usar en comparación con las terminales de interfaz de usuario basadas en texto. A continuación, tienes algunos otros para hacerte una idea:

Fuente: @dan_nanni

Usando las teclas Ctrl+Alt+F1-F6 podemos ir a los inicios de sesión de la consola que abren un shell de inicio de sesión interactivo basado en texto y la combinación de Ctrl+Alt+F7 llevará la sesión de vuelta al escritorio.

Después de iniciar sesión, escribe bash en una terminal para abrir un nuevo shell (técnicamente, este shell es un proceso hijo del shell actual). Al iniciar el proceso hijo de bash, podemos especificar varias opciones para definir qué tipo de shell queremos iniciar. Aquí hay algunas importantes a la hora invocarlo:

  • bash -l o bash --login -> Invocará un shell de inicio de sesión.

  • bash -i -> Invocará un shell interactivo.

  • bash --noprofile -> Con shell de inicio de sesión ignorará tanto el archivo de inicio de todo el sistema /etc/profile como los archivos de inicio a nivel de usuario ~/.bash_profile, ~/.bash_login y ~/.profile.

  • bash --norc -> Con shell interactivo ignorará tanto el archivo de inicio del sistema /etc/bash.bashrc como el archivo de inicio a nivel de usuario ~/.bashrc.

  • bash --rcfile -> Con shell interactivo tomará como el archivo de inicio ignorando a nivel de sistema etc/bash.bashrc y a nivel de usuario ~/.bashrc.

Ejecutando shells con su y sudo

A través del uso de estos dos programas (similares) podemos obtener tipos específicos de shells:

su cambia el ID de usuario o lo convierte en superusuario (root). Con este comando podemos invocar ambos shells, el de con inicio de sesión y sin inicio de sesión:

  • su - user2, su -l user2 o su --login user2 iniciará un shell de inicio de sesión interactivo como user2.

  • su user2 iniciará un shell interactivo y sin inicio de sesión como user2.

  • su - root o su - iniciará un shell de inicio de sesión interactivo como root.

  • su root o su iniciará un shell interactivo y sin inicio de sesión como root.

sudo por su lado, ejecuta comandos como otro usuario (incluyendo tambien el superusuario). Debido a que este comando se usa principalmente para obtener privilegios temporales de root, el usuario que lo use debe estar en el archivo ~/sudoers (aunque por cuestiones de seguridad no es recomendable). Para añadir usuarios a ~/sudoers necesitamos convertirnos en root y luego ejecutar:

Así como su, sudo nos permite invocar tanto los shells de inicio de sesión como los de no de inicio de sesión:

  • sudo su - user2, sudo su -l user2 o sudo su --login user2 iniciará un shell de inicio de sesión interactivo como user2.

  • sudo su user2 o sudo -u user2 -s iniciará un shell interactivo sin inicio de sesión como user2.

  • sudo su - root or sudo su - iniciará un shell de inicio de sesión interactivo como root.

  • sudo -i iniciará un shell de inicio de sesión interactivo como root.

  • sudo -i <some_command> iniciará un shell de inicio de sesión interactivo como root, ejecuta el comando y volverá al usuario original.

  • sudo su root or sudo su iniciará un shell interactivo sin inicio de sesión como root.

  • sudo -s or sudo -u root -s iniciará un shell sin inicio de sesión como root.

Para saber en qué tipo de shell estamos trabajando, podemos escribir:

También, para observar cuántos bash shells tenemos en ejecución en el sistema, podemos usar el comando:

  • Esto significa que el user2 ha entrado en una sesión de GUI (Sistema de Ventanas X o en ingles: X Window System) y ha abierto gnome-terminal, luego ha pulsado Ctrl+Alt+F1 para entrar en una sesión terminal con inicio de sesión interactivo detty (Mira como el último campo de cada línea es bash para el primero y -bash para el segundo). Finalmente, ha vuelto a la sesión del GUI presionando Ctrl+Alt+F7 y ha escrito el comando ps aux | grep bash. De esta manera, la salida muestra un shell interactivo sin inicio de sesión a través del emulador de terminal (pts/0) y un shell de inicio de sesión interactivo a través de la propia terminal basada en texto (tty1).

Shell interactivo de inicio de sesión

Primero de todo, para este shell, mencionar que los scripts de todo el sistema o globales se colocan en el directorio /etc/, mientras que los locales o de nivel de usuario (local) se encuentran en el directorio /home del usuario (~):

Nivel Global

  • /etc/profile -> el archivo .profile usado para todos los shell Bourne y shells compatibles (Incluido bash). A través de una serie de declaraciones if este archivo establece un número de variables como $PATH y $PS1, así como origen tanto del archivo /etc/bash.bashrc como los del directorio /etc/profile.d

  • /etc/profile.d/* -> Directorio que puede contener scripts que son ejecutados por /etc/profile.

Nivel Local (~)

  • ~/.bash_profile -> archivo específico de Bash utilizado para configurar el entorno del usuario. También puede ser usado para crear el ~/.bash_login y ~/.profile

  • ~/.bash_login -> este archivo (específicamente), sólo se ejecutará si no hay un archivo ~/.bash_profile.

  • ~/.profile -> Este archivo no es específico de Bash y se obtiene sólo si no existen ni ~/.bash_profile ni ~/.bash_login, que es lo que normalmente ocurre. Por lo tanto, el propósito principal de este es el de revisar si se está ejecutando un shell de Bash, y si fuese afirmativo, obtener ~/.bashrc (si existe). Normalmente establece la variable $PATH para que incluya el directorio privado del usuario ~/bin (si existe).

  • ~/.bash_logout -> Si existe, este archivo específico de Bash hace algunas operaciones de limpieza al salir del shell, lo cual puede ser conveniente en casos como los de las sesiones remotas.

Para probar a mostrar algunos de estos archivos en acción, vamos a modificar /etc/profile y /home/user2/.profile. Añadiremos a cada uno una línea que nos recuerde el archivo que se está ejecutando:

Para probarlo, veamos qué pasa cuando user2 se conecta vía ssh desde otra máquina:

Como se observan en las dos últimas líneas, funcionó y nótese tres cosas:

  • El archivo global se ejecutó primero.

  • No había archivos .bash_profile o .bash_login en el directorio "home" de user2.

  • La tilde (~) se expandió a la ruta absoluta del archivo (/home/user2/.profile).

Shell Interactivo sin inicio de sesión

Nivel Global

  • /etc/bash.bashrc -> Este es el archivo .bashrc de todo el sistema para los shells interactivos bash. A través de su ejecución, bash se asegura de que se está ejecutando interactivamente, comprueba el tamaño de la ventana después de cada comando (actualizando los valores de LÍNEAS y COLUMNAS, si es necesario) y establece algunas variables.

Nivel Local

  • ~/.bashrc -> Además de llevar a cabo tareas similares a las descritas para /etc/bash.bashrc a nivel de usuario (como comprobar el tamaño de la ventana o si se está ejecutando de forma interactiva), este archivo específico de Bash suele establecer algunas variables de historial y origen ~/.bash_aliases (si existe). Aparte de eso, este archivo se utiliza normalmente para almacenar alias y funciones específicas de los usuarios. Asimismo, también vale la pena señalar que ~/.bashrc se lee si bash detecta que su es una conexión de red (como ssh).

Modifiquemos ahora /etc/bash.bashrc y /home/user2/.bashrc:

Shell no Interactivo de inicio de sesión

Un shell no interactivo con las opciones -l o --login es forzado a comportarse como un shell de inicio de sesión y así los archivos de inicio a ser ejecutados serán los mismos que los de los shells de inicio de sesión interactivos.

Para probarlo, escribamos un simple script y hagámoslo ejecutable. No incluiremos ningún shebangs porque invocaremos el ejecutable bash (/bin/bash con la opción de inicio de sesión) desde la línea de comandos:

  1. Creamos el script test.sh para probar que el script se ejecuta con éxito: echo "echo 'Hello from a script'" > test.sh

  2. Hacemos que nuestro script sea ejecutable: chmod +x ./test.sh

  3. Finalmente, invocamos a bash con la opción l para ejecutar el script: bash -l ./test.sh

  4. Esto da como resultado:

¡Funciona! Antes de ejecutar el script, el login tuvo lugar y tanto el /etc/profile como el ~/.profile fueron ejecutados. Lo mismo ocurriria con una sesión de ssh.

Archivos fuentes

En las secciones anteriores hemos discutido que algunos scripts de inicio incluyen o ejecutan otros scripts, a este mecanismo se le llama "sourcing" y se explica en esta sección.

El punto (.) se encuentra normalmente en los archivos de inicio, en el archivo .profile de nuestro servidor de Debian podemos encontrar un ejemplo en el siguiente bloque:

Hemos observado cómo la ejecución de un script puede llevar a la de otro. Así la declaración if garantiza que el archivo $HOME/.bashrc — si existe (-f) — se obtendrá (es decir, se leerá y se ejecutará) en el inicio de sesión.

El tema de alias y variables lo veremos más a fondo en el siguiente topic 105.2

Además, podemos usar el . siempre que hayamos modificado un archivo de inicio y queramos hacer efectivos los cambios sin necesidad de reiniciar. Por ejemplo, vamos a agregar un alias a /.bashrc:

El comando source es un sinónimo de (.) así que para ejecutar /.bashrc también podemos hacerlo de esta manera:

SKEL

SKEL es por definición, el origen de los archivos de inicio de Shell, una variable cuyo valor es la ruta absoluta al directorio skel el cual, sirve como plantilla para la estructura del sistema de archivos de los principales directorios de los usuarios. Incluye los archivos que serán heredados por cualquier nueva cuenta de usuario que se cree (incluyendo, por supuesto, los archivos de configuración de los shells). El SKEL y otras variables relacionadas se almacenan en el /etc/adduser.conf, que es el archivo de configuración para adduser:

Una buena práctica es crear un directorio en /etc/skel para que todos los nuevos usuarios almacenen sus scripts personales:

Variables

Las variables son como una caja imaginaria en la que coloca temporalmente una información y al igual que los scripts de inicialización, Bash clasifica las variables como shell/local (las que se ubican sólo dentro de los límites del shell en el que fueron creadas) o entorno/global (las que son heredadas por shells y/o procesos hijos).

En Bash, dar un valor a un nombre se llama asignación de variables y es la forma en que creamos o establecemos las variables. Por otro lado, el proceso de acceder al valor contenido en el nombre se llama variable referenciada.

La sintaxis para la asignación de variables es: <variable_name>=<variable_value> . Ten en cuenta de no dejar espacios a ninguno de los lados, por ejemplo:

Para los valores también hay una serie de normas a seguir, estas pueden contener cualquier carácter alfanumérico (a-z,A-Z,0-9) así como la mayoría de los caracteres (?,!,*,.,/, etc.) pero si deseas poner un espacio, deberá ir entre comillas (" " o ' ' ) lo mismo ocurre si queremos poner símbolos como los utilizados para la redirección (<,>) o el símbolo de "pipe" (|).

Sin embargo, las comillas simples (' ') y dobles (" ") no siempre son intercambiables ya que según lo que hagamos con una variable (asignación o referencia), el uso de una u otra tiene implicaciones y dará resultados diferentes.

  • Las comillas simples toman todos los caracteres del valor de la variable LITERALMENTE.

  • Las comillas dobles permiten la sustitución de la variable por su valor.

Esto se ve muy bien con el siguiente ejemplo:

Variables locales o de Shell

Las variables locales o de shell existen sólo en el shell en el que se crean (De hecho, por convención, las estas las escribiremos en minúsculas). Es el caso de las variables que hemos visto en la sección anterior. Estas — siendo una variable local — no serán heredadas por ningún proceso hijo generado desde el shell actual.

En ciertos scripts la inmutabilidad puede ser una característica interesante de las variables. Para ello, podemos crearlas en modo sólo lectura (readonly):

Un comando útil cuando se trata de variables locales es set. Este da salida a todas las variables y funciones de shell que se encuentran actualmente asignadas. Dado que pueden ser muchas líneas (¡pruébalo tú mismo!), se recomienda usarlo en combinación con un buscador como set | less

De la misma forma, para eliminar cualquier variable (ya sea local o global), usamos el comando unset seguido del nombre de la variable sin el $

Variables globales o de entorno

Existen variables globales o de entorno para el shell actual, así como para todos los procesos subsecuentes que se derivan de este (Por convención, las variables de entorno se escriben en mayúsculas) es el caso de $SHELL o de $PATH.

Para que una variable de shell local se convierta en una variable de entorno, se debe utilizar el comando export <nombre> (o tambien declare -x), de esta formas convertimos nuestra variable local en una variable de entorno para que los shells hijos puedan reconocerla y usarla. Para volverla local otra vez simplemente usamos export -n <nombre> .

El comando export por si mismo mostrará un listado de las variables globales aunque también podemos listarlo con export -p, env y printenv.

Es hora de revisar algunas de las variables de entorno más relevantes que se establecen en los archivos de configuración de Bash:

  • DISPLAY -> En relación con el servidor X. Un valor vacío (:0) para esta variable significa un servidor sin un sistema X WIndow, mientras que un número extra (como en mi.xserver:0:1) se referiría al número de pantalla (si existe más de uno).

  • HISTFILE -> El nombre del archivo que almacena todos los comandos a medida que se escriben. Por defecto este archivo se encuentra en ~/.bash_history y podemos verlo con el comando history.

  • HISTCONTROL -> Esta variable controla qué comandos se guardan en HISTFILE. Hay tres valores posibles:

    • ignorespace Los comandos que empiecen con un espacio no se guardarán.

    • ignoredups Un comando que es el mismo que el anterior no se guardará.

    • ignoreboth Los comandos que caen en cualquiera de las dos categorías anteriores no se guardarán.

  • HISTSIZE -> Establece el número de comandos que se almacenarán en la memoria mientras dure la sesión de shell.

  • HISTFILESIZE -> Establece el número de comandos que se guardarán en HISTFILE tanto al principio como al final de la sesión.

  • HOME -> La variable almacena la ruta absoluta del directorio principal del usuario y se establece cuando el usuario se conecta. Recuerda que ~ es equivalente a $HOME

  • HOSTNAME -> Variable que almacena nombre del computador en la red.

  • HOSTTYPE -> Almacena la arquitectura de la unidad central de procesamiento (CPU) del computador.

  • LANG -> Esta variable guarda la información de localización que utiliza el sistema.

  • LD_LIBRARY_PATH -> Esta variable consiste en un conjunto de directorios separados por dos puntos donde las bibliotecas compartidas (shared libraries) son compartidas por los programas, se habló más de esto en el topic 102.3.

  • MAIL -> Esta variable almacena el archivo en el que Bash revisa el correo electrónico.

  • MAILCHECK -> Esta variable almacena un valor numérico que indica en segundos la frecuencia con la que Bash comprueba si hay correo nuevo.

  • PATH -> Esta almacena la lista de directorios donde Bash busca los archivos ejecutables cuando se le indica que ejecute cualquier programa, en la mayoría de casos, esta variable se establece a través del archivo /etc/profile de todo el sistema. Si quisiéramos incluir la carpeta /usr/local/sbin en el $PATH para usuarios habituales, modificaremos la línea de ese archivo:

También podríamos hacerlo desde la linea de comandos así:

  • PS1 -> Esta variable almacena el valor del indicador Bash, también de /etc/profile, la sentencia if comprueba la identidad del usuario y en consecuencia brinda un indicador muy discreto ( # para root o $ para usuarios regulares):

RECUERDA: El id de root es 0. Conviértete en root y puedes comprobarlo tú mismo con id -u.

  • SHELL -> Almacena la ruta absoluta del shell actual.

  • USER -> Almacena el nombre del usuario actual.

Ejecución de programa en entorno modificado

El comando env también puede ser usado para modificar el entorno del shell en el momento de la ejecución de un programa, si quisieramos abrir un shell lo más vacio posible de variables escribiriamos:

En este mismo topic, mas arriba, cuando hablamos de los shells no interactivos sin inicio de sesión, dijimos como los scripts no leen ningún archivo de inicio estándar sino que buscan el valor de la variable $BASH_ENV y lo usan como su archivo de inicio si existe, vamos a comprobarlo:

  • Recuerda que usamos ./test_env.sh para ejecutar el script desde su propio directorio.

Creando Alias

Un alias es un nombre sustituto de otro(s) comando(s)y puede ejecutarse como un comando normal, pero en su lugar ejecuta otro comando según la definición de alias. Su sintaxis es muy sencilla:

Entre algunas de las cosas que se pueden hacer incluye asignar y referenciar variables dentro de la declaración del alias, generar alias con nombres de comandos ya existentes (siempre tendrán prioridad los alias aunque puedes "escapar" de ellos escribiendo \ delante) o inlcuso poner un alias dentro de un alias.

Al igual que con las variables, para que nuestros alias ganen persistencia, debemos escribirlos en scripts de inicialización que se ejecuten al inicio como es ~/.bashrc. Probablemente encontrarás algunos alias allí (la mayoría de ellos comentados y listos para ser usados eliminando el # principal) e incluso como se puede leer en las últimas tres líneas, se nos ofrece la posibilidad de tener nuestro propio archivo dedicado a los alias ( ~/.bash_aliases ) y ser ejecutado por .bashrc con cada inicio del sistema así que podemos ir por esa opción, crear y editar dicho archivo para añadir los alias necesarios.

Creando funciones

En comparación con los alias, las funciones son más programables y flexibles, especialmente cuando se trata de explotar todo el potencial de las variables incorporadas y los parámetros posicionales de Bash. Se trata de comandos que incluye la lógica a través de bloques o colecciones de otros comandos.

Hay dos sintaxis válidas para definir las funciones:

Es común agregar funciones en archivos o scripts pero, también se puede escribir con cada comando en el shell prompt, en una línea diferente. Si decidimos saltarnos los ENTER y escribir una función en una sola línea, los comandos deben estar separados por punto y coma (también el último comando):

Ahora, para invocarla, tan solo debemos escribir su nombre en la terminal.

Bash trae un conjunto de variables especiales que son particularmente útiles para funciones y scripts, estas son especiales porque sólo pueden ser referenciadas — no asignadas:

  • $? -> Esta variable es usada para referenciar si el último comando usado ha dado error o no, un valor de 0 significa éxito y un valor distinto significara error.

  • $$ -> Muestra el PID del shell.

  • $! -> Muestra el PID del ultimo comando ejecutado de fondo (por defecto se ejecuta con & al final para que se hagan de fondo).

  • $# -> Muestra cuantos argumentos que se le pasan al comando.

  • $@, $* -> Se extienden a los argumentos pasados al comando.

  • $_ -> Se expande hasta el último parámetro o el nombre del script (¡revisa "man bash" para conocer más!).

  • $0 a $9 -> Parámetros posicionales, se usan para hacer una especie de matriz unitaria o como parámetros de orden y llamar a un listado siendo $0 siempre la del shell.

De esta forma se pueden pasar parámetros para usarlos dentro de la función:

Caso de ejemplo, script de información al usuario

Haciendo uso de la variable global PS1 vamos a crear una función llamada fyi (que se colocará en un script de inicio) que le dará al usuario la siguiente información:

  • Nombre del usuario

  • Directorio principal

  • Nombre del host

  • Tipo de sistema operativo

  • Buscar la ruta (PATH) de ejecutables

  • Directorio de correo

  • Con qué frecuencia se revisa el correo

  • ¿Cuántos shells tiene la sesión actual?

  • prompt (deberías modificarlo para que muestre @)

Para lograr ese propósito, hemos puesto la función en /home/user2/.bashrc

Como se ha dicho antes, podemos agregar una función dentro de un alias:

Vamos a desglosarlo:

  • Primero está la función en sí misma que, desglosada sería así:

  • El último comando de la función (unset -f gr8_ed) borra la función para que no permanezca en la sesión actual de bash después de que el alias sea llamado.

  • Por último, invocamos la propia función: gr8_ed.

Caso de ejemplo, saludo y advertencia a usuario

Vamos a probar otro ejemplo, pongámonos en que queremos comunicar dos cosas a user2 cada vez que se registre en el sistema que será, saludar y recomendar un editor de texto y advertirle sobre que la carpeta de videos esta añadiendo muchas cosas ($HOME/Video).

Para lograr ese propósito, hemos puesto las siguientes dos funciones en /home/user2/.bashrc.

La primera función (check_vids) hace el chequeo de los archivos .mkv y la advertencia:

Esto hace tres cosas:

  • Lista los archivos .mkv en ~/Video enviando la salida (y cualquier error 2>&1) al llamado bit-bucket (/dev/null).

  • Prueba la salida del comando anterior para el éxito (recuerda, si $?=0 es que ha tenido exito).

  • Dependiendo del resultado de la prueba, imprime uno de los dos mensajes.

Ahora vamos con la segunda función que es una versión modificada del ejemplo de más arriba:

Es importante observar dos cosas:

  • El último comando de editors invoca check_vids para que ambas funciones se encadenen: El saludo, el elogio, el chequeo y la advertencia se ejecutan en secuencia.

  • editors es el mismo punto de entrada a la secuencia de funciones, por lo que se invoca en la última línea (editors).

Ahora, entremos como user2 y probemos que funciona:


105.2 Personalización y escritura de scripts sencillos

Importancia

4

Descripción

El candidato debe ser capaz de personalizar scripts existentes o de escribir nuevos scripts sencillos en Bash.

Áreas de conocimiento clave:

  • Usar la sintaxis estándar sh (bucles, tests).

  • Usar la sustitución de comandos.

  • Evaluar correctamente el código de retorno de un comando en caso de éxito, fracaso o cualquier otra información que proporcione la salida del comando.

  • Ejecutar comandos en cadena.

  • Realizar envío de correo condicional al superusuario.

  • Seleccionar correctamente el intérprete del script mediante la línea inicial o shebang (#!).

  • Gestionar la ubicación, los propietarios, la ejecución y los permisos suid de los scripts.

Contenidos

Last updated