¿Cómo recorrer las líneas de un archivo?

61

Digamos que tengo este archivo:

hello
world
hello world

Este programa

#!/bin/bash

for i in $(cat $1); do
    echo "tester: $i"
done

salidas

tester: hello
tester: world
tester: hello
tester: world

Sin forembargo, me gustaría que la iteración sobre cada línea ignore los espacios en blanco, es decir, las dos últimas líneas deben reemplazarse por

tester: hello world

El uso de comillas for i in "$(cat $1)";resulta en que ise le asigne todo el archivo de una vez ¿Qué debería cambiar?

Tobias Kienzler
fuente

Respuestas:

69

Con fore IFS :

#!/bin/bash

IFS=$'\n'       # make newlines the only separator
set -f          # disable globbing
for i in $(cat < "$1"); do
  echo "tester: $i"
done

Sin embargo, tenga en cuenta que omitirá las líneas vacías ya que la nueva línea es un carácter de espacio en blanco IFS, las secuencias de la misma cuentan como 1 y las iniciales y finales se ignoran. Con zshy ksh93(no bash), puede cambiarlo IFS=$'\n\n'para que la nueva línea no sea tratada especialmente, sin embargo, tenga en cuenta que todos los caracteres de nueva línea finales (por lo que incluye las líneas vacías finales) siempre se eliminarán mediante la sustitución del comando.

O conread (no más cat):

#!/bin/bash

while IFS= read -r line; do
  echo "tester: $line"
done < "$1"

Allí, las líneas vacías se conservan, pero tenga en cuenta que omitiría la última línea si no se delimita correctamente por un carácter de nueva línea.

meneo
fuente
55
gracias, no sabía que se pudiera hacer <un ciclo completo. Aunque tiene mucho sentido ahora lo vi
Tobias Kienzler
1
Veo IFS \ read -r line' in second example. Is really IFS = `¿necesario? En mi humilde opinión es suficiente decir:while read -r line; do echo "tester: $line"; done < "$1"
Grzegorz Wierzowiecki
44
@GrzegorzWierzowiecki IFS=desactiva la eliminación de los espacios en blanco iniciales y finales. Ver En while IFS= read.., ¿por qué IFS no tiene ningún efecto?
Gilles 'SO- deja de ser malvado'
0

Para lo que vale, necesito hacer eso con bastante frecuencia, y nunca puedo recordar la forma exacta de usarlo while IFS= read..., así que definí la siguiente función en mi perfil de bash:

# iterate the line of a file and call input function
iterlines() {
    (( $# < 2 )) && { echo "Usage: iterlines <File> <Callback>"; return; }
    local File=$1
    local Func=$2
    n=$(cat "$File" | wc -l)
    for (( i=1; i<=n; i++ )); do
        "$Func" "$(sed "${i}q;d" "$File")"
    done
}

Esta función primero determina el número de líneas en el archivo, luego usa sedpara extraer línea tras línea y pasa cada línea como un argumento de cadena simple a cualquier función dada. Supongo que esto podría volverse realmente ineficiente con archivos grandes, pero eso no ha sido un problema para mí hasta ahora (sugerencias sobre cómo mejorar esta bienvenida, por supuesto).

El uso es bastante dulce IMO:

>> cat example.txt # note the use of spaces, whitespace, etc.
a/path

This is a sentence.
"wi\th quotes"
$End
>> iterlines example.txt echo # preserves quotes, $ and whitespace
a/path

This is a sentence.
"wi\th quotes"
$End
>> x() { echo "$#"; }; iterlines example.txt x # line always passed as single input string
1
1 
1
1
1
Sheljohn
fuente