Những câu trả lời khác sẽ phá vỡ nếu đầu ra của lệnh chứa dấu cách (mà là khá thường xuyên) hoặc glob nhân vật như *
, ?
, [...]
.
Để nhận đầu ra của một lệnh trong một mảng, với một dòng cho mỗi phần tử, về cơ bản có 3 cách:
Với việc sử dụng Bash≥4 mapfile
— đó là hiệu quả nhất:
mapfile -t my_array < <( my_command )
Nếu không, một vòng lặp đọc đầu ra (chậm hơn, nhưng an toàn):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Theo đề xuất của Charles Duffy trong phần nhận xét (cảm ơn!), Cách sau có thể hoạt động tốt hơn phương pháp lặp trong số 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Hãy đảm bảo rằng bạn sử dụng chính xác biểu mẫu này, tức là, hãy đảm bảo rằng bạn có những điều sau:
IFS=$'\n'
trên cùng dòng với read
câu lệnh: điều này sẽ chỉ thiết lập biến môi trường IFS
cho read
câu lệnh mà thôi. Vì vậy, nó sẽ không ảnh hưởng đến phần còn lại của tập lệnh của bạn. Mục đích của biến này là thông báo read
ngắt luồng tại ký tự EOL \n
.
-r
: điều này quan trọng. Nó yêu read
cầu không giải thích các dấu gạch chéo ngược là các chuỗi thoát.
-d ''
: vui lòng lưu ý khoảng cách giữa -d
tùy chọn và đối số của nó ''
. Nếu bạn không để lại khoảng trắng ở đây, dấu ''
sẽ không bao giờ được nhìn thấy, vì nó sẽ biến mất trong bước xóa dấu ngoặc kép khi Bash phân tích cú pháp câu lệnh. Điều này cho biết read
dừng đọc ở byte nil. Một số người viết nó là -d $'\0'
, nhưng nó không thực sự cần thiết. -d ''
tốt hơn.
-a my_array
yêu read
cầu điền mảng my_array
trong khi đọc luồng.
- Bạn phải sử dụng
printf '\0'
câu lệnh after my_command
, để read
trả về 0
; Nó thực sự không phải là vấn đề lớn nếu bạn không sử dụng (bạn sẽ chỉ nhận được mã trả lại 1
, điều này cũng không sao nếu bạn không sử dụng set -e
- điều mà bạn không nên làm), nhưng hãy ghi nhớ điều đó. Nó rõ ràng hơn và đúng ngữ nghĩa hơn. Lưu ý rằng điều này khác với printf ''
, không xuất ra bất kỳ thứ gì. printf '\0'
in một byte rỗng, cần thiết read
để vui vẻ dừng đọc ở đó (nhớ -d ''
tùy chọn?).
Nếu bạn có thể, tức là nếu bạn chắc chắn mã của mình sẽ chạy trên Bash≥4, hãy sử dụng phương pháp đầu tiên. Và bạn có thể thấy nó cũng ngắn hơn.
Nếu bạn muốn sử dụng read
, vòng lặp (phương pháp 2) có thể có lợi thế hơn phương pháp 3 nếu bạn muốn thực hiện một số xử lý khi các dòng được đọc: bạn có quyền truy cập trực tiếp vào nó (thông qua $line
biến trong ví dụ tôi đã đưa ra) và bạn cũng có quyền truy cập vào các dòng đã đọc (thông qua mảng ${my_array[@]}
trong ví dụ tôi đã đưa ra).
Lưu ý rằng mapfile
cung cấp một cách để có một lệnh gọi lại đánh giá trên mỗi dòng được đọc và trên thực tế, bạn thậm chí có thể yêu cầu nó chỉ gọi lệnh gọi lại này sau mỗi N dòng được đọc; xem qua help mapfile
và các tùy chọn -C
và -c
trong đó. (Ý kiến của tôi về điều này là nó hơi rắc rối, nhưng đôi khi có thể được sử dụng nếu bạn chỉ có những việc đơn giản để làm - Tôi thực sự không hiểu tại sao điều này thậm chí đã được thực hiện ngay từ đầu!).
Bây giờ tôi sẽ cho bạn biết lý do tại sao phương pháp sau:
my_array=( $( my_command) )
bị hỏng khi có khoảng trắng:
$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!
Sau đó, một số người sẽ khuyên bạn nên sử dụng IFS=$'\n'
để khắc phục nó:
$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!
Nhưng bây giờ chúng ta hãy sử dụng một lệnh khác, với các globs :
$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?
Đó là bởi vì tôi có một tệp được gọi t
trong thư mục hiện tại… và tên tệp này được khớp với hình cầu [three four]
… tại thời điểm này, một số người sẽ khuyên bạn nên sử dụng set -f
để tắt tính năng hiển thị: nhưng hãy nhìn vào nó: bạn phải thay đổi IFS
và sử dụng set -f
để có thể sửa lỗi kỹ thuật bị hỏng (và bạn thậm chí không sửa chữa nó thực sự)! khi làm điều đó, chúng ta đang thực sự chống lại shell, không phải làm việc với shell .
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
ở đây chúng tôi đang làm việc với shell!