Đọc một dòng tệp bằng cách gán giá trị cho một biến


753

Tôi có tệp .txt sau:

Marco
Paolo
Antonio

Tôi muốn đọc từng dòng một và cho mỗi dòng tôi muốn gán giá trị dòng .txt cho một biến. Giả sử biến của tôi là $name, luồng là:

  • Đọc dòng đầu tiên từ tập tin
  • Chỉ định $name= "Marco"
  • Làm một số nhiệm vụ với $name
  • Đọc dòng thứ hai từ tập tin
  • Chỉ định $name= "Paolo"


3
Những câu hỏi có thể được hợp nhất bằng cách nào đó? Cả hai đều có một số câu trả lời thực sự tốt làm nổi bật các khía cạnh khác nhau của vấn đề, các câu trả lời xấu có giải thích sâu sắc trong các nhận xét về những gì xấu về chúng và đến bây giờ bạn không thể thực sự có được một cái nhìn tổng quan về những gì cần xem xét, từ các câu trả lời của một câu hỏi duy nhất từ ​​cặp. Sẽ rất hữu ích khi có tất cả trong một vị trí, thay vì được chia thành 2 trang.
Egor Hans

Câu trả lời:


1357

Sau đây đọc một tệp được truyền dưới dạng một dòng đối số theo dòng:

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

Đây là hình thức tiêu chuẩn để đọc các dòng từ một tệp trong một vòng lặp. Giải trình:

  • IFS=(hoặc IFS='') ngăn không gian hàng đầu / dấu vết bị cắt bớt.
  • -r ngăn chặn dấu gạch chéo ngược thoát khỏi bị giải thích.

Hoặc bạn có thể đặt nó trong tập lệnh trình trợ giúp tệp bash, nội dung ví dụ:

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

Nếu ở trên được lưu vào một tập lệnh có tên tệp readfile, nó có thể được chạy như sau:

chmod +x readfile
./readfile filename.txt

Nếu tệp không phải là tệp văn bản POSIX tiêu chuẩn (= không bị chấm dứt bởi ký tự dòng mới), vòng lặp có thể được sửa đổi để xử lý các dòng một phần:

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

Ở đây, || [[ -n $line ]]ngăn dòng cuối cùng bị bỏ qua nếu nó không kết thúc bằng một \n(vì readtrả về mã thoát khác không khi nó gặp EOF).

Nếu các lệnh bên trong vòng lặp cũng đọc từ đầu vào tiêu chuẩn, bộ mô tả tệp được sử dụng readcó thể được chuyển sang một thứ khác (tránh các bộ mô tả tệp tiêu chuẩn ), ví dụ:

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

(Các vỏ không phải Bash có thể không biết read -u3; sử dụng read <&3thay thế.)


23
Có một cảnh báo với phương pháp này. Nếu bất cứ điều gì bên trong vòng lặp while là tương tác (ví dụ đọc từ stdin), thì nó sẽ lấy đầu vào từ $ 1. Bạn sẽ không có cơ hội nhập dữ liệu theo cách thủ công.
Carpie

10
Lưu ý - một số lệnh phá vỡ (như trong, chúng phá vỡ vòng lặp) này. Ví dụ, sshkhông có -ncờ sẽ khiến bạn thoát khỏi vòng lặp một cách hiệu quả. Có lẽ có một lý do tốt cho việc này, nhưng phải mất một thời gian tôi mới hiểu được nguyên nhân khiến mã của tôi bị lỗi trước khi tôi phát hiện ra điều này.
Alex

6
dưới dạng một lớp lót: trong khi IFS = '' dòng đọc -r | | [[-n "$ line"]]; làm tiếng vang "$ line"; thực hiện <tên tệp
Joseph Johnson

8
@ OndraŽižka, đó là do ffmpegtiêu thụ stdin. Thêm </dev/nullvào ffmpegdòng của bạn và nó sẽ không thể hoặc sử dụng FD thay thế cho vòng lặp. Cách tiếp cận "thay thế FD" đó trông như thế nào while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy

9
càu nhàu : tư vấn .shmở rộng. Các tệp thực thi trên UNIX thường không có tiện ích mở rộng (bạn không chạy ls.elf) và có bash shebang (và công cụ chỉ dùng bash như [[ ]]) và một tiện ích mở rộng ngụ ý khả năng tương thích POSIX sh là mâu thuẫn trong nội bộ.
Charles Duffy

309

Tôi khuyến khích bạn sử dụng -rcờ readđại diện cho:

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

Tôi đang trích dẫn từ man 1 read .

Một điều nữa là lấy tên tệp làm đối số.

Đây là mã cập nhật:

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

4
Trims không gian hàng đầu và dấu vết từ dòng
barfuin

@Thomas và những gì xảy ra với không gian ở giữa? Gợi ý: Không mong muốn thực hiện lệnh.
kmarsh

1
Điều này làm việc cho tôi, trái ngược với câu trả lời được chấp nhận.
Chất dẫn truyền thần kinh

3
@TranslucentCloud, nếu điều này có hiệu quả và câu trả lời được chấp nhận thì không, tôi nghi ngờ rằng vỏ của bạn là sh, không phải bash; lệnh kiểm tra mở rộng được sử dụng trong || [[ -n "$line" ]]cú pháp trong câu trả lời được chấp nhận là một bashism. Điều đó nói rằng, cú pháp đó thực sự có ý nghĩa thích hợp: Nó khiến vòng lặp tiếp tục cho dòng cuối cùng trong tệp đầu vào ngay cả khi nó không có dòng mới. Nếu bạn muốn làm điều đó theo cách tuân thủ POSIX, bạn muốn || [ -n "$line" ], sử dụng [chứ không phải [[.
Charles Duffy

3
Điều đó nói rằng, đây không vẫn cần phải được sửa đổi để thiết lập IFS=cho readđể ngăn chặn cắt tỉa khoảng trắng.
Charles Duffy

132

Sử dụng mẫu Bash sau sẽ cho phép bạn đọc một giá trị tại một thời điểm từ một tệp và xử lý nó.

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

14
như một lót: trong khi đọc tên; làm vang $ {name}; thực hiện <tên tệp
Joseph Johnson

4
@CalculusKnight, nó chỉ "hoạt động" vì bạn không sử dụng đủ dữ liệu thú vị để kiểm tra. Hãy thử nội dung với dấu gạch chéo ngược hoặc có một dòng chỉ chứa *.
Charles Duffy

7
@Matthias, các giả định cuối cùng hóa ra là sai là một trong những nguồn lỗi lớn nhất, cả ảnh hưởng đến bảo mật và mặt khác. Sự kiện mất dữ liệu lớn nhất mà tôi từng thấy là do một kịch bản mà ai đó giả định sẽ "không bao giờ xuất hiện" - một bộ đệm tràn bộ nhớ ngẫu nhiên vào bộ đệm được sử dụng để đặt tên cho các tệp, gây ra một kịch bản đưa ra giả định về tên nào có thể xảy ra xảy ra có hành vi rất, rất đáng tiếc.
Charles Duffy

5
@Matthias, ... và điều đó đặc biệt đúng ở đây, vì các mẫu mã được hiển thị tại StackOverflow được dự định sẽ được sử dụng làm công cụ giảng dạy, để mọi người sử dụng lại các mẫu trong công việc của chính họ!
Charles Duffy

5
@Matthias, tôi hoàn toàn không đồng ý với tuyên bố rằng "bạn chỉ nên nghĩ ra mã của mình cho dữ liệu bạn mong đợi". Các trường hợp không mong đợi là lỗi của bạn, nơi có lỗ hổng bảo mật của bạn - xử lý chúng là sự khác biệt giữa mã slapdash và mã mạnh. Được cho phép, việc xử lý đó không cần phải cầu kỳ - đó chỉ có thể là "thoát với một lỗi" - nhưng nếu bạn không có cách xử lý nào, thì hành vi của bạn trong các trường hợp không mong muốn là không xác định.
Charles Duffy

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

8
Không có gì chống lại các câu trả lời khác, có thể chúng phức tạp hơn, nhưng tôi nêu lên câu trả lời này vì nó đơn giản, dễ đọc và đủ cho những gì tôi cần. Lưu ý rằng, để nó hoạt động, tệp văn bản cần đọc phải kết thúc bằng một dòng trống (tức là người ta cần nhấn Entersau dòng cuối cùng), nếu không dòng cuối cùng sẽ bị bỏ qua. Ít nhất đó là những gì đã xảy ra với tôi.
Antonio Vinicius Menezes Medei 16/2/2016

12
Sử dụng vô dụng của con mèo, một cách an toàn?
Brian Agnew

5
Và trích dẫn bị hỏng; và bạn không nên sử dụng tên biến chữ hoa vì chúng được dành riêng cho sử dụng hệ thống.
tripleee

7
@AntonioViniciusMenezesMedei, ... hơn nữa, tôi đã thấy mọi người chịu tổn thất tài chính vì họ cho rằng những cảnh báo này sẽ không bao giờ quan trọng với họ; không học được các thực hành tốt; và sau đó tuân theo những thói quen mà họ đã quen khi viết các tập lệnh quản lý sao lưu dữ liệu thanh toán quan trọng. Học cách làm những điều đúng là quan trọng.
Charles Duffy

6
Một vấn đề khác ở đây là đường ống mở một lớp con mới, tức là tất cả các biến được đặt bên trong vòng lặp không thể được đọc sau khi vòng lặp kết thúc.
mxmlnkn

20

Nhiều người đã đăng một giải pháp tối ưu hóa quá mức. Tôi không nghĩ rằng nó không chính xác, nhưng tôi khiêm tốn nghĩ rằng một giải pháp ít tối ưu hóa sẽ được mong muốn để cho phép mọi người dễ dàng hiểu cách thức hoạt động của nó. Đây là đề xuất của tôi:

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

Sử dụng:

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

Nếu bạn đã thiết lập IFSkhác nhau, bạn sẽ nhận được kết quả kỳ lạ.


34
Đây là một phương pháp kinh khủng . Vui lòng không sử dụng nó trừ khi bạn muốn có vấn đề với Globing sẽ diễn ra trước khi bạn nhận ra điều đó!
gniourf_gniourf

13
@MUYBelgium bạn đã thử với một tệp có chứa *một dòng trên một dòng không? Dù sao, đây là một antipotype . Đừng đọc các dòng với .
gniourf_gniourf

2
@ OndraŽižka, readcách tiếp cận là cách tiếp cận thực tiễn tốt nhất bởi sự đồng thuận của cộng đồng . Thông báo mà bạn đề cập trong nhận xét của mình là một thông báo áp dụng khi vòng lặp của bạn chạy các lệnh (chẳng hạn như ffmpeg) đọc từ stdin, được giải quyết một cách tầm thường bằng cách sử dụng FD không phải stdin cho vòng lặp hoặc chuyển hướng đầu vào của các lệnh đó. Ngược lại, làm việc xung quanh lỗi toàn cầu trong forcách tiếp cận -loop của bạn có nghĩa là thực hiện (và sau đó cần đảo ngược) thay đổi cài đặt toàn cầu.
Charles Duffy

1
@ OndraŽižka, ... hơn nữa, forcách tiếp cận vòng lặp bạn sử dụng ở đây có nghĩa là tất cả nội dung phải được đọc trước khi vòng lặp có thể bắt đầu thực thi, khiến nó hoàn toàn không sử dụng được nếu bạn lặp qua gigabyte dữ liệu ngay cả khi bạn đã tắt hình cầu; các while readvòng lặp cần phải lưu trữ không nhiều hơn dữ liệu một dòng duy nhất tại một thời điểm, có nghĩa là nó có thể bắt đầu thực hiện trong khi nội dung tạo tiến trình con vẫn chạy (như vậy là có thể sử dụng cho các mục đích truyền), và cũng có mức tiêu thụ bộ nhớ giới hạn.
Charles Duffy

1
Trên thực tế, các whilephương pháp tiếp cận dựa trên cơ sở dường như có vấn đề * -character. Xem ý kiến ​​của câu trả lời được chấp nhận ở trên. Mặc dù vậy, không tranh cãi về việc lặp đi lặp lại đối với các tệp là một antipotype.
Egor Hans

9

Nếu bạn cần xử lý cả tệp đầu vào và đầu vào của người dùng (hoặc bất cứ thứ gì khác từ stdin), thì hãy sử dụng giải pháp sau:

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

Dựa trên câu trả lời được chấp nhận và trên hướng dẫn chuyển hướng bash-hacker .

Ở đây, chúng tôi mở bộ mô tả tệp 3 cho tệp được truyền dưới dạng đối số tập lệnh và yêu readcầu sử dụng bộ mô tả này làm đầu vào ( -u 3). Do đó, chúng tôi để bộ mô tả đầu vào mặc định (0) được gắn vào một thiết bị đầu cuối hoặc nguồn đầu vào khác, có thể đọc đầu vào của người dùng.


7

Để xử lý lỗi thích hợp:

#!/bin/bash

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

Bạn có thể vui lòng thêm một số lời giải thích?
Tyler Christian

Thật không may, nó bỏ lỡ dòng cuối cùng trong tập tin.
ungalcrys

... Và cũng vì lý do thiếu trích dẫn, các dòng có chứa ký tự đại diện - như được mô tả trong BashPit thác # 14 .
Charles Duffy

0

Sau đây sẽ chỉ in ra nội dung của tập tin:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
Câu trả lời này thực sự không thêm bất cứ điều gì vào các câu trả lời hiện có, không hoạt động do lỗi chính tả / lỗi và phá vỡ theo nhiều cách.
Konrad Rudolph

0

Sử dụng công cụ IFS (dấu tách trường nội bộ) trong bash, xác định ký tự sử dụng để phân tách các dòng thành mã thông báo, theo mặc định bao gồm < tab > / <dấu cách > / < newLine >

Bước 1 : Tải dữ liệu tệp và chèn vào danh sách:

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

Bước 2 : bây giờ lặp lại và in kết quả đầu ra:

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

echo chỉ số cụ thể trong mảng : Truy cập vào một biến trong mảng:

echo "${array[0]}"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.