Tôi có nên quan tâm đến những con mèo không cần thiết?


50

Rất nhiều tiện ích dòng lệnh có thể lấy đầu vào của chúng từ một đường ống hoặc làm đối số tên tệp. Đối với các tập lệnh shell dài, tôi thấy việc bắt đầu chuỗi tắt bằng một catlàm cho nó dễ đọc hơn, đặc biệt nếu lệnh đầu tiên sẽ cần các đối số nhiều dòng.

So sánh

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

Là phương pháp sau kém hiệu quả? Nếu vậy, sự khác biệt có đủ để quan tâm nếu kịch bản được chạy không, giả sử, một lần một giây? Sự khác biệt về khả năng đọc không lớn.


30
Tôi dành nhiều thời gian hơn để xem mọi người tấn công nhau về việc sử dụng mèo vô dụng trên trang web này hơn là hệ thống của tôi thực sự bắt đầu các quy trình của mèo
Michael Mrozek

4
@Michael: 100% đồng ý. Tôi đã mất nhiều thời gian hơn để liên kết với giải thưởng usenet cũ một lần so với máy tính của tôi sẽ lãng phí ngay lập tức cat. Tuy nhiên tôi nghĩ rằng câu hỏi lớn hơn ở đây là khả năng đọc mã thường được ưu tiên hơn hiệu năng. Khi nhanh hơn thực sự có thể được viết đẹp hơn , tại sao không? Việc chỉ ra vấn đề catthường dẫn đến việc người dùng hiểu rõ hơn về đường ống và quy trình nói chung. Đó là giá trị nỗ lực để họ viết mã dễ hiểu vào lần tới.
Caleb

3
Tôi thực sự có một lý do khác mà tôi không thích biểu mẫu đầu tiên - nếu bạn muốn thêm một lệnh khác ở đầu đường ống, bạn cũng phải di chuyển đối số, vì vậy việc chỉnh sửa sẽ khó chịu hơn. (Tất nhiên, điều này không có nghĩa là bạn phải sử dụng cat; Quan điểm của Caleb về việc sử dụng các chức năng và chuyển hướng cũng giải quyết được điều đó.)
Cascabel


1
Đó là buổi tối trong công việc, tôi đã từ chối làm việc. Tôi mở stackoverflow và tìm một câu hỏi, có tiêu đề "Tôi có nên quan tâm đến những con mèo không cần thiết?" và nhìn thấy một số động vật vô gia cư và một lập trình viên, suy nghĩ về việc có cho chúng ăn hay không ...
Boris Burkov

Câu trả lời:


46

Câu trả lời "dứt khoát" dĩ nhiên được mang đến cho bạn bởi The Usless Use of catAward .

Mục đích của mèo là ghép các tệp (hoặc "catenate"). Nếu đó chỉ là một tệp, việc kết hợp nó với không có gì là lãng phí thời gian và khiến bạn mất một quá trình.

Con mèo tức thời chỉ để mã của bạn đọc khác nhau làm cho chỉ một quá trình và một bộ luồng đầu vào / đầu ra không cần thiết. Thông thường, sự cố thực sự trong các tập lệnh của bạn sẽ là các vòng lặp không hiệu quả và xử lý Actuall. Trên hầu hết các hệ thống hiện đại, một phần bổ sung catsẽ không giết chết hiệu suất của bạn, nhưng hầu như luôn có một cách khác để viết mã của bạn.

Hầu hết các chương trình, như bạn lưu ý, có thể chấp nhận một đối số cho tệp đầu vào. Tuy nhiên, luôn có phần mềm dựng sẵn <có thể được sử dụng ở bất cứ nơi nào có luồng STDIN được mong đợi sẽ giúp bạn tiết kiệm một quy trình bằng cách thực hiện công việc trong quy trình shell đang chạy.

Bạn thậm chí có thể sáng tạo với WHERE bạn viết nó. Thông thường nó sẽ được đặt ở cuối lệnh trước khi bạn chỉ định bất kỳ chuyển hướng đầu ra hoặc đường ống như thế này:

sed s/blah/blaha/ < data | pipe

Nhưng nó không phải theo cách đó. Nó thậm chí có thể đến đầu tiên. Ví dụ, mã ví dụ của bạn có thể được viết như thế này:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

Nếu khả năng đọc tập lệnh là mối quan tâm của bạn và mã của bạn đủ lộn xộn thì việc thêm một dòng cho catdự kiến ​​sẽ giúp dễ theo dõi hơn, có nhiều cách khác để làm sạch mã của bạn. Một cái mà tôi sử dụng rất nhiều giúp tạo ra các kịch bản dễ dàng tìm ra sau này là chia các ống thành các bộ logic và lưu chúng trong các hàm. Mã script sau đó trở nên rất tự nhiên và bất kỳ một phần nào của đường ống cũng dễ gỡ lỗi hơn.

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

Sau đó bạn có thể tiếp tục với fix_blahs < data | fix_frogs | reorder | format_for_sql. Một pipleline đọc như thế thực sự dễ theo dõi, và các thành phần riêng lẻ có thể được gỡ lỗi dễ dàng trong các chức năng tương ứng của chúng.


26
Tôi không biết rằng <filecó thể đến trước lệnh. Điều này giải quyết tất cả các vấn đề của tôi!

3
@Tim: Bash và Zsh đều ủng hộ điều đó, mặc dù tôi nghĩ nó thật xấu xí. Khi tôi lo lắng về mã của mình đẹp và có thể bảo trì được, tôi thường sử dụng các hàm để dọn sạch. Xem chỉnh sửa cuối cùng của tôi.
Caleb

8
@ Tim <filecó thể đến bất cứ nơi nào trên dòng lệnh: <file grep needlehay grep <file needlehay grep needle <file. Ngoại lệ là các lệnh phức tạp như vòng lặp và nhóm; có chuyển hướng phải đến sau khi đóng done/ }/ )/ vv. @Caleb Điều này giữ trong tất cả các vỏ Bourne / POSIX. Và tôi không đồng ý rằng nó xấu.
Gilles 'SO- ngừng trở nên xấu xa'

9
@Gilles, trong bash bạn có thể thay thế $(cat /some/file)với $(< /some/file), mà làm điều tương tự nhưng tránh đẻ trứng một quá trình.
cjm

3
Chỉ cần xác nhận rằng đó $(< /some/file)là tính di động hạn chế. Nó không hoạt động trong bash, nhưng không phải tro BusyBox, hoặc FreeBSD sh. Có lẽ cũng không hoạt động trong dấu gạch ngang, vì ba cái vỏ cuối cùng này đều là anh em họ thân thiết.
dubiousjim

22

Dưới đây là tóm tắt một số nhược điểm của:

cat $file | cmd

kết thúc

< $file cmd
  • Đầu tiên, một lưu ý: có (cố ý cho mục đích thảo luận) thiếu dấu ngoặc kép xung quanh $fileở trên. Trong trường hợp cat, đó luôn là một vấn đề ngoại trừ zsh; trong trường hợp chuyển hướng, đó chỉ là vấn đề đối với bashhoặc ksh88, đối với một số hệ vỏ khác chỉ khi tương tác (không phải trong tập lệnh).
  • Hạn chế thường được trích dẫn nhất là quá trình bổ sung được sinh ra. Lưu ý rằng nếu cmdđược dựng sẵn, đó thậm chí là 2 quá trình trong một số shell như thế nào bash.
  • Vẫn ở mặt trước hiệu năng, ngoại trừ trong các shell catđược dựng sẵn, đó cũng là một lệnh bổ sung đang được thực thi (và tất nhiên được tải và khởi tạo (và các thư viện cũng được liên kết với nó)).
  • Vẫn ở mặt trước hiệu năng, đối với các tệp lớn, điều đó có nghĩa là hệ thống sẽ phải luân phiên lên lịch catcmdxử lý và liên tục lấp đầy và làm trống bộ đệm ống. Thậm chí nếu cmdkhông 1GBlớn read()các cuộc gọi hệ thống tại một thời điểm, điều khiển sẽ phải đi lại giữa catcmdbởi vì một đường ống không thể chứa nhiều hơn một vài kilobyte dữ liệu tại một thời điểm.
  • Một số cmd(như wc -c) có thể thực hiện một số tối ưu hóa khi stdin của họ là một tệp thông thường mà họ không thể làm với cat | cmdvì stdin của họ chỉ là một đường ống. Với catvà một đường ống, điều đó cũng có nghĩa là chúng không thể seek()trong tập tin. Đối với các lệnh như tachoặc tail, điều đó tạo ra sự khác biệt lớn về hiệu suất vì điều đó có nghĩa là với catchúng cần lưu trữ toàn bộ đầu vào trong bộ nhớ.
  • Các cat $file, và thậm chí cả phiên bản chính xác hơn nó cat -- "$file"sẽ không hoạt động đúng đối với một số tên tập tin cụ thể như -(hoặc --helphoặc bất cứ điều gì bắt đầu với -nếu bạn quên --). Nếu một người khăng khăng sử dụng cat, có lẽ anh ta nên sử dụng cat < "$file" | cmdthay vì độ tin cậy.
  • Nếu $filekhông thể mở để đọc (truy cập bị từ chối, không tồn tại ...), < "$file" cmdsẽ báo cáo một thông báo lỗi nhất quán (bằng vỏ) và không chạy cmd, trong khi cat $file | cmdvẫn chạy cmdnhưng với stdin của nó trông giống như một tệp trống. Điều đó cũng có nghĩa là trong những thứ như < file cmd > file2, file2sẽ không bị đóng băng nếu filekhông thể mở được.

2
Về hiệu suất: Thử nghiệm này cho thấy sự khác biệt theo thứ tự 1 phần trăm trừ khi bạn đang xử lý rất ít trên luồng oletange.blogspot.dk/2013/10/usless-use-of-cat.html
Ole Tange

2
@OleTange. Đây là một thử nghiệm khác : truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Có rất nhiều thông số được đưa vào hình ảnh. Hình phạt hiệu suất có thể đi từ 0 đến 100%. Trong mọi trường hợp, tôi không nghĩ rằng hình phạt có thể là tiêu cực.
Stéphane Chazelas

2
wc -clà một trường hợp khá độc đáo, bởi vì nó có một phím tắt. Nếu bạn thay vào wc -wđó thì nó có thể so sánh với grepví dụ của tôi (nghĩa là xử lý rất ít - đó là tình huống '<' có thể tạo ra sự khác biệt).
Ole Tange

@OleTange, thậm chí ( wc -wtrên tệp thưa 1GB trong ngôn ngữ C trên linux 4.9 amd64) sau đó tôi thấy cách tiếp cận của mèo mất thêm 23% thời gian khi trên hệ thống đa lõi và 5% khi liên kết chúng với một lõi. Hiển thị thêm chi phí phát sinh bằng cách có dữ liệu được truy cập bởi nhiều hơn một lõi. Bạn có thể sẽ nhận được các kết quả khác nhau nếu bạn thay đổi kích thước của đường ống, sử dụng dữ liệu khác nhau, liên quan đến I / O thực sử dụng triển khai mèo sử dụng splice () ... Tất cả đều xác nhận rằng có rất nhiều tham số xuất hiện trong ảnh và trong mọi trường hợp catsẽ không giúp đỡ.
Stéphane Chazelas

1
Đối với tôi với tệp 1GB, wc -wnó chênh lệch khoảng 2% ... chênh lệch 15% nếu nó thành một grep đơn giản. Sau đó, thật kỳ lạ, nếu trên tệp NFS chia sẻ, nó thực sự nhanh hơn 20% để đọc nó nếu được dẫn từ cat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Lạ ...
rogerdpack

16

Đặt <filevào cuối của một đường ống là ít đọc hơn so với cat filelúc bắt đầu. Tiếng Anh tự nhiên đọc từ trái sang phải.

Đặt <filemột khởi đầu của đường ống cũng khó đọc hơn mèo, tôi nói. Một từ dễ đọc hơn một biểu tượng, đặc biệt là một biểu tượng dường như chỉ sai cách.

Sử dụng catbảo tồn các command | command | commandđịnh dạng.


Tôi đồng ý, sử dụng <một lần làm cho mã ít đọc hơn, vì nó phá hủy tính nhất quán cú pháp của một đa dòng.
A.Danischewski

@Jim Bạn có thể giải quyết khả năng đọc bằng cách tạo bí danh để <thích điều này: alias load='<'và sau đó sử dụng ví dụ load file | sed .... Bí danh có thể được sử dụng trong các tập lệnh sau khi chạy shopt -s expand_aliases.
niieani

1
Vâng, tôi biết về bí danh. Tuy nhiên, mặc dù bí danh này thay thế biểu tượng bằng một từ, nó yêu cầu người đọc biết về cài đặt bí danh cá nhân của bạn, vì vậy không dễ mang theo.
Jim

8

Một điều mà các câu trả lời khác ở đây dường như không được đề cập trực tiếp là việc sử dụng catnhư thế này không "vô dụng" theo nghĩa là "một quá trình mèo ngoại lai được sinh ra mà không có tác dụng"; nó vô dụng theo nghĩa là "một quá trình mèo được sinh ra mà chỉ làm những việc không cần thiết".

Trong trường hợp của hai:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

shell bắt đầu một quá trình sed đọc từ somefile hoặc stdin (tương ứng) và sau đó thực hiện một số xử lý - nó đọc lên cho đến khi chạm vào một dòng mới, thay thế 'foo' đầu tiên (nếu có) trên dòng đó bằng 'bar', sau đó in dòng đó đến thiết bị xuất chuẩn và vòng lặp.

Trong trường hợp:

cat somefile | sed 's/foo/bar/'

Vỏ sinh ra một quá trình mèo và một quá trình sed, và nối dây của mèo với stdin của sed. Quá trình con mèo đọc một đoạn dữ liệu vài kilo hoặc có thể là byte lớn ra khỏi tệp, sau đó ghi nó ra thiết bị xuất chuẩn của nó, trong đó sed sommand nhặt lên từ đó như trong ví dụ thứ hai ở trên. Trong khi sed đang xử lý đoạn đó, con mèo đang đọc một đoạn khác và viết nó vào thiết bị xuất chuẩn của nó để sed làm việc tiếp theo.

Nói cách khác, công việc bổ sung cần thiết bằng cách thêm catlệnh không chỉ là công việc bổ sung để sinh ra một catquy trình bổ sung , đó cũng là công việc bổ sung để đọc và ghi các byte của tệp hai lần thay vì một lần. Bây giờ, thực tế nói và trên các hệ thống hiện đại, điều đó không tạo ra sự khác biệt lớn - nó có thể khiến hệ thống của bạn thực hiện một vài phần triệu công việc không cần thiết. Nhưng nếu đó là tập lệnh mà bạn dự định phân phối, có khả năng cho những người sử dụng tập lệnh đó trên các máy đã bị thiếu năng lượng, một vài micro giây có thể tăng thêm rất nhiều lần lặp.


2
Xem oletange.blogspot.dk/2013/10/usless-use-of-cat.html để kiểm tra chi phí sử dụng bổ sung cat.
Ole Tange

@OleTange: Tôi chỉ tình cờ thấy điều này và ghé thăm blog của bạn. (1) Trong khi tôi thấy nội dung (phần lớn) bằng tiếng Anh, tôi thấy một loạt các từ trong (tôi đoán) tiếng Đan Mạch: , Hồi Tidsskyder, nhật ký Blog Bạn có biết về điều này, và nó nằm dưới sự kiểm soát của bạn? (2) Tôi gặp khó khăn khi đọc các bảng của bạn (2a) vì các đường lưới không đầy đủ và (2b) Tôi không hiểu ý của bạn là gì bởi Diff Diff (pct).
G-Man nói 'Tái lập Monica'

blogspot.dk được điều hành bởi Google. Hãy thử thay thế bằng blogspot.com. "Diff (pct)" là ms catđược chia cho ms mà không tính cattheo phần trăm (ví dụ: 264 ms / 216 ms = 1,22 = 122% = 22% chậm hơn với cat)
Ole Tange
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.