Cómo procesar un archivo línea por línea en un script Bash de Linux

Contenidos

Una ventana de terminal en un sistema informático Linux.

Es bastante sencillo leer el contenido de un archivo de texto de Linux línea por línea en un script de shell, siempre que se enfrente a algunos problemas sutiles. He aquí cómo hacerlo de forma segura.

Archivos, texto y modismos

Cada lenguaje de programación tiene un conjunto de modismos. Estas son las formas estándar y sencillas de realizar un conjunto de tareas comunes. Son la forma elemental o predeterminada de utilizar una de las características del lenguaje con el que está trabajando el programador. Se convierten en parte del conjunto de herramientas de un programador de planos mentales.

Acciones como leer datos de archivos, trabajar con bucles e intercambiar los valores de dos variables son buenos ejemplos. El programador conocerá al menos una forma de lograr sus fines de manera genérica o básica. Tal vez eso sea suficiente para el requisito que nos ocupa. O tal vez embellecerán el código para hacerlo más eficiente o aplicable a la respuesta específica que están desarrollando. Pero tener el idioma básico al alcance de la mano es un gran punto de partida.

Conocer y comprender modismos en un idioma además facilita la adquisición de un nuevo lenguaje de programación. Saber cómo se construyen las cosas en un idioma y buscar el semejante —o lo más parecido— en otro idioma es una buena manera de apreciar las semejanzas y diferencias entre los lenguajes de programación que ya conoce y el que está aprendiendo.

Leer líneas de un archivo: una línea

En Bash, puedes utilizar un while bucle en la línea de comando para leer cada línea de texto de un archivo y hacer algo con él. Nuestro archivo de texto se llama «data.txt». Contiene una lista de los meses del año.

January
February
March
.
.
October
November
December

Nuestro sencillo de una sola línea es:

while read line; do echo $line; done < data.txt

los while loop lee una línea del archivo, y el flujo de ejecución del pequeño programa pasa al cuerpo del loop. los echo comando escribe la línea de texto en la ventana de terminal. El intento de lectura falla cuando no hay más líneas para leer y el ciclo está terminado.

Un buen truco es la capacidad de redirigir un archivo a un bucle. En otros lenguajes de programación, necesitaría abrir el archivo, leerlo y cerrarlo nuevamente cuando haya terminado. Con Bash, simplemente puede utilizar la redirección de archivos y dejar que el shell maneje todas esas cosas de bajo nivel por usted.

Desde luego, esta frase no es muy útil. Linux ya proporciona cat comando, que hace exactamente eso por nosotros. Hemos creado una forma larga de reemplazar un comando de tres letras. Pero sí demuestra visiblemente los principios de la lectura de un archivo.

Eso funciona bastante bien, hasta cierto punto. Supongamos que tenemos otro archivo de texto que contiene los nombres de los meses. En este archivo, la secuencia de escape para un carácter de nueva línea se ha agregado a cada línea. Lo llamaremos «data2.txt».

Januaryn
Februaryn
Marchn
.
.
Octobern
Novembern
Decembern

Usemos nuestro one-liner en nuestro nuevo archivo.

while read line; do echo $line; done < data2.txt

El carácter de escape de barra invertida » ”Ha sido descartado. El resultado es que se ha agregado una «n» a cada línea. Bash está interpretando la barra invertida como el inicio de una secuencia de escape. A menudo, no queremos que Bash interprete lo que está leyendo. Puede ser más conveniente leer una línea en su totalidad (secuencias de escape con barra invertida y todo) y seleccionar qué analizar o reemplazar usted mismo, dentro de su propio código.

Si queremos hacer algún procesamiento o análisis significativo de las líneas de texto, necesitaremos utilizar un script.

Leer líneas de un archivo con un script

Aquí está nuestro guión. Se llama «script1.sh».

#!/bin/bash

Counter=0

while IFS='' read -r LinefromFile || [[ -n "${LinefromFile}" ]]; do

    ((Counter++))
    echo "Accessing line $Counter: ${LinefromFile}"

done < "$1"

Establecemos una variable llamada Counter a cero, entonces definimos nuestro while círculo.

La primera declaración en la línea while es IFS='' . IFS significa separador de campo interno. Contiene valores que Bash utiliza para identificar los límites de las palabras. De forma predeterminada, el comando de lectura elimina los espacios en blanco iniciales y finales. Si queremos leer las líneas del archivo exactamente como son, necesitamos configurar IFS ser una cadena vacía.

Podríamos determinar esto una vez fuera del ciclo, del mismo modo que establecemos el valor de Counter . Pero con scripts más complejos, especialmente aquellos con muchas funciones definidas por el usuario en ellos, es factible que IFS podría establecerse en diferentes valores en otra parte del script. Asegurando que IFS se establece en una cadena vacía cada vez que el while El bucle itera garantiza que sabemos cuál será su comportamiento.

Vamos a leer una línea de texto en una variable llamada LinefromFile . Estamos usando el -r (lea la barra invertida como un carácter normal) para ignorar las barras invertidas. Serán tratados como cualquier otro personaje y no recibirán ningún tratamiento especial.

Hay dos condiciones que satisfarán la while bucle y posibilita que el texto sea procesado por el cuerpo del bucle:

  • read -r LinefromFile : Cuando se lee correctamente una línea de texto del archivo, el read El comando envía una señal de éxito al while , y el while bucle pasa el flujo de ejecución al cuerpo del bucle. Tenga en cuenta que el read El comando necesita ver un carácter de nueva línea al final de la línea de texto para considerarlo una lectura exitosa. Si el archivo no es un POSIX archivo de texto compatible, el la última línea puede no incluir un carácter de nueva línea. Si el read comando ve el marcador de fin de archivo (EOF) antes de que la línea termine con una nueva línea, no trátelo como una lectura exitosa. Si eso sucede, la última línea de texto no se pasará al cuerpo del bucle y no se procesará.
  • [ -n "${LinefromFile}" ] : Necesitamos hacer un trabajo adicional para manejar archivos no compatibles con POSIX. Esta comparación verifica el texto que se lee del archivo. Si no se termina con un carácter de nueva línea, esta comparación aún devolverá el éxito al while círculo. Esto asegura que cualquier fragmento de línea final sea procesado por el cuerpo del bucle.

Estas dos cláusulas están separadas por el operador lógico OR » || «Para que si cualquiera La cláusula devuelve éxito, el texto recuperado es procesado por el cuerpo del bucle, ya sea que haya un carácter de nueva línea o no.

En el cuerpo de nuestro bucle, estamos incrementando el Counter variable por uno y usando echo para enviar alguna salida a la ventana del terminal. Se muestran el número de línea y el texto de cada línea.

Aún podemos utilizar nuestro truco de redirección para redirigir un archivo a un bucle. Para este caso, estamos redirigiendo $ 1, una variable que contiene el nombre del primer parámetro de línea de comando que pasó al script. Con este truco, podemos pasar fácilmente el nombre del archivo de datos en el que queremos que trabaje el script.

Copie y pegue el script en un editor y guárdelo con el nombre de archivo «script1.sh». Usar el chmod mando para hacerlo ejecutable.

chmod +x script1.sh

Veamos qué hace nuestro script con el archivo de texto data2.txt y las barras invertidas que contiene.

./script1.sh data2.txt

Cada carácter de la línea se muestra literalmente. Las barras invertidas no se interpretan como caracteres de escape. Están impresos como caracteres regulares.

Pasar la línea a una función

Aún estamos haciendo eco del texto en la pantalla. En un escenario de programación del mundo real, probablemente estaríamos a punto de hacer algo más interesante con la línea de texto. En la mayoría de los casos, es una buena práctica de programación manejar el procesamiento posterior de la línea en otra función.

Así es como podríamos hacerlo. Esto es «script2.sh».

#!/bin/bash

Counter=0

function process_line() {

    echo "Processing line $Counter: $1"

}

while IFS='' read -r LinefromFile || [[ -n "${LinefromFile}" ]]; do

    ((Counter++))
    process_line "$LinefromFile"

done < "$1"

Definimos nuestro Counter variable como antes, y posteriormente definimos una función llamada process_line() . Debe aparecer la definición de una función antes de la función se llama primero en el script.

A nuestra función se le pasará la línea de texto recién leída en cada iteración del while círculo. Podemos tener acceso a ese valor dentro de la función usando el $1 variable. Si hubieran pasado dos variables a la función, podríamos tener acceso a esos valores usando $1 y $2 y así sucesivamente para más variables.

La While bucle es principalmente el mismo. Solo hay un cambio dentro del cuerpo del bucle. los echo La línea ha sido reemplazada por una llamada al process_line() función. Tenga en cuenta que no necesita utilizar los corchetes “()” en el nombre de la función cuando la está llamando.

El nombre de la variable que contiene la línea de texto, LinefromFile , se incluye entre comillas cuando se pasa a la función. Esto se adapta a las líneas que disponen espacios en ellos. Sin las comillas, la primera palabra se trata como $1 por la función, la segunda palabra se considera $2 , etcétera. El uso de comillas garantiza que toda la línea de texto se maneje, en conjunto, como $1. Tenga en cuenta que esto es no lo mismo $1 que contiene el mismo archivo de datos pasado al script.

Debido a que Counter se ha declarado en el cuerpo principal del script y no dentro de una función, se puede hacer referencia dentro de la process_line() función.

Copie o escriba el script anterior en un editor y guárdelo con el nombre de archivo «script2.sh». Hágalo ejecutable con chmod :

chmod +x script2.sh

Ahora podemos ejecutarlo y pasar un nuevo archivo de datos, «data3.txt». Esto cuenta con una lista de los meses y una línea con muchas palabras.

January
February
March
.
.
October
November nMore text "at the end of the line"
December

Nuestro comando es:

./script2.sh data3.txt

Las líneas se leen del archivo y se pasan una por una al process_line() función. Todas las líneas se muestran correctamente, incluida la que tiene el retroceso, las comillas y varias palabras.

Los bloques de construcción son útiles

Hay una línea de pensamiento que dice que un idioma debe contener algo exclusivo de ese idioma. Esa no es una creencia a la que me suscriba. Lo importante es que hace un buen uso del lenguaje, es fácil de recordar y proporciona una forma confiable y sólida de poner en práctica algunas funciones en su código.

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.