Làm thế nào tôi có thể ống stderr, và không stdout?


981

Tôi có một chương trình mà viết thông tin stdoutstderr, và tôi cần phải grepthông qua những gì sắp tới để stderr , trong khi không tính đến stdout .

Tất nhiên tôi có thể làm điều đó trong 2 bước:

command > /dev/null 2> temp.file
grep 'something' temp.file

nhưng tôi muốn có thể làm điều này mà không cần tập tin tạm thời. Có bất kỳ thủ thuật đường ống thông minh?


Một câu hỏi tương tự, nhưng vẫn giữ nguyên tiêu chuẩn: unix.stackexchange.com/questions35314/iêu
joeytwiddle

Câu hỏi này là dành cho Bash nhưng đáng để đề cập đến bài viết liên quan này cho vỏ Bourne / Almquist.
Stephen Niedzielski

10
Tôi đã mong đợi một cái gì đó như thế này : command 2| othercommand. Bash hoàn hảo đến mức sự phát triển kết thúc vào năm 1982, vì vậy chúng tôi sẽ không bao giờ thấy điều đó trong bash, tôi sợ.
Rolf

Câu trả lời:


1189

Đầu tiên chuyển hướng stderr đến stdout - đường ống; sau đó chuyển hướng stdout đến /dev/null(mà không thay đổi nơi stderr sẽ đi):

command 2>&1 >/dev/null | grep 'something'

Để biết chi tiết về chuyển hướng I / O trong tất cả các loại của nó, hãy xem chương về Chuyển hướng trong tài liệu tham khảo Bash.

Lưu ý rằng trình tự chuyển hướng I / O được diễn giải từ trái sang phải, nhưng các đường ống được thiết lập trước khi chuyển hướng I / O được diễn giải. Các mô tả tệp như 1 và 2 là các tham chiếu để mở các mô tả tệp. Hoạt động 2>&1làm cho mô tả tệp 2 aka stderr tham chiếu đến cùng một mô tả tệp mở như mô tả tệp 1 aka stdout hiện đang đề cập đến (xem dup2()open()). Thao tác >/dev/nullsau đó thay đổi mô tả tệp 1 để nó mô tả tệp mô tả mở /dev/null, nhưng điều đó không thay đổi thực tế là mô tả tệp 2 đề cập đến mô tả tệp mở mà mô tả tệp 1 ban đầu chỉ vào - cụ thể là đường ống.


44
Tôi chỉ tình cờ gặp / dev / stdout / dev / stderr / dev / stdin vào ngày khác, và tôi tò mò liệu đó có phải là những cách tốt để làm điều tương tự không? Tôi luôn nghĩ 2> & 1 là một chút xáo trộn. Vì vậy, một cái gì đó như: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons

17
Bạn có thể sử dụng /dev/stdoutet al, hoặc sử dụng /dev/fd/N. Chúng sẽ kém hiệu quả hơn một chút trừ khi vỏ xử lý chúng như những trường hợp đặc biệt; ký hiệu số thuần túy không liên quan đến việc truy cập các tệp theo tên, nhưng sử dụng các thiết bị có nghĩa là tra cứu tên tệp. Cho dù bạn có thể đo lường đó là tranh cãi. Tôi thích sự cô đọng của ký hiệu số - nhưng tôi đã sử dụng nó quá lâu (hơn một phần tư thế kỷ; ouch!) Mà tôi không đủ tư cách để đánh giá giá trị của nó trong thế giới hiện đại.
Jonathan Leffler

23
@Jonathan Leffler: Tôi có một vấn đề nhỏ với lời giải thích bằng văn bản đơn giản của bạn 'Chuyển hướng stderr sang stdout và sau đó chuyển sang / dev / null' - Vì người ta phải đọc các chuỗi chuyển hướng từ phải sang trái (không phải từ trái sang phải), chúng tôi cũng nên điều chỉnh giải thích văn bản đơn giản của chúng tôi cho điều này: 'Chuyển hướng thiết bị xuất chuẩn sang / dev / null, và sau đó thiết bị xuất chuẩn đến nơi thiết bị xuất chuẩn được sử dụng' .
Kurt Pfeifle

116
@KurtPfeifle: au contraire! Người ta phải đọc các chuỗi chuyển hướng từ trái sang phải vì đó là cách vỏ xử lý chúng. Hoạt động đầu tiên là 2>&1, có nghĩa là 'kết nối thiết bị lỗi chuẩn với bộ mô tả tệp mà thiết bị xuất chuẩn hiện đang hoạt động'. Hoạt động thứ hai là "thay đổi thiết bị xuất chuẩn để chuyển sang /dev/null", để thiết bị xuất chuẩn đi đến thiết bị xuất chuẩn ban đầu, đường ống. Vỏ phân tách mọi thứ tại biểu tượng đường ống trước tiên, vì vậy, chuyển hướng ống xảy ra trước khi chuyển hướng 2>&1hoặc >/dev/null, nhưng đó là tất cả; các hoạt động khác là từ trái sang phải. (Từ phải sang trái sẽ không hoạt động.)
Jonathan Leffler

14
Điều thực sự làm tôi ngạc nhiên về điều này là nó cũng hoạt động trên Windows (sau khi đổi tên /dev/nullthành Windows tương đương nul).
Michael Burr

364

Hoặc để hoán đổi đầu ra từ lỗi tiêu chuẩn và đầu ra tiêu chuẩn, sử dụng:

command 3>&1 1>&2 2>&3

Điều này tạo ra một bộ mô tả tệp mới (3) và gán nó cho cùng một vị trí là 1 (đầu ra tiêu chuẩn), sau đó gán fd 1 (đầu ra tiêu chuẩn) cho cùng một vị trí với fd 2 (lỗi tiêu chuẩn) và cuối cùng gán fd 2 (lỗi tiêu chuẩn ) đến cùng một nơi với fd 3 (đầu ra tiêu chuẩn).

Lỗi tiêu chuẩn hiện có sẵn như đầu ra tiêu chuẩn và đầu ra tiêu chuẩn cũ được bảo toàn trong lỗi tiêu chuẩn. Điều này có thể là quá mức cần thiết, nhưng hy vọng sẽ cung cấp thêm chi tiết về các mô tả tệp Bash (có chín quy trình có sẵn cho mỗi quy trình).


100
Một điều chỉnh cuối cùng sẽ là 3>&-đóng bộ mô tả dự phòng mà bạn đã tạo từ thiết bị xuất chuẩn
Jonathan Leffler

1
Chúng ta có thể tạo một mô tả tập tin có stderrvà một cái khác có sự kết hợp của stderrstdoutkhông? Nói cách khác có thể stderrđi đến hai tập tin khác nhau cùng một lúc?
Stuart

Sau đây vẫn in lỗi vào thiết bị xuất chuẩn. Tôi đang thiếu gì? ls -l not_a_file 3> & 1 1> & 2 2> & 3> lỗi.txt
user48956

1
@ JonasDahlbæk: tinh chỉnh chủ yếu là vấn đề gọn gàng. Trong các tình huống thực sự phức tạp, nó có thể tạo ra sự khác biệt giữa một quá trình phát hiện và không phát hiện EOF, nhưng điều đó đòi hỏi các trường hợp rất đặc biệt.
Jonathan Leffler

1
Thận trọng : điều này giả sử FD 3 chưa được sử dụng, không đóng nó và không hoàn tác việc hoán đổi mô tả tập tin 1 và 2, vì vậy bạn không thể chuyển lệnh này sang lệnh khác. Xem câu trả lời này để biết thêm chi tiết và giải quyết. Để biết cú pháp rõ ràng hơn cho {ba, z} sh, hãy xem câu trả lời này .
Tom Hale

218

Trong Bash, bạn cũng có thể chuyển hướng đến một lớp con bằng cách sử dụng thay thế quy trình :

command > >(stdlog pipe)  2> >(stderr pipe)

Đối với trường hợp trong tầm tay:

command 2> >(grep 'something') >/dev/null

1
Hoạt động rất tốt cho đầu ra màn hình. Bạn có biết tại sao nội dung không được bao phủ lại xuất hiện nếu tôi chuyển hướng đầu ra grep thành một tệp không? Sau command 2> >(grep 'something' > grep.log)grep.log chứa cùng một đầu ra như ungrepping.log từcommand 2> ungrepped.log
Tim

9
Sử dụng 2> >(stderr pipe >&2). Nếu không, đầu ra của "ống stderr" sẽ đi qua "ống stdlog".
ceving

yeah!, 2> >(...)hoạt động, tôi đã thử 2>&1 > >(...)nhưng không được
datdinhquoc

Đây là một ví dụ nhỏ có thể giúp tôi trong lần tới khi tôi tìm kiếm cách thực hiện việc này. Hãy xem xét những điều sau đây ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) Trong trường hợp này tôi muốn cũng xem chuyện gì đang sắp ra như lỗi trên giao diện điều khiển của tôi. Nhưng STDOUT đã đi đến tập tin đầu ra. Vì vậy, bên trong lớp vỏ phụ, bạn cần chuyển hướng STDOUT đó trở lại STDERR bên trong dấu ngoặc đơn. Trong khi nó hoạt động, đầu ra STDOUT từ teelệnh cuộn lên ở cuối out-content.txttập tin. Điều đó dường như không phù hợp với tôi.
sẽ

@datdinhquoc Tôi đã làm điều đó bằng cách nào đó2>&1 1> >(dest pipe)
Alireza Mohamadi

195

Kết hợp tốt nhất những câu trả lời này, nếu bạn làm:

command 2> >(grep -v something 1>&2)

... sau đó tất cả các thiết bị xuất chuẩn được bảo tồn như thiết bị xuất chuẩn tất cả các thiết bị xuất chuẩn được bảo tồn như thiết bị xuất chuẩn, nhưng bạn sẽ không thấy bất kỳ dòng nào trong thiết bị xuất chuẩn có chứa chuỗi "thứ gì đó".

Điều này có lợi thế duy nhất là không đảo ngược hoặc loại bỏ thiết bị xuất chuẩn và thiết bị xuất chuẩn, cũng không làm nhòe chúng với nhau, cũng không sử dụng bất kỳ tệp tạm thời nào.


Không command 2> >(grep -v something)(không có 1>&2) giống nhau sao?
Francesc Rosas

11
Không, không có điều đó, thiết bị lỗi chuẩn được lọc sẽ được chuyển đến thiết bị xuất chuẩn.
Pinko

1
Đây là những gì tôi cần - tệp đầu ra tar "thay đổi khi chúng tôi đọc nó" cho một thư mục luôn luôn, vì vậy chỉ muốn lọc ra một dòng đó nhưng xem có lỗi nào khác xảy ra không. Vì vậy tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)nên làm việc.
đánh bại

thật sai khi nói "bắt đầu bằng chuỗi". Không có gì về cú pháp được trình bày của grep sẽ làm cho nó chỉ loại trừ các dòng bắt đầu bằng chuỗi đã cho. Các dòng sẽ bị loại trừ nếu chúng chứa chuỗi đã cho ở bất cứ đâu trong chúng.
Mike Nakis

@MikeNakis cảm ơn - đã sửa! (Đó là phần còn lại từ bản nháp câu trả lời ban đầu của tôi, trong đó nó có ý nghĩa ...)
Pinko

102

Dễ dàng hình dung mọi thứ hơn nếu bạn nghĩ về những gì đang thực sự xảy ra với "chuyển hướng" và "đường ống". Chuyển hướng và đường ống trong bash làm một việc: sửa đổi nơi mô tả tệp quy trình 0, 1 và 2 điểm đến (xem / Proc / [pid] / fd / *).

Khi một đường ống hoặc "|" Toán tử có mặt trên dòng lệnh, điều đầu tiên xảy ra là bash tạo ra fifo và trỏ lệnh FD 1 của bên trái tới fifo này và trỏ FD 0 của lệnh bên phải vào cùng fifo.

Tiếp theo, các toán tử chuyển hướng cho mỗi bên được đánh giá từ trái sang phải và các cài đặt hiện tại được sử dụng mỗi khi xảy ra sự trùng lặp của bộ mô tả. Điều này rất quan trọng vì vì đường ống được thiết lập đầu tiên, FD1 (bên trái) và FD0 (bên phải) đã được thay đổi so với những gì chúng có thể thường có, và bất kỳ sự trùng lặp nào trong số này sẽ phản ánh thực tế đó.

Do đó, khi bạn gõ một cái gì đó như sau:

command 2>&1 >/dev/null | grep 'something'

Đây là những gì xảy ra, theo thứ tự:

  1. một đường ống (fifo) được tạo ra. "Lệnh FD1" được chỉ vào đường ống này. "grep FD0" cũng được chỉ vào đường ống này
  2. "Lệnh FD2" được trỏ đến nơi "lệnh FD1" hiện đang trỏ (đường ống)
  3. "lệnh FD1" được trỏ đến / dev / null

Vì vậy, tất cả đầu ra mà "lệnh" ghi vào FD 2 (stderr) của nó đi đến đường ống và được đọc bởi "grep" ở phía bên kia. Tất cả đầu ra mà "lệnh" ghi vào FD 1 (stdout) thực hiện theo cách của nó thành / dev / null.

Nếu thay vào đó, bạn chạy như sau:

command >/dev/null 2>&1 | grep 'something'

Đây là những gì xảy ra:

  1. một đường ống được tạo và "lệnh FD 1" và "grep FD 0" được chỉ vào nó
  2. "lệnh FD 1" được trỏ đến / dev / null
  3. "Lệnh FD 2" được chỉ đến nơi FD 1 hiện đang trỏ (/ dev / null)

Vì vậy, tất cả các thiết bị xuất chuẩn và thiết bị xuất chuẩn từ "lệnh" chuyển đến / dev / null. Không có gì đi vào đường ống, và do đó "grep" sẽ đóng mà không hiển thị bất cứ điều gì trên màn hình.

Cũng lưu ý rằng các chuyển hướng (mô tả tệp) có thể chỉ đọc (<), chỉ ghi (>) hoặc đọc-ghi (<>).

Một lưu ý cuối cùng. Cho dù một chương trình viết một cái gì đó cho FD1 hoặc FD2, hoàn toàn phụ thuộc vào lập trình viên. Thực hành lập trình tốt chỉ ra rằng các thông báo lỗi sẽ chuyển đến FD 2 và đầu ra bình thường sang FD 1, nhưng bạn sẽ thường thấy lập trình cẩu thả trộn lẫn hai hoặc bỏ qua quy ước.


6
Câu trả lời thực sự tốt đẹp. Một gợi ý của tôi là thay thế việc sử dụng "fifo" đầu tiên của bạn bằng "fifo (một ống có tên)". Tôi đã sử dụng Linux được một thời gian nhưng bằng cách nào đó, không bao giờ quản lý để tìm hiểu đó là một thuật ngữ khác cho tên ống. Điều này sẽ cứu tôi khỏi việc tìm kiếm nó, nhưng một lần nữa tôi sẽ không học được những thứ khác mà tôi thấy khi tôi phát hiện ra điều đó!
Đánh dấu Edington

3
@MarkEdington Xin lưu ý rằng FIFO chỉ là một thuật ngữ khác cho đường ống được đặt tên trong bối cảnh đường ống và IPC . Trong ngữ cảnh tổng quát hơn, FIFO có nghĩa là Đầu tiên vào trước, ra trước, mô tả việc chèn và xóa khỏi cấu trúc dữ liệu hàng đợi.
Loomchild

5
@Loomchild Tất nhiên rồi. Quan điểm của nhận xét của tôi là ngay cả khi là một nhà phát triển dày dạn kinh nghiệm, tôi chưa bao giờ thấy FIFO được sử dụng như một từ đồng nghĩa với tên ống. Nói cách khác, tôi không biết điều này: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Làm rõ rằng trong câu trả lời sẽ giúp tôi tiết kiệm thời gian.
Đánh dấu Edington

39

Nếu bạn đang sử dụng Bash, thì hãy sử dụng:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines


4
Không, |&bằng với sự 2>&1kết hợp giữa thiết bị xuất chuẩn và thiết bị xuất chuẩn. Câu hỏi rõ ràng yêu cầu đầu ra mà không có thiết bị xuất chuẩn.
Profpatsch

3
„Nếu '| &' được sử dụng, lỗi tiêu chuẩn của lệnh1 được kết nối với đầu vào tiêu chuẩn của lệnh2 thông qua đường ống; nó là viết tắt cho 2> & 1 | Lấy nguyên văn từ đoạn thứ tư tại liên kết của bạn.
Profpatsch

9
@Profpatsch: Câu trả lời của Ken là chính xác, hãy xem rằng anh ta chuyển hướng thiết bị xuất chuẩn thành null trước khi kết hợp thiết bị xuất chuẩn và thiết bị xuất chuẩn, do đó, bạn sẽ chỉ nhận được thông báo về thiết bị xuất chuẩn, vì thiết bị xuất chuẩn trước đó đã bị giảm xuống / dev / null.
Luciano

3
Nhưng tôi vẫn thấy câu trả lời của bạn là sai, >/dev/null |&mở rộng sang >/dev/null 2>&1 | và có nghĩa là inode stdout trống đối với pipe vì không ai (cả # 1 # 2 được gắn với / dev / null inode) được gắn với inode stdout (ví dụ ls -R /tmp/* >/dev/null 2>&1 | grep isẽ cho trống, nhưng ls -R /tmp/* 2>&1 >/dev/null | grep isẽ cho phép # 2 mà gắn với stdout inode sẽ ống).
Trái cây

3
Ken Sharp, tôi đã thử nghiệm và ( echo out; echo err >&2 ) >/dev/null |& grep "."không đưa ra kết quả nào (nơi chúng tôi muốn "err"). man bashnói If | & is used sử dụng là tốc ký cho 2> & 1 |. Điều này chuyển hướng ngầm định của lỗi tiêu chuẩn đến đầu ra tiêu chuẩn được thực hiện sau khi bất kỳ chuyển hướng nào được chỉ định bởi lệnh. Vì vậy, trước tiên, chúng tôi chuyển hướng lệnh FD1 thành null, sau đó chúng tôi chuyển hướng lệnh FD2 đến nơi FD1 chỉ, nghĩa là. null, vì vậy FD0 của grep không có đầu vào. Xem stackoverflow.com/a/18342079/69663 để được giải thích sâu hơn.
unhammer

11

Đối với những người muốn chuyển hướng thiết bị xuất chuẩn và thiết bị xuất chuẩn vĩnh viễn sang tệp, grep trên thiết bị xuất chuẩn, nhưng hãy giữ thiết bị xuất chuẩn để viết tin nhắn đến tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3

6

Điều này sẽ chuyển hướng lệnh1 stderr sang lệnh2 stdin, đồng thời để lại lệnh stdout lệnh1.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Lấy từ LDP


2

Tôi vừa đưa ra một giải pháp để gửi stdouttới một lệnh và stderrtới một lệnh khác, sử dụng các đường ống có tên.

Ở đây đi.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Có lẽ đó là một ý tưởng tốt để loại bỏ các đường ống được đặt tên sau đó.


0

Bạn có thể sử dụng vỏ RC .

Đầu tiên cài đặt gói (nó ít hơn 1 MB).

Đây là một ví dụ về cách bạn sẽ loại bỏ lỗi đầu ra tiêu chuẩn và lỗi tiêu chuẩn đường ống để grep in rc:

find /proc/ >[1] /dev/null |[2] grep task

Bạn có thể làm điều đó mà không cần rời Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

Như bạn có thể nhận thấy, bạn có thể chỉ định mô tả tệp nào bạn muốn được đặt bằng cách sử dụng dấu ngoặc sau đường ống.

Mô tả tập tin tiêu chuẩn được đánh số như vậy:

  • 0: Đầu vào tiêu chuẩn
  • 1: Sản lượng tiêu chuẩn
  • 2: Lỗi tiêu chuẩn

-3

Tôi thử làm theo, thấy nó hoạt động tốt,

command > /dev/null 2>&1 | grep 'something'

Không hoạt động. Nó chỉ gửi stderr đến thiết bị đầu cuối. Bỏ qua đường ống.
Động học Tripp
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.