Sử dụng mèo vô ích?


101

Điều này có lẽ có trong nhiều Câu hỏi thường gặp - thay vì sử dụng:

cat file | command

(được gọi là sử dụng mèo vô ích), cách đúng được cho là:

command < file

Theo cách thứ hai, "đúng" - Hệ điều hành không phải tạo thêm một quy trình.
Dù biết điều đó, tôi vẫn tiếp tục sử dụng con mèo vô dụng vì 2 lý do.

  1. thẩm mỹ hơn - tôi thích khi dữ liệu chỉ di chuyển đồng nhất từ ​​trái sang phải. Và nó dễ dàng hơn để thay thế catvới cái gì khác ( gzcat, echo, ...), thêm một tập tin thứ 2 hoặc chèn bộ lọc mới ( pv, mbuffer, grep...).

  2. Tôi "cảm thấy" rằng nó có thể nhanh hơn trong một số trường hợp. Nhanh hơn vì có 2 quá trình, thứ nhất ( cat) thực hiện việc đọc và thứ hai làm bất cứ điều gì. Và chúng có thể chạy song song, có nghĩa là đôi khi thực thi nhanh hơn.

Logic của tôi có đúng không (vì lý do thứ 2)?


22
catlà một đường ống nhận dạng . Nó chỉ truyền đầu vào đến đầu ra của nó. Nếu chương trình thứ hai trong chuỗi có thể mất đầu vào của nó từ lập luận cùng bạn vượt qua để cat(hoặc từ đầu vào tiêu chuẩn, nếu bạn vượt qua không có đối số), sau đó cathoàn toàn vô ích và chỉ có kết quả trong một quá trình bổ sung được chia hai và một ống thêm hạnh phúc tạo.
Frédéric Hamidi,

11
@ FrédéricHamidi khi con mèo không có lập luận hoặc lập luận của nó -, đó là một đường ống nhận dạng. Tuy nhiên, khi nó có nhiều hơn một đối số tên tệp không phải là dấu gạch ngang, nó sẽ trở thành một cái gì đó hơn là một đường ống nhận dạng và bắt đầu phục vụ một mục đích thực sự.
kojiro 29/07/12

3
Rất tiếc, liên kết phổ biến trước đây đến partmaps.org đã bị chết. Nội dung hiện có tại pigmail.org/era/unix/award.html
tripleee


2
Tôi nhận thấy rằng nếu bạn muốn hiển thị luồng dữ liệu sang phải (lý do 1), bạn có thể làm như vậy bằng cách đặt chuyển hướng tệp trước lệnh, như trong <file command1 | command2, mặc dù sẽ có bất đồng về tính thẩm mỹ.
holdenweb

Câu trả lời:


81

Tôi đã không biết về giải thưởng cho đến hôm nay khi một số tân binh cố gắng ghim UUOC vào tôi cho một trong những câu trả lời của tôi. Đó là một cat file.txt | grep foo | cut ... | cut .... Tôi đã đưa cho anh ấy một đoạn tâm trí của mình, và chỉ sau khi làm như vậy, anh ấy đã truy cập vào liên kết mà anh ấy đưa cho tôi đề cập đến nguồn gốc của giải thưởng và cách làm như vậy. Tìm kiếm thêm đã dẫn tôi đến câu hỏi này. Hơi tiếc mặc dù đã cân nhắc có ý thức, không câu trả lời nào bao gồm lý do của tôi.

Tôi không có ý định phòng thủ khi đáp lại anh ta. Rốt cuộc, trong những năm còn trẻ của tôi, tôi đã viết lệnh như grep foo file.txt | cut ... | cut ...vì bất cứ khi nào bạn làm đơn thường xuyêngrep , bạn sẽ học được vị trí của đối số tệp và biết rằng cái đầu tiên là mẫu và những cái sau là tên tệp.

Đó là một lựa chọn có ý thức để sử dụng cat khi tôi trả lời câu hỏi, một phần vì lý do "ngon" (theo cách nói của Linus Torvalds) nhưng chủ yếu là vì lý do thuyết phục về chức năng.

Lý do sau quan trọng hơn nên tôi sẽ nêu ra trước. Khi tôi cung cấp một đường ống như một giải pháp, tôi hy vọng nó có thể tái sử dụng. Rất có thể một đường ống sẽ được thêm vào cuối hoặc nối vào một đường ống khác. Trong trường hợp đó, có một đối số tệp để tăng khả năng sử dụng lại và hoàn toàn có thể làm như vậy âm thầm mà không có thông báo lỗi nếu đối số tệp tồn tại. I E. grep foo xyz | grep bar xyz | wcsẽ cung cấp cho bạn bao nhiêu dòng xyzchứa bartrong khi bạn đang mong đợi số dòng chứa cả foobar. Việc phải thay đổi các đối số thành một lệnh trong đường dẫn trước khi sử dụng nó rất dễ xảy ra lỗi. Thêm vào đó là khả năng xảy ra những thất bại thầm lặng và nó trở thành một thực hành đặc biệt quỷ quyệt.

Lý do trước đây cũng không phải là không quan trọng vì rất nhiều " khẩu vị tốt " chỉ đơn thuần là một lý do tiềm thức trực quan cho những điều như những thất bại thầm lặng ở trên mà bạn không thể nghĩ đến ngay tại thời điểm một số người cần được giáo dục nói "nhưng không phải con mèo đó vô dụng ”.

Tuy nhiên, tôi cũng sẽ cố gắng giải thích lý do "khẩu vị tốt" trước đây mà tôi đã đề cập. Lý do đó liên quan đến tinh thần thiết kế trực giao của Unix. grepkhông cutlskhông grep. Do đó ít nhất cũng grep foo file1 file2 file3đi ngược lại tinh thần thiết kế. Cách làm việc trực giao là như vậy cat file1 file2 file3 | grep foo. Bây giờ, grep foo file1chỉ là một trường hợp đặc biệt grep foo file1 file2 file3, và nếu bạn không đối xử với nó như cũ, thì ít nhất bạn đang sử dụng hết chu kỳ đồng hồ não để cố gắng tránh giải thưởng vô ích cho con mèo.

Điều đó dẫn chúng ta đến lập luận grep foo file1 file2 file3là nối, và catnối sao cho đúng cat file1 file2 file3nhưng vì catkhông nối trong nên cat file1 | grep foochúng ta đang vi phạm tinh thần của cả catUnix và Unix toàn năng. Chà, nếu đúng như vậy thì Unix sẽ cần một lệnh khác để đọc kết quả đầu ra của một tệp và đưa nó vào stdout (không phân trang nó hoặc bất cứ thứ gì chỉ là một đoạn trích thuần túy để stdout). Vì vậy, bạn sẽ gặp trường hợp bạn nói cat file1 file2hoặc bạn nói dog file1và hãy nhớ cẩn thận cat file1để tránh nhận được giải thưởng, trong khi cũng tránh dog file1 file2vì hy vọng thiết kế của dogsẽ xảy ra lỗi nếu nhiều tệp được chỉ định.

Hy vọng rằng ở điểm này, bạn thông cảm cho các nhà thiết kế Unix vì đã không đưa vào một lệnh riêng để nhổ một tệp thành stdout, đồng thời đặt tên catcho nối thay vì đặt tên khác cho nó. Trên thực tế, <edit>đã loại bỏ các nhận xét không chính xác <, <là một phương tiện không sao chép hiệu quả để tạo một tệp để tạo stdout mà bạn có thể đặt ở đầu đường dẫn, vì vậy các nhà thiết kế Unix đã bao gồm một cái gì đó cụ thể cho việc này</edit>

Câu hỏi tiếp theo là tại sao điều quan trọng là phải có các lệnh chỉ đơn thuần lấy một tệp hoặc nối nhiều tệp thành stdout mà không cần xử lý thêm? Một lý do là tránh có mọi lệnh Unix hoạt động trên đầu vào chuẩn để biết cách phân tích cú pháp ít nhất một đối số tệp dòng lệnh và sử dụng nó làm đầu vào nếu nó tồn tại. Lý do thứ hai là để tránh người dùng phải nhớ: (a) đối số tên tệp đi đâu; và (b) tránh lỗi đường ống im lặng như đã đề cập ở trên.

Điều đó mang lại cho chúng ta lý do tại sao greplại có thêm logic. Cơ sở lý luận là cho phép người dùng thông thạo các lệnh được sử dụng thường xuyên và trên cơ sở độc lập (chứ không phải là một đường dẫn). Đó là một sự thỏa hiệp nhỏ của tính trực giao để có được một lợi ích đáng kể về khả năng sử dụng. Không phải tất cả các lệnh đều nên được thiết kế theo cách này và các lệnh không được sử dụng thường xuyên nên tránh hoàn toàn logic bổ sung của các đối số tệp (hãy nhớ logic bổ sung dẫn đến sự mong manh không cần thiết (khả năng xảy ra lỗi)). Ngoại lệ là cho phép các đối số tệp như trong trường hợp grep. (Nhân tiện, lưu ý rằng lscó một lý do hoàn toàn khác để không chỉ chấp nhận mà còn yêu cầu khá nhiều đối số tệp)

Cuối cùng, những gì có thể được thực hiện tốt hơn là nếu các lệnh ngoại lệ như grep(nhưng không nhất thiết ls) tạo ra lỗi nếu đầu vào chuẩn cũng có sẵn khi các đối số tệp được chỉ định.


52
Lưu ý rằng khi grepđược gọi với nhiều tên tệp, nó sẽ đặt tiền tố cho các dòng được tìm thấy bằng tên của tệp được tìm thấy trong đó (trừ khi bạn tắt hành vi đó). Nó cũng có thể báo cáo số dòng trong các tệp riêng lẻ. Nếu chỉ sử dụng catđể cấp dữ liệu grep, bạn sẽ mất tên tệp và số dòng liên tục trên tất cả các tệp, không phải trên mỗi tệp. Vì vậy, có những lý do để grepxử lý nhiều tệp chính nó catkhông thể xử lý. Trường hợp tệp đơn và tệp không chỉ đơn giản là các trường hợp đặc biệt của việc sử dụng nhiều tệp chung grep.
Jonathan Leffler

38
Như đã lưu ý trong câu trả lời của kojiro , hoàn toàn có thể và hợp pháp để bắt đầu đường ống < file command1 .... Mặc dù vị trí quy ước cho các toán tử chuyển hướng I / O là sau tên lệnh và các đối số của nó, đó chỉ là quy ước và không phải là vị trí bắt buộc. Không <phải đứng trước tên tệp. Vì vậy, có một gần đối xứng hoàn hảo giữa >output<inputchuyển hướng: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler

15
Tôi nghĩ một lý do khiến mọi người ném đá UUoC (bao gồm cả tôi) là chủ yếu để giáo dục. Đôi khi mọi người xử lý các tệp văn bản khổng lồ hàng gigabyte trong trường hợp này việc thu nhỏ các đường ống (UUoC, thu gọn các tệp liên tiếp thành một, aso) là rất quan trọng và thường nó có thể được giả định một cách an toàn dựa trên câu hỏi rằng OP thực sự không biết rằng các chỉnh sửa nhỏ có thể có tác động lớn đến hiệu suất. Tôi hoàn toàn đồng ý với quan điểm của bạn về chu kỳ não và đó là lý do tại sao tôi thấy mình sử dụng mèo thường xuyên ngay cả khi không cần thiết. Nhưng điều quan trọng cần biết là nó không cần thiết.
Adrian Frühwirth

13
Làm ơn hãy hiểu; Tôi không có ý nghĩa gì nói rằng điều đó catlà vô ích. Nó không phải catlà vô ích; đó là một cấu trúc cụ thể không cần sử dụng cat. Nếu bạn thích, hãy lưu ý rằng đó là UUoC (Sử dụng Vô ích cat), chứ không phải UoUC (Sử dụng Vô ích cat). Có rất nhiều trường hợp khi nào catlà công cụ chính xác để sử dụng; Tôi không có vấn đề gì với việc nó được sử dụng khi nó là công cụ chính xác để sử dụng (và thực sự, đề cập đến một trường hợp trong câu trả lời của tôi).
Jonathan Leffler

6
@randomstring Tôi nghe bạn, nhưng tôi nghĩ nó thực sự phụ thuộc vào trường hợp sử dụng. Khi được sử dụng trên dòng lệnh, một bổ sung cattrong đường ống có thể không phải là vấn đề lớn tùy thuộc vào dữ liệu, nhưng khi được sử dụng như một môi trường lập trình, có thể hoàn toàn cần thiết để thực hiện những điều quan trọng về hiệu suất này; đặc biệt là khi xử lý bashnó, về mặt hiệu suất, giống như một bánh xe hình chữ nhật (so với kshdù sao thì ở đây tôi đang nói chậm hơn tới 10 lần - không đùa đâu). Bạn làm muốn tối ưu hóa dĩa của bạn (và không chỉ có vậy) khi giao dịch với các kịch bản lớn hơn hoặc vòng khổng lồ.
Adrian Frühwirth

58

Không!

Trước hết, không quan trọng việc chuyển hướng xảy ra ở đâu trong một lệnh. Vì vậy, nếu bạn muốn chuyển hướng sang bên trái lệnh của mình, điều đó tốt:

< somefile command

giống như

command < somefile

Thứ hai, có n + 1 quy trình và một vỏ con xảy ra khi bạn sử dụng một đường ống. Chắc chắn là nó chậm hơn. Trong một số trường hợp, n sẽ bằng 0 (ví dụ: khi bạn đang chuyển hướng đến nội trang shell), vì vậy bằng cách sử dụng, catbạn đang thêm một quy trình mới hoàn toàn không cần thiết.

Nói chung, bất cứ khi nào bạn thấy mình đang sử dụng một đường ống, bạn nên dành 30 giây để xem liệu bạn có thể loại bỏ nó hay không. (Nhưng có lẽ không đáng để mất nhiều hơn 30 giây.) Dưới đây là một số ví dụ mà các đường ống và quy trình thường được sử dụng một cách không cần thiết:

for word in $(cat somefile);  # for word in $(<somefile); … (or better yet, while read < somefile)

grep something | awk stuff; # awk '/something/ stuff' (similar for sed)

echo something | command; # command <<< something (although echo would be necessary for pure POSIX)

Vui lòng chỉnh sửa để thêm nhiều ví dụ.


2
Chà, tốc độ tăng sẽ không nhiều.
Dakkaron

9
Đặt "<somefile" trước "lệnh" về mặt kỹ thuật sẽ cho bạn từ trái sang phải, nhưng nó làm cho việc đọc không rõ ràng vì không có sự phân chia cú pháp: < cat grep doglà một ví dụ có sẵn để cho thấy rằng bạn không thể dễ dàng phân biệt giữa tệp đầu vào, lệnh nhận đầu vào và các đối số cho lệnh.
Necromancer

2
Quy tắc chung mà tôi đã áp dụng để quyết định nơi chuyển hướng STDIN đi là làm bất cứ điều gì để giảm thiểu sự xuất hiện của sự mơ hồ / tiềm năng gây ngạc nhiên. Nói một cách giáo điều rằng nó đi trước làm nảy sinh vấn đề của người chết, nhưng nói một cách giáo điều rằng nó đi sau có thể làm điều tương tự. Xem xét: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). Q. Có <quxáp dụng cho foo, hoặc cho baz, là -exec'd bởi foo? A. Nó áp dụng cho foo, nhưng có thể xuất hiện không rõ ràng. Đặt <qux trước foo trong trường hợp này rõ ràng hơn, mặc dù ít phổ biến hơn và tương tự như dấu sau ENV=VAR quux.
Mark G.

3
@necromancer, <"cat" grep dogdễ đọc hơn ở đó. (Tôi thường ủng hộ khoảng trắng, nhưng trường hợp cụ thể này là một ngoại lệ rất nhiều).
Charles Duffy

1
@kojiro "Rõ ràng là chậm hơn." Bạn không thể viết mà không sao lưu bằng các con số. Số của tôi ở đây: oletange.blogspot.com/2013/10/useless-use-of-cat.html (và chúng chỉ ra rằng nó chỉ chậm hơn khi bạn có thông lượng cao) Đâu là của bạn?
Ole Tange

30

Tôi không đồng ý với hầu hết các trường hợp Giải thưởng UUOC quá tự mãn vì khi dạy người khác, cat nó là một nơi giữ chỗ thuận tiện cho bất kỳ lệnh nào hoặc đường ống lệnh phức tạp nào tạo ra kết quả phù hợp cho vấn đề hoặc nhiệm vụ đang được thảo luận.

Điều này đặc biệt đúng trên các trang web như Stack Overflow, ServerFault, Unix & Linux hoặc bất kỳ trang SE nào.

Nếu ai đó hỏi cụ thể về tối ưu hóa, hoặc nếu bạn muốn thêm thông tin bổ sung về nó thì tuyệt vời, hãy nói về cách sử dụng mèo không hiệu quả. Nhưng đừng chê bai mọi người bởi vì họ đã chọn hướng đến sự đơn giản và dễ hiểu trong các ví dụ của họ hơn là nhìn-nhìn-tôi-như-thế-nào-mát-mẻ-tôi! sự phức tạp.

Tóm lại, vì mèo không phải lúc nào cũng là mèo.

Cũng bởi vì hầu hết những người thích đi khắp nơi trao giải cho các UUOC đều làm điều đó bởi vì họ quan tâm đến việc thể hiện mình 'thông minh như thế nào' hơn là giúp đỡ hoặc dạy dỗ mọi người. Trong thực tế, họ chứng minh rằng họ có thể chỉ là một người mới khác đã tìm thấy một cây gậy nhỏ để đánh bại các đồng nghiệp của họ.


Cập nhật

Đây là một UUOC khác mà tôi đã đăng trong một câu trả lời tại https://unix.stackexchange.com/a/301194/7696 :

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--delete-blank-lines" ] ; then
    filter='grep -v "^$"'
    shift
  fi

  # each arg is piped into sqlplus as a separate command
  printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter
}

UUOC pedants sẽ nói rằng đó là UUOC vì có thể dễ dàng đặt $filtermặc định thành chuỗi trống và có ifcâu lệnh làm filter='| grep -v "^$"'nhưng IMO, bằng cách không nhúng ký tự ống dẫn vào $filter, "vô dụng" này catphục vụ mục đích cực kỳ hữu ích là tự ghi lại thực tế mà $filtertrên printfdòng không chỉ là một đối số khác sqlplus, nó là một bộ lọc đầu ra tùy chọn do người dùng lựa chọn.

Nếu cần thiết phải có nhiều bộ lọc đầu ra tùy chọn, quá trình xử lý tùy chọn có thể thêm | whatevervào $filterthường xuyên nếu cần - một bộ lọc bổ sung cattrong đường dẫn sẽ không ảnh hưởng gì hoặc gây ra bất kỳ sự mất hiệu suất đáng chú ý nào.


11
Như một bên - ==bên trong [ ]không được POSIX chỉ định và không phải tất cả các triển khai đều chấp nhận nó. Toán tử được tiêu chuẩn hóa chỉ là =.
Charles Duffy

26

Với phiên bản UUoC, catphải đọc tệp vào bộ nhớ, sau đó ghi nó ra đường ống và lệnh phải đọc dữ liệu từ đường ống, vì vậy hạt nhân phải sao chép toàn bộ tệp ba lần trong khi trong trường hợp được chuyển hướng, nhân chỉ phải sao chép tệp một lần. Làm điều gì đó một lần sẽ nhanh hơn làm ba lần.

Sử dụng:

cat "$@" | command

là một cách sử dụng hoàn toàn khác và không nhất thiết là vô ích cat. Sẽ vẫn vô ích nếu lệnh là một bộ lọc tiêu chuẩn chấp nhận không hoặc nhiều đối số tên tệp và xử lý chúng lần lượt. Hãy xem xét trlệnh: nó là một bộ lọc thuần túy bỏ qua hoặc từ chối các đối số tên tệp. Để cấp nhiều tệp cho nó, bạn phải sử dụng catnhư được hiển thị. (Tất nhiên, có một cuộc thảo luận riêng rằng thiết kế của trnó không tốt lắm; không có lý do thực sự nào mà nó không được thiết kế như một bộ lọc tiêu chuẩn.) Điều này cũng có thể hợp lệ nếu bạn muốn lệnh coi tất cả đầu vào là một tệp thay vì nhiều tệp riêng biệt, ngay cả khi lệnh chấp nhận nhiều tệp riêng biệt: ví dụ, wclà một lệnh như vậy.

Đó là cat single-filetrường hợp vô điều kiện.


26

Để bảo vệ mèo:

Đúng,

   < input process > output 

hoặc là

   process < input > output 

hiệu quả hơn, nhưng nhiều lời gọi không có vấn đề về hiệu suất, vì vậy bạn không cần quan tâm.

lý do công thái học:

Chúng ta thường đọc từ trái sang phải, vì vậy một lệnh như

    cat infile | process1 | process2 > outfile

là tầm thường để hiểu.

    process1 < infile | process2 > outfile

phải nhảy qua process1, và sau đó đọc từ trái sang phải. Điều này có thể được chữa lành bằng cách:

    < infile process1 | process2 > outfile

Bằng cách nào đó, trông như thể có một mũi tên chỉ sang trái, nơi không có gì. Khó hiểu hơn và trông giống như trích dẫn lạ mắt là:

    process1 > outfile < infile

và việc tạo tập lệnh thường là một quá trình lặp đi lặp lại,

    cat file 
    cat file | process1
    cat file | process1 | process2 
    cat file | process1 | process2 > outfile

nơi bạn thấy tiến trình của mình từng bước, trong khi

    < file 

thậm chí không hoạt động. Những cách đơn giản ít bị lỗi hơn và phân loại lệnh tiện lợi cũng đơn giản với mèo.

Một chủ đề khác là hầu hết mọi người đã tiếp xúc với các toán tử so sánh> và <rất lâu trước khi sử dụng máy tính và khi sử dụng máy tính với tư cách là lập trình viên, thường tiếp xúc với các toán tử này hơn nhiều.

Và so sánh hai toán hạng với <và> là trái ngược nhau, có nghĩa là

(a > b) == (b < a)

Tôi nhớ lần đầu tiên sử dụng <để chuyển hướng đầu vào, tôi sợ

a.sh < file 

có thể có nghĩa giống như

file > a.sh

và bằng cách nào đó ghi đè tập lệnh a.sh của tôi. Có thể đây là một vấn đề đối với nhiều người mới bắt đầu.

sự khác biệt hiếm hoi

wc -c journal.txt
15666 journal.txt
cat journal.txt | wc -c 
15666

Sau này có thể được sử dụng trong tính toán trực tiếp.

factor $(cat journal.txt | wc -c)

Tất nhiên, dấu <cũng có thể được sử dụng ở đây, thay vì tham số tệp:

< journal.txt wc -c 
15666
wc -c < journal.txt
15666
    

nhưng ai quan tâm - 15k?

Nếu thỉnh thoảng gặp vấn đề, chắc chắn tôi sẽ thay đổi thói quen gọi mèo của mình.

Khi sử dụng rất lớn hoặc nhiều, nhiều tệp, tránh mèo là tốt. Đối với hầu hết các câu hỏi, việc sử dụng mèo là trực giao, lạc đề, không phải là một vấn đề.

Bắt đầu những cuộc thảo luận vô ích về mèo về mọi chủ đề lớp vỏ thứ hai chỉ gây khó chịu và nhàm chán. Nhận một cuộc sống và chờ đợi phút nổi tiếng của bạn, khi giải quyết các câu hỏi về hiệu suất.


5
+11111 .. Là tác giả của câu trả lời hiện được chấp nhận, tôi thực sự khuyên bạn nên bổ sung thú vị này. Các ví dụ cụ thể làm sáng tỏ các lập luận thường trừu tượng và dài dòng của tôi, và bạn nhận được tiếng cười từ sự bối rối ban đầu của tác giả file > a.shchỉ có giá trị thời gian đọc này :) Cảm ơn vì đã chia sẻ!
Necromancer

Trong lời kêu gọi này cat file | wc -c, wccần phải đọc stdin cho đến EOF, đếm từng byte. Nhưng trong điều này, wc -c < filenó chỉ thống kê stdin, phát hiện ra đó là một tệp thông thường và in st_size thay vì đọc bất kỳ đầu vào nào. Đối với một tệp lớn, sự khác biệt về hiệu suất sẽ được nhìn thấy rõ ràng.
oguz ismail

18

Một vấn đề khác là đường ống có thể che giấu một vỏ con. Đối với ví dụ này, tôi sẽ thay thế catbằng echo, nhưng vấn đề tương tự vẫn tồn tại.

echo "foo" | while read line; do
    x=$line
done

echo "$x"

Bạn có thể mong đợi xđể chứa foo, nhưng nó không. Các xbạn thiết lập là trong một subshell sinh ra để thực hiện whilevòng lặp. xtrong shell bắt đầu đường ống có một giá trị không liên quan hoặc không được đặt ở tất cả.

Trong bash4, bạn có thể định cấu hình một số tùy chọn shell để lệnh cuối cùng của một đường ống thực thi trong cùng một trình bao như lệnh bắt đầu đường ống, nhưng sau đó bạn có thể thử điều này

echo "foo" | while read line; do
    x=$line
done | awk '...'

xmột lần nữa là cục bộ cho whilevỏ con của.


5
Trong các shell POSIX nghiêm ngặt, đây có thể là một vấn đề phức tạp vì bạn không có ở đây các chuỗi hoặc quá trình thay thế để tránh đường ống. BashFAQ 24 có một số giải pháp hữu ích ngay cả trong trường hợp đó.
kojiro 29/07/12

4
Trong một số shell, đường ống minh họa không tạo ra một subshell. Ví dụ bao gồm Korn và Z. Chúng cũng hỗ trợ thay thế quy trình và đây là chuỗi. Tất nhiên chúng không hoàn toàn là POSIX. Bash 4 shopt -s lastpipephải tránh tạo vỏ con.
Tạm dừng cho đến khi có thông báo mới.

13

Là một người thường xuyên chỉ ra điều này và một số phản vật chất lập trình shell khác, tôi cảm thấy có trách nhiệm, muộn màng, cân nhắc.

Shell script là một ngôn ngữ sao chép / dán. Đối với hầu hết những người viết kịch bản shell, họ không ở trong đó để học ngôn ngữ; đó chỉ là một trở ngại mà họ phải vượt qua để tiếp tục làm những việc bằng (các) ngôn ngữ mà họ thực sự quen thuộc.

Trong bối cảnh đó, tôi thấy việc tuyên truyền các mô hình chống kịch bản shell khác nhau là gây rối và thậm chí có khả năng phá hoại. Lý tưởng nhất là mã mà ai đó tìm thấy trên Stack Overflow phải có thể sao chép / dán vào môi trường của họ với những thay đổi tối thiểu và sự hiểu biết không đầy đủ.

Trong số nhiều tài nguyên kịch bản shell trên mạng, Stack Overflow là một điều bất thường ở chỗ người dùng có thể giúp định hình chất lượng của trang web bằng cách chỉnh sửa các câu hỏi và câu trả lời trên trang web. Tuy nhiên, các chỉnh sửa mã có thể có vấn đề vì rất dễ thực hiện các thay đổi mà tác giả mã không có ý định. Do đó, chúng tôi có xu hướng để lại nhận xét để đề xuất các thay đổi đối với mã.

UUCA và các bình luận phản vật chất liên quan không chỉ dành cho tác giả của đoạn mã mà chúng ta bình luận; họ cũng là người thông báo trước để giúp người đọc của trang web nhận thức được các vấn đề trong mã họ tìm thấy ở đây.

Chúng tôi không thể hy vọng đạt được tình huống mà không có câu trả lời nào trên Stack Overflow đề xuất catcác s vô dụng (hoặc các biến chưa được trích dẫn, hoặc chmod 777, hoặc nhiều loại bệnh dịch phản vật chất khác), nhưng ít nhất chúng tôi có thể giúp giáo dục người dùng sắp sao chép / dán mã này vào vòng lặp chặt chẽ trong cùng của tập lệnh của chúng, thực thi hàng triệu lần.

Về lý do kỹ thuật, điều khôn ngoan truyền thống là chúng ta nên cố gắng giảm thiểu số lượng các quy trình bên ngoài; điều này tiếp tục giữ như một hướng dẫn chung tốt khi viết các kịch bản shell.


1
Ngoài ra, đối với các tệp lớn, việc chuyển qua đường ống catlà rất nhiều công tắc ngữ cảnh bổ sung và băng thông bộ nhớ (và ô nhiễm bộ nhớ đệm L3 từ các bản sao bổ sung của dữ liệu trong catbộ đệm đọc và bộ đệm đường ống). Đặc biệt trên một máy đa lõi lớn (giống như nhiều thiết lập lưu trữ) băng thông bộ nhớ đệm / bộ nhớ là tài nguyên được chia sẻ.
Peter Cordes

1
@PeterCordes Vui lòng đăng số đo của bạn. Vì vậy, chúng ta có thể làm được nếu nó thực sự quan trọng trong thực tế. Kinh nghiệm của tôi là nó thường không quan trọng: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange

1
Blog của riêng bạn cho thấy tốc độ chậm 50% đối với thông lượng cao và bạn thậm chí không xem xét tác động lên tổng thông lượng (nếu bạn có thứ khiến các lõi khác bận rộn). Nếu tôi gặp vấn đề, tôi có thể chạy thử nghiệm của bạn trong khi x264 hoặc x265 đang mã hóa video bằng tất cả các lõi và xem nó làm chậm quá trình mã hóa video đến mức nào. bzip2và quá trình gzipnén đều rất chậm so với lượng chi phí cộng catthêm vào đó (với máy không hoạt động). Thật khó để đọc các bảng của bạn (dòng nằm ở giữa một số?). systhời gian tăng lên rất nhiều, nhưng vẫn nhỏ so với người dùng hay thực?
Peter Cordes

8

Tôi thường sử dụng cat file | myprogramtrong các ví dụ. Đôi khi tôi bị buộc tội sử dụng mèo vô ích ( http://porkmail.org/era/unix/award.html ). Tôi không đồng ý vì những lý do sau:

  • Rất dễ hiểu chuyện gì đang xảy ra.

    Khi đọc một lệnh UNIX, bạn mong đợi một lệnh được theo sau bởi các đối số theo sau là chuyển hướng. Có thể đặt chuyển hướng ở bất cứ đâu nhưng nó hiếm khi được nhìn thấy - do đó mọi người sẽ khó đọc ví dụ hơn. tôi tin

    cat foo | program1 -o option -b option | program2

    dễ đọc hơn

    program1 -o option -b option < foo | program2

    Nếu bạn chuyển hướng chuyển hướng từ đầu, bạn sẽ gây nhầm lẫn cho những người không quen với cú pháp này:

    < foo program1 -o option -b option | program2

    và các ví dụ phải dễ hiểu.

  • Nó rất dễ dàng để thay đổi.

    Nếu bạn biết chương trình có thể đọc từ đó cat, bạn thường có thể cho rằng nó có thể đọc đầu ra từ bất kỳ chương trình nào xuất ra STDOUT, và do đó bạn có thể điều chỉnh nó cho phù hợp với nhu cầu của mình và nhận được kết quả có thể dự đoán được.

  • Nó nhấn mạnh rằng chương trình không bị lỗi, nếu STDIN không phải là một tệp.

    Sẽ không an toàn nếu cho rằng nếu program1 < foohoạt động thì cat foo | program1cũng sẽ hoạt động. Tuy nhiên, nó là an toàn để giả định ngược lại. Chương trình này hoạt động nếu STDIN là một tệp, nhưng không thành công nếu đầu vào là một đường ống, vì nó sử dụng seek:

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

Chi phí hiệu suất

Có một chi phí để thực hiện bổ sung cat . Để đưa ra ý tưởng về mức độ tôi đã chạy một số thử nghiệm để mô phỏng đường cơ sở ( cat), thông lượng thấp ( bzip2), thông lượng trung bình ( gzip) và thông lượng cao ( grep).

cat $ISO | cat
< $ISO cat
cat $ISO | bzip2
< $ISO | bzip2
cat $ISO | gzip
< $ISO gzip
cat $ISO | grep no_such_string
< $ISO grep no_such_string

Các bài kiểm tra được chạy trên hệ thống cấp thấp (0,6 GHz) và máy tính xách tay thông thường (2,2 GHz). Chúng được chạy 10 lần trên mỗi hệ thống và thời gian tốt nhất được chọn để bắt chước tình huống tối ưu cho mỗi bài kiểm tra. $ ISO là ubuntu-11.04-desktop-i386.iso. (Bảng đẹp hơn tại đây: http://oletange.blogspot.com/2013/10/useless-use-of-cat.html )

CPU                       0.6 GHz ARM
Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114

CPU                       Core i7 2.2 GHz
Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158

Kết quả cho thấy rằng đối với thông lượng thấp và trung bình, chi phí theo thứ tự là 1%. Điều này nằm trong độ không đảm bảo của phép đo, vì vậy trong thực tế không có sự khác biệt.

Đối với thông lượng cao, sự khác biệt lớn hơn và có sự khác biệt rõ ràng giữa hai loại.

Điều đó dẫn đến kết luận: Bạn nên sử dụng < thay vì cat |if:

  • sự phức tạp của quá trình xử lý tương tự như một grep đơn giản
  • hiệu suất quan trọng hơn khả năng đọc.

Nếu không, nó không quan trọng cho dù bạn sử dụng < hoặc cat |.

Và do đó bạn chỉ nên trao giải thưởng UUoC nếu và chỉ khi:

  • bạn có thể đo lường sự khác biệt đáng kể về hiệu suất (công bố số đo của bạn khi bạn trao giải thưởng)
  • hiệu suất quan trọng hơn khả năng đọc.

-3

Tôi nghĩ rằng (cách truyền thống) sử dụng đường ống nhanh hơn một chút; trên hộp của tôi tôi đã sử dụngstrace lệnh để xem điều gì đang xảy ra:

Không có đường ống:

toc@UnixServer:~$ strace wc -l < wrong_output.c
execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0
brk(0)                                  = 0x8b50000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000
mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000
mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb779f000, 8192, PROT_READ)   = 0
mprotect(0x804f000, 4096, PROT_READ)    = 0
mprotect(0xb77ce000, 4096, PROT_READ)   = 0
munmap(0xb77a5000, 29107)               = 0
brk(0)                                  = 0x8b50000
brk(0x8b71000)                          = 0x8b71000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000
close(3)                                = 0
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb77ac000, 4096)                = 0
open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0
mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000
close(3)                                = 0
open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0
mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000
close(3)                                = 0
read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180
read(0, "", 16384)                      = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000
write(1, "13\n", 313
)                     = 3
close(0)                                = 0
close(1)                                = 0
munmap(0xb7260000, 4096)                = 0
close(2)                                = 0
exit_group(0)                           = ?

Và với đường ống:

toc@UnixServer:~$ strace cat wrong_output.c | wc -l
execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0
brk(0)                                  = 0xa017000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000
mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000
mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb773d000, 8192, PROT_READ)   = 0
mprotect(0x8051000, 4096, PROT_READ)    = 0
mprotect(0xb776c000, 4096, PROT_READ)   = 0
munmap(0xb7743000, 29107)               = 0
brk(0)                                  = 0xa017000
brk(0xa038000)                          = 0xa038000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000
close(3)                                = 0
fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0
read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180
write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180
read(3, "", 32768)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
13

Bạn có thể thực hiện một số thử nghiệm với stracetimelệnh với nhiều lệnh hơn và dài hơn để có điểm chuẩn tốt.


9
Tôi không hiểu ý bạn (theo cách truyền thống) sử dụng pipe , hoặc tại sao bạn nghĩ điều này stracecho thấy nó nhanh hơn - stracekhông phải là theo dõi việc wc -lthực thi trong trường hợp thứ hai. Nó chỉ theo dõi lệnh đầu tiên của đường ống ở đây.
kojiro

@kojiro: ý tôi là theo cách truyền thống = cách được sử dụng nhiều nhất (tôi nghĩ rằng chúng tôi sử dụng đường ống nhiều hơn là chuyển hướng), tôi không thể xác nhận rằng nó nhanh hơn hay không, theo dấu vết của tôi, tôi đã thấy nhiều lệnh gọi hệ thống hơn cho chuyển hướng. Bạn có thể sử dụng chương trình xoay chiều và một vòng lặp để xem với một vòng lặp tiêu tốn nhiều thời gian hơn. Nếu bạn quan tâm, chúng tôi có thể đặt nó ở đây :)
TOC

3
Một so sánh giữa táo với táo sẽ được đặt strace -f sh -c 'wc -l < wrong_output.c'cùng với strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy

5
Dưới đây là kết quả từ ideone.com, mà hiện nay rõ ràng là ủng hộ mà không cat: ideone.com/2w1W42#stderr
tripleee

1
@CharlesDuffy: mkfifotạo một đường ống được đặt tên . Một đường ống ẩn danh được thiết lập với pipe(2)và sau đó fork, và yêu cầu cha mẹ và con đóng các đầu khác nhau của đường ống. Nhưng vâng, câu trả lời này là tổng vô nghĩa, và thậm chí không cố gắng đếm các cuộc gọi hệ thống hoặc sử dụng strace -Ođể đo lường chi phí, hoặc -rđể đánh dấu thời gian mỗi cuộc gọi liên quan đến người cuối cùng ...
Peter Cordes
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.