Czytaj linię pliku po linii przypisując wartość do zmiennej

752

Mam następujący plik .txt:

Marco
Paolo
Antonio

Chcę go czytać wiersz po wierszu i dla każdej linii chcę przypisać wartość zmiennej .txt do zmiennej. Zakładając, że moja zmienna jest $nametaka, przepływ jest następujący:

  • Przeczytaj pierwszy wiersz z pliku
  • Przypisz $name= „Marco”
  • Wykonuj niektóre zadania za pomocą $name
  • Czytaj drugi wiersz z pliku
  • Przypisz $name= „Paolo”
Marco
źródło
3
Czy te pytania można w jakiś sposób połączyć? Oba mają kilka naprawdę dobrych odpowiedzi, które podkreślają różne aspekty problemu, złe odpowiedzi mają dogłębne wyjaśnienia w komentarzach, co jest z nimi nie tak, a na razie nie można naprawdę uzyskać pełnego przeglądu tego, co należy wziąć pod uwagę, z odpowiedzi jedno pytanie od pary. Przydałoby się mieć to wszystko w jednym miejscu, zamiast podzielić na 2 strony.
Egor Hans,

Odpowiedzi:

1356

Poniżej czytany jest plik przekazywany jako argument linia po linii:

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

Jest to standardowa forma odczytu linii z pliku w pętli. Wyjaśnienie:

  • IFS=(lub IFS='') zapobiega przycinaniu wiodących / końcowych białych znaków.
  • -r zapobiega interpretacji ucieczek odwrotnego ukośnika.

Lub możesz umieścić go w skrypcie pomocniczym pliku bash, przykładowa zawartość:

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

Jeśli powyższe zapisano w skrypcie z nazwą pliku readfile, można go uruchomić w następujący sposób:

chmod +x readfile
./readfile filename.txt

Jeśli plik nie jest standardowym plikiem tekstowym POSIX (= nie zakończony znakiem nowej linii), można zmodyfikować pętlę, aby obsługiwała końcowe wiersze częściowe:

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

Tutaj || [[ -n $line ]]zapobiega ignorowaniu ostatniego wiersza, jeśli nie kończy się on na \n(ponieważ readzwraca niezerowy kod wyjścia, gdy napotka EOF).

Jeśli polecenia wewnątrz pętli również odczytują ze standardowego wejścia, readmożna użyć innego deskryptora pliku do czegoś innego (unikaj standardowych deskryptorów plików ), np .:

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

(Powłoki inne niż Bash mogą nie wiedzieć read -u3; użyj read <&3zamiast tego.)

cppcoder
źródło
23
Istnieje zastrzeżenie dotyczące tej metody. Jeśli cokolwiek w pętli while jest interaktywne (np. Czyta ze standardowego wejścia), wówczas pobierze dane wejściowe od 1 $. Nie będziesz mieć szansy na ręczne wprowadzenie danych.
carpie
10
Uwaga - niektóre polecenia psują (jak w, przerywają pętlę) to. Na przykład sshbez -nflagi skutecznie spowoduje ucieczkę z pętli. Prawdopodobnie jest to dobry powód, ale zajęło mi trochę czasu, aby ustalić, co spowodowało awarię mojego kodu, zanim to odkryłem.
Alex
6
jako jeden wiersz: podczas gdy IFS = '' czytaj -r wiersz || [[-n „$ line”]]; wykonaj echo „$ line”; gotowe <nazwa pliku
Joseph Johnson
8
@ OndraŽižka, jest to spowodowane ffmpegspożywaniem standardowego wejścia. Dodaj </dev/nulldo ffmpeglinii i nie będzie w stanie, ani użyć alternatywnego FD dla pętli. To podejście „alternatywnego FD” wygląda while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy
9
grumble re: doradzanie .shrozszerzenia. Pliki wykonywalne w systemie UNIX zazwyczaj nie mają żadnych rozszerzeń (nie uruchamiasz ls.elf), a szesnastkowy bash (i tylko narzędzia bash, takie jak [[ ]]) oraz rozszerzenie sugerujące kompatybilność sh POSIX są wewnętrznie wewnętrznie sprzeczne.
Charles Duffy
309

Zachęcam do korzystania z -rflagi, readktórej skrót oznacza:

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

Cytuję od man 1 read .

Inną rzeczą jest przyjęcie nazwy pliku jako argumentu.

Oto zaktualizowany kod:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"
Grzegorz Wierzowiecki
źródło
4
Przycina wiodącą i końcową przestrzeń od linii
barfuin
@Thomas i co dzieje się ze spacjami na środku? Wskazówka: Niepożądana próba wykonania polecenia.
kmarsh
1
To zadziałało dla mnie, w przeciwieństwie do przyjętej odpowiedzi.
Neuroprzekaźnik
3
@TranslucentCloud, jeśli to zadziałało, a zaakceptowana odpowiedź nie, podejrzewam, że twoja powłoka była sh, nie bash; rozszerzone polecenie testowe użyte w || [[ -n "$line" ]]składni w zaakceptowanej odpowiedzi to bzdura. To powiedziawszy, ta składnia faktycznie ma istotne znaczenie: powoduje, że pętla kontynuuje ostatnią linię w pliku wejściowym, nawet jeśli nie ma nowej linii. Jeśli chcesz to zrobić w sposób zgodny z POSIX, powinieneś raczej || [ -n "$line" ]użyć [zamiast [[.
Charles Duffy
3
To powiedziawszy, to nie muszą jeszcze zostać zmodyfikowany zestaw IFS=dla readzapobiegać przycinanie spacje.
Charles Duffy
132

Korzystanie z następującego szablonu Bash powinno umożliwiać odczytanie jednej wartości na raz z pliku i przetworzenie jej.

while read name; do
    # Do what you want to $name
done < filename
OneWinged
źródło
14
jako jeden wiersz: podczas odczytu nazwy; wykonaj echo $ {name}; gotowe <nazwa pliku
Joseph Johnson
4
@CalculusKnight, to tylko „działało”, ponieważ nie użyłeś wystarczająco interesujących danych do przetestowania. Wypróbuj zawartość z ukośnikami odwrotnymi lub z linią zawierającą tylko *.
Charles Duffy
7
@Matthias, założenia, które ostatecznie okazują się fałszywe, są jednym z największych źródeł błędów, zarówno wpływających na bezpieczeństwo, jak i innych. Największe zdarzenie utraty danych, jakie kiedykolwiek widziałem, było spowodowane scenariuszem, który, jak się przypuszczano, „dosłownie nigdy nie nadejdzie” - przepełnienie bufora wyrzucające losową pamięć do bufora używanego do nazywania plików, powodując, że skrypt przyjmował założenia, które nazwy mogłyby kiedykolwiek okazuje się mieć bardzo, bardzo niefortunne zachowanie.
Charles Duffy
5
@Matthias, ... i jest to szczególnie prawdziwe tutaj, ponieważ próbki kodu pokazane na StackOverflow są przeznaczone do użycia jako narzędzia do nauczania, aby ludzie mogli ponownie wykorzystywać wzorce we własnej pracy!
Charles Duffy
5
@ Matthias, całkowicie nie zgadzam się z twierdzeniem, że „powinieneś opracowywać swój kod tylko dla danych, których oczekujesz”. Nieoczekiwane przypadki dotyczą błędów, w których występują luki w zabezpieczeniach - radzenie sobie z nimi to różnica między kodem slapdash a solidnym kodem. To prawda, że ​​obsługa nie musi być fantazyjna - może to być po prostu „wyjście z błędem” - ale jeśli w ogóle nie masz obsługi, twoje zachowanie w nieoczekiwanych przypadkach jest niezdefiniowane.
Charles Duffy
76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done
Gert
źródło
8
Nic w porównaniu z innymi odpowiedziami, być może są bardziej wyrafinowane, ale głosuję za odpowiedzią, ponieważ jest prosta, czytelna i wystarcza na to, czego potrzebuję. Zauważ, że aby zadziałał, plik tekstowy, który ma być odczytany, musi kończyć się pustą linią (tzn. Należy nacisnąć Enterpo ostatniej linii), w przeciwnym razie ostatnia linia zostanie zignorowana. Przynajmniej tak się stało.
Antonio Vinicius Menezes Medei
12
Bezużyteczne użycie kota, na pewno?
Brian Agnew
5
Cytat jest zepsuty; i nie powinieneś używać nazw zmiennych pisanych wielkimi literami, ponieważ są one zastrzeżone do użytku systemowego.
tripleee
7
@AntonioViniciusMenezesMedei, ... ponadto widziałem ludzi ponoszących straty finansowe, ponieważ zakładali, że te zastrzeżenia nigdy nie będą dla nich ważne; nie nauczył się dobrych praktyk; a następnie przestrzegał nawyków, do których byli przyzwyczajeni podczas pisania skryptów zarządzających kopiami zapasowymi krytycznych danych rozliczeniowych. Ważna jest nauka właściwego robienia rzeczy.
Charles Duffy
6
Innym problemem jest to, że potok otwiera nową podpowłokę, tzn. Nie można odczytać wszystkich zmiennych ustawionych w pętli po zakończeniu pętli.
mxmlnkn,
20

Wiele osób opublikowało nadmiernie zoptymalizowane rozwiązanie. Nie sądzę, aby było to nieprawidłowe, ale pokornie uważam, że pożądane będzie mniej zoptymalizowane rozwiązanie, aby każdy mógł łatwo zrozumieć, jak to działa. Oto moja propozycja:

#!/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
źródło
20

Posługiwać się:

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

Jeśli ustawiłeś IFSinaczej, otrzymasz dziwne wyniki.

użytkownik3546841
źródło
34
To okropna metoda . Nie używaj go, chyba że chcesz mieć problemy z globowaniem, które będą miały miejsce, zanim zdasz sobie z tego sprawę!
gniourf_gniourf
13
@MUYBelgium próbowałeś z plikiem zawierającym singiel *w wierszu? W każdym razie jest to antypattern . Nie czytaj wierszy za .
gniourf_gniourf
2
@ OndraŽižka, readpodejście to podejście oparte na najlepszych praktykach w drodze konsensusu społeczności . Zastrzeżenie, o którym wspominasz w komentarzu, ma zastosowanie, gdy twoja pętla uruchamia polecenia (takie jak ffmpeg), które odczytują ze standardowego wejścia, rozwiązane w trywialny sposób za pomocą niedyskryminacyjnego FD dla pętli lub przekierowującego wejście takich poleceń. Natomiast obejście błędu globbing w forpodejściu pętli oznacza wprowadzenie (a następnie konieczność cofnięcia) zmian ustawień globalnej powłoki.
Charles Duffy
1
@ OndraŽižka ... Ponadto forpodejście pętli użyć tutaj oznacza, że cała zawartość musi być odczytywane przed pętla może zacząć realizować w ogóle, co czyni go całkowicie bezużyteczne, jeśli jesteś zapętlenie nad gigabajtów danych, nawet jeśli mają wyłączone globbing; while readpętla potrzebuje nie więcej niż jednej linii danych za przechowywanie w czasie, dzięki czemu można ją zacząć realizować podczas gdy zawartość generujący podproces nadal działa (a więc jest użyteczny dla celów transmisji strumieniowej), a także ma ograniczone zużycie pamięci.
Charles Duffy
1
W rzeczywistości, nawet whileoparte na podejściu wydaje się mieć * charakterystyczne problemy. Zobacz komentarze do zaakceptowanej odpowiedzi powyżej. Jednak nie kłóci się o to, że pliki są antypatternem.
Egor Hans,
9

Jeśli potrzebujesz przetworzyć zarówno plik wejściowy, jak i dane wejściowe użytkownika (lub cokolwiek innego ze standardowego wejścia), skorzystaj z następującego rozwiązania:

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

Na podstawie zaakceptowanej odpowiedzi i samouczka przekierowania bash-hakerów .

Tutaj otwieramy deskryptor pliku 3 dla pliku przekazanego jako argument skryptu i informujemy read aby użyć tego deskryptora jako input ( -u 3). Dlatego pozostawiamy domyślny deskryptor wejściowy (0) podłączony do terminala lub innego źródła wejściowego, zdolnego do odczytu danych wejściowych użytkownika.

gluk47
źródło
7

Do prawidłowej obsługi błędów:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}
Bviktor
źródło
Czy możesz dodać jakieś wyjaśnienie?
Tyler Christian
Niestety brakuje ostatniej linii w pliku.
ungalcrys,
... a także, z powodu braku cytowania, munges wiersze zawierające symbole wieloznaczne - jak opisano w BashPitfalls # 14 .
Charles Duffy
0

Po prostu wydrukuje zawartość pliku:

cat $Path/FileName.txt

while read line;
do
echo $line     
done
Rekha Ghanate
źródło
1
Ta odpowiedź naprawdę nie dodaje niczego do istniejących odpowiedzi, nie działa z powodu literówki / błędu i psuje się na wiele sposobów.
Konrad Rudolph
0

Użyj narzędzia IFS (wewnętrzny separator pól) w bash, definiuje znak za pomocą, aby podzielić linie na tokeny, domyślnie zawiera < tab > / < space > / < newLine >

krok 1 : Załaduj dane pliku i wstaw do listy:

# 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>"

krok 2 : teraz iteruj i wydrukuj wynik:

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

indeks specyficzny dla echa w tablicy : Dostęp do zmiennej w tablicy:

echo "${array[0]}"
avivamg
źródło