อ่านบรรทัดไฟล์โดยบรรทัดที่กำหนดค่าให้กับตัวแปร


752

ฉันมีไฟล์. txt ต่อไปนี้:

Marco
Paolo
Antonio

ฉันต้องการอ่านมันทีละบรรทัดและสำหรับแต่ละบรรทัดฉันต้องการกำหนดค่าบรรทัด. txt ให้กับตัวแปร หากว่าตัวแปรของฉันคือ$nameการไหลคือ:

  • อ่านบรรทัดแรกจากไฟล์
  • กำหนด$name= "มาร์โก"
  • ทำงานบางอย่างกับ $name
  • อ่านบรรทัดที่สองจากไฟล์
  • กำหนด$name= "เปาโล"

2
สามารถทำซ้ำการวน
andand

3
คำถามเหล่านั้นอาจถูกรวมเข้าด้วยกันได้ไหม? ทั้งสองมีคำตอบที่ดีจริง ๆ ที่เน้นแง่มุมต่าง ๆ ของปัญหาคำตอบที่ไม่ดีมีคำอธิบายในเชิงลึกในความคิดเห็นที่ไม่ดีเกี่ยวกับพวกเขาและ ณ ตอนนี้คุณไม่สามารถรับภาพรวมทั้งหมดเกี่ยวกับสิ่งที่ควรพิจารณาจากคำตอบของ คำถามเดียวจากคู่ มันจะมีประโยชน์หากมีทั้งหมดในที่เดียวแทนที่จะแยกเป็น 2 หน้า
Egor Hans

คำตอบ:


1357

ต่อไปนี้อ่านไฟล์ที่ส่งผ่านเป็นอาร์กิวเมนต์บรรทัดต่อบรรทัด:

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

นี่เป็นรูปแบบมาตรฐานสำหรับการอ่านบรรทัดจากไฟล์ในลูป คำอธิบาย:

  • IFS=(หรือIFS='') ป้องกันช่องว่างนำหน้า / ต่อท้ายไม่ให้ถูกตัดแต่ง
  • -r ป้องกัน backslash escapes จากการตีความ

หรือคุณสามารถวางไว้ในสคริปต์ผู้ช่วยเหลือไฟล์ bash เนื้อหาตัวอย่าง:

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

หากบันทึกไว้ข้างต้นในสคริปต์ด้วยชื่อไฟล์readfileมันสามารถเรียกใช้ดังนี้:

chmod +x readfile
./readfile filename.txt

หากไฟล์นั้นไม่ใช่ไฟล์ข้อความ POSIX มาตรฐาน (= ไม่สิ้นสุดด้วยอักขระขึ้นบรรทัดใหม่) คุณสามารถแก้ไขลูปเพื่อจัดการกับบางส่วนของบรรทัดที่ต่อท้ายได้:

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

ที่นี่|| [[ -n $line ]]ป้องกันไม่ให้บรรทัดสุดท้ายถูกละเว้นหากไม่ได้ลงท้ายด้วย a \n(เนื่องจากreadส่งคืนโค้ดออกที่ไม่เป็นศูนย์เมื่อพบ EOF)

หากคำสั่งภายในลูปยังอ่านจากอินพุตมาตรฐานตัวอธิบายไฟล์ที่ใช้โดยreadสามารถนำไปใช้กับสิ่งอื่นได้ (หลีกเลี่ยงตัวอธิบายไฟล์มาตรฐาน ) เช่น:

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

(อาจไม่ทราบว่าไม่ใช่ Bash shell ให้read -u3ใช้read <&3แทน)


23
มีข้อแม้ด้วยวิธีนี้ หากสิ่งใดในขณะที่ลูปเป็นแบบอินเทอร์แอคทีฟ (เช่นอ่านจาก stdin) ก็จะใช้อินพุตจาก $ 1 คุณจะไม่ได้รับโอกาสให้ป้อนข้อมูลด้วยตนเอง
Carpie

10
จากหมายเหตุ - คำสั่งบางคำสั่งแบ่ง (เช่นเดียวกับพวกมันจะแบ่งลูป) สิ่งนี้ ตัวอย่างเช่นหากsshไม่มีการ-nตั้งค่าสถานะจะทำให้คุณหนีจากลูปได้อย่างมีประสิทธิภาพ อาจมีเหตุผลที่ดีสำหรับสิ่งนี้ แต่มันใช้เวลาสักครู่หนึ่งในการจดบันทึกสิ่งที่ทำให้รหัสของฉันล้มเหลวก่อนที่ฉันจะค้นพบสิ่งนี้
อเล็กซ์

6
เป็นหนึ่งซับในขณะที่ IFS = '' read -r line || [[-n "$ line"]]; ทำเสียงสะท้อน "$ line"; เสร็จสิ้นแล้ว <ชื่อไฟล์
โจเซฟจอห์นสัน

8
@ OndraŽižkaนั่นเกิดจากการffmpegกิน stdin เพิ่ม</dev/nullในffmpegบรรทัดของคุณและจะไม่สามารถใช้หรือใช้ FD สำรองสำหรับการวนซ้ำ ว่า "สลับ FD" while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1"วิธีการลักษณะเหมือน
Charles Duffy

9
บ่นอีกครั้ง: ให้คำแนะนำ.shส่วนขยาย โดยทั่วไปแล้วไฟล์เอ็กซีคิวต์ใน UNIX จะไม่มีส่วนขยายเลย (คุณไม่ได้รันls.elf) และมี bash shebang (และเครื่องมือ bash-only เช่นเช่น[[ ]]) และส่วนขยายที่อ้างถึงความเข้ากันได้ของ POSIX sh นั้นขัดแย้งภายใน
Charles Duffy

309

ฉันขอแนะนำให้คุณใช้-rธงreadที่ย่อมาจาก:

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

man 1 readฉันกำลังอ้างจาก

อีกอย่างคือการใช้ชื่อไฟล์เป็นอาร์กิวเมนต์

นี่คือรหัสปรับปรุง:

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

4
ภายนอกข้อมูลรถนำและต่อท้ายจากบรรทัด
barfuin

@ โทมัสและเกิดอะไรขึ้นกับช่องว่างตรงกลาง? คำแนะนำ: การพยายามดำเนินการคำสั่งที่ไม่ต้องการ
kmarsh

1
สิ่งนี้ใช้ได้กับฉันตรงกันข้ามกับคำตอบที่ยอมรับได้
สารสื่อประสาท

3
@TranslucentCloud ถ้าทำงานนี้และคำตอบที่ได้รับการยอมรับไม่ได้ฉันสงสัยว่าเปลือกของคุณก็shไม่ได้bash; คำสั่งทดสอบเพิ่มเติมที่ใช้ใน|| [[ -n "$line" ]]ไวยากรณ์ในคำตอบที่ยอมรับคือการทุบตี ที่กล่าวว่าไวยากรณ์นั้นจริง ๆ แล้วมีความหมายที่เกี่ยวข้อง: มันทำให้เกิดการวนรอบต่อไปสำหรับบรรทัดสุดท้ายในไฟล์อินพุตแม้ว่ามันจะไม่ได้ขึ้นบรรทัดใหม่ หากคุณต้องการที่จะทำเช่นนั้นในทาง POSIX สอดคล้องที่คุณต้องการ|| [ -n "$line" ]ใช้มากกว่า[ [[
Charles Duffy

3
ที่กล่าวมานี้จะยังคงต้องมีการปรับเปลี่ยนชุดIFS=สำหรับreadช่องว่างเพื่อป้องกันการตัดแต่ง
Charles Duffy

132

การใช้เท็มเพลต Bash ต่อไปนี้จะช่วยให้คุณสามารถอ่านค่าหนึ่งค่าจากไฟล์และดำเนินการได้

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

14
เป็นสายการบินเดียว: ในขณะที่อ่านชื่อ; ทำ echo $ {name}; เสร็จสิ้นแล้ว <ชื่อไฟล์
โจเซฟจอห์นสัน

4
@CalculusKnight เป็นเพียง "ทำงาน" เพราะคุณไม่ได้ใช้ข้อมูลที่น่าสนใจเพียงพอในการทดสอบ *ลองเนื้อหาที่มีเครื่องหมายหรือมีสายที่มีเพียง
Charles Duffy

7
@ Matias, สมมติฐานที่ในที่สุดกลายเป็นเท็จเป็นหนึ่งในแหล่งใหญ่ที่สุดของข้อบกพร่องทั้งความปลอดภัยที่ส่งผลกระทบต่อและอื่น ๆ เหตุการณ์การสูญเสียข้อมูลที่ใหญ่ที่สุดที่ฉันเคยเห็นเป็นเพราะสถานการณ์สมมติว่ามีคนสันนิษฐานว่า "ไม่เคยเกิดขึ้นจริง" - บัฟเฟอร์ล้นหน่วยความจำแบบสุ่มทิ้งลงในบัฟเฟอร์ที่ใช้ในการตั้งชื่อไฟล์ทำให้สคริปต์ที่ทำให้สมมติฐาน มีพฤติกรรมที่โชคร้ายมาก
Charles Duffy

5
@Matthias, ... และนี่เป็นเรื่องจริงโดยเฉพาะอย่างยิ่งเนื่องจากตัวอย่างโค้ดที่แสดงที่ StackOverflow นั้นมีจุดประสงค์เพื่อใช้เป็นเครื่องมือในการสอน
Charles Duffy

5
@Matthias ฉันไม่เห็นด้วยอย่างยิ่งกับการอ้างสิทธิ์ว่า "คุณควรประดิษฐ์รหัสสำหรับข้อมูลที่คุณคาดหวัง" กรณีที่ไม่คาดคิดคือที่ที่บั๊กของคุณอยู่ที่จุดอ่อนด้านความปลอดภัยของคุณ - การจัดการมันคือความแตกต่างระหว่างรหัส slapdash และโค้ดที่มีประสิทธิภาพ จริงอยู่การจัดการนั้นไม่จำเป็นต้องเป็นเรื่องแฟนซี - มันเป็นเพียงแค่ "ทางออกด้วยข้อผิดพลาด" - แต่ถ้าคุณไม่มีการจัดการเลยพฤติกรรมของคุณในกรณีที่ไม่คาดคิดก็ไม่ได้กำหนดไว้
Charles Duffy

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

8
ไม่มีอะไรเทียบกับคำตอบอื่น ๆ บางทีพวกมันอาจจะซับซ้อนกว่านี้ แต่ฉันตอบโต้คำตอบนี้เพราะมันง่ายอ่านง่ายและเพียงพอสำหรับสิ่งที่ฉันต้องการ โปรดทราบว่าเพื่อให้สามารถใช้งานได้ไฟล์ข้อความที่จะอ่านจะต้องลงท้ายด้วยบรรทัดว่าง (นั่นคือต้องกดEnterหลังจากบรรทัดสุดท้าย) มิฉะนั้นบรรทัดสุดท้ายจะถูกละเว้น อย่างน้อยนั่นก็เป็นสิ่งที่เกิดขึ้นกับฉัน
Antonio Vinicius Menezes Medei

12
ใช้แมวอย่างไร้ประโยชน์เหรอ?
Brian Agnew

5
และข้อความก็ถูกทำลาย และคุณไม่ควรใช้ชื่อตัวแปรตัวพิมพ์ใหญ่เนื่องจากถูกสงวนไว้สำหรับการใช้งานระบบ
tripleee

7
@AntonioViniciusMenezesMedei, ... ยิ่งไปกว่านั้นฉันเคยเห็นคนรักษาความสูญเสียทางการเงินเพราะพวกเขาคิดว่าคำเตือนเหล่านี้จะไม่สำคัญกับพวกเขา ล้มเหลวในการเรียนรู้แนวปฏิบัติที่ดี จากนั้นจึงติดตามพฤติกรรมที่เคยใช้เมื่อเขียนสคริปต์ที่จัดการการสำรองข้อมูลข้อมูลการเรียกเก็บเงินที่สำคัญ การเรียนรู้ที่จะทำสิ่งที่ถูกต้องเป็นสิ่งสำคัญ
Charles Duffy

6
อีกปัญหาหนึ่งที่นี่คือไปป์ที่เปิด subshell ใหม่นั่นคือตัวแปรทั้งหมดที่ตั้งค่าภายในลูปไม่สามารถอ่านได้หลังจากลูปเสร็จสิ้น
mxmlnkn

20

มีหลายคนที่โพสต์โซลูชันที่ปรับให้เหมาะสม ฉันไม่คิดว่ามันไม่ถูกต้อง แต่ฉันคิดอย่างถ่อมตนว่าโซลูชันที่ได้รับการปรับให้เหมาะสมน้อยที่สุดจะเป็นที่ต้องการที่จะอนุญาตให้ทุกคนเข้าใจได้ง่ายว่ามันทำงานอย่างไร นี่คือข้อเสนอของฉัน:

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

ใช้:

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

หากคุณตั้งค่าIFSแตกต่างกันคุณจะได้รับผลลัพธ์ที่แปลก


34
นี้เป็นวิธีการที่น่ากลัว โปรดอย่าใช้มันหากคุณไม่ต้องการมีปัญหากับการโค้งงอที่จะเกิดขึ้นก่อนที่คุณจะรู้ตัว!
gniourf_gniourf

13
@MUYBelgium คุณลองกับไฟล์ที่มีหนึ่ง*บรรทัดหรือไม่? อย่างไรก็ตามนี่คือ antipattern อย่าอ่านบรรทัดด้วยสำหรับ
gniourf_gniourf

2
@ OndraŽižkaที่readวิธีการที่เป็นที่ดีที่สุดวิธีการปฏิบัติโดยฉันทามติของชุมชน คำเตือนที่คุณพูดถึงในความคิดเห็นของคุณนั้นเป็นสิ่งที่ใช้เมื่อลูปของคุณรันคำสั่ง (เช่นffmpeg) ที่อ่านจาก stdin ซึ่งแก้ไขได้เล็กน้อยโดยใช้ non-stdin FD สำหรับลูปหรือเปลี่ยนทิศทางอินพุตคำสั่งดังกล่าว ในทางตรงกันข้ามการแก้ไขข้อผิดพลาดแบบforกลมในวิธี -loop ของคุณหมายถึงการเปลี่ยนแปลงการตั้งค่าเชลล์ - โกลบอล (แล้วต้องย้อนกลับ)
Charles Duffy

1
@ เพิ่มเติม, ... ยิ่งไปกว่านั้นforวิธีการวนรอบที่คุณใช้ที่นี่หมายความว่าเนื้อหาทั้งหมดจะต้องอ่านก่อนที่วงจะเริ่มดำเนินการเลยทำให้ไม่สามารถใช้งานได้อย่างสมบูรณ์ถ้าคุณวนลูปมากกว่ากิกะไบต์ของข้อมูลแม้ว่าคุณจะปิดการใช้งาน globbing; while readห่วงความต้องการในการจัดเก็บไม่เกินข้อมูลที่เป็นเส้นเดียวในช่วงเวลาที่ความหมายมันสามารถเริ่มดำเนินการในขณะที่เนื้อหาที่ก่อให้เกิดกระบวนการย่อยจะยังคงทำงาน (จึงถูกใช้งานสำหรับการสตรีมวัตถุประสงค์) และยังมีการใช้หน่วยความจำที่ จำกัด
Charles Duffy

1
ที่จริงแล้วwhileแนวทางที่ใช้พื้นฐานดูเหมือนว่าจะมีปัญหา * -character ดูความคิดเห็นของคำตอบที่ยอมรับข้างต้น แม้ว่าจะไม่ได้มีการโต้เถียงกันในเรื่องการวนซ้ำไฟล์ต่าง ๆ ก็ตาม
Egor Hans

9

หากคุณต้องการประมวลผลทั้งไฟล์อินพุตและอินพุตผู้ใช้ (หรือสิ่งอื่นจาก stdin) ให้ใช้วิธีแก้ไขปัญหาต่อไปนี้:

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

ขึ้นอยู่กับคำตอบที่ได้รับการยอมรับและในทุบตีแฮกเกอร์เปลี่ยนเส้นทางการกวดวิชา

ที่นี่เราเปิดไฟล์ descriptor 3 สำหรับไฟล์ที่ส่งผ่านเป็นอาร์กิวเมนต์ของสคริปต์และบอกreadให้ใช้ descriptor นี้เป็นอินพุต ( -u 3) ดังนั้นเราจึงปล่อยให้ตัวอธิบายอินพุตเริ่มต้น (0) ติดอยู่กับเทอร์มินัลหรือแหล่งอินพุตอื่นสามารถอ่านอินพุตของผู้ใช้ได้


7

สำหรับการจัดการข้อผิดพลาดที่เหมาะสม:

#!/bin/bash

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

คุณช่วยเพิ่มคำอธิบายหน่อยได้ไหม?
Tyler Christian

น่าเสียดายที่มันขาดบรรทัดสุดท้ายในไฟล์
ungalcrys

... และยังอยู่ในบัญชีของการขาดของ quoting เส้น munges ที่มีสัญลักษณ์ - ตามที่อธิบายไว้ในBashPitfalls #
Charles Duffy

0

ต่อไปนี้จะพิมพ์เนื้อหาของไฟล์:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
คำตอบนี้ไม่ได้เพิ่มอะไรเลยมากกว่าคำตอบที่มีอยู่ไม่ทำงานเนื่องจากการพิมพ์ผิด / และหยุดในหลาย ๆ
Konrad Rudolph

0

ใช้เครื่องมือ IFS (ตัวคั่นเขตข้อมูลภายใน) ใน bash กำหนดอักขระที่ใช้เพื่อแยกบรรทัดออกเป็นโทเค็นโดยค่าเริ่มต้นจะมี < tab > / < space > / < newLine >

ขั้นตอนที่ 1 : โหลดข้อมูลไฟล์และแทรกลงในรายการ:

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

ขั้นตอนที่ 2 : ตอนนี้ทำซ้ำและพิมพ์ผลลัพธ์:

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

echo index เฉพาะในอาเรย์ : การเข้าถึงตัวแปรในอาเรย์:

echo "${array[0]}"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.