11 Pertanyaan: Looping melalui isi file di Bash

pertanyaan dibuat di Tue, Jun 19, 2018 12:00 AM

Bagaimana cara saya mengulangi setiap baris file teks dengan Bash ?

Dengan skrip ini:

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

Saya mendapatkan output ini di layar:

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

(Nanti saya ingin melakukan sesuatu yang lebih rumit dengan $p dari sekadar output ke layar.)


Variabel lingkungan SHELL adalah (dari env):

 
SHELL=/bin/bash

/bin/bash --version keluaran:

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

cat /proc/version keluaran:

 
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

File peptides.txt berisi:

 
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
    
1154
  1. Oh, saya melihat banyak hal telah terjadi di sini: semua komentar telah dihapus dan pertanyaan dibuka kembali. Hanya untuk referensi, jawaban yang diterima di Baca file baris demi baris yang menetapkan nilai ke variabel menangani masalah dalam kanonik jalan dan harus lebih disukai daripada yang diterima di sini.
    2016-08-30 09: 44: 09Z
11 Jawaban                              11                         

Salah satu cara untuk melakukannya adalah:

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

Seperti yang ditunjukkan dalam komentar, ini memiliki efek samping memangkas spasi putih terkemuka, menginterpretasikan urutan backslash, dan melewatkan garis trailing jika tidak ada linefeed terminasi. Jika ini masalah, Anda dapat melakukannya:

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

Luar biasa, jika badan loop dapat membaca dari input standar , Anda dapat membuka file menggunakan deskriptor file yang berbeda:

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

Di sini, 10 hanyalah angka yang berubah-ubah (berbeda dari 0, 1, 2).

    
1825
2018-08-20 20: 55: 56Z
  1. Bagaimana seharusnya saya mengartikan baris terakhir? File peptides.txt dialihkan ke input standar dan entah bagaimana ke seluruh blok while?
    2009-10-05 18: 16: 05Z
  2. "Serup peptides.txt ke loop sementara ini, jadi perintah 'baca' memiliki sesuatu untuk dikonsumsi." Metode "cat" saya mirip, mengirimkan output dari perintah ke blok sementara untuk konsumsi dengan 'membaca', juga, hanya meluncurkan program lain untuk menyelesaikan pekerjaan.
    2009-10-05 18: 30: 07Z
  3. Metode ini tampaknya melewati baris terakhir file.
    2013-11-07 07: 48: 39Z
  4. Kutip dua barisnya !! gema "$p" dan file .. percayalah itu akan menggigit Anda jika Anda tidak !!! AKU TAHU! lol
    2014-08-19 17: 01: 57Z
  5. Kedua versi gagal membaca baris terakhir jika tidak diakhiri dengan baris baru. Selalu gunakan while read p || [[ -n $p ]]; do ...
    2016-09-07 14: 15: 52Z
 
cat peptides.txt | while read line
do
   # do something with $line here
done
    
355
2009-10-05 17: 54: 38Z
  1. Secara umum, jika Anda menggunakan "cat" dengan hanya satu argumen, Anda melakukan sesuatu yang salah (atau kurang optimal).
    2009-10-05 18: 02: 21Z
  2. Ya, itu tidak seefisien Bruno, karena meluncurkan program lain, tidak perlu. Jika efisiensi penting, lakukan dengan Bruno. Saya ingat cara saya menjadikarena Anda dapat menggunakannya dengan perintah lain, di mana sintaks "redirect masuk dari" tidak berfungsi.
    2009-10-05 18: 12: 45Z
  3. Ada masalah lain yang lebih serius dengan ini: karena loop sementara adalah bagian dari pipa, ia berjalan dalam subkulit, dan karenanya setiap variabel yang diatur dalam loop hilang ketika keluar (lihat bash-hackers.org/wiki/doku.php /mirroring /bashfaq /024 ). Ini bisa sangat menjengkelkan (tergantung pada apa yang Anda coba lakukan dalam loop).
    2009-10-06 00: 57: 27Z
  4. Saya menggunakan "cat file |" sebagai awal dari banyak perintah saya semata-mata karena saya sering membuat prototipe dengan "head file |"
    2014-02-26 21: 33: 56Z
  5. Ini mungkin tidak efisien, tetapi jauh lebih mudah dibaca daripada jawaban lain.
    2014-12-22 13: 02: 48Z

Opsi 1a: Loop sementara: Satu baris sekaligus: Pengalihan input

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

Opsi 1b: Loop sementara: Satu baris sekaligus:
Buka file, baca dari deskriptor file (dalam hal ini deskriptor file # 4).

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

Opsi 2: Untuk pengulangan: Baca file menjadi variabel tunggal dan parsing.
Sintaks ini akan mengurai "garis" berdasarkan spasi putih di antara token. Ini masih berfungsi karena baris file input yang diberikan adalah token kata tunggal. Jika ada lebih dari satu token per baris, maka metode ini tidak akan berfungsi. Juga, membaca file lengkap menjadi satu variabel bukanlah strategi yang baik untuk file besar.

 
#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
    echo $line
done
    
132
2018-07-06 10: 42: 03Z
  1. Untuk opsi 1b: apakah deskriptor file perlu ditutup lagi? Misalnya. loop bisa menjadi loop dalam.
    2009-10-05 20: 03: 56Z
  2. File deskriptor akan dibersihkan dengan proses keluar. Penutupan eksplisit dapat dilakukan untuk menggunakan kembali nomor fd. Untuk menutup fd, gunakan eksekutif lain dengan sintaks & -, seperti ini: exec 4 < & -
    2009-10-05 21: 09: 15Z
  3. Terima kasih untuk Opsi 2. Saya mengalami masalah besar dengan Opsi 1 karena saya perlu membaca dari stdin di dalam loop; dalam kasus seperti itu Opsi 1 tidak akan berfungsi.
    2014-06-04 13: 50: 49Z
  4. Anda harus menunjukkan dengan lebih jelas bahwa Opsi 2 adalah dengan kuat berkecil hati . @masgo Opsi 1b harus berfungsi dalam kasus itu, dan dapat dikombinasikan dengan sintaks redirection input dari Opsi 1a dengan mengganti done < $filename dengan done 4<$filename (yang berguna jika Anda ingin membaca nama file dari parameter perintah, dalam hal ini Anda hanya bisa ganti $filename dengan $1).
    2017-11-12 16: 44: 57Z
  5. Saya perlu mengulang isi file seperti tail -n +2 myfile.txt | grep 'somepattern' | cut -f3, saat menjalankan perintah ssh di dalam loop (mengkonsumsi stdin); opsi 2 di sini tampaknya merupakan satu-satunya cara?
    2018-11-12 23: 21: 43Z

Ini tidak lebih baik dari jawaban lain, tetapi merupakan satu lagi cara untuk menyelesaikan pekerjaan dalam file tanpa spasi (lihat komentar). Saya menemukan bahwa saya sering perlu satu kalimat untuk menggali daftar dalam file teks tanpa langkah tambahan menggunakan file skrip terpisah.

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

Format ini memungkinkan saya untuk meletakkan semuanya dalam satu baris perintah. Ubah bagian "echo $word" menjadi apa pun yang Anda inginkan dan Anda dapat mengeluarkan beberapa komands dipisahkan oleh titik koma. Contoh berikut menggunakan konten file sebagai argumen ke dua skrip lain yang mungkin Anda tulis.

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

Atau jika Anda ingin menggunakan ini seperti editor aliran (pelajari sed) Anda dapat membuang hasilnya ke file lain sebagai berikut.

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

Saya menggunakan ini seperti yang ditulis di atas karena saya telah menggunakan file teks tempat saya membuatnya dengan satu kata per baris. (Lihat komentar) Jika Anda memiliki spasi yang tidak ingin Anda pisahkan kata-kata /kalimat Anda, itu akan menjadi sedikit lebih buruk, tetapi perintah yang sama masih berfungsi sebagai berikut:

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

Ini hanya memberi tahu shell untuk membagi pada baris baru saja, bukan spasi, lalu mengembalikan lingkungan kembali ke apa yang sebelumnya. Pada titik ini, Anda mungkin ingin mempertimbangkan untuk memasukkan semuanya ke dalam skrip shell daripada memerasnya menjadi satu baris.

Semoga berhasil!

    
71
2013-12-22 15: 47: 48Z
  1. Bash $(< peptides.txt) mungkin lebih elegan, tetapi masih salah, apa yang dikatakan Joao benar, Anda melakukan logika substitusi perintah di mana spasi atau baris baru adalah hal yang sama. Jika sebuah baris memiliki spasi di dalamnya, loop mengeksekusi DUA KALI atau lebih untuk satu baris. Jadi kode Anda harus membaca dengan benar: untuk kata dalam $(< peptides.txt); lakukan .... Jika Anda tahu pasti tidak ada spasi, maka satu baris sama dengan satu kata dan Anda baik-baik saja.
    2013-12-08 17: 58: 50Z
  2. @ JoaoCosta, maxpolk: Poin bagus yang belum saya pertimbangkan. Saya telah mengedit posting asli untuk mencerminkan mereka. Terima kasih!
    2013-12-22 15: 49: 26Z
  3. Menggunakan for membuat token input /baris tunduk pada ekspansi shell, yang biasanya tidak diinginkan; coba ini: for l in $(echo '* b c'); do echo "[$l]"; done - seperti yang akan Anda lihat, * - meskipun awalnya literal dikutip - diperluas ke file dalam direktori saat ini.
    2013-12-22 16: 09: 52Z
  4. @ dblanchard: Contoh terakhir, menggunakan $IFS, harus mengabaikan spasi. Sudahkah Anda mencoba versi itu?
    2015-11-24 00: 53: 16Z
  5. Cara bagaimana perintah ini menjadi jauh lebih kompleks karena masalah-masalah penting telah diperbaiki, menyajikan dengan sangat baik mengapa menggunakan for untuk beralih baris file adalah ide yang buruk. Plus, aspek ekspansi yang disebutkan oleh @ mklement0 (meskipun itu mungkin dapat dielakkan dengan membawa tanda kutip yang lolos, yang lagi-lagi membuat hal-hal menjadi lebih kompleks dan kurang dapat dibaca).
    2017-11-12 14: 23: 33Z

Beberapa hal lagi yang tidak dicakup oleh jawaban lain:

Membaca dari file yang dibatasi

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

Membaca dari output perintah lain, menggunakan subtitusi proses

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

Pendekatan ini lebih baik daripada command ... | while read -r line; do ... karena loop sementara di sini berjalan di shell saat ini daripada subkulit seperti dalam kasus yang terakhir. Lihat posting terkait Variabel yang dimodifikasi di dalam loop sementara adalah tidak diingat .

Membaca dari input terbatas nol, misalnya 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)

Bacaan terkait: BashFAQ /020 - Bagaimana saya bisa menemukan dan dengan aman menangani nama file yang mengandung baris baru, spasi atau keduanya ?

Membaca dari lebih dari satu file sekaligus

 
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

Berdasarkan pada @ chepner's , jawab di sini :

-u adalah ekstensi bash. Untuk kompatibilitas POSIX, setiap panggilan akan terlihat seperti read -r X <&3.

Membaca seluruh file menjadi array (versi Bash lebih awal hingga 4)

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

Jika file berakhir dengan baris yang tidak lengkap (baris baru hilang di akhir), maka:

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

Membaca seluruh file menjadi sebuah array (Bash versi 4x dan yang lebih baru)

 
readarray -t my_array < my_file

atau

 
mapfile -t my_array < my_file

Dan kemudian

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

Kiriman terkait:

58
2018-10-26 05: 40: 34Z
  1. perhatikan bahwa alih-alih command < input_filename.txt Anda selalu dapat melakukan input_generating_command | command atau command < <(input_generating_command)
    2019-03-07 14: 00: 26Z

Gunakan loop sementara, seperti ini:

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

Catatan:

  1. Jika Anda tidak mengatur IFS dengan benar, Anda akan kehilangan indentasi.

  2. Anda hampir selalu harus menggunakan opsi -r dengan membaca.

  3. Jangan membaca baris dengan for

42
2017-03-29 00: 10: 24Z
  1. Mengapa opsi -r?
    2015-06-23 02: 31: 22Z
  2. @ DavidC.Rankin Opsi -r mencegah interpretasi backslash. Note #2 adalah tautan yang dijelaskan secara terperinci ...
    2015-06-23 06: 01: 40Z
  3. Gabungkan ini dengan opsi "read -u" di jawaban lain dan kemudian sempurna.
    2017-02-17 00: 06: 23Z
  4. @ FlorinAndrei: Contoh di atas tidak memerlukan opsi -u, apakah Anda berbicara tentang contoh lain dengan -u?
    2017-02-17 05: 37: 47Z
  5. Melihat melalui tautan Anda, dan terkejut tidak ada jawaban yang hanya menautkan tautan Anda di Catatan 2. Halaman itu menyediakan semua yang perlu Anda ketahui tentang subjek itu. Atau apakah jawaban hanya tautan tidak disarankan atau semacamnya?
    2017-11-12 16: 49: 02Z

Jika Anda tidak ingin bacaan Anda terbelah oleh karakter baris baru, gunakan -

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

Kemudian jalankan skrip dengan nama file sebagai parameter.

    
13
2016-03-08 16: 10: 51Z

Misalkan Anda memiliki file ini:

 
$ 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

Ada empat elemen yang akan mengubah arti dari output file yang dibaca oleh banyak solusi Bash:

  1. Baris kosong 4;
  2. Memimpin atau mengekor spasi pada dua baris;
  3. Mempertahankan makna setiap baris (mis., setiap baris adalah catatan);
  4. Baris 6 tidak diakhiri dengan CR.

Jika Anda ingin file teks baris demi baris termasuk baris kosong dan mengakhiri baris tanpa CR, Anda harus menggunakan loop sementara dan Anda harus memiliki tes alternatif untuk baris terakhir.

Berikut adalah metode yang dapat mengubah file (dibandingkan dengan apa yang dikembalikan cat):

1) Menurunkan baris terakhir dan memimpin dan trspasi yang sakit:

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

(Jika Anda melakukan while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt sebagai gantinya, Anda mempertahankan spasi awal dan akhir tetapi masih kehilangan baris terakhir jika tidak diakhiri dengan CR)

2) Menggunakan subtitusi proses dengan cat akan membaca seluruh file dalam satu tegukan dan kehilangan arti setiap baris:

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

(Jika Anda menghapus " dari $(cat /tmp/test.txt), Anda membaca file kata demi kata alih-alih satu tegukan. Juga mungkin bukan yang dimaksudkan ...)


Cara paling kuat dan paling sederhana untuk membaca file baris demi baris dan mempertahankan semua spasi adalah:

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

Jika Anda ingin menghapus ruang terkemuka dan berdagang, hapus bagian IFS=:

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

(File teks tanpa penghentian \n, sementara cukup umum, dianggap rusak di bawah POSIX. Jika Anda dapat mengandalkan \n yang tertinggal, Anda tidak perlu || [[ -n $line ]] dalam loop while.)

Lebih banyak di BASH FAQ

    
13
2017-05-02 17: 17: 53Z
  1. Saya, saya bertanya mengapa downvote?
    2018-11-04 16: 30: 40Z
 
#!/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
2014-03-24 17: 57: 03Z
  1. Jawaban ini membutuhkan peringatan yang disebutkan dalam jawaban mayypile , dan bisa gagal jika ada baris berisi karakter meta shell (karena tanda kutip "$x").
    2015-06-08 16: 32: 22Z
  2. Saya benar-benar terkejut orang belum menemukan Jangan membaca baris dengan untuk ...
    2017-11-12 14: 17: 01Z

Ini adalah contoh kehidupan nyata saya bagaimana membuat loop dari output program lain, memeriksa substring, menjatuhkan tanda kutip ganda dari variabel, menggunakan variabel itu di luar loop. Saya kira cukup banyak yang menanyakan pertanyaan ini cepat atau lambat.

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

Deklarasikan variabel di luar loop, tetapkan nilai dan gunakan di luar loop membutuhkan dilakukan < < < "$(...)" sintaksis. Aplikasi perlu dijalankan dalam konteks konsol saat ini. Kutipan di sekitar perintah menjaga baris arus keluaran baru.

Lingkaran yang cocok untuk substring kemudian membaca pasangan name = value , membagi bagian sisi kanan dari karakter = terakhir, menjatuhkan kutipan pertama, menjatuhkan kutipan terakhir, kami memiliki pembersihan nilai untuk digunakan di tempat lain.

    
3
2015-06-30 08: 15: 45Z
  1. Sementara jawabannya benar, saya mengerti bagaimana akhirnya di sini. Metode esensial sama seperti yang diusulkan oleh banyak jawaban lain. Plus, itu benar-benar tenggelam dalam contoh FPS Anda.
    2017-11-12 14: 14: 37Z

@Peter: Ini bisa berhasil untuk Anda-

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

Ini akan mengembalikan output-

 
Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
    
0
2015-08-30 05: 00: 05Z
  1. 2016-06-16 10: 43: 25Z
  2. Jawaban ini mengalahkan semua prinsip yang ditetapkan olehjawaban bagus di atas!
    2017-01-14 02: 55: 12Z
  3. Silakan hapus jawaban ini.
    2017-05-02 17: 18: 50Z
  4. Sekarang teman-teman, jangan melebih-lebihkan. Jawabannya buruk, tetapi tampaknya berhasil, setidaknya untuk kasus penggunaan sederhana. Selama itu diberikan, menjadi jawaban yang buruk tidak menghilangkan hak untuk ada.
    2017-11-12 14: 08: 38Z
  5. @ EgorHans, saya sangat tidak setuju: Inti dari jawabannya adalah mengajar orang cara menulis perangkat lunak. Mengajar orang untuk melakukan hal-hal dengan cara yang Anda tahu berbahaya bagi mereka dan orang-orang yang menggunakan perangkat lunak mereka (memperkenalkan bug /perilaku tak terduga /dll) dengan sengaja merugikan orang lain. Sebuah jawaban yang diketahui berbahaya tidak memiliki "hak untuk ada" dalam sumber daya pengajaran yang dikuratori dengan baik (dan mengkuratorinya adalah apa yang seharusnya kita, orang-orang yang pilih dan pilih, lakukan di sini).
    2018-09-20 16: 36: 07Z
sumber ditempatkan sini
Pertanyaan Lain
11
Looping melalui isi file di Bash
tanya 1 tahun yang lalu
5
android-buat panggilan whatsapp
tanya 2 tahun yang lalu