Como fazer um loop pelas linhas de um arquivo?

61

Digamos que eu tenho este arquivo:

hello
world
hello world

Este programa

#!/bin/bash

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

saídas

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

Eu gostaria que a foriteração de cada linha ignorasse individualmente os espaços em branco, ou seja, as duas últimas linhas deveriam ser substituídas por

tester: hello world

O uso de aspas for i in "$(cat $1)";resulta na iatribuição de todo o arquivo de uma só vez. O que devo mudar?

Tobias Kienzler
fonte

Respostas:

69

Com 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

Observe, no entanto, que ela ignorará as linhas vazias, pois a nova linha é um caractere de espaço em branco do IFS, as seqüências contadas como 1 e as iniciais e as finais são ignoradas. Com zshe ksh93(not bash), você pode alterá-lo para que a IFS=$'\n\n'nova linha não seja tratada especialmente; no entanto, observe que todos os caracteres de nova linha à direita (para incluir linhas vazias à direita) sempre serão removidos pela substituição do comando.

Ou comread (não mais cat):

#!/bin/bash

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

Lá, as linhas vazias são preservadas, mas observe que ela pularia a última linha se não fosse delimitada adequadamente por um caractere de nova linha.

abanar
fonte
5
obrigado, eu não sabia que era possível fazer <um loop inteiro. Embora faça todo o sentido agora eu vi
Tobias Kienzler 07/02
11
Vejo IFS \ read -r line' in second example. Is really IFS = `necessário? IMHO basta dizer:while read -r line; do echo "tester: $line"; done < "$1"
Grzegorz Wierzowiecki 19/03/12
4
@GrzegorzWierzowiecki IFS=desativa a remoção dos espaços em branco à esquerda e à direita. Consulte Em while IFS= read.., por que o IFS não tem efeito?
Gilles 'SO- stop be evil'
0

Pelo que vale a pena, eu preciso fazer isso com bastante frequência e nunca consigo me lembrar da maneira exata de usar while IFS= read..., então defini a seguinte função no meu perfil do 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
}

Essa função determina primeiro o número de linhas no arquivo, depois usa sedpara extrair linha após linha e passa cada linha como um argumento de cadeia única para qualquer função. Suponho que isso possa ficar realmente ineficiente com arquivos grandes, mas isso não foi um problema para mim até agora (sugestões sobre como melhorar essa recepção, é claro).

O uso é muito doce 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
fonte