Baca file baris demi baris yang menetapkan nilai ke variabel


753

Saya memiliki file .txt berikut:

Marco
Paolo
Antonio

Saya ingin membacanya baris demi baris, dan untuk setiap baris saya ingin menetapkan nilai baris .txt ke suatu variabel. Misalkan variabel saya adalah $name, alurnya adalah:

  • Baca baris pertama dari file
  • Tetapkan $name= "Marco"
  • Lakukan beberapa tugas dengan $name
  • Baca baris kedua dari file
  • Tetapkan $name= "Paolo"


3
Bisakah pertanyaan-pertanyaan itu digabungkan? Keduanya memiliki beberapa jawaban yang sangat baik yang menyoroti berbagai aspek masalah, jawaban yang buruk memiliki penjelasan mendalam di komentar apa yang buruk tentang mereka, dan sampai sekarang Anda tidak dapat benar-benar mendapatkan gambaran keseluruhan tentang apa yang harus dipertimbangkan, dari jawaban dari satu pertanyaan dari pasangan. Akan sangat membantu untuk memiliki semuanya di satu tempat, alih-alih terbagi menjadi 2 halaman.
Egor Hans

Jawaban:


1357

Berikut ini membaca file yang diteruskan sebagai argumen baris demi baris:

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

Ini adalah bentuk standar untuk membaca baris dari file dalam satu lingkaran. Penjelasan:

  • IFS=(atau IFS='') mencegah spasi spasi awalan / jejak.
  • -r mencegah backslash lolos dari ditafsirkan.

Atau Anda bisa memasukkannya ke skrip pembantu file bash, contoh konten:

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

Jika di atas disimpan ke skrip dengan nama file readfile, itu dapat dijalankan sebagai berikut:

chmod +x readfile
./readfile filename.txt

Jika file tersebut bukan file teks POSIX standar (= tidak diakhiri oleh karakter baris baru), loop dapat dimodifikasi untuk menangani trailing sebagian baris:

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

Di sini, || [[ -n $line ]]mencegah baris terakhir dari diabaikan jika tidak diakhiri dengan \n(karena readmengembalikan kode keluar non-nol ketika bertemu EOF).

Jika perintah-perintah di dalam loop juga membaca dari input standar, deskriptor file yang digunakan oleh readbisa kebetulan ke yang lain (hindari deskriptor file standar ), misal:

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

(Kerang Non-Bash mungkin tidak tahu read -u3; gunakan read <&3saja.)


23
Ada peringatan dengan metode ini. Jika sesuatu di dalam loop while bersifat interaktif (mis. Membaca dari stdin), maka ia akan mengambil input dari $ 1. Anda tidak akan diberi kesempatan untuk memasukkan data secara manual.
carpie

10
Dari catatan - beberapa perintah istirahat (seperti dalam, mereka memutus loop) ini. Misalnya, sshtanpa -nbendera akan secara efektif menyebabkan Anda lolos dari loop. Mungkin ada alasan bagus untuk ini, tetapi perlu beberapa saat untuk menentukan apa yang menyebabkan kode saya gagal sebelum saya menemukan ini.
Alex

6
sebagai satu-baris: sedangkan IFS = '' baca -r baris || [[-n "$ line"]]; lakukan echo "$ line"; dilakukan <nama file
Joseph Johnson

8
@ OndraŽižka, itu disebabkan oleh ffmpegkonsumsi stdin. Tambahkan </dev/nullke ffmpegbaris Anda dan itu tidak akan bisa, atau menggunakan FD alternatif untuk loop. Pendekatan "alternatif FD" itu seperti while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy

9
menggerutu re: menasihati .shekstensi. Executables di UNIX biasanya tidak memiliki ekstensi sama sekali (Anda tidak menjalankan ls.elf), dan memiliki bash shebang (dan bash-only tooling seperti [[ ]]) dan ekstensi yang menyiratkan kompatibilitas POSIX sh secara internal bertentangan.
Charles Duffy

309

Saya mendorong Anda untuk menggunakan -rbendera readyang merupakan kependekan dari:

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

Saya mengutip dari man 1 read.

Hal lain adalah mengambil nama file sebagai argumen.

Berikut kode yang diperbarui:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

4
Trims memimpin dan mengikuti ruang dari garis
barfuin

@ Thomas dan apa yang terjadi pada spasi di tengah? Petunjuk: Upaya eksekusi perintah yang tidak diinginkan.
kmarsh

1
Ini bekerja untuk saya, berbeda dengan jawaban yang diterima.
Neurotransmitter

3
@ TranslucentCloud, jika ini berhasil dan jawaban yang diterima tidak, saya curiga shell Anda shtidak bash; perintah tes diperpanjang yang digunakan dalam || [[ -n "$line" ]]sintaks dalam jawaban yang diterima adalah bashism. Yang mengatakan, sintaksis itu sebenarnya memiliki makna yang relevan: Itu menyebabkan loop untuk melanjutkan untuk baris terakhir dalam file input bahkan jika itu tidak memiliki baris baru. Jika Anda ingin melakukannya dengan cara yang sesuai dengan POSIX, Anda ingin || [ -n "$line" ], menggunakan [daripada [[.
Charles Duffy

3
Yang mengatakan, ini tidak masih perlu dimodifikasi untuk set IFS=untuk readmencegah pemangkasan spasi.
Charles Duffy

132

Menggunakan templat Bash berikut akan memungkinkan Anda membaca satu nilai pada satu waktu dari suatu file dan memprosesnya.

while read name; do
    # Do what you want to $name
done < filename

14
sebagai one-liner: saat membaca nama; lakukan echo $ {name}; dilakukan <nama file
Joseph Johnson

4
@ CalculusKnight, itu hanya "berhasil" karena Anda tidak menggunakan data yang cukup menarik untuk diuji. Coba konten dengan garis miring terbalik, atau memiliki garis yang hanya berisi *.
Charles Duffy

7
@Matthias, asumsi yang akhirnya berubah menjadi salah adalah salah satu sumber bug terbesar, baik yang berdampak pada keamanan dan sebaliknya. Peristiwa kehilangan data terbesar yang pernah saya lihat adalah karena skenario seseorang diasumsikan akan "benar-benar tidak pernah muncul" - buffer overflow membuang memori acak ke dalam buffer yang digunakan untuk menamai file, menyebabkan skrip yang membuat asumsi tentang nama mana yang mungkin pernah ada kebetulan memiliki perilaku yang sangat, sangat disayangkan.
Charles Duffy

5
@Matthias, ... dan itu terutama benar di sini, karena contoh kode yang ditampilkan di StackOverflow dimaksudkan untuk digunakan sebagai alat pengajaran, bagi orang-orang untuk menggunakan kembali pola dalam pekerjaan mereka sendiri!
Charles Duffy

5
@Matthias, saya sama sekali tidak setuju dengan klaim bahwa "Anda hanya harus menyusun kode Anda untuk data yang Anda harapkan". Kasing tak terduga adalah lokasi bug Anda, di mana kerentanan keamanan Anda berada - penanganannya adalah perbedaan antara kode slapdash dan kode yang kuat. Memang, penanganan itu tidak perlu mewah - itu bisa saja "keluar dengan kesalahan" - tetapi jika Anda tidak memiliki penanganan sama sekali, maka perilaku Anda dalam kasus yang tidak terduga tidak ditentukan.
Charles Duffy

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
Tidak ada yang menentang jawaban lain, mungkin mereka lebih canggih, tetapi saya membenarkan jawaban ini karena sederhana, mudah dibaca dan cukup untuk apa yang saya butuhkan. Perhatikan bahwa, agar bisa berfungsi, file teks yang akan dibaca harus diakhiri dengan baris kosong (yaitu orang harus menekan Entersetelah baris terakhir), jika tidak, baris terakhir akan diabaikan. Setidaknya itulah yang terjadi pada saya.
Antonio Vinicius Menezes Medei

12
Penggunaan kucing yang tidak berguna, shurely?
Brian Agnew

5
Dan kutipannya rusak; dan Anda tidak boleh menggunakan nama variabel huruf besar karena itu disediakan untuk penggunaan sistem.
tripleee

7
@AntonioViniciusMenezesMedei, ... apalagi, saya telah melihat orang-orang mengalami kerugian finansial karena mereka menganggap peringatan ini tidak akan pernah berarti bagi mereka; gagal mempelajari praktik yang baik; dan kemudian mengikuti kebiasaan mereka ketika menulis skrip yang mengelola cadangan data penagihan kritis. Belajar melakukan hal yang benar itu penting.
Charles Duffy

6
Masalah lain di sini adalah bahwa pipa membuka subkulit baru, yaitu semua variabel yang ditetapkan di dalam loop tidak dapat dibaca setelah loop selesai.
mxmlnkn

20

Banyak orang memposting solusi yang terlalu dioptimalkan. Saya tidak berpikir itu salah, tetapi saya dengan rendah hati berpikir bahwa solusi yang kurang dioptimalkan akan diinginkan untuk memungkinkan semua orang dengan mudah memahami bagaimana ini bekerja. Ini proposal saya:

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

20

Menggunakan:

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

Jika Anda menetapkan IFSberbeda, Anda akan mendapatkan hasil yang aneh.


34
Ini adalah metode yang mengerikan . Tolong jangan menggunakannya kecuali Anda ingin memiliki masalah dengan globbing yang akan terjadi sebelum Anda menyadarinya!
gniourf_gniourf

13
@ MUYBelgium apakah Anda mencoba dengan file yang berisi satu *pada satu baris? Bagaimanapun, ini adalah antipattern . Jangan baca baris dengan untuk .
gniourf_gniourf

2
@ OndraŽižka, readpendekatan ini adalah pendekatan praktik terbaik dengan konsensus komunitas . Peringatan yang Anda sebutkan dalam komentar Anda adalah salah satu yang berlaku ketika loop Anda menjalankan perintah (seperti ffmpeg) yang membaca dari stdin, diselesaikan secara sepele dengan menggunakan FD non-stdin untuk loop atau mengarahkan input perintah tersebut. Sebaliknya, mengatasi bug globbing dalam forpendekatan -loop Anda berarti membuat (dan kemudian perlu membalikkan) perubahan pengaturan global shell.
Charles Duffy

1
@ OndraŽižka, ... selain itu, forpendekatan loop yang Anda gunakan di sini berarti bahwa semua konten harus dibaca sebelum loop dapat mulai dijalankan sama sekali, menjadikannya sama sekali tidak dapat digunakan jika Anda mengulang-ulang gigabyte data bahkan jika Anda telah menonaktifkan globbing; yang while readlingkaran perlu menyimpan tidak lebih dari data yang satu baris pada satu waktu, yang berarti dapat mulai mengeksekusi sedangkan konten pembangkit sub proses masih berjalan (sehingga menjadi dapat digunakan untuk tujuan mengalir), dan juga memiliki konsumsi memori terbatas.
Charles Duffy

1
Sebenarnya, bahkan whilependekatan berbasis tampaknya memiliki * masalah karakter. Lihat komentar dari jawaban yang diterima di atas. Namun, tidak membantah iterasi atas file sebagai antipattern.
Egor Hans

9

Jika Anda perlu memproses file input dan input pengguna (atau apa pun dari stdin), maka gunakan solusi berikut:

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

Berdasarkan jawaban yang diterima dan pada tutorial pengalihan bash-hacker .

Di sini, kami membuka file deskriptor 3 untuk file yang dikirimkan sebagai argumen skrip dan meminta readuntuk menggunakan deskriptor ini sebagai input ( -u 3). Dengan demikian, kita membiarkan deskriptor input default (0) melekat pada terminal atau sumber input lain, dapat membaca input pengguna.


7

Untuk penanganan kesalahan yang tepat:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}

Bisakah Anda menambahkan beberapa penjelasan?
Tyler Christian

Sayangnya ia melewatkan baris terakhir dalam file.
ungalcrys

... dan juga, karena kurangnya kutipan, garis hijau yang mengandung wildcard - seperti yang dijelaskan dalam BashPitfalls # 14 .
Charles Duffy

0

Berikut ini hanya akan mencetak konten file:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
Jawaban ini benar-benar tidak menambahkan apa pun dari jawaban yang ada, tidak berfungsi karena kesalahan ketik / bug, dan rusak dalam banyak hal.
Konrad Rudolph

0

Gunakan alat IFS (pemisah bidang internal) di bash, tentukan karakter yang digunakan untuk memisahkan garis menjadi token, secara default termasuk < tab > / < space > / < newLine >

langkah 1 : Muat data file dan masukkan ke dalam daftar:

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

langkah 2 : sekarang iterate dan cetak output:

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

gema indeks spesifik dalam array : Mengakses ke variabel dalam array:

echo "${array[0]}"
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.