Bash'teki bir dosyanın içeriğini dolaşma


1388

Bash ile bir metin dosyasının her satırı boyunca nasıl yineleyebilirim ?

Bu kod ile:

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

Bu çıktıyı ekranda alıyorum:

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

(Daha sonra $pekrana çıkış yapmaktan daha karmaşık bir şey yapmak istiyorum .)


SHELL ortam değişkeni (env'den):

SHELL=/bin/bash

/bin/bash --version çıktı:

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

cat /proc/version çıktı:

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

Peptides.txt dosyası şunları içerir:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

19
Burada birçok şey oldu: tüm yorumlar silindi ve soru tekrar açıldı. Sadece referans olarak, Bir değişkene değer atayarak satır satırını oku dosyasında kabul edilen cevap sorunu kanonik bir şekilde ele alır ve burada kabul edilene göre tercih edilmelidir.
fedorqui 'SO

Yanıtlar:


2091

Bunu yapmanın bir yolu:

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

Yorumlarda belirtildiği gibi, bunun önde gelen beyaz alanı kırpmanın, ters eğik çizgi dizilerini yorumlamanın ve sonlandırıcı bir satır besleme eksikse son satırı atlamanın yan etkileri vardır. Bunlar endişe kaynağı ise, şunları yapabilirsiniz:

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

İstisnai olarak, döngü gövdesi standart girdiden okuyabiliyorsa , dosyayı farklı bir dosya tanımlayıcı kullanarak açabilirsiniz:

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

Burada, 10 sadece rastgele bir sayıdır (0, 1, 2'den farklı).


7
Son satırı nasıl yorumlamalıyım? Peptides.txt dosyası standart girdiye ve bir şekilde while bloğuna mı yönlendiriliyor?
Peter Mortensen

11
"Peptides.txt dosyasını bu while döngüsüne karıştırın, böylece 'read' komutunun tüketecek bir şeyi vardır." Benim "kedi" yöntemim de benzerdir, 'read' komutuyla tüketmek için while komutuna bir komutun çıkışını gönderir, işi yapmak için sadece başka bir program başlatır.
Warren Young

8
Bu yöntem bir dosyanın son satırını atlıyor gibi görünüyor.
xastor

5
Çift satır alıntı !! echo "$ p" ve dosya .. güven bana eğer sen ısırır !!! BİLİYORUM! lol
Mike Q

5
Her iki sürüm de, yeni satırla sonlandırılmazsa son satırı okuyamaz. Her zaman kullanwhile read p || [[ -n $p ]]; do ...
dawg

447
cat peptides.txt | while read line 
do
   # do something with $line here
done

ve tek katlı varyant:

cat peptides.txt | while read line; do something_with_$line_here; done

Sondaki satır beslemesi yoksa bu seçenekler dosyanın son satırını atlar.

Bunu aşağıdakilerden kaçınabilirsiniz:

cat peptides.txt | while read line || [[ -n $line ]];
do
   # do something with $line here
done

68
Genel olarak, tek bir argümanla "kedi" kullanıyorsanız, yanlış (veya yetersiz) bir şey yapıyorsunuzdur.
JesperE

27
Evet, Bruno'nunki kadar verimli değil, çünkü gereksiz yere başka bir program başlatıyor. Verimlilik önemliyse, bunu Bruno'nun yolundan yapın. Yolumu hatırlıyorum çünkü "yeniden yönlendirme" sözdiziminin çalışmadığı diğer komutlarla kullanabilirsiniz.
Warren Young

74
Bununla ilgili daha ciddi bir sorun daha var: while döngüsü bir boru hattının parçası olduğundan, bir alt kabukta çalışır ve bu nedenle döngü içinde ayarlanan tüm değişkenler, çıktıkça kaybolur (bkz. Bash-hackers.org/wiki/doku. php / yansıtma / bashfaq / 024 ). Bu çok sinir bozucu olabilir (döngüde ne yapmaya çalıştığınıza bağlı olarak).
Gordon Davisson

25
Komutlarımın birçoğunun başlangıcı olarak "cat file |" kullanıyorum çünkü sık sık "head file |" ile prototip oluşturuyorum
mat kelcey

62
Bu o kadar etkili olmayabilir, ancak diğer cevaplardan çok daha okunabilir.
Savage Reader

144

Seçenek 1a: Süre döngüsü : Her seferinde tek satır: Giriş yeniden yönlendirmesi

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

Seçenek 1b: While döngüsü : Her seferinde tek satır:
Dosyayı açın, dosya tanımlayıcısından okuyun (bu durumda dosya tanımlayıcı # 4).

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

Seçenek 1b için: Dosya tanımlayıcısının tekrar kapatılması gerekiyor mu? Örneğin, döngü bir iç döngü olabilir.
Peter Mortensen

3
Dosya tanımlayıcı işlem çıkışlarıyla birlikte temizlenecektir. Fd numarasını tekrar kullanmak için açık bir kapanış yapılabilir. Bir fd'yi kapatmak için, & - sözdizimi ile başka bir exec kullanın, örneğin: exec 4 <& -
Stan Graves

1
Seçenek 2 için teşekkür ederim. Seçenek 1 ile büyük sorunlar yaşadım çünkü döngü içinde stdin okumak gerekir; böyle bir durumda Seçenek 1 çalışmaz.
masgo

4
Seçenek 2'nin kesinlikle önerilmez . @masgo Opsiyon 1b bu durumda çalışmalıdır ve değiştirerek Opsiyon 1a gelen giriş yönlendirme sözdizimi ile kombine edilebilir done < $filenameile done 4<$filenamebu durumda sadece yerini alabilir hangi bir komut parametresinden dosya adını okumak istiyorsanız yararlıdır, ( $filenametarafından $1).
Egor Hans

Döngü içinde tail -n +2 myfile.txt | grep 'somepattern' | cut -f3ssh komutları çalışırken (stdin tüketir) gibi dosya içeriği üzerinde döngü gerekiyor ; seçenek 2 burada tek yolu gibi görünüyor?
user5359531

85

Bu, diğer yanıtlardan daha iyi değildir, ancak işi boşluk içermeyen bir dosyada yapmanın bir yoludur (yorumlara bakın). Ben sık sık ayrı komut dosyaları kullanma ek adım olmadan metin dosyaları listeleri kazmak için tek satırları gerektiğini buluyorum.

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

Bu biçim, hepsini tek bir komut satırına koymamı sağlıyor. "Echo $ word" bölümünü istediğiniz gibi değiştirin ve noktalı virgülle ayırarak birden fazla komut verebilirsiniz. Aşağıdaki örnek, dosyanın içeriğini yazmış olabileceğiniz diğer iki komut dosyasına bağımsız değişken olarak kullanır.

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

Veya bunu bir akış düzenleyicisi (sed öğrenmek) gibi kullanmak istiyorsanız, çıktıyı aşağıdaki gibi başka bir dosyaya dökebilirsiniz.

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

Bunları yukarıda yazıldığı gibi kullandım çünkü her satırda bir kelime ile oluşturduğum metin dosyalarını kullandım. (Yorumlara bakın) Kelimelerinizi / satırlarınızı bölmek istemediğiniz boşluklarınız varsa, biraz daha çirkinleşir, ancak aynı komut hala aşağıdaki gibi çalışır:

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

Bu, kabuğa boşluklara değil yalnızca satırlara ayrılmasını söyler, daha sonra çevreyi önceki haline geri döndürür. Bu noktada, hepsini tek bir satıra sıkıştırmak yerine hepsini bir kabuk betiğine koymayı düşünebilirsiniz.

İyi şanslar!


6
Bash $ (<peptides.txt) belki de daha zarif, ama yine de yanlış, Joao'nun söylediği doğru, boşluk veya yeni satırın aynı şey olduğu komut yerine koyma mantığı gerçekleştiriyorsunuz. Bir satırın içinde boşluk varsa, döngü bu satır için TWICE veya daha fazlasını yürütür. Kodunuz düzgün bir şekilde okunmalıdır: $ (<peptides.txt) kelimesi için; Eğer bir boşluk olduğunu biliyorsanız, o zaman bir çizgi bir kelimeye eşittir ve iyisinizdir.
maxpolk

2
@ JoaoCosta, maxpolk: Düşünmediğim iyi noktalar. Orijinal yayını, onları yansıtacak şekilde düzenledim. Teşekkürler!
mightypile

2
Kullanmak for, giriş belirteçlerini / satırlarını, genellikle istenmeyen bir durum olan kabuk genişletmelerine tabi kılar; şunu deneyin: for l in $(echo '* b c'); do echo "[$l]"; done- Gördüğünüz gibi, *- başlangıçta alıntı yapılan bir kelimenin tam anlamıyla - geçerli dizindeki dosyalara genişler.
mklement0

2
@dblanchard: $ IFS kullanan son örnek boşlukları yok saymalıdır. Bu sürümü denedin mi?
mightypile

4
Bu komutun önemli sorunlar giderildiğinden çok daha karmaşık forhale gelme şekli, dosya satırlarını yinelemenin neden kötü bir fikir olduğunu çok iyi gösteriyor . Ayrıca, @ mklement0 tarafından bahsedilen genişleme yönü (muhtemelen kaçan alıntılar getirilerek atlatılabilir, ancak yine de işleri daha karmaşık ve daha az okunabilir hale getirir).
Egor Hans

69

Diğer cevapların kapsamadığı birkaç şey daha:

Sınırlandırılmış bir dosyadan okuma

# ':' 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

Süreç ikamesi kullanarak başka bir komutun çıktısından okuma

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

Bu yaklaşım, command ... | while read -r line; do ...burada while döngüsü, ikincisinde olduğu gibi bir alt kabuk yerine geçerli kabukta çalıştığından daha iyidir. İlgili gönderiye bakın while döngüsü içinde değiştirilen bir değişken hatırlanmaz .

Boş bir sınırlandırılmış girişten okuma, örneğin 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)

İlgili okuma: BashFAQ / 020 - Yeni satırlar, boşluklar veya her ikisini içeren dosya adlarını nasıl bulabilir ve güvenli bir şekilde işleyebilirim?

Aynı anda birden fazla dosyadan okuma

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

Dayanarak chepner en @ cevap burada :

-ubir bash uzantısıdır. POSIX uyumluluğu için her çağrı benzer görünecektir read -r X <&3.

Bir dosyanın tamamını bir diziye okuma (4'ten önceki Bash sürümleri)

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

Dosya tamamlanmamış bir satırla bitiyorsa (sonunda yeni satır eksik), o zaman:

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

Tüm dosyayı bir diziye okuma (Bash sürüm 4x ve üstü)

readarray -t my_array < my_file

veya

mapfile -t my_array < my_file

Ve sonra

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

İlgili Mesajlar:


bunun yerine command < input_filename.txther zaman yapabileceğinizi unutmayın input_generating_command | commandveyacommand < <(input_generating_command)
masterxilo

1
Diziye dosya okuduğunuz için teşekkür ederiz. Tam olarak ihtiyacım olan şey, çünkü her satırı iki kez ayrıştırmak, yeni değişkenlere eklemek, bazı doğrulamalar yapmak vb.
Gerekiyor

45

Bunun gibi bir while döngüsü kullanın:

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

Notlar:

  1. Doğru IFSşekilde ayarlamazsanız, girintiyi kaybedersiniz.

  2. Hemen her zaman okuma ile -r seçeneğini kullanmalısınız.

  3. İle satır okuma for


2
Neden -rseçenek?
David C. Rankin

2
@ DavidC.Rankin -r seçeneği ters eğik çizgi yorumlamasını önler. Note #2detaylı olarak açıklandığı bir linktir ...
Jahid

Bunu başka bir cevapta "read -u" seçeneği ile birleştirin ve sonra mükemmel.
Florin Andrei

@FlorinAndrei: Yukarıdaki örnek -useçeneğe ihtiyaç duymuyor, başka bir örnekten bahsediyor -umusunuz?
Jahid

Bağlantılarınızı inceledikten ve Not 2'deki bağlantınızı bağlayan bir yanıt olmadığına şaşırdım. Bu sayfa, bu konu hakkında bilmeniz gereken her şeyi sağlar. Yoksa yalnızca bağlantıya verilen yanıtlar önerilmez mi?
Egor Hans

14

Diyelim ki bu dosyanız var:

$ 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

Birçok Bash çözümü tarafından okunan dosya çıktısının anlamını değiştirecek dört öğe vardır:

  1. Boş satır 4;
  2. İki satırda öndeki veya sondaki boşluklar;
  3. Tek tek satırların anlamını korumak (yani her satır bir kayıttır);
  4. Çizgi 6 bir CR ile sonlandırılmadı.

Metin dosyasının boş satırlar ve CR içermeyen sonlandırma satırları da dahil olmak üzere satır satır olmasını istiyorsanız, while döngüsü kullanmalı ve son satır için alternatif bir testiniz olmalıdır.

İşte dosyayı değiştirebilecek yöntemler ( cat döndürenlere ):

1) Son çizgiyi ve ön ve arka boşlukları kaybedin:

$ 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'

(Bunu yaparsanız while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt, önde gelen ve arkadaki boşlukları korursunuz, ancak CR ile sonlandırılmazsa son satırı kaybedersiniz)

2) İşlem ikamesi ile birlikte kullanıldığında cat, tüm dosya tek bir yığında okunacak ve satırların anlamı kaybedilecektir:

$ 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'

(Eğer kaldırırsanız "dan $(cat /tmp/test.txt)size ziyade bir yudum daha kelime dosya kelimeyi okuyun. Ayrıca amaçlanan muhtemelen neyi ...)


Bir dosyayı satır satır okumanın ve tüm boşlukları korumanın en sağlam ve basit yolu:

$ 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'

Öncü ve ticaret alanlarını soymak istiyorsanız, IFS=parçayı çıkarın :

$ 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'

Bir sonlandırıcı olmadan (bir metin dosyası \n, oldukça yaygın iken, POSIX altında kırık olarak kabul edilir. Eğer sondaki güvenebilirsiniz varsa \nsize ihtiyacım yok || [[ -n $line ]]içindewhile döngü.)

Daha fazla bilgi için BASH SSS


13

Okumanızın satırsonu karakteriyle kırılmasını istemiyorsanız, -

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

Daha sonra komut dosyasını dosya adı ile parametre olarak çalıştırın.


4
#!/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

7
Bu cevap, güçlü bir cevapta belirtilen uyarılara ihtiyaç duyar ve herhangi bir satırda kabuk metakarakterleri varsa (başarısız olan "$ x" nedeniyle) kötü bir şekilde başarısız olabilir.
Toby Speight

7
Aslında insanlar henüz her zamanki gibi satırları ile gelmedi şaşırdım ...
Egor Hans

3

İşte benim gerçek hayat örneği başka bir program çıktı satırları döngü, alt dizeleri kontrol, değişken çift tırnak bırakın, bu değişken döngü dışında kullanın. Sanırım pek çok kişi bu soruları er ya da geç soruyor.

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

Değişkeni döngü dışında bildirin, değeri ayarlayın ve döngü dışında kullanın <<< "$ (...)" sözdizimi gerektirir. Uygulamanın geçerli konsol bağlamında çalıştırılması gerekir. Komutun etrafındaki alıntılar çıktı akışının satırlarını tutar.

Alt dizeler için döngü eşleşmesi daha sonra ad = değer çiftini okur , sonun sağ tarafını böler = karakter, ilk alıntıyı düşürür, son alıntıyı düşürür, başka bir yerde kullanılacak temiz bir değere sahibiz.


3
Cevap doğru olsa da, bunun nasıl sonuçlandığını anlıyorum. Temel yöntem, diğer birçok cevap tarafından önerilenle aynıdır. Ayrıca, FPS örneğinizde tamamen boğulur.
Egor Hans

0

Bu çok geç geliyor, ama birine yardımcı olabileceği düşüncesiyle, cevabı ekliyorum. Ayrıca bu en iyi yol olmayabilir. headkomut, n satırını dosya başlangıcından -nokumak için argümanla ve komut da alttan okumak için kullanılabilir. Şimdi, getirilemedi n'inci dosyadan satır, biz baş n hatları kuyruğuna borulu verilerden sadece 1 satır, boru verileri. tail

   TOTAL_LINES=`wc -l $USER_FILE | cut -d " " -f1 `
   echo $TOTAL_LINES       # To validate total lines in the file

   for (( i=1 ; i <= $TOTAL_LINES; i++ ))
   do
      LINE=`head -n$i $USER_FILE | tail -n1`
      echo $LINE
   done

1
Bunu yapma. Satır numaralarının üzerinden geçmek ve her bir satırı sedveya head+ tailile almak inanılmaz derecede verimsizdir ve elbette buradaki diğer çözümlerden birini neden kullanmadığınız sorusuna yalvarır. Satır numarasını bilmeniz gerekiyorsa, döngünüze bir sayaç ekleyin while read -rveya nl -badöngüden önce her satıra bir satır numarası öneki eklemek için kullanın .
tripleee

-1

@Peter: Bu sizin için işe yarayabilir-

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

Bu, çıktı

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL


3
Bu cevap, yukarıdaki iyi cevapların belirlediği tüm ilkeleri yenmektedir!
codeforester

3
Lütfen bu yanıtı silin.
dawg

3
Şimdi beyler, abartma. Cevap kötü, ama en azından basit kullanım durumlarında işe yarıyor gibi görünüyor. Sağlandığı müddetçe, kötü bir cevap olmak cevabın var olma hakkını ortadan kaldırmaz.
Egor Hans

3
@EgorHans, kesinlikle katılmıyorum: Cevaplar insanlara nasıl yazılım yazacaklarını öğretmektir. Eğer bir şekilde şeyler yapmak için insanları öğretmek biliyorum onlara zararlıdır ve yazılımlarını kullanan herkes (tanıtan böcek / beklenmeyen davranışlar / vs) bilerek başkalarının zarar veriyor. Zararlı olduğu bilinen bir cevabın, iyi seçilmiş bir öğretim kaynağında "var olma hakkı" yoktur (ve onu seçmenin, oylama ve işaretleme yapan bizler, burada ne yapmamız gerekiyordu).
Charles Duffy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.