Zapętlanie zawartości pliku w Bash

Jak przejrzeć każdy wiersz pliku tekstowego za pomocą Bash?

Z tym skryptem:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

Dostaję to wyjście na ekranie:

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(później chcę zrobić coś bardziej skomplikowanego z $p, niż tylko wyjście na ekran.)


Zmienna środowiskowa SHELL jest (z ENV):

SHELL=/bin/bash

/bin/bash --version wyjście:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

cat /proc/version wyjście:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

Peptydy plików.txt zawiera:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
Author: iPython, 2009-10-05

11 answers

Jednym ze sposobów jest:

while read p; do
  echo "$p"
done <peptides.txt

Jak wspomniano w komentarzach, ma to skutki uboczne przycinania wiodących białych znaków, interpretacji sekwencji ukośników wstecznych i pomijania końcowej linii, jeśli brakuje jej szybkości kończącej linię. Jeśli są to obawy, można zrobić:

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

Wyjątkowo, jeśli ciało pętli może odczytywać ze standardowego wejścia , możesz otworzyć plik używając innego deskryptora pliku:

while read -u 10 p; do
  ...
done 10<peptides.txt

Tutaj 10 to tylko dowolna liczba (Inna od 0, 1, 2).

 1571
Author: Bruno De Fraine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-20 20:55:56
cat peptides.txt | while read line
do
   # do something with $line here
done
 307
Author: Warren Young,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-05 17:54:38

Option 1a: While loop: Single line at a time: input redirection

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

Opcja 1b: pętla While: pojedyncza linia na raz:
Otwórz plik, odczytany z deskryptora pliku (w tym przypadku deskryptor pliku #4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

Opcja 2: For loop: wczytuje plik do pojedynczej zmiennej i analizuje.
Składnia ta będzie przetwarzać "linie" na podstawie dowolnych białych spacji między tokenami. To nadal działa, ponieważ podane linie plików wejściowych są jednowyrazowymi tokenami. Jeśli było więcej niż jeden token na linię, wtedy ta metoda nie będzie działać. Ponadto odczyt pełnego pliku do pojedynczej zmiennej nie jest dobrą strategią dla dużych plików.

#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
    echo $line
done
 114
Author: Stan Graves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-06 10:42:03

To nie jest lepsze niż inne odpowiedzi, ale jest jeszcze jeden sposób na wykonanie zadania w pliku bez spacji (patrz komentarze). Uważam, że często potrzebuję one-linerów, aby przekopywać się przez listy w plikach tekstowych bez dodatkowego kroku używania oddzielnych plików skryptów.

for word in $(cat peptides.txt); do echo $word; done

Ten format pozwala mi umieścić to wszystko w jednym wierszu poleceń. Zmień część "echo $word" na dowolną i możesz wydać wiele poleceń oddzielonych średnikami. Poniższy przykład wykorzystuje zawartość pliku jako argumenty do dwóch innych skryptów, które mogłeś napisać.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done

Lub jeśli zamierzasz używać tego jak edytor strumienia (learn sed), możesz zrzucić wyjście do innego pliku w następujący sposób.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt

Użyłem ich tak, jak napisano powyżej, ponieważ użyłem plików tekstowych, w których utworzyłem je z jednym słowem w linii. (Zobacz komentarze) Jeśli masz spacje, których nie chcesz dzielić słów/linii, robi się to trochę brzydsze, ale to samo polecenie nadal działa w następujący sposób:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

To po prostu mówi powłoce, aby dzieliła się tylko na nowe linie, a nie na spacje, a następnie zwraca środowisko z powrotem do tego, co było wcześniej. W tym momencie możesz rozważyć umieszczenie tego wszystkiego w skrypcie powłoki, a nie wyciskanie go w jednej linii.

Powodzenia!
 63
Author: mightypile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-22 15:47:48

Użyj pętli while, tak:

while IFS= read -r line; do
   echo "$line"
done <file

Uwagi:

  1. Jeśli nie ustawisz IFS poprawnie, utracisz wcięcia.

  2. Prawie zawsze należy używać opcji-r z read.

  3. Nie Czytaj wierszy z for

 39
Author: Jahid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-29 00:10:24

Jeszcze kilka rzeczy nie objętych innymi odpowiedziami:

Odczyt z rozdzielonego pliku

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt

Odczyt z wyjścia innego polecenia, przy użyciu podstawienia procesu

while read -r line; do
  # process the line
done < <(command ...)

To podejście jest lepsze niż command ... | while read -r line; do ... ponieważ pętla while działa w bieżącej powłoce, a nie w subshell, jak w przypadku tej drugiej. Zobacz related post zmienna zmodyfikowana wewnątrz pętli while nie jest pamiętana .

Odczyt z ograniczonego wejścia null, dla przykład find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

Related read: BashFAQ / 020-Jak mogę znaleźć i bezpiecznie obsługiwać nazwy plików zawierające nowe linie, spacje lub oba?

Odczyt z więcej niż jednego pliku na raz

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt

Na podstawie @chepner ' s odpowiedz tutaj :

-u jest rozszerzeniem bash. Dla zgodności z POSIX, każde wywołanie będzie wyglądało jak read -r X <&3.

Odczyt całego pliku do tablicy (wersje Bash wcześniejsze do 4)

while read -r line; do
    my_array+=("$line")
done < my_file

Jeśli plik kończy się niepełną linią (na końcu brakuje nowej linii), a następnie:

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

Odczyt całego pliku do tablicy (wersje Bash 4x i nowsze)

readarray -t my_array < my_file

Lub

mapfile -t my_array < my_file

A potem

for line in "${my_array[@]}"; do
  # process the lines
done

Related posts:

 32
Author: codeforester,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-20 20:38:35

Jeśli nie chcesz, aby odczyt był łamany przez znak nowej linii, użyj -

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

Następnie uruchom skrypt z nazwą pliku jako parametrem.

 10
Author: Anjul Sharma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-08 16:10:51

Załóżmy, że masz ten plik:

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR

Istnieją cztery elementy, które zmienią znaczenie pliku wyjściowego odczytywanego przez wiele rozwiązań Bash:

  1. pusta linia 4;
  2. spacje wiodące lub końcowe na dwóch liniach;
  3. zapisywanie wartości poszczególnych wierszy (tj. każdy wiersz jest rekordem);
  4. linia 6 nie zakończona CR.

Jeśli chcesz, aby plik tekstowy linia po linii, w tym puste linie i kończące linie bez CR, musisz użyć pętli while i musisz mieć alternatywny test dla ostatniej linii.

Oto metody, które mogą zmienić plik (w porównaniu do tego, co zwraca cat):

1) traci ostatnią linię oraz spacje wiodące i końcowe:

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'

(jeśli zamiast tego wykonasz while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt, zachowasz początkowe i końcowe spacje, ale nadal stracisz ostatnią linię, jeśli nie zostanie zakończona CR)

2) użycie substytucji procesu za pomocą cat spowoduje odczytanie całego pliku jednym łykiem i traci znaczenie poszczególnych wierszy:

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'

(jeśli usuniesz " z $(cat /tmp/test.txt), odczytasz plik słowo po słowie, a nie jeden łyk. Również prawdopodobnie nie to, co jest zamierzone...)


Najbardziej solidnym i najprostszym sposobem odczytu pliku linia po linii i zachowania wszystkich odstępów jest:

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'

Jeśli chcesz usunąć wiodące i handlowe przestrzenie, Usuń IFS= część:

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'

(plik tekstowy bez zakończenia \n, choć dość powszechny, uważany jest za uszkodzony pod POSIX. Jeśli możesz liczyć na końcowe \n, nie potrzebujesz || [[ -n $line ]] W pętli while.)

Więcej w Bash FAQ

 10
Author: dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-02 17:17:53
#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done
 4
Author: Sine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-24 17:57:03

Oto mój prawdziwy przykład, jak zapętlić linie innego wyjścia programu, sprawdzić podciągi, zrzucić podwójne cudzysłowy ze zmiennej, użyć tej zmiennej poza pętlą. Myślę, że dość wielu zadaje te pytania prędzej czy później.

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

Zadeklaruj zmienną poza pętlą, ustaw wartość i użyj jej poza pętlą wymaga zrobione składnia. Aplikacja musi być uruchamiana w kontekście bieżącej konsoli. Cudzysłowy wokół polecenia zachowują nowe linie wyjścia strumień.

Loop match dla podłańcuchów odczytuje nazwa = wartość para, dzieli prawą część ostatniego = znak, krople pierwszy cytat, krople ostatni cytat, mamy czystą wartość do użycia gdzie indziej.

 2
Author: Whome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-30 08:15:45

@Piotr: to może Ci się udać-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

To zwróci Wyjście -

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
 1
Author: Alan Jebakumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-30 05:00:05