Leia um arquivo linha por linha atribuindo o valor a uma variável

753

Eu tenho o seguinte arquivo .txt:

Marco
Paolo
Antonio

Eu quero lê-lo linha por linha, e para cada linha eu quero atribuir um valor de linha .txt a uma variável. Supondo que minha variável seja $name, o fluxo é:

  • Ler a primeira linha do arquivo
  • Atribuir $name= "Marco"
  • Faça algumas tarefas com $name
  • Ler a segunda linha do arquivo
  • Atribuir $name= "Paolo"
Marco
fonte
3
Essas perguntas podem ser mescladas de alguma forma? Ambos têm respostas realmente boas que destacam aspectos diferentes do problema, as respostas ruins têm explicações detalhadas nos comentários sobre o que há de ruim nelas e, a partir de agora, você não pode realmente obter uma visão geral completa do que considerar, a partir das respostas de uma única pergunta do par. Seria útil ter tudo isso em um único local, em vez de dividir em duas páginas.
Egor Hans

Respostas:

1357

A seguir, um arquivo passado como um argumento linha por linha:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

Este é o formulário padrão para ler linhas de um arquivo em um loop. Explicação:

  • IFS=(ou IFS='') impede que os espaços em branco à esquerda / à direita sejam cortados.
  • -r impede que as fugas de barra invertida sejam interpretadas.

Ou você pode colocá-lo em um script auxiliar de arquivo bash, como exemplo de conteúdo:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

Se o acima foi salvo em um script com nome de arquivo readfile, ele poderá ser executado da seguinte maneira:

chmod +x readfile
./readfile filename.txt

Se o arquivo não for um arquivo de texto POSIX padrão (= não finalizado por um caractere de nova linha), o loop poderá ser modificado para manipular linhas parciais finais:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Aqui, || [[ -n $line ]]impede que a última linha seja ignorada se não terminar com a \n(desde que readretorna um código de saída diferente de zero quando encontrar EOF).

Se os comandos dentro do loop também lerem da entrada padrão, o descritor de arquivo usado readpode ser alterado para outra coisa (evite os descritores de arquivo padrão ), por exemplo:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Os shells não Bash podem não saber read -u3; use em read <&3vez disso.)

cppcoder
fonte
23
Há uma ressalva com este método. Se algo dentro do loop while for interativo (por exemplo, leituras de stdin), ele receberá sua entrada de $ 1. Você não terá a chance de inserir dados manualmente.
22614
10
De nota - alguns comandos quebram (como em, eles quebram o loop) isso. Por exemplo, sshsem o -nsinalizador efetivamente fará com que você escape do loop. Provavelmente, há uma boa razão para isso, mas demorei um pouco para identificar o que estava causando a falha do meu código antes que eu descobrisse isso.
Alex
6
como uma linha: enquanto IFS = '' leia -r linha || [[-n "$ line"]]; faça eco de "$ line"; done <nome do arquivo
Joseph Johnson
8
@ OndraŽižka, causado pelo ffmpegconsumo de stdin. Adicione </dev/nullà sua ffmpeglinha e ela não poderá, ou usar um FD alternativo para o loop. Essa abordagem de "FD alternativo" parece while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy
9
resmungar re: aconselhar uma .shextensão. Os executáveis ​​no UNIX normalmente não têm extensões (você não executa ls.elf), e ter um shebang do bash (e ferramentas somente do bash, como [[ ]]) e uma extensão que implique compatibilidade com POSIX sh é internamente contraditório.
Charles Duffy
309

Convido você a usar a -rbandeira readque significa:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

Eu estou citando man 1 read.

Outra coisa é usar um nome de arquivo como argumento.

Aqui está o código atualizado:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"
Grzegorz Wierzowiecki
fonte
4
Apara o espaço inicial e final da linha
barfuin
@ Thomas e o que acontece com os espaços no meio? Dica: tentativa indesejada de execução de comando.
kmarsh
1
Isso funcionou para mim, em contraste com a resposta aceita.
Neurotransmitter
3
@TranslucentCloud, se isso funcionou e a resposta aceita não, eu suspeito que seu shell shnão estava bash; o comando de teste estendido usado na || [[ -n "$line" ]]sintaxe na resposta aceita é um basismo. Dito isso, a sintaxe realmente tem um significado pertinente: faz com que o loop continue na última linha do arquivo de entrada, mesmo que não tenha uma nova linha. Se você quiser fazer isso de uma maneira compatível com POSIX || [ -n "$line" ], use, em [vez de [[.
Charles Duffy
3
Dito isto, este não ainda precisa ser modificado para definir IFS=para o readpara evitar aparar espaço em branco.
Charles Duffy
132

O uso do modelo Bash a seguir deve permitir que você leia um valor de cada vez de um arquivo e processe-o.

while read name; do
    # Do what you want to $name
done < filename
OneWinged
fonte
14
como uma linha: enquanto lê o nome; faça eco de $ {name}; done <nome do arquivo
Joseph Johnson
4
@CalculusKnight, apenas "funcionou" porque você não usou dados suficientemente interessantes para testar. Experimente o conteúdo com barras invertidas ou com uma linha que contém apenas *.
Charles Duffy
7
@ Matthias, suposições que acabam sendo falsas são uma das maiores fontes de bugs, com impacto na segurança e de outra forma. O maior evento de perda de dados que eu já vi foi devido a um cenário que alguém assumiu que "literalmente nunca surgiria" - um estouro de buffer que despejava memória aleatória em um buffer usado para nomear arquivos, causando um script que fazia suposições sobre quais nomes poderiam ocorrer um comportamento muito, muito infeliz.
Charles Duffy
5
@ Matthias, ... e isso é especialmente verdade aqui, já que os exemplos de código mostrados no StackOverflow destinam-se a ser usados ​​como ferramentas de ensino, para que as pessoas reutilizem os padrões em seu próprio trabalho!
Charles Duffy
5
@ Matthias, discordo totalmente da afirmação de que "você só deve criar seu código para os dados que espera". Casos inesperados são onde estão seus bugs, onde estão suas vulnerabilidades de segurança - lidar com eles é a diferença entre código slapdash e código robusto. É verdade que esse tratamento não precisa ser extravagante - pode ser apenas "sair com um erro" - mas se você não tiver nenhum tratamento, seu comportamento em casos inesperados é indefinido.
Charles Duffy
76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done
Gert
fonte
8
Nada contra as outras respostas, talvez elas sejam mais sofisticadas, mas eu voto positivo essa resposta porque é simples, legível e suficiente para o que eu preciso. Observe que, para que funcione, o arquivo de texto a ser lido deve terminar com uma linha em branco (ou seja, é preciso pressionar Enterapós a última linha), caso contrário, a última linha será ignorada. Pelo menos foi o que aconteceu comigo.
Antonio Vinicius Menezes Medei
12
Uso inútil de gato, timidamente?
Brian Agnew
5
E a citação está quebrada; e você não deve usar nomes de variáveis ​​em maiúsculas porque eles são reservados para uso do sistema.
tripleee
7
@AntonioViniciusMenezesMedei, ... além do mais, vi pessoas sofrerem perdas financeiras porque presumiram que essas advertências nunca lhes importariam; falhou em aprender boas práticas; e seguiu os hábitos aos quais estavam acostumados ao escrever scripts que gerenciavam backups de dados críticos de cobrança. Aprender a fazer as coisas certas é importante.
Charles Duffy
6
Outro problema aqui é que o canal abre um novo subshell, ou seja, todas as variáveis ​​definidas no loop não podem ser lidas após o término do loop.
Mxmlnkn # 10/17
20

Muitas pessoas postaram uma solução super otimizada. Não acho que esteja incorreto, mas acho humildemente que uma solução menos otimizada será desejável para permitir que todos compreendam facilmente como isso está funcionando. Aqui está a minha proposta:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"
Raul Luna
fonte
20

Usar:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

Se você definir de forma IFSdiferente, obterá resultados ímpares.

user3546841
fonte
34
Este é um método horrível . Por favor, não o use, a menos que você queira ter problemas com globbing que ocorrerão antes que você perceba!
Gniourf_gniourf 20/05
13
@MUYBelgium você tentou com um arquivo que contém um único *em uma linha? Enfim, este é um antipadrão . Não leia linhas com para .
gniourf_gniourf
2
@ OndraŽižka, a readabordagem é a melhor prática por consenso da comunidade . A ressalva que você menciona no seu comentário é aquela que se aplica quando o loop executa comandos (como ffmpeg) que são lidos no stdin, resolvidos trivialmente usando um FD não stdin para o loop ou redirecionando a entrada desses comandos. Por outro lado, contornar o bug globbing na sua forabordagem -loop significa fazer (e depois precisar reverter) as mudanças nas configurações globais do shell.
Charles Duffy
1
@ OndraŽižka, ... além disso, a forabordagem de loop usada aqui significa que todo o conteúdo deve ser lido antes que o loop possa começar a ser executado, tornando-o totalmente inutilizável se você estiver repetindo gigabytes de dados, mesmo que tenha desativado globbing; o while readloop precisa armazenar não mais do que os dados de uma única linha de cada vez, o que significa que ele pode começar a executar enquanto o subprocesso que gera conteúdo ainda estiver em execução (sendo assim utilizável para fins de streaming) e também limitar o consumo de memória.
Charles Duffy
1
Na verdade, whileabordagens uniformes parecem ter problemas com * caracteres. Veja os comentários da resposta aceita acima. Porém, não argumentando contra a iteração de arquivos ser um antipadrão.
Egor Hans
9

Se você precisar processar o arquivo de entrada e a entrada do usuário (ou qualquer outra coisa do stdin), use a seguinte solução:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

Com base na resposta aceita e no tutorial de redirecionamento bash-hackers .

Aqui, abrimos o descritor de arquivo 3 para o arquivo passado como argumento do script e pedimos readpara usar esse descritor como input ( -u 3). Assim, deixamos o descritor de entrada padrão (0) conectado a um terminal ou outra fonte de entrada, capaz de ler a entrada do usuário.

gluk47
fonte
7

Para manipulação adequada de erros:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}
bviktor
fonte
Você poderia adicionar alguma explicação?
Tyler Christian
Infelizmente, falta a última linha do arquivo.
Ungalcrys 19/04/19
... e também, devido à falta de citação, linhas munges que contêm caracteres curinga - conforme descrito em BashPitfalls # 14 .
Charles Duffy
0

A seguir, apenas será impresso o conteúdo do arquivo:

cat $Path/FileName.txt

while read line;
do
echo $line     
done
Rekha Ghanate
fonte
1
Essa resposta realmente não adiciona nada sobre as respostas existentes, não funciona devido a um erro de digitação / erro e quebra de várias maneiras.
Konrad Rudolph
0

Use a ferramenta IFS (separador de campo interno) no bash, define o caractere usado para separar linhas em tokens; por padrão, inclui < tab > / < space > / < newLine >

Etapa 1 : Carregue os dados do arquivo e insira na lista:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

etapa 2 : agora itere e imprima a saída:

for line in "${array[@]}"
  do
    echo "$line"
  done

eco índice específico na matriz : Acessando uma variável na matriz:

echo "${array[0]}"
avivamg
fonte