Nếu bạn phải lưu trữ đầu ra và bạn muốn một mảng, mapfile
làm cho nó dễ dàng.
Trước tiên, hãy xem xét nếu bạn cần lưu trữ đầu ra của lệnh của bạn. Nếu bạn không, chỉ cần chạy lệnh.
Nếu bạn quyết định muốn đọc đầu ra của một lệnh dưới dạng một mảng các dòng, thì đúng là có một cách để làm là vô hiệu hóa tính năng tạo khối, thiết lập IFS
để phân tách trên các dòng, sử dụng thay thế lệnh trong (
)
cú pháp tạo mảng và đặt lại IFS
sau đó, đó là phức tạp hơn nó dường như. Câu trả lời của terdon bao gồm một số cách tiếp cận đó. Nhưng tôi khuyên bạn nên sử dụng mapfile
, lệnh tích hợp của shell Bash để đọc văn bản dưới dạng một dòng các dòng, thay vào đó. Đây là một trường hợp đơn giản khi bạn đọc từ một tệp:
mapfile < filename
Điều này đọc các dòng thành một mảng được gọi là MAPFILE
. Nếu không có chuyển hướng đầu vào , bạn sẽ đọc từ đầu vào tiêu chuẩn của shell (thường là thiết bị đầu cuối của bạn) thay vì tệp có tên . Để đọc vào một mảng khác với mặc định , hãy truyền tên của nó. Ví dụ, điều này đọc vào mảng :< filename
filename
MAPFILE
lines
mapfile lines < filename
Một hành vi mặc định khác mà bạn có thể chọn để thay đổi là các ký tự dòng mới ở cuối dòng được đặt đúng chỗ; chúng xuất hiện dưới dạng ký tự cuối cùng trong mỗi thành phần mảng (trừ khi đầu vào kết thúc mà không có ký tự dòng mới, trong trường hợp đó, phần tử cuối cùng không có ký tự). Để kiểm tra các dòng mới này để chúng không xuất hiện trong mảng, hãy chuyển -t
tùy chọn sang mapfile
. Ví dụ, điều này đọc vào mảng records
và không ghi các ký tự dòng mới vào các thành phần mảng của nó:
mapfile -t records < filename
Bạn cũng có thể sử dụng -t
mà không cần chuyển tên mảng; đó là, nó hoạt động với một tên mảng ẩn của MAPFILE
quá.
Các mapfile
vỏ được xây dựng trong hỗ trợ tùy chọn khác, và nó cách khác có thể được gọi là readarray
. Chạy help mapfile
(và help readarray
) để biết chi tiết.
Nhưng bạn không muốn đọc từ tệp, bạn muốn đọc từ đầu ra của lệnh. Để đạt được điều đó, sử dụng quá trình thay thế . Lệnh này đọc các dòng từ lệnh some-command
với arguments...
các đối số dòng lệnh của nó và đặt chúng trong mapfile
mảng mặc định MAPFILE
của chúng, với các ký tự dòng mới kết thúc của chúng bị xóa:
mapfile -t < <(some-command arguments...)
Quá trình thay thế thay thế bằng một tên tệp thực tế mà từ đó đầu ra của việc chạy có thể được đọc. Tệp này là một ống có tên chứ không phải là tệp thông thường và trên Ubuntu, nó sẽ được đặt tên như (đôi khi với một số khác ), nhưng bạn không cần phải quan tâm đến các chi tiết, vì trình bao quan tâm đến nó Tất cả đằng sau hậu trường.<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Bạn có thể nghĩ rằng bạn có thể từ bỏ quá trình thay thế bằng cách sử dụng thay vào đó, nhưng điều đó sẽ không hiệu quả bởi vì, khi bạn có một đường dẫn gồm nhiều lệnh được phân tách bằng cách , Bash chạy tất cả các lệnh trong các chuỗi con . Do đó, cả hai và chạy trong môi trường riêng của chúng , được khởi tạo từ nhưng tách biệt với môi trường của vỏ mà bạn chạy đường ống. Trong subshell nơi chạy, mảng sẽ được điền, nhưng sau đó mảng đó bị loại bỏ khi lệnh kết thúc. không bao giờ được tạo hoặc sửa đổi cho người gọi.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
Dưới đây là toàn bộ ví dụ trong câu trả lời của terdon , nếu bạn sử dụng mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
Đó là nó. Bạn không cần kiểm tra xem IFS
đã được đặt chưa , theo dõi xem nó có được đặt hay không và với giá trị nào, đặt thành dòng mới, sau đó đặt lại hoặc hủy đặt lại sau. Bạn không cần phải vô hiệu hóa globbing (ví dụ, với set -f
) - mà thực sự là cần thiết nếu bạn đang sử dụng phương pháp mà nghiêm túc, vì tên tập tin có thể chứa *
, ?
và [
--then tái kích hoạt nó ( set +f
) sau đó.
Bạn cũng có thể thay thế hoàn toàn vòng lặp cụ thể đó bằng một printf
lệnh duy nhất - mặc dù điều này thực sự không phải là lợi ích của nó mapfile
, vì bạn có thể làm điều đó cho dù bạn sử dụng mapfile
hay phương thức khác để điền vào mảng. Đây là phiên bản ngắn hơn:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Điều quan trọng cần ghi nhớ là, như terdon đề cập , hoạt động từng dòng không phải lúc nào cũng phù hợp và sẽ không hoạt động chính xác với tên tệp có chứa dòng mới. Tôi khuyên bạn không nên đặt tên tệp theo cách đó, nhưng nó có thể xảy ra, bao gồm cả tình cờ.
Thực sự không có một giải pháp nào phù hợp cho tất cả mọi người.
Bạn đã yêu cầu "một lệnh chung cho tất cả các kịch bản" và cách tiếp cận sử dụng mapfile
phần nào cách tiếp cận mục tiêu đó, nhưng tôi khuyên bạn nên xem xét lại các yêu cầu của mình.
Nhiệm vụ hiển thị ở trên đạt được tốt hơn chỉ với một find
lệnh:
find . -type d -printf 'DIR: %p\n'
Bạn cũng có thể sử dụng một lệnh bên ngoài như sed
để thêm DIR:
vào đầu mỗi dòng. Điều này được cho là hơi xấu, và không giống như lệnh find, nó sẽ thêm "tiền tố" bổ sung vào tên tệp có chứa dòng mới, nhưng nó hoạt động bất kể đầu vào của nó để đáp ứng yêu cầu của bạn và vẫn nên đọc đầu ra vào biến hoặc mảng :
find . -type d | sed 's/^/DIR: /'
Nếu bạn cần liệt kê và cũng thực hiện một hành động trên mỗi thư mục được tìm thấy, chẳng hạn như chạy some-command
và chuyển đường dẫn của thư mục làm đối số, find
cho phép bạn cũng làm điều đó:
find . -type d -print -exec some-command {} \;
Một ví dụ khác, chúng ta hãy quay trở lại nhiệm vụ chung là thêm tiền tố vào mỗi dòng. Giả sử tôi muốn xem đầu ra của help mapfile
nhưng đánh số các dòng. Tôi thực sự sẽ không sử dụng mapfile
cho điều này, cũng như bất kỳ phương pháp nào khác đọc nó vào một biến shell hoặc mảng shell. Giả sử help mapfile | cat -n
không đưa ra định dạng tôi muốn, tôi có thể sử dụng awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
Đọc tất cả đầu ra của một lệnh vào một biến hoặc mảng đôi khi hữu ích và phù hợp, nhưng nó có nhược điểm lớn. Bạn không chỉ phải đối phó với sự phức tạp bổ sung của việc sử dụng trình bao của mình khi một lệnh hiện có hoặc kết hợp các lệnh hiện có có thể đã làm những gì bạn cần hoặc tốt hơn, nhưng toàn bộ đầu ra của lệnh phải được lưu trữ trong bộ nhớ. Đôi khi bạn có thể biết đó không phải là vấn đề, nhưng đôi khi bạn có thể đang xử lý một tệp lớn.
Một cách khác thường được cố gắng - và đôi khi được thực hiện đúng - là đọc từng dòng đầu vào với read -r
một vòng lặp. Nếu bạn không cần lưu trữ các dòng trước trong khi hoạt động trên các dòng sau và bạn phải sử dụng đầu vào dài, thì nó có thể tốt hơn mapfile
. Nhưng nó cũng nên tránh trong các trường hợp khi bạn có thể chuyển nó thành một lệnh có thể thực hiện công việc, đó là hầu hết các trường hợp .