Es bastante fácil leer el contenido de un archivo de texto de Linux línea por línea en un script de shell, siempre y cuando se trate de algunos gotchas sutiles. A continuación, le indicamos cómo hacerlo de manera segura.
¿De qué vamos a hablar?
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 usar una de las características del lenguaje con el que trabaja el programador. Se convierten en parte del conjunto de herramientas de planos mentales de un programador.
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 vainilla. Tal vez eso sea suficiente para el requisito en cuestión. O tal vez embellezcen el código para hacerlo más eficiente o aplicable a la solución específica que están desarrollando. Pero tener el lenguaje de bloque de construcción a su alcance es un gran punto de partida.
Conocer y comprender los modismos en un lenguaje también hace que sea más fácil elegir un nuevo lenguaje de programación. Saber cómo se construyen las cosas en un lenguaje y buscar el equivalente, o lo más cercano, en otro lenguaje es una buena manera de apreciar las similitudes y diferencias entre los lenguajes de programación que ya conoces y el que estás aprendiendo.
Lectura de líneas de un archivo: el one-liner
En Bash, puedes usar un while
bucle en la línea de comandos 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 one-liner es:
while read line; do echo €line; done < data.txt
El while
loop lee una línea del archivo y el flujo de ejecución del pequeño programa pasa al cuerpo del bucle. El echo
escribe la línea de texto en la ventana del terminal. El intento de lectura falla cuando no hay más líneas que leer y el bucle está listo.
Un buen truco es la capacidad de redirigir un archivo a un bucle. En otros lenguajes de programación, tendrías que abrir el archivo, leerlo y cerrarlo de nuevo cuando hayas terminado. Con Bash, simplemente puede usar la redirección de archivos y dejar que el shell maneje todas esas cosas de bajo nivel por usted.
Por supuesto, este one-liner no es terriblemente útil. Linux ya proporciona el 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 de un carácter de nueva línea se ha anexado a cada línea. Lo llamaremos “data2.txt”.
January/n February/n March/n . . October/n November/n December/n
Usemos nuestra línea única en nuestro nuevo archivo.
while read line; do echo €line; done < data2.txt
El personaje de escape de barra diagonal inversa ” /
” ha sido descartado. El resultado es que se ha anexado una “n” a cada línea. Bash está interpretando la barra diagonal inversa como el comienzo de un 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 de barra diagonal inversa y todo) y elegir qué analizar o reemplazar usted mismo, dentro de su propio código.
Si queremos hacer algún procesamiento o análisis significativo en las líneas de texto, necesitaremos usar un script.
Lectura de líneas de un archivo con un script
Aquí está nuestro guión. Se llama “script1.sh”.
#!/bin/bash
Counter=
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
bucle.
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 principales y finales. Si queremos leer las líneas del archivo exactamente como están, necesitamos establecer IFS
para ser una cadena vacía.
Podríamos establecer esto una vez fuera del bucle, al igual que estamos estableciendo el valor de Counter
. Pero con scripts más complejos, especialmente aquellos con muchas funciones definidas por el usuario, es posible que IFS
se podría establecer en diferentes valores en otra parte del script. Asegurando que IFS
se establece en una cadena vacía cada vez que el while
Loop iterates garantiza que sepamos cuál será su comportamiento.
Vamos a leer una línea de texto en una variable llamada LinefromFile
. Estamos usando el -r
(lea barra diagonal inversa como un carácter normal) opción para ignorar las barras diagonales inversas. Serán tratados como cualquier otro personaje y no recibirán ningún trato especial.
Hay dos condiciones que satisfarán el while
bucle y permitir que el texto sea procesado por el cuerpo del bucle:
read -r LinefromFile
: Cuando una línea de texto se lee correctamente desde el archivo, elread
El comando envía una señal de éxito a lawhile
, y elwhile
loop pasa el flujo de ejecución al cuerpo del bucle. Tenga en cuenta que elread
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 Es posible que la última línea no incluya un carácter de línea nueva. Si elread
el comando ve el comando marcador de fin de archivo (EOF) antes de que la línea sea terminada por 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 poco de trabajo adicional para manejar archivos no compatibles con POSIX. Esta comparación comprueba el texto que se lee del archivo. Si no termina con un carácter de nueva línea, esta comparación aún devolverá el éxito a lawhile
bucle. 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 ” ||
” de modo que si cualquiera de los dos devuelve el é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 algunos resultados a la ventana del terminal. Se muestran el número de línea y el texto de cada línea.
Todavía podemos usar nuestro truco de redirección para redirigir un archivo a un bucle. En este caso, estamos redirigiendo €1, una variable que contiene el nombre del primer parámetro de línea de comandos que pasó al script. Usando este truco, podemos pasar fácilmente el nombre del archivo de datos en el que queremos que funcione el script.
Copie y pegue el script en un editor y guárdelo con el nombre de archivo “script1.sh”. Utilice el chmod
mandar para que sea ejecutable.
chmod +x script1.sh
Veamos qué hace nuestro script con el archivo de texto data2.txt y las barras diagonales inversas que contiene.
./script1.sh data2.txt
Cada carácter de la línea se muestra textualmente. Las barras traseras no se interpretan como personajes de escape. Se imprimen como caracteres regulares.
Pasar la línea a una función
Todavía 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=
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 luego definimos una función llamada process_line()
. La definición de una función debe aparecer antes la función se llama primero en el script.
A nuestra función se le va a pasar la línea de texto recién leída en cada iteración del while
bucle. Podemos acceder a ese valor dentro de la función utilizando el €1
variable. Si hubiera dos variables pasadas a la función, podríamos acceder a esos valores usando €1
y €2
, y así sucesivamente para más variables.
La while
el bucle es principalmente el mismo. Solo hay un cambio dentro del cuerpo del bucle. El echo
la línea se ha sustituido por una llamada al process_line()
función. Tenga en cuenta que no es necesario utilizar los corchetes “()” en el nombre de la función cuando la llama.
El nombre de la variable que contiene la línea de texto, LinefromFile
, se envuelve entre comillas cuando se pasa a la función. Esto atiende a las líneas que tienen espacios en ellas. Sin las comillas, la primera palabra se trata como €1
por la función, la segunda palabra se considera que es €2
, y así sucesivamente. El uso de comillas garantiza que toda la línea de texto se maneje, por completo, como €1
. Tenga en cuenta que esto es no Igualmente €1
que contiene el mismo archivo de datos pasado al script.
Porque Counter
se ha declarado en el cuerpo principal de la secuencia de comandos y no dentro de una función, se puede hacer referencia a ella 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”. Hazlo ejecutable con chmod
:
chmod +x script2.sh
Ahora podemos ejecutarlo y pasar un nuevo archivo de datos, “data3.txt”. Esto tiene una lista de los meses en él, y one línea con muchas palabras en ella.
January February March . . October November /nMore text "at the end of the line" December
Nuestro mandato es:
./script2.sh data3.txt
Las líneas se leen del archivo y se pasan una por una a la process_line()
función. Todas las líneas se muestran correctamente, incluida la impar con el retroceso, las comillas y varias palabras en ella.
Los bloques de construcción son útiles
Hay una línea de pensamiento que dice que un idioma debe contener algo único para 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 robusta de implementar alguna funcionalidad en su código.