Có một số điều cần xem xét ở đây.
i=`cat input`
có thể đắt tiền và có rất nhiều biến thể giữa các vỏ.
Đó là một tính năng được gọi là thay thế lệnh. Ý tưởng là lưu trữ toàn bộ đầu ra của lệnh trừ đi các ký tự dòng mới theo sau vào i
biến trong bộ nhớ.
Để làm điều đó, shell shell lệnh trong một subshell và đọc đầu ra của nó thông qua một đường ống hoặc ổ cắm. Bạn thấy rất nhiều biến thể ở đây. Trên tệp 50MiB ở đây, tôi có thể thấy bash chậm hơn 6 lần so với ksh93 nhưng nhanh hơn một chút so với zsh và nhanh gấp đôi yash
.
Lý do chính cho bash
việc chậm là nó đọc từ ống 128 byte tại một thời điểm (trong khi các shell khác đọc 4KiB hoặc 8KiB tại một thời điểm) và bị phạt bởi hệ thống gọi qua đầu.
zsh
cần thực hiện một số xử lý hậu kỳ để thoát các byte NUL (các shell khác phá vỡ các byte NUL) và yash
thậm chí còn xử lý các nhiệm vụ nặng nề hơn bằng cách phân tích các ký tự nhiều byte.
Tất cả các shell cần phải loại bỏ các ký tự dòng mới mà chúng có thể thực hiện ít nhiều hiệu quả.
Một số có thể muốn xử lý các byte NUL duyên dáng hơn các byte khác và kiểm tra sự hiện diện của chúng.
Sau đó, khi bạn có biến lớn đó trong bộ nhớ, mọi thao tác trên nó thường liên quan đến việc phân bổ thêm bộ nhớ và đối phó dữ liệu.
Ở đây, bạn đang vượt qua (đang có ý định vượt qua) nội dung của biến echo
.
May mắn thay, echo
được tích hợp trong trình bao của bạn, nếu không việc thực thi có thể đã thất bại với một danh sách arg quá dài . Thậm chí sau đó, việc xây dựng mảng danh sách đối số sẽ có thể liên quan đến việc sao chép nội dung của biến.
Vấn đề chính khác trong cách tiếp cận thay thế lệnh của bạn là bạn đang gọi toán tử split + global (bằng cách quên trích dẫn biến).
Vì thế, các shell cần coi chuỗi là một chuỗi các ký tự (mặc dù một số shell không và có lỗi về vấn đề đó) nên trong các ngôn ngữ UTF-8, điều đó có nghĩa là phân tích các chuỗi UTF-8 (nếu chưa được thực hiện như thế yash
) , tìm kiếm các $IFS
ký tự trong chuỗi. Nếu $IFS
chứa không gian, tab hoặc dòng mới (theo trường hợp theo mặc định), thuật toán thậm chí còn phức tạp và đắt tiền hơn. Sau đó, các từ kết quả từ việc phân tách đó cần phải được phân bổ và sao chép.
Phần toàn cầu sẽ còn đắt hơn. Nếu bất kỳ của những lời nói chứa các ký tự glob ( *
, ?
, [
), sau đó vỏ sẽ phải đọc nội dung của một số thư mục và làm một số mô hình kết hợp đắt tiền ( bash
's thực hiện ví dụ nổi tiếng là rất xấu tại đó).
Nếu đầu vào chứa thứ gì đó tương tự /*/*/*/../../../*/*/*/../../../*/*/*
, điều đó sẽ cực kỳ tốn kém vì điều đó có nghĩa là liệt kê hàng ngàn thư mục và có thể mở rộng đến vài trăm MiB.
Sau đó echo
thường sẽ làm một số xử lý thêm. Một số triển khai mở rộng \x
trình tự trong đối số mà nó nhận được, có nghĩa là phân tích nội dung và có thể là phân bổ và sao chép dữ liệu khác.
Mặt khác, OK, trong hầu hết các shell cat
không được tích hợp sẵn, vì vậy điều đó có nghĩa là hủy bỏ một quy trình và thực thi nó (để tải mã và các thư viện), nhưng sau lần gọi đầu tiên, mã đó và nội dung của tệp đầu vào sẽ được lưu trữ trong bộ nhớ. Mặt khác, sẽ không có trung gian. cat
sẽ đọc số lượng lớn tại một thời điểm và viết ngay lập tức mà không cần xử lý và không cần phân bổ số lượng lớn bộ nhớ, chỉ cần một bộ đệm mà nó sử dụng lại.
Điều đó cũng có nghĩa là nó đáng tin cậy hơn nhiều vì nó không bị nghẹt các byte NUL và không cắt các ký tự dòng mới (và không phân tách + global, mặc dù bạn có thể tránh điều đó bằng cách trích dẫn biến và không mở rộng trình tự thoát mặc dù bạn có thể tránh điều đó bằng cách sử dụng printf
thay vì echo
).
Nếu bạn muốn tối ưu hóa nó hơn nữa, thay vì gọi cat
nhiều lần, chỉ cần chuyển input
nhiều lần đến cat
.
yes input | head -n 100 | xargs cat
Sẽ chạy 3 lệnh thay vì 100.
Để làm cho phiên bản biến trở nên đáng tin cậy hơn, bạn cần sử dụng zsh
(các shell khác không thể đối phó với byte NUL) và thực hiện:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
Nếu bạn biết đầu vào không chứa NUL byte, thì bạn có thể thực hiện nó một cách đáng tin cậy POSIXly (mặc dù nó có thể không hoạt động khi printf
không được dựng sẵn) với:
i=$(cat input && echo .) || exit # add an extra .\n to avoid trimming newlines
i=${i%.} # remove that trailing dot (the \n was removed by cmdsubst)
n=10
while [ "$n" -gt 10 ]; do
printf %s "$i"
n=$((n - 1))
done
Nhưng điều đó sẽ không bao giờ hiệu quả hơn việc sử dụng cat
trong vòng lặp (trừ khi đầu vào rất nhỏ).
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)