Tạo mảng từ tệp văn bản trong Bash


87

Một tập lệnh lấy một URL, phân tích cú pháp nó cho các trường bắt buộc và chuyển hướng đầu ra của nó để được lưu trong tệp, file.txt . Đầu ra được lưu trên một dòng mới mỗi khi một trường được tìm thấy.

file.txt

A Cat
A Dog
A Mouse 
etc... 

Tôi muốn lấy file.txtvà tạo một mảng từ nó trong một tập lệnh mới, nơi mỗi dòng trở thành biến chuỗi của riêng nó trong mảng. Cho đến nay tôi đã thử:

#!/bin/bash

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

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

Khi tôi chạy tập lệnh này, khoảng trắng dẫn đến các từ bị tách ra và thay vì nhận được

Kết quả mong muốn

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

Tôi cuối cùng nhận được điều này:

Sản lượng thực tế

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

Làm cách nào để điều chỉnh vòng lặp bên dưới để toàn bộ chuỗi trên mỗi dòng sẽ tương ứng 1-1 với mỗi biến trong mảng?


5
Đây là những gì về Bash FAQ 001 . Cũng là phần này của chủ đề mảng trong Câu hỏi thường gặp về Bash 005 .
Etan Reisner

1
Tôi muốn liên kết điều này như một bản sao của stackoverflow.com/questions/11393817/… , nhưng câu trả lời được chấp nhận ở đó là khủng khiếp.
Charles Duffy

Etan, cảm ơn bạn rất nhiều vì đã trả lời nhanh chóng và chính xác! Tôi đã cố gắng tìm kiếm câu hỏi của mình trong các diễn đàn, nhưng không nghĩ là phải tìm Câu hỏi thường gặp về stackoverflow. Lệnh mapfile đã giải quyết chính xác nhu cầu của tôi! Cảm ơn một lần nữa :) Trả lời trong phần 2.1 .
dùng2856414

2
(Thiết lập liên kết theo hướng ngược lại, vì chúng tôi có một câu trả lời được chấp nhận ở đây tốt hơn chúng tôi có ở đó).
Charles Duffy

Câu trả lời:


108

Sử dụng mapfilelệnh:

mapfile -t myArray < file.txt

Lỗi đang sử dụng for- cách thành ngữ để lặp qua các dòng của tệp là:

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

Xem BashFAQ / 005 để biết thêm chi tiết.


5
Vì đây đang được quảng cáo là q kinh điển & a, bạn cũng có thể bao gồm những gì được đề cập trong liên kết: while IFS= read -r; do lines+=("$REPLY"); done <file.
fedorqui 'VẬY đừng làm hại nữa'

10
mapfile không tồn tại trong các phiên bản bash trước 4.x
ericslaw

14
Bash 4 hiện đã được khoảng 5 tuổi. Nâng cấp.
glenn Jackman

5
Mặc dù bash 4 được phát hành vào năm 2009, nhận xét của @ ericslaw vẫn có liên quan vì nhiều máy vẫn xuất xưởng với bash 3.x (và sẽ không nâng cấp, miễn là bash được phát hành theo GPLv3). Nếu bạn đang quan tâm đến tính di động, đó là một điều quan trọng cần lưu ý
De Novo

12
vấn đề không phải là nhà phát triển không thể cài đặt phiên bản nâng cấp, mà là nhà phát triển nên biết rằng một tập lệnh sử dụng mapfilesẽ không chạy như mong đợi trên nhiều máy nếu không có các bước bổ sung. @ericslaw macs sẽ tiếp tục xuất xưởng với bash 3.2.57 trong tương lai gần. Các phiên bản gần đây hơn sử dụng giấy phép yêu cầu apple chia sẻ hoặc cho phép những thứ họ không muốn chia sẻ hoặc cho phép.
De Novo

23

mapfilereadarray(đồng nghĩa) có sẵn trong phiên bản Bash 4 trở lên. Nếu bạn có phiên bản Bash cũ hơn, bạn có thể sử dụng vòng lặp để đọc tệp thành một mảng:

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

Trong trường hợp tệp có dòng cuối cùng không hoàn chỉnh (thiếu dòng mới), bạn có thể sử dụng thay thế này:

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

Có liên quan:


Tôi thấy rằng tôi phải đặt dấu ngoặc đơn xung quanh IFS= read -r line || [[ "$line" ]]để nó hoạt động. Nếu không, nó hoạt động tuyệt vời!
Tatiana Racheva

@TatianaRacheva: đó không phải là dấu chấm phẩy bị thiếu trước đó do?
codeforester

9

Bạn cũng có thể làm nó như vậy:

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

Ghi chú:

Mở rộng tên tệp vẫn xảy ra. Ví dụ: nếu có một dòng có chữ, *nó sẽ mở rộng đến tất cả các tệp trong thư mục hiện tại. Vì vậy, chỉ sử dụng nó nếu tệp của bạn không có loại kịch bản này.


Có cách nào để IFSchỉ đặt tạm thời (để nó khôi phục giá trị ban đầu sau lệnh này), trong khi vẫn tiếp tục gán cho arrkhông?
Hugues

1
Lưu ý rằng việc mở rộng tên tệp vẫn xảy ra; ví dụIFS=$'\n' arr=($(echo 'a 1'; echo '*'; echo 'b 2')); printf "%s\n" "${arr[@]}"
Hugues

@Hugues: yap, mở rộng tên tệp vẫn xảy ra. Tôi sẽ thêm một chút thông tin đó..cảm ơn ..
Jahid

Xin lỗi, tôi không đồng ý. IFS=... commandkhông thay đổi IFStrong shell hiện tại. Tuy nhiên, IFS=... other_variable=...(không có bất kỳ lệnh nào) không thay đổi cả IFSother_variabletrong trình bao hiện tại.
Hugues

1
Cảm ơn! Những công việc này; Thật không may là không có cách nào đơn giản hơn như tôi thích arr=ký hiệu (so với mapfile/ readarray).
Hugues

4

Bạn có thể chỉ cần đọc từng dòng từ tệp và gán nó vào một mảng.

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

1
Làm thế nào để bạn truy cập vào mảng?
hola

4

Sử dụng mapfile hoặc read -a

Luôn kiểm tra mã của bạn bằng cách sử dụng shellcheck . Nó thường sẽ cho bạn câu trả lời chính xác. Trong trường hợp này, SC2207 bao gồm việc đọc tệp có các giá trị được phân tách bằng dấu cách hoặc các giá trị được phân tách bằng dòng mới thành một mảng.

Đừng làm điều này

array=( $(mycommand) )

Các tệp có giá trị được phân tách bằng dòng mới

mapfile -t array < <(mycommand)

Các tệp có giá trị được phân tách bằng dấu cách

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

Trang shellcheck sẽ cung cấp cho bạn lý do tại sao đây được coi là phương pháp hay nhất.


0

Câu trả lời này nói để sử dụng

mapfile -t myArray < file.txt

Tôi đã thực hiện một shim cho mapfilenếu bạn muốn sử dụng mapfiletrên bash <4.x vì lý do gì. Nó sử dụng mapfilelệnh hiện có nếu bạn đang sử dụng bash> = 4.x

Hiện tại, chỉ có tùy chọn -d-thoạt động. Nhưng như vậy là đủ cho lệnh trên. Tôi chỉ thử nghiệm trên macOS. Trên macOS Sierra 10.12.6, hệ thống cơ bản là 3.2.57(1)-release. Vì vậy, miếng chêm có thể có ích. Bạn cũng có thể chỉ cập nhật bash của mình bằng homebrew, tự xây dựng bash, v.v.

Nó sử dụng kỹ thuật này để thiết lập các biến thiết lập một ngăn xếp cuộc gọi.

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.