Đọc đầu ra của một lệnh vào một mảng trong Bash


111

Tôi cần đọc đầu ra của một lệnh trong tập lệnh của mình thành một mảng. Ví dụ:

ps aux | grep | grep | x 

và nó đưa ra đầu ra từng dòng như thế này:

10
20
30

Tôi cần đọc các giá trị từ đầu ra lệnh vào một mảng và sau đó tôi sẽ thực hiện một số công việc nếu kích thước của mảng nhỏ hơn ba.


5
Xin chào @barp, TRẢ LỜI CÁC CÂU HỎI CỦA BẠN, kẻo kiểu của bạn trở thành vật cản trở toàn bộ cộng đồng.
James

9
@James vấn đề không phải là thực tế là anh ấy không trả lời câu hỏi của mình ... đây là một trang web Q / A. Anh ấy chỉ không đánh dấu chúng là đã trả lời. Anh ta nên đánh dấu chúng. Dấu. @ barp
DDPWNAGE

4
Vui lòng @barp, đánh dấu câu hỏi là đã trả lời.
smonff

Liên quan: Lặp qua nội dung của một tệp trong Bash vì việc đọc đầu ra của một lệnh thông qua quá trình thay thế tương tự như đọc từ một tệp.
codeforester

Câu trả lời:


161

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:

  1. Với việc sử dụng Bash≥4 mapfile— đó là hiệu quả nhất:

    mapfile -t my_array < <( my_command )
  2. 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 )
  3. 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 readcâu lệnh: điều này sẽ chỉ thiết lập biến môi trường IFS cho readcâ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 readngắ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 -dtù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 readdừ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_arrayyêu readcầu điền mảng my_arraytrong khi đọc luồng.
    • Bạn phải sử dụng printf '\0'câu lệnh after my_command , để readtrả 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 $linebiế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 mapfilecung 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 mapfilevà các tùy chọn -C-ctrong đó. (Ý 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 ttrong 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 IFSvà 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!


4
Điều này thật tuyệt, tôi chưa bao giờ nghe nói về mapfilenó trước đây, nó chính xác là thứ mà tôi đã bỏ lỡ trong nhiều năm. Tôi đoán rằng các phiên bản gần đây của bashcó rất nhiều tính năng mới hay, tôi chỉ nên dành một vài ngày để đọc tài liệu và viết ra một bản cheatsheet đẹp.
Gene Pavlovsky

6
Btw, để sử dụng cú pháp này < <(command)trong các tập lệnh shell, dòng shebang phải là #!/bin/bash- nếu chạy dưới dạng #!/bin/sh, bash sẽ thoát với lỗi cú pháp.
Gene Pavlovsky

1
Mở rộng trên @ lưu ý hữu ích GenePavlovsky của, kịch bản cũng phải được chạy với lệnh bash bash my_script.shvà không phải là lệnh shsh my_script.sh
Vito

2
@Vito: thực sự, câu trả lời này chỉ dành cho Bash, nhưng đây không phải là vấn đề, vì các shell POSIX tuân thủ nghiêm ngặt thậm chí không triển khai các mảng ( shdashkhông biết gì về mảng, tất nhiên, ngoại trừ $@mảng tham số vị trí ).
gniourf_gniourf

3
Là một giải pháp thay thế khác không yêu cầu bash 4.0, hãy xem xét IFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0')- nó vừa hoạt động chính xác trong bash 3.x, vừa chuyển qua trạng thái thoát không thành công từ my_commandsang read.
Charles Duffy

86

Bạn có thể dùng

my_array=( $(<command>) )

để lưu kết quả đầu ra của lệnh <command>vào mảng my_array.

Bạn có thể truy cập độ dài của mảng đó bằng cách sử dụng

my_array_length=${#my_array[@]}

Bây giờ chiều dài được lưu trữ trong my_array_length.


19
Điều gì sẽ xảy ra nếu đầu ra của $ (lệnh) có dấu cách và nhiều dòng có dấu cách? Tôi đã thêm "$ (command)" và nó đặt tất cả đầu ra từ tất cả các dòng vào phần tử [0] đầu tiên của mảng.
ikwyl6

3
@ ikwyl6 một cách giải quyết là gán đầu ra lệnh cho một biến và sau đó tạo một mảng với nó hoặc thêm nó vào một mảng. VAR="$(<command>)"và sau đó my_array=("$VAR")hoặcmy_array+=("$VAR")
Vito

10

Hãy tưởng tượng rằng bạn sắp đặt các tệp và tên thư mục (trong thư mục hiện tại) vào một mảng và đếm các mục của nó. Kịch bản sẽ như thế nào;

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

Hoặc, bạn có thể lặp lại mảng này bằng cách thêm tập lệnh sau:

for element in "${my_array[@]}"
do
   echo "${element}"
done

Xin lưu ý rằng đây là khái niệm cốt lõi và đầu vào được coi là đã được làm sạch trước đó, tức là loại bỏ các ký tự thừa, xử lý các Chuỗi trống, v.v. (nằm ngoài chủ đề của chủ đề này).


3
Ý tưởng khủng khiếp vì những lý do được đề cập trong câu trả lời ở trên
Hubert Grzeskowiak
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.