Tại sao sử dụng vòng lặp shell để xử lý văn bản được coi là thực tiễn xấu?


196

Việc sử dụng vòng lặp while để xử lý văn bản thường được coi là thực hành xấu trong hệ vỏ POSIX?

Như Stéphane Chazelas đã chỉ ra , một số lý do không sử dụng vòng lặp shell là khái niệm , độ tin cậy , mức độ dễ đọc , hiệu suấtbảo mật .

Câu trả lời này giải thích các khía cạnh độ tin cậy và mức độ dễ đọc :

while IFS= read -r line <&3; do
  printf '%s\n' "$line"
done 3< "$InputFile"

Đối với hiệu suất , whilevòng lặp và đọc rất chậm khi đọc từ tệp hoặc đường ống, vì vỏ đọc được tích hợp sẵn đọc một ký tự một lần.

Làm thế nào về các khía cạnh khái niệmbảo mật ?



1
Vỏ đọc tích hợp không đọc một ký tự một lần, nó đọc một dòng duy nhất tại một thời điểm. wiki.bash-hackers.org/commands/builtin/read
A.Danischewski

@ A.Danischewski: Nó phụ thuộc vào vỏ của bạn. Trong bashđó, nó đọc một kích thước bộ đệm tại một thời điểm, dashví dụ thử . Xem thêm unix.stackexchange.com/q/209123/38906
cuonglm

Câu trả lời:


256

Vâng, chúng tôi thấy một số điều như:

while read line; do
  echo $line | cut -c3
done

Hoặc tồi tệ hơn:

for line in `cat file`; do
  foo=`echo $line | awk '{print $2}'`
  echo whatever $foo
done

(đừng cười, tôi đã thấy nhiều trong số đó).

Nói chung từ người mới bắt đầu kịch bản shell. Đó là những bản dịch theo nghĩa đen ngây thơ về những gì bạn sẽ làm bằng các ngôn ngữ bắt buộc như C hoặc python, nhưng đó không phải là cách bạn làm mọi thứ bằng vỏ sò, và những ví dụ đó rất không hiệu quả, hoàn toàn không đáng tin cậy (có thể dẫn đến các vấn đề bảo mật) và nếu bạn từng quản lý để sửa hầu hết các lỗi, mã của bạn trở nên không thể đọc được.

Về mặt khái niệm

Trong C hoặc hầu hết các ngôn ngữ khác, các khối xây dựng chỉ là một cấp trên hướng dẫn máy tính. Bạn nói với bộ xử lý của bạn phải làm gì và sau đó phải làm gì. Bạn cầm bộ xử lý của mình bằng tay và quản lý vi mô: bạn mở tệp đó, bạn đọc nhiều byte đó, bạn làm điều này, bạn làm điều đó với nó.

Vỏ là một ngôn ngữ cấp cao hơn. Người ta có thể nói nó thậm chí không phải là một ngôn ngữ. Họ trước tất cả các thông dịch viên dòng lệnh. Công việc được thực hiện bởi những lệnh bạn chạy và shell chỉ nhằm mục đích sắp xếp chúng.

Một trong những điều tuyệt vời mà Unix giới thiệu là đường ống và các luồng stdin / stdout / stderr mặc định mà tất cả các lệnh xử lý theo mặc định.

Trong 45 năm, chúng tôi đã không tìm thấy API tốt hơn để khai thác sức mạnh của các lệnh và để chúng hợp tác với một nhiệm vụ. Đó có lẽ là lý do chính tại sao mọi người vẫn sử dụng đạn pháo ngày nay.

Bạn đã có một công cụ cắt và một công cụ chuyển ngữ, và bạn có thể chỉ cần làm:

cut -c4-5 < in | tr a b > out

Shell chỉ đang thực hiện hệ thống ống nước (mở các tệp, thiết lập các đường ống, gọi các lệnh) và khi tất cả đã sẵn sàng, nó chỉ chảy mà không cần vỏ làm gì cả. Các công cụ thực hiện công việc của chúng đồng thời, hiệu quả theo tốc độ của riêng chúng với đủ bộ đệm để không phải cái này chặn cái kia, nó chỉ đẹp và đơn giản.

Gọi một công cụ mặc dù có chi phí (và chúng tôi sẽ phát triển công cụ đó trên điểm hiệu suất). Những công cụ đó có thể được viết với hàng ngàn hướng dẫn trong C. Một quy trình phải được tạo ra, công cụ phải được tải, khởi tạo, sau đó dọn sạch, xử lý bị phá hủy và chờ đợi.

Gọi cutcũng giống như mở ngăn kéo nhà bếp, lấy con dao, sử dụng nó, rửa sạch, lau khô, đặt lại vào ngăn kéo. Khi bạn làm:

while read line; do
  echo $line | cut -c3
done < file

Giống như từng dòng của tập tin, lấy readcông cụ từ ngăn kéo nhà bếp (một thứ rất vụng về vì nó không được thiết kế cho điều đó ), đọc một dòng, rửa công cụ đọc của bạn, đặt lại vào ngăn kéo. Sau đó lên lịch một cuộc họp cho echocutcông cụ, lấy chúng từ ngăn kéo, gọi chúng, rửa chúng, lau khô, đặt chúng trở lại trong ngăn kéo và như vậy.

Một số trong số các công cụ đó ( readecho) được xây dựng trong hầu hết các shell, nhưng điều đó hầu như không tạo ra sự khác biệt ở đây echocutvẫn cần phải được chạy trong các quy trình riêng biệt.

Nó giống như cắt hành tây nhưng rửa dao và đặt lại vào ngăn kéo bếp giữa mỗi lát.

Ở đây cách rõ ràng là lấy cutcông cụ của bạn từ ngăn kéo, cắt toàn bộ hành tây của bạn và đặt lại vào ngăn kéo sau khi hoàn thành toàn bộ công việc.

IOW, trong shell, đặc biệt là để xử lý văn bản, bạn gọi càng ít tiện ích càng tốt và để chúng hợp tác với nhiệm vụ, không chạy hàng ngàn công cụ theo trình tự chờ từng cái bắt đầu, chạy, dọn sạch trước khi chạy cái tiếp theo.

Đọc thêm trong câu trả lời tốt của Bruce . Các công cụ nội bộ xử lý văn bản cấp thấp trong shell (ngoại trừ có thể zsh) bị hạn chế, cồng kềnh và thường không phù hợp để xử lý văn bản chung.

Hiệu suất

Như đã nói trước đó, chạy một lệnh có chi phí. Một chi phí rất lớn nếu lệnh đó không được dựng sẵn, nhưng ngay cả khi chúng được dựng sẵn, chi phí vẫn rất lớn.

Và shell không được thiết kế để chạy như vậy, chúng không có ý định trở thành ngôn ngữ lập trình biểu diễn. Họ không phải, họ chỉ là thông dịch viên dòng lệnh. Vì vậy, ít tối ưu hóa đã được thực hiện trên mặt trận này.

Ngoài ra, shell chạy các lệnh trong các quy trình riêng biệt. Những khối xây dựng đó không chia sẻ một bộ nhớ hoặc trạng thái chung. Khi bạn làm một fgets()hoặc fputs()trong C, đó là một chức năng trong stdio. stdio giữ bộ đệm nội bộ cho đầu vào và đầu ra cho tất cả các chức năng của stdio, để tránh thực hiện các cuộc gọi hệ thống tốn kém quá thường xuyên.

Thậm chí tiện ích dựng sẵn trình bao tương ứng ( read, echo, printf) có thể không làm điều đó. readcó nghĩa là để đọc một dòng. Nếu nó đọc qua ký tự dòng mới, điều đó có nghĩa là lệnh tiếp theo bạn chạy sẽ bỏ lỡ nó. Vì vậy, readphải đọc một byte đầu vào cùng một lúc (một số triển khai có tối ưu hóa nếu đầu vào là một tệp thông thường trong đó chúng đọc các đoạn và tìm kiếm lại, nhưng điều đó chỉ hoạt động đối với các tệp thông thường và bashví dụ chỉ đọc các đoạn 128 byte. vẫn còn ít hơn nhiều so với các tiện ích văn bản sẽ làm).

Tương tự ở phía đầu ra, echokhông thể chỉ đệm đầu ra của nó, nó phải xuất ngay lập tức vì lệnh tiếp theo bạn chạy sẽ không chia sẻ bộ đệm đó.

Rõ ràng, chạy các lệnh một cách tuần tự có nghĩa là bạn phải chờ đợi chúng, đó là một điệu nhảy lịch trình nhỏ giúp kiểm soát từ vỏ và các công cụ và trở lại. Điều đó cũng có nghĩa (trái ngược với việc sử dụng các công cụ chạy dài trong một đường ống) rằng bạn không thể khai thác nhiều bộ xử lý cùng một lúc khi khả dụng.

Giữa while readvòng lặp đó và tương đương (được cho là) cut -c3 < file, trong thử nghiệm nhanh của tôi, có tỷ lệ thời gian CPU khoảng 40000 trong các thử nghiệm của tôi (một giây so với nửa ngày). Nhưng ngay cả khi bạn chỉ sử dụng nội dung shell:

while read line; do
  echo ${line:2:1}
done

(ở đây với bash), đó vẫn là khoảng 1: 600 (một giây so với 10 phút).

Độ tin cậy / mức độ dễ đọc

Rất khó để có được mã đúng. Các ví dụ tôi đưa ra được nhìn thấy quá thường xuyên trong tự nhiên, nhưng chúng có nhiều lỗi.

readlà một công cụ tiện dụng có thể làm nhiều việc khác nhau. Nó có thể đọc đầu vào từ người dùng, chia nó thành các từ để lưu trữ trong các biến khác nhau. read linekhông không đọc một dòng đầu vào, hoặc có thể nó đọc một dòng trong một cách rất đặc biệt. Nó thực sự đọc các từ từ đầu vào những từ được phân tách bằng $IFSvà trong đó dấu gạch chéo ngược có thể được sử dụng để thoát khỏi dấu phân cách hoặc ký tự dòng mới.

Với giá trị mặc định là $IFS, trên một đầu vào như:

   foo\/bar \
baz
biz

read linesẽ lưu trữ "foo/bar baz"vào $line, không " foo\/bar \"như bạn mong đợi.

Để đọc một dòng, bạn thực sự cần:

IFS= read -r line

Điều đó không trực quan lắm, nhưng đó là như vậy, hãy nhớ rằng đạn pháo không được sử dụng như thế.

Tương tự cho echo. echomở rộng trình tự. Bạn không thể sử dụng nó cho các nội dung tùy ý như nội dung của một tệp ngẫu nhiên. Bạn cần printfở đây để thay thế.

Và tất nhiên, có một cách quên điển hình là trích dẫn biến của bạn mà mọi người đều rơi vào. Vì vậy, nó nhiều hơn:

while IFS= read -r line; do
  printf '%s\n' "$line" | cut -c3
done < file

Bây giờ, một vài cảnh báo nữa:

  • ngoại trừ zsh, điều đó không hoạt động nếu đầu vào chứa các ký tự NUL trong khi ít nhất các tiện ích văn bản GNU sẽ không gặp vấn đề.
  • nếu có dữ liệu sau dòng mới nhất, nó sẽ bị bỏ qua
  • Trong vòng lặp, stdin được chuyển hướng, do đó bạn cần chú ý rằng các lệnh trong nó không được đọc từ stdin.
  • đối với các lệnh trong các vòng lặp, chúng tôi không chú ý đến việc chúng có thành công hay không. Thông thường, các điều kiện lỗi (đĩa đầy, lỗi đọc ...) sẽ được xử lý kém, thường kém hơn so với tương đương chính xác .

Nếu chúng tôi muốn giải quyết một số vấn đề ở trên, điều đó sẽ trở thành:

while IFS= read -r line <&3; do
  {
    printf '%s\n' "$line" | cut -c3 || exit
  } 3<&-
done 3< file
if [ -n "$line" ]; then
    printf '%s' "$line" | cut -c3 || exit
fi

Điều đó ngày càng trở nên dễ đọc hơn.

Có một số vấn đề khác với việc truyền dữ liệu tới các lệnh thông qua các đối số hoặc truy xuất đầu ra của chúng trong các biến:

  • giới hạn về kích thước của các đối số (một số triển khai tiện ích văn bản cũng có một giới hạn, mặc dù hiệu quả của những điều đó đạt được thường ít có vấn đề hơn)
  • ký tự NUL (cũng là một vấn đề với các tiện ích văn bản).
  • các đối số được dùng làm tùy chọn khi chúng bắt đầu bằng -(hoặc +đôi khi)
  • nhiều quirks khác nhau của các lệnh khác nhau thường được sử dụng trong các vòng lặp như expr, test...
  • các toán tử thao tác văn bản (giới hạn) của các shell khác nhau xử lý các ký tự nhiều byte theo các cách không nhất quán.
  • ...

Cân nhắc về Bảo mật

Khi bạn bắt đầu làm việc với các biến shell và đối số cho các lệnh , bạn đang nhập vào trường mỏ.

Nếu bạn quên trích dẫn các biến của mình , hãy quên kết thúc đánh dấu tùy chọn , làm việc ở các vị trí có các ký tự nhiều byte (định mức ngày nay), bạn chắc chắn sẽ đưa ra các lỗi mà sớm muộn gì cũng sẽ trở thành lỗ hổng.

Khi bạn có thể muốn sử dụng các vòng lặp.

TBD


24
Rõ ràng (sống động), dễ đọc và cực kỳ hữu ích. Cảm ơn bạn một lần nữa. Đây thực sự là lời giải thích tốt nhất mà tôi đã thấy ở bất cứ đâu trên internet về sự khác biệt cơ bản giữa kịch bản shell và lập trình.
tự đại diện

2
Những bài đăng như thế này giúp người mới bắt đầu tìm hiểu về Shell ScScript và thấy được sự khác biệt tinh tế của nó. Nên thêm biến tham chiếu là $ {VAR: -default_value} để đảm bảo bạn không nhận được null. và đặt -o danh từ để la mắng bạn khi tham chiếu một giá trị không xác định.
unsignzero

6
@ A.Danischewski, tôi nghĩ bạn đang thiếu điểm. Có cutví dụ là hiệu quả. cut -f1 < a-very-big-filelà hiệu quả, hiệu quả như bạn nhận được nếu bạn viết nó vào C. Điều gì là cực kỳ kém hiệu quả và dễ bị lỗi đang gọi cutcho mỗi dòng a-very-big-filetrong một vòng lặp shell là điểm được đưa ra trong câu trả lời này. Điều đó đồng tình với tuyên bố cuối cùng của bạn về việc viết mã không cần thiết khiến tôi nghĩ có lẽ tôi không hiểu bình luận của bạn.
Stéphane Chazelas

5
"Trong 45 năm, chúng tôi đã không tìm thấy API tốt hơn để khai thác sức mạnh của các lệnh và để chúng hợp tác với một nhiệm vụ." - thực sự, PowerShell, đối với một người, đã giải quyết vấn đề phân tích cú pháp đáng sợ bằng cách chuyển xung quanh dữ liệu có cấu trúc thay vì luồng byte. Lý do duy nhất các shell chưa sử dụng nó (ý tưởng đã tồn tại khá lâu và về cơ bản đã kết tinh đôi khi xung quanh Java khi các loại thùng chứa từ điển & danh sách tiêu chuẩn hiện nay trở thành chủ đạo) là những người bảo trì của họ chưa thể đồng ý về định dạng dữ liệu có cấu trúc phổ biến để sử dụng (.
ivan_pozdeev

6
@OlivierDulac Tôi nghĩ đó là một chút hài hước. Phần đó sẽ mãi mãi là TBD.
muru

43

Theo như khái niệm và mức độ dễ đọc, shell thường quan tâm đến các tập tin. "Đơn vị địa chỉ" của họ là tệp và "địa chỉ" là tên tệp. Shell có tất cả các loại phương pháp kiểm tra sự tồn tại của tệp, loại tệp, định dạng tên tệp (bắt đầu bằng hình cầu). Shell có rất ít nguyên thủy để xử lý nội dung tệp. Các lập trình viên Shell phải gọi một chương trình khác để xử lý nội dung tệp.

Do định hướng tên tệp và tệp, thực hiện thao tác văn bản trong trình bao thực sự rất chậm, như bạn đã lưu ý, nhưng cũng đòi hỏi một phong cách lập trình không rõ ràng và méo mó.


25

Có một số câu trả lời phức tạp, đưa ra rất nhiều chi tiết thú vị cho các chuyên viên máy tính trong số chúng ta, nhưng nó thực sự khá đơn giản - xử lý một tệp lớn trong vòng lặp shell là quá chậm.

Tôi nghĩ rằng người hỏi rất thú vị trong một loại kịch bản shell điển hình, có thể bắt đầu bằng một số phân tích cú pháp dòng lệnh, cài đặt môi trường, kiểm tra tệp và thư mục và khởi tạo thêm một chút, trước khi bắt đầu công việc chính của nó: trải qua một công việc lớn tập tin định hướng dòng.

Đối với các phần đầu tiên ( initialization), thông thường các lệnh shell không chậm - nó chỉ chạy vài chục lệnh, có thể với một vài vòng lặp ngắn. Ngay cả khi chúng ta viết phần đó không hiệu quả, thường sẽ mất ít hơn một giây để thực hiện tất cả việc khởi tạo đó, và điều đó tốt - nó chỉ xảy ra một lần.

Nhưng khi chúng tôi nhận được trên để xử lý các tập tin lớn, có thể có hàng ngàn hoặc hàng triệu dòng, đó là không tốt cho các kịch bản để mất một phần đáng kể của một giây (ngay cả khi nó chỉ là một vài chục mili giây) cho mỗi dòng, vì điều đó có thể thêm đến giờ.

Đó là khi chúng ta cần sử dụng các công cụ khác và vẻ đẹp của các kịch bản shell Unix là chúng giúp chúng ta dễ dàng thực hiện điều đó.

Thay vì sử dụng một vòng lặp để xem xét từng dòng, chúng ta cần truyền toàn bộ tệp thông qua một chuỗi các lệnh . Điều này có nghĩa là, thay vì gọi các lệnh hàng nghìn hoặc hàng triệu thời gian, shell chỉ gọi chúng một lần. Đúng là các lệnh đó sẽ có các vòng lặp để xử lý từng dòng tệp, nhưng chúng không phải là các tập lệnh shell và chúng được thiết kế để nhanh và hiệu quả.

Unix có nhiều công cụ được xây dựng tuyệt vời, từ đơn giản đến phức tạp, mà chúng ta có thể sử dụng để xây dựng các đường ống dẫn của mình. Tôi thường sẽ bắt đầu với những cái đơn giản, và chỉ sử dụng những cái phức tạp hơn khi cần thiết.

Tôi cũng sẽ cố gắng gắn bó với các công cụ tiêu chuẩn có sẵn trên hầu hết các hệ thống và cố gắng duy trì việc sử dụng của mình, mặc dù điều đó không phải lúc nào cũng có thể. Và nếu ngôn ngữ yêu thích của bạn là Python hoặc Ruby, có lẽ bạn sẽ không phải nỗ lực thêm để đảm bảo rằng nó được cài đặt trên mọi nền tảng mà phần mềm của bạn cần chạy trên :-)

Công cụ đơn giản bao gồm head, tail, grep, sort, cut, tr, sed, join(khi sáp nhập 2 files), và awkmột lớp lót, trong số nhiều người khác. Thật đáng kinh ngạc những gì một số người có thể làm với sedcác lệnh và khớp mẫu .

Khi nó trở nên phức tạp hơn và bạn thực sự phải áp dụng một số logic cho từng dòng, awklà một lựa chọn tốt - hoặc là một lớp lót (một số người đặt toàn bộ tập lệnh awk trong 'một dòng', mặc dù điều đó không dễ đọc) hoặc trong một kịch bản bên ngoài ngắn.

awkmột ngôn ngữ được giải thích (như vỏ của bạn), thật đáng kinh ngạc khi nó có thể xử lý từng dòng một cách hiệu quả, nhưng nó được xây dựng có mục đích cho việc này và nó thực sự rất nhanh.

Và sau đó, có Perlmột số lượng lớn các ngôn ngữ kịch bản lệnh khác rất tốt trong việc xử lý các tệp văn bản, và cũng đi kèm với rất nhiều thư viện hữu ích.

Và cuối cùng, có C cũ tốt, nếu bạn cần tốc độ tối đa và tính linh hoạt cao (mặc dù xử lý văn bản hơi tẻ nhạt). Nhưng có lẽ việc bạn sử dụng thời gian rất tệ để viết chương trình C mới cho mọi tác vụ xử lý tệp khác nhau mà bạn gặp phải. Tôi làm việc với các tệp CSV rất nhiều, vì vậy tôi đã viết một số tiện ích chung trong C mà tôi có thể sử dụng lại trong nhiều dự án khác nhau. Trên thực tế, điều này mở rộng phạm vi 'các công cụ Unix đơn giản, nhanh chóng' mà tôi có thể gọi từ các tập lệnh shell của mình, vì vậy tôi có thể xử lý hầu hết các dự án bằng cách chỉ viết các tập lệnh, nhanh hơn nhiều so với việc viết và gỡ lỗi mã C mỗi lần!

Một số gợi ý cuối cùng:

  • đừng quên khởi động tập lệnh shell chính của bạn export LANG=C, hoặc nhiều công cụ sẽ coi các tệp ASCII cũ đơn giản của bạn là Unicode, làm cho chúng chậm hơn nhiều
  • cũng xem xét cài đặt export LC_ALL=Cnếu bạn muốn sorttạo ra thứ tự nhất quán, bất kể môi trường!
  • nếu bạn cần sortdữ liệu của mình, điều đó có thể sẽ tốn nhiều thời gian (và tài nguyên: CPU, bộ nhớ, đĩa) hơn mọi thứ khác, vì vậy hãy cố gắng giảm thiểu số lượng sortlệnh và kích thước của các tệp mà chúng sắp xếp
  • một đường ống duy nhất, khi có thể, thường hiệu quả nhất - chạy nhiều đường ống theo trình tự, với các tệp trung gian, có thể dễ đọc và gỡ lỗi hơn, nhưng sẽ tăng thời gian chương trình của bạn mất

6
Đường ống của nhiều công cụ đơn giản (cụ thể là các công cụ được đề cập, như đầu, đuôi, grep, sort, cut, tr, sed, ...) thường được sử dụng một cách không cần thiết, đặc biệt nếu bạn cũng có một ví dụ awk trong đường ống đó có thể làm được nhiệm vụ của những công cụ đơn giản là tốt. Một vấn đề khác cần được xem xét là trong các đường ống, bạn không thể đơn giản và đáng tin cậy chuyển thông tin trạng thái từ các quy trình ở phía trước của đường ống sang các quy trình xuất hiện ở phía sau. Nếu bạn sử dụng cho các chương trình đơn giản như vậy, một chương trình awk bạn có một không gian trạng thái duy nhất.
Janis

14

Đúng nhưng...

Các câu trả lời đúng của Stéphane Chazelas được dựa trên khái niệm về ủy thác tất cả các hoạt động văn bản để mã nhị phân cụ thể, như grep, awk, sedvà những người khác.

có khả năng tự làm rất nhiều việc, việc thả dĩa có thể trở nên nhanh hơn (thậm chí hơn cả việc điều hành một phiên dịch viên khác để làm tất cả công việc).

Đối với mẫu, hãy xem bài viết này:

https://stackoverflow.com/a/38790442/1765658

https://stackoverflow.com/a/7180078/1765658

kiểm tra và so sánh ...

Tất nhiên

Không có sự xem xét về đầu vàobảo mật của người dùng !

Đừng viết ứng dụng web dưới !!

Nhưng đối với nhiều tác vụ quản trị máy chủ, trong đó có thể được sử dụng thay cho , sử dụng hàm bash có thể rất hiệu quả.

Ý tôi là

Các công cụ viết như bin utils không phải là loại công việc giống như quản trị hệ thống.

Thế là không cùng người!

Trường hợp các sysadins phải biết shell, họ có thể viết các nguyên mẫu bằng cách sử dụng công cụ ưa thích (và được biết đến nhiều nhất) của mình.

Nếu tiện ích mới này (nguyên mẫu) thực sự hữu ích, một số người khác có thể phát triển công cụ chuyên dụng bằng cách sử dụng một số ngôn ngữ phù hợp hơn.


1
Ví dụ tốt. Cách tiếp cận của bạn chắc chắn hiệu quả hơn so với lololux, nhưng lưu ý cách trả lời của tenibai (cách đúng để thực hiện IMO này, đó là không sử dụng các vòng lặp shell) là các đơn đặt hàng có cường độ nhanh hơn so với của bạn. Và của bạn sẽ nhanh hơn rất nhiều nếu bạn không sử dụng bash. (nhanh hơn 3 lần với ksh93 trong thử nghiệm trên hệ thống của tôi). bashnói chung là vỏ chậm nhất. Thậm chí zshlà nhanh gấp đôi trên kịch bản đó. Bạn cũng có một vài vấn đề với các biến không được trích dẫn và cách sử dụng read. Vì vậy, bạn đang thực sự minh họa rất nhiều điểm của tôi ở đây.
Stéphane Chazelas

@ StéphaneChazelas Tôi đồng ý, bash có lẽ là loại vỏ chậm nhất mọi người có thể sử dụng ngày nay, nhưng dù sao cũng được sử dụng rộng rãi nhất.
F. Hauri

@ StéphaneChazelas Tôi đã đăng một phiên bản perl vào câu trả lời của mình
F. Hauri

1
@Tensibai, bạn sẽ tìm thấy POSIXsh , AWK , Sed , grep, ed, ex, cut, sort, join... tất cả với độ tin cậy hơn Bash hoặc Perl.
tự đại diện

1
@Tensibai, trong tất cả các hệ thống có liên quan của U & L, hầu hết trong số họ (Solaris, FreeBSD, HP / UX, AIX, hầu hết các hệ thống nhúng nhúng ...) đều không được bashcài đặt theo mặc định. bashchủ yếu được tìm thấy duy nhất trên Apple MacOS và hệ thống GNU (Tôi cho rằng đó là những gì bạn gọi nhà phân phối lớn ), mặc dù nhiều hệ thống cũng có nó như là một gói tùy chọn (như zsh, tcl, python...)
Stéphane Chazelas
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.