educación, informática y demás

Shell scripts

Datos en ficheros

Muchas veces tendremos que obtener mucha más información que la que podemos leer desde parámetros o a través de peticiones al usuario por teclado. En estos casos leeremos la información desde un fichero.

También nos encontraremos casos en los que la información de salida para el usuario se la daremos a través de un fichero, puesto que es mucha información la que tenemos que mostrar o porque no la vamos a utilizar en el momento en el que se esta información se obtiene o se crea.

Cómo leer datos de un fichero

La forma más sencilla de leer datos de un fichero es recorrerlo con un for. Para ello tendremos que dejar en la lista de elementos a recorrer el contenido del fichero.

¿Cómo metemos el contenido del fichero ahí? Podemos utilizar $(CMD) (si no recuerdas cómo usar esta herramienta puedes consultar la entrada Obteniendo valores de la salida de un comando.

Ahora solo nos falta recordar qué comando usamos para mostrar el contenido de un fichero en salida estándar que no sea interactivo.

for variable in $(cat rutaFichero); do CMD; done

Veamos esto con un ejemplo.

Script de ejemplo. Se podría refinar bastante añadiendo comprobaciones, como comprobación de parámetros, de ejecución con root, existencia de ficheros, etc.

En este ejemplo tenemos un script que recibe por parámetro el nombre de un usuario y una lista variable de nombres de ficheros. ¿Qué sucede si la lista de nombres de ficheros es excesivamente larga?. Por ejemplo, supongamos que le tenemos que pasar 40 nombres de ficheros.

Además, estos datos se los pasamos de forma periódica cada cierto tiempo. Los tenemos que leer, como usuarios, desde un fichero y escribirlos en la terminal.

Esta forma de utilizar el script no es la más adecuada.

Vamos a cambiar el script de forma que reciba dos parámetros, el nombre de un usuario y la ruta de un fichero que contiene la lista de nombres de ficheros con las que trabajará el script. En cada línea de este fichero, habrá un nombre de fichero.

Contenido de un fichero de datos. Los números de línea no son datos que estén en el contenido del fichero, los está mostrando el editor de texto mousepad.

Lo primero que deberiamos hacer es obtener la ruta del fichero que contiene los nombres de ficheros con los que vamos a trabajar en el script.

Quitamos el comando shift, puesto que ya no vamos a recorrer los parámetros, y quitamos el $@ del conjunto de datos a recorrer, por el mismo motivo.

Ahí tenemos que poner el contenido del fichero. Para ello usamos $(…) con un comando que muestre el contenido del fichero en salida estádar

Al ejecutarse $(cat $dataFile) bash va a sustituir esa cadena de texto por la salida del comando que está entre paréntesis. La salida del comando cat $dataFile será el contenido del fichero $dataFile.

Podemos cambiar un poco el script para comprobar que recorre el fichero. Para ello, simplemente podemos añadir un echo delante de cada comando a ejecutar.

De esta forma se mostrará en pantalla el comando que se ejecutaría si no le hubieramos puesto un echo delante y rodeado por comillas.

Refinando el script: Control de errores

Estaría bien controlar ciertos errores que se pueden dar en tiempo de ejecución. Como ya sabemos los más habituales son: la falta de parámetros, que el usuario no exista, que el directorio backup no exista, el fichero de datos no exista, que no tengamos permisos para cambiar propietario, que el fichero dentro del directorio /backup no exista, etc.

Varios datos en cada línea

En el ejemplo de obtención de datos de un fichero en cada línea del fichero tenemos un solo dato, el nombre de un fichero. Sin embargo, habrá veces en las que tengamos que leer varios datos de cada línea del fichero. De esta forma, cada línea del fichero será como un registro que contiene varios campos y tendremos que leer registro a registro, utilizando los campos de cada registro.

En estos casos tendremos que leer la línea completa en cada vuelta o iteración del bucle for y después obtener cada campo de información que queramos utilizar del registro.

Veamos un ejemplo simple con el fichero de cuentas de usuario y después realizaremos un par de ejemplos con información proporcionada en ficheros.

Ejemplo 01 – Leyendo el fichero de cuentas de usuario

Paso 1 – mostrando cada registro de datos

Vamos a mostrar información en pantalla para cada cuenta de usuario del sistema.

Consideramos como cuenta de usuario del sistema aquella que utilice /bin/bash como interprete de comandos.

Solución

Vamos a usar un for para recorrer el fichero.

Filtramos, no queremos todas las cuentas de usuario del fichero /etc/passwd, tan solo aquellas que utilicen /bin/bash

Ahora que sabemos mostrar en pantalla la información del fichero que queremos recorrer lo único que tenemos que hacer es usarla en el bucle for.

Vamos a ver si funciona

Con esto nos damos cuenta de que somos capaces de recorrer los registros del fichero que nos interesan. Ahora nos queda obtener la información de los campos que necesitemos de cada registro.

Paso 2 – Obteniendo información de cada registro de datos

Ahora, vamos a mostrar tan solo el nombre de la cuenta de usuario, la ruta de su directorio personal y la información de permisos de su directorio personal. Esta información la vamos a formatear de forma que sea fácil de entender para el usuario.

Solución

Si nos fijamos en cada línea tenemos un registro, es decir información relacionada entre si sobre una entidad u objeto. En nuestro ejemplo el registro muestra información de una cuenta de usuario.

Como ya estudiamos en su día, el fichero /etc/passwd tiene siete campos separados por el delimitador «:». También conocemos un comando muy útil para obtener información de una cadena de texto, el comando cut.

Según el enunciado del paso 02 tan solo necesitamos dos datos de cada registro: nombre de usuario y ruta del fichero personal.

Vamos primero a obtener estos datos y mostrarlos en pantalla, para comprobar que lo hemos hecho bien y después trabajamos con ellos.

Con esto, si funciona, sabemos que tenemos los datos que nos interesan, nombre de usuario y ruta del directorio personal, de cada registro.

Ahora, tenemos que usar los campos del registro para llevar a cabo la función que nos piden: Mostrar el nombre del usuario, la ruta de su directorio personal y la información extendida de permisos, propietario y grupo de su directorio personal.

Ejemplo 02 – Leyendo información de un fichero

En este caso vamos a obtener una lista de información cursos y el gestor que lo dirige. Cada curso tendrá un único usuario que será su gestor. Un curso solo puede ser gestionado por un usuario, pero un mismo usuario puede gestionar varios cursos.

Cada curso tendrá asignado un directorio con el nombre del curso dentro del directorio /cursos. el nombre del directorio tendrá el mismo nombre que el curso. De esta forma, para el curso sistemas, el directorio del curso será /cursos/sistema.

Si no existe el directorio del curso, lo crearemos.

El directorio de un curso pertenecerá al usuario que lo gestiona, de forma que tendremos que cambiar el propietario del directorio del curso y todo su contenido al usuario gestor.

Además, vamos a cambiar también el grupo del curso. El grupo de todos los curso será educatica. Suponemos que el grupo educatica existe en el fichero (créalo antes si es necesario).

La información que nos da el gestor de la empresa es la siguiente:

En cada línea del fichero tan solo se almacena la información de un curso. Primero se proporcionará el nombre del curso y después el nombre del usuario que lo gestionará.

Paso 1 – Crea el fichero de datos

Deberás crear un fichero de datos con la información que se proporciona en la tabla.

Solución

En cada línea almacenaremos un registro con información sobre un curso y su gestor, es decir el usuario que gestionará el curso.

Sabemos como recorrer un fichero leyendo línea a línea con un bucle for. Ahora nos quedaría procesar la información del registro, siendo capaces de separar los campos de información que componen cada registro. Para ello utilizamos el comando cut. Como ahora somos nosotros quienes decidimos el carácter separador que vamos a usar puesto que crearemos el fichero, podemos elegir el carácter separador que más nos convenga.

Hasta ahora hemos recorrido, sobre todo, el fichero /etc/passwd que utiliza como carácter delimitador :. Pues, vamos a utilizar en este fichero el mismo carácter delimitador. Lo podemos crear con cualquier editor de texto, por ejemplo nano. Elegimos como nombre del fichero informacionCursos.txt.

Con esto, el fichero de información nos quedaría de la siguiente forma:

Salimos del editor de texto guardando los cambios y vamos a mostrar el contenido del fichero para comprobar que todo está como esperamos

Paso 2 – Crea el script que lea los datos del fichero. Recorre cada registro

Crea un script llamado configuraCursos.sh que se encargue de crear los directorios de los cursos y asignar los permisos para el gestor y el grupo educatica como se mencionaba en el enunciado. El script recibirá como único parámetro la ruta del fichero que contiene la información de los cursos.

Puede realizar el control de errores básico.

Lo importante en este paso es que el script sea capaz de recorrer el fichero de datos mostrando en pantalla cada registro.

Solución

Vamos a asegurarnos primero que el grupo educatica existe. Comprobamos si el grupo está en el fichero de grupos

Como no está creado, lo vamos a crear en un momento con el comando addgroup. Como superusuario o con un usuario con permisos de sudo, añadimos el grupo al sistema.

Comprobamos si el grupo existe

El siguiente paso será crear un script que lea uno a uno los registros del fichero.

Paso 3 – Utiliza los datos de cada registro

Somos capaces de mostrar en pantalla cada registro, es decir recorremos cada registro del fichero correctamente. Llega el momento de obtener la información concreta de cada registro, cada campo, y usarla con los comandos adecuados para que el script realice su función.

Solución

Comencemos por dividir los campos de cada registro. Somos capaces de mostrar cada registro en pantalla.

Si ejecutamos el código

Ahora, con la ayuda del comando / filtro cut vamos a cortar el registro en los campos que lo componen, asignando cada campo a una variable.

Si nos fijamos la salida es identica, solo que ya tenemos cada campo en una variable.

Ahora solo tenemos que utilizar las variables que contienen los campos para realizar la tarea que nos piden. De hecho, podríamos sacar del bucle la funcionalidad y meterla en una función. Vamos a empezar metiendo la funcionalidad en el cuerpo del bucle.

Paso 4 – Control de errores

Personalmente, me gusta realizar el control de errores a medida que voy desarrollando el script, desde el principio. No obstante en esta serie de ejemplos lo he dejado para el final para poder centrarnos en la funcionalidad del script de forma que se mejore la legibilidad del mismo.

En nuestro caso deberíamos controlar:

  • Que el script se ejecute como root
  • Que se pase la ruta de un fichero por parámetro
  • Que el fichero exista
  • Que el usuario del registro exista en el sistema

Vamos a añadir estos controles de errores. En todos los casos, si no se cumple la condición no podríamos continuar con la ejecución del script.

Quizá el último requisito nos permitiría seguir ejecutando el script, pero con el siguiente registro, sin realizar ninguna acción. Vamos a dejar esto para una revisión futura.

Vamos a probar si esto funciona y cómo 🙂

En el caso de que un usuario no exista, podemos crearlo o utilizar el comando continue para seguir con la siguiente interacción del bucle en ese punto.

Es decir, al ejecutar un comando continúe en un bucle, el interprete de comandos dejará de ejecutar en ese punto el cuerpo del bucle, comenzando con la siguiente vuelta o iteración del bucle.

Nos avisa de que no existen los usuarios, pero sigue ejecutando el bucle.

Ejemplo 03 – Varios cursos por gestor

En este caso, nos proporcionan un fichero creado con LibreOffice Calc en el que cada fila contiene un registro. Cada registro contiene un usuario y una lista de cursos de la que es gestor. Esta lista contiene uno o varios cursos.

Aquí tienes el fichero en CSV para que puedas trabajar con él en tu script

Ejemplo de recorrido

A continuación tienes un ejemplo que recorre de forma similar a la que se pide en el ejercicio el fichero de cuentas de usuario, mostrando información para las cuentas de usuario que utilizan una shell /bin/bash

Paso 1 – Descargar el fichero y recorrerlo registro a registro

Como siempre, el primer paso para realizar la tarea que nos piden con los datos de un fichero, será ser capaces de recorrerlo.

Ojo! realmente, siempre que nos enfrentemos a un script deberíamos tener en cuenta el control de errores. No obstante, con fines didácticos, nos vamos a centrar en la funcionalidad del script.

Solución

Vamos a probarlo, pero antes descargamos el fichero.

Lo podríamos haber descargado desde la línea de comandos con wget. Lo dejamos para una próxima aventura por la terminal 😛

Vamos a copiarlo al directorio personal del usuario

Ahora si, vamos a comprobar si nuestro script recorre el fichero bien

Paso 2 – Separar el nombre del usuario de la lista de cursos para cada registro

Nos centramos ahora en cómo podemos recorrer la lista de cursos. Para ello podríamos utilizar un bucle for. No obstante, el separador que utilizar for en la lista de palabras del in es espacio o salto de línea. Lo que vamos a hacer es sustituir las comas en la lista de cursos por espacios y después recorremos esa lista de cursos.

Esto sucede porque, aunque hemos usado tr -s «,» » » y tan solo ha quedado una coma al final en las líneas en las que hubiera varias comas, el script ha leido un curso vacío, porque hemos sustituido la coma por un espacio. De esta forma, el bucle for ha leido algo, posiblemente un espacio.

Paso 3 – Recorrer los cursos de la lista de cursos

Dejar una respuesta