การสร้างอาร์เรย์จากไฟล์ข้อความใน Bash


88

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

file.txt

A Cat
A Dog
A Mouse 
etc... 

ฉันต้องการรับfile.txtและสร้างอาร์เรย์จากมันในสคริปต์ใหม่โดยที่ทุกบรรทัดจะเป็นตัวแปรสตริงของตัวเองในอาร์เรย์ จนถึงตอนนี้ฉันได้ลอง:

#!/bin/bash

filename=file.txt
declare -a myArray
myArray=(`cat "$filename"`)

for (( i = 0 ; i < 9 ; i++))
do
  echo "Element [$i]: ${myArray[$i]}"
done

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

เอาต์พุตที่ต้องการ

Element [0]: A Cat 
Element [1]: A Dog 
etc... 

ฉันได้รับสิ่งนี้:

เอาท์พุทจริง

Element [0]: A 
Element [1]: Cat 
Element [2]: A
Element [3]: Dog 
etc... 

ฉันจะปรับลูปด้านล่างเพื่อให้สตริงทั้งหมดในแต่ละบรรทัดสอดคล้องแบบหนึ่งต่อหนึ่งกับตัวแปรแต่ละตัวในอาร์เรย์ได้อย่างไร


5
นี่คือสิ่งที่Bash FAQ 001เป็นข้อมูลเกี่ยวกับ นอกจากนี้ในส่วนนี้ของหัวข้ออาร์เรย์ในทุบตีคำถามที่พบบ่อย 005
Etan Reisner

1
ฉันจะเชื่อมโยงสิ่งนี้ว่าซ้ำกับstackoverflow.com/questions/11393817/…แต่คำตอบที่ยอมรับนั้นแย่มาก
Charles Duffy

Etan ขอบคุณมากสำหรับการตอบกลับที่รวดเร็วและแม่นยำ! ฉันพยายามค้นหาคำถามของฉันในฟอรัมแล้ว แต่ไม่คิดที่จะค้นหาคำถามที่พบบ่อยเกี่ยวกับ stackoverflow คำสั่ง mapfile ตอบสนองความต้องการของฉันอย่างแน่นอน! ขอขอบคุณอีกครั้ง :) คำตอบในส่วน 2.1
user2856414

2
(ตั้งค่าลิงก์ในทิศทางตรงกันข้ามเนื่องจากเรามีคำตอบที่ยอมรับได้ดีกว่าที่นี่)
Charles Duffy

คำตอบ:


110

ใช้mapfileคำสั่ง:

mapfile -t myArray < file.txt

ข้อผิดพลาดกำลังใช้for- วิธีที่ใช้สำนวนในการวนซ้ำบรรทัดของไฟล์คือ:

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

ดูBashFAQ / 005สำหรับรายละเอียดเพิ่มเติม


5
while IFS= read -r; do lines+=("$REPLY"); done <fileตั้งแต่นี้จะถูกเลื่อนเป็นคิวที่ยอมรับและคุณยังอาจรวมถึงสิ่งที่ถูกกล่าวถึงในการเชื่อมโยง:
fedorqui 'SO หยุดทำร้าย'

10
mapfile ไม่มีอยู่ในเวอร์ชัน bash ก่อน 4.x
ericslaw

14
Bash 4 อายุประมาณ 5 ขวบแล้ว อัพเกรด.
glenn jackman

5
แม้ว่า bash 4 จะออกในปี 2009 แต่ความคิดเห็นของ @ ericslaw ก็ยังคงมีความเกี่ยวข้องเนื่องจากเครื่องจำนวนมากยังคงจัดส่งด้วย bash 3.x (และจะไม่อัปเกรดตราบใดที่ bash ได้รับการเผยแพร่ภายใต้ GPLv3) หากคุณสนใจในการพกพาสิ่งสำคัญที่ควรทราบ
เดอโนโว

12
ปัญหาไม่ได้อยู่ที่นักพัฒนาไม่สามารถติดตั้งเวอร์ชันที่อัปเกรดได้นักพัฒนาควรทราบว่าสคริปต์ที่ใช้mapfileจะไม่ทำงานตามที่คาดไว้ในหลาย ๆ เครื่องโดยไม่มีขั้นตอนเพิ่มเติม แมค @ericslaw จะยังคงจัดส่งพร้อมกับ bash 3.2.57 สำหรับอนาคตอันใกล้ เวอร์ชันล่าสุดใช้ใบอนุญาตที่ต้องการให้ Apple แชร์หรืออนุญาตสิ่งที่ไม่ต้องการแชร์หรืออนุญาต
De Novo

24

mapfileและreadarray(ซึ่งตรงกัน) มีอยู่ใน Bash เวอร์ชัน 4 ขึ้นไป หากคุณมี Bash เวอร์ชันเก่าคุณสามารถใช้ลูปเพื่ออ่านไฟล์ลงในอาร์เรย์:

arr=()
while IFS= read -r line; do
  arr+=("$line")
done < file

ในกรณีที่ไฟล์มีบรรทัดสุดท้ายที่ไม่สมบูรณ์ (ไม่มีการขึ้นบรรทัดใหม่) คุณสามารถใช้ทางเลือกนี้:

arr=()
while IFS= read -r line || [[ "$line" ]]; do
  arr+=("$line")
done < file

ที่เกี่ยวข้อง:


ฉันพบว่าฉันต้องใส่วงเล็บIFS= read -r line || [[ "$line" ]]เพื่อให้มันทำงานได้ มิฉะนั้นจะใช้งานได้ดี!
Tatiana Racheva

@TatianaRacheva: มันเป็นอัฒภาคที่หายไปก่อนหน้านี้doไม่ใช่เหรอ?
codeforester

9

คุณสามารถทำได้เช่นกัน:

oldIFS="$IFS"
IFS=$'\n' arr=($(<file))
IFS="$oldIFS"
echo "${arr[1]}" # It will print `A Dog`.

บันทึก:

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


มีวิธีใดที่จะตั้งIFSเพียงชั่วคราว (เพื่อที่จะกู้คืนค่าเดิมหลังจากที่คำสั่งนี้) ขณะที่ยังคงเรื้อรังมอบหมายให้arr?
Hugues

1
โปรดทราบว่าการขยายชื่อไฟล์ยังคงเกิดขึ้น เช่นIFS=$'\n' arr=($(echo 'a 1'; echo '*'; echo 'b 2')); printf "%s\n" "${arr[@]}"
Hugues

@Hugues: เห่าการขยายชื่อไฟล์ยังคงเกิดขึ้น ฉันจะเพิ่มข้อมูล .. thnks ..
Jahid

ขออภัยฉันไม่เห็นด้วย IFS=... commandไม่เปลี่ยนแปลงIFSในเชลล์ปัจจุบัน อย่างไรก็ตามIFS=... other_variable=...(ไม่มีคำสั่งใด ๆ ) จะเปลี่ยนทั้งIFSและother_variableในเชลล์ปัจจุบัน
Hugues

1
ขอบคุณ! งานนี้; โชคไม่ดีที่ไม่มีวิธีที่ง่ายกว่านี้เพราะฉันชอบarr=สัญกรณ์ (เทียบกับmapfile/ readarray)
Hugues

4

คุณสามารถอ่านแต่ละบรรทัดจากไฟล์และกำหนดให้กับอาร์เรย์ได้

#!/bin/bash
i=0
while read line 
do
        arr[$i]="$line"
        i=$((i+1))
done < file.txt

1
คุณเข้าถึงอาร์เรย์ได้อย่างไร?
hola

4

ใช้ mapfile หรืออ่าน -a

ตรวจสอบรหัสของคุณโดยใช้shellcheckเสมอ มักจะให้คำตอบที่ถูกต้อง ในกรณีนี้SC2207ครอบคลุมการอ่านไฟล์ที่มีช่องว่างคั่นหรือค่าที่แยกขึ้นบรรทัดใหม่ลงในอาร์เรย์

อย่าทำอย่างนี้

array=( $(mycommand) )

ไฟล์ที่มีค่าคั่นด้วยขึ้นบรรทัดใหม่

mapfile -t array < <(mycommand)

ไฟล์ที่มีค่าคั่นด้วยช่องว่าง

IFS=" " read -r -a array <<< "$(mycommand)"

หน้าตรวจสอบเชลล์จะให้เหตุผลว่าทำไมจึงถือว่าเป็นแนวทางปฏิบัติที่ดีที่สุด


0

คำตอบนี้บอกว่าจะใช้

mapfile -t myArray < file.txt

ฉันทำไฟล์ shimสำหรับmapfileถ้าคุณต้องการใช้mapfileกับ bash <4.x ด้วยเหตุผลใดก็ตาม ใช้mapfileคำสั่งที่มีอยู่หากคุณใช้ bash> = 4.x

ขณะนี้มีเพียงตัวเลือกเท่านั้น -dและการ-tทำงาน แต่นั่นก็เพียงพอแล้วสำหรับคำสั่งข้างต้น ฉันได้ทดสอบกับ macOS เท่านั้น บน macOS Sierra 10.12.6 ระบบทุบตีคือ3.2.57(1)-release. ดังนั้นชิมจึงมีประโยชน์ คุณยังสามารถอัปเดต bash ของคุณด้วย homebrew สร้าง bash ด้วยตัวคุณเองและอื่น ๆ

มันใช้เทคนิคนี้ในการตั้งค่าตัวแปรขึ้นหนึ่ง call stack

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