Làm thế nào để grep luồng lỗi tiêu chuẩn (stderr)?


76

Tôi đang sử dụng ffmpeg để lấy thông tin meta của clip âm thanh. Nhưng tôi không thể grep nó.

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --arch=i386 --extra-cflags=-O2 
      ...

Tôi đã kiểm tra, đầu ra ffmpeg này được chuyển đến stderr.

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

Vì vậy, tôi nghĩ rằng grep không thể đọc luồng lỗi để bắt các dòng khớp. Làm thế nào chúng ta có thể kích hoạt grep để đọc luồng lỗi?

Sử dụng liên kết nixCraft , tôi đã chuyển hướng luồng lỗi tiêu chuẩn sang luồng đầu ra tiêu chuẩn, sau đó grep hoạt động.

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

Nhưng nếu chúng ta không muốn chuyển hướng stderr sang thiết bị xuất chuẩn thì sao?


1
Tôi tin rằng grepchỉ có thể hoạt động trên thiết bị xuất chuẩn (Mặc dù tôi không thể tìm thấy nguồn chính tắc để sao lưu), điều đó có nghĩa là bất kỳ luồng nào cũng cần được chuyển đổi thành thiết bị xuất chuẩn trước.
Stefan Lasiewski

9
@Stefan: grepchỉ có thể hoạt động trên stdin. Đó là đường ống được tạo bởi lớp vỏ kết nối stdin của grep với thiết bị xuất chuẩn của lệnh khác. Và shell chỉ có thể kết nối một thiết bị xuất chuẩn với một stdin.
Gilles

Rất tiếc, bạn đã đúng. Tôi nghĩ đó là những gì tôi thực sự muốn nói, tôi chỉ không nghĩ đến nó. Cảm ơn @Giles.
Stefan Lasiewski

Bạn có muốn nó vẫn in stdout?
Mikel

Câu trả lời:


52

Nếu bạn đang sử dụng bashtại sao không sử dụng các đường ống ẩn danh, thực chất là viết tắt cho những gì phunehehe nói:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)


3
+1 Tốt đẹp! Bash chỉ, nhưng cách sạch hơn so với các lựa chọn thay thế.
Mikel

1
+1 Tôi đã sử dụng điều đó để kiểm tra cpđầu racp -r dir* to 2> >(grep -v "svn")
Betlista

5
Nếu bạn muốn đầu ra chuyển hướng lọc trên stderr một lần nữa, thêm một >&2, ví dụ:command 2> >(grep something >&2)
tlo

2
Hãy giải thích làm thế nào điều này hoạt động. 2>chuyển hướng stderr để tập tin, tôi nhận được điều đó. Điều này đọc từ tập tin >(grep -i Duration). Nhưng tập tin không bao giờ được lưu trữ? Kỹ thuật này được gọi là gì để tôi có thể đọc thêm về nó?
Marko Avlijaš

1
Đó là "đường cú pháp" để tạo và một đường ống (không phải tệp) và khi hoàn thành loại bỏ đường ống đó. Chúng có hiệu lực ẩn danh vì chúng không được đặt tên trong hệ thống tập tin. Bash gọi quá trình này thay thế.
Jé Queue

48

Không có lớp vỏ thông thường nào (thậm chí zsh) cho phép các đường ống khác ngoài từ stdout đến stdin. Nhưng tất cả các shell kiểu Bourne đều hỗ trợ gán lại mô tả tệp (như trong 1>&2). Vì vậy, bạn có thể tạm thời chuyển hướng thiết bị xuất chuẩn sang fd 3 và stderr sang thiết bị xuất chuẩn, và sau đó đưa fd 3 trở lại thiết bị xuất chuẩn. Nếu stufftạo ra một số đầu ra trên thiết bị xuất chuẩn và một số đầu ra trên thiết bị xuất chuẩn và bạn muốn áp dụng filtercho đầu ra lỗi để lại đầu ra tiêu chuẩn không bị ảnh hưởng, bạn có thể sử dụng { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1.

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error

Cách tiếp cận này hoạt động. Thật không may, trong trường hợp của tôi, nếu giá trị trả về khác không được trả về, nó sẽ bị mất - giá trị được trả về là 0 đối với tôi. Điều này có thể không luôn luôn xảy ra, nhưng nó xảy ra trong trường hợp tôi hiện đang xem xét. Có cách nào để cứu nó không?
Faheem Mitha

2
@FaheemMitha Không chắc chắn những gì bạn đang làm, nhưng có thể pipestatussẽ giúp
Gilles

1
@FaheemMitha, cũng set -o pipefailcó thể hữu ích ở đây, tùy thuộc vào những gì bạn muốn làm với trạng thái lỗi. (Ví dụ: nếu bạn đã set -ebật không thành công với bất kỳ lỗi nào, bạn cũng có thể muốn set -o pipefail.)
Wildcard

@Wildcard Có, tôi đã set -ebật không thành công với bất kỳ lỗi nào.
Faheem Mitha

Các rcvỏ cho phép đường ống stderr. Xem câu trả lời của tôi dưới đây.
Rolf

20

Điều này tương tự như "thủ thuật tập tin tạm thời" của phunehehe, nhưng sử dụng một đường ống có tên thay vào đó, cho phép bạn có được kết quả gần hơn một chút khi chúng xuất ra, có thể thuận tiện cho các lệnh chạy dài:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

Trong cấu trúc này, stderr sẽ được dẫn đến đường ống có tên là "mypipe". Vì grepđã được gọi với một đối số tệp, nó sẽ không tìm đến STDIN cho đầu vào của nó. Thật không may, bạn vẫn sẽ phải dọn sạch đường ống có tên đó sau khi bạn hoàn thành.

Nếu bạn đang sử dụng Bash 4, có một cú pháp phím tắt cho command1 2>&1 | command2, đó là command1 |& command2. Tuy nhiên, tôi tin rằng đây hoàn toàn là một phím tắt cú pháp, bạn vẫn đang chuyển hướng STDERR sang STDOUT.



1
|&Cú pháp đó là hoàn hảo để dọn sạch dấu vết ngăn xếp Ruby stderr cồng kềnh. Cuối cùng tôi cũng có thể grep những thứ đó mà không gặp quá nhiều rắc rối.
pgr

8

Câu trả lời của Gilles và Stefan Lasiewski đều tốt, nhưng cách này đơn giản hơn:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

Tôi giả sử bạn không muốn ffmpeg'sthiết bị xuất chuẩn.

Làm thế nào nó hoạt động:

  • ống đầu tiên
    • ffmpeg và grep được bắt đầu, với thiết bị xuất chuẩn của ffmpeg sẽ chuyển sang stdin của grep
  • chuyển hướng tiếp theo, từ trái sang phải
    • stderr của ffmpeg được đặt thành bất kể thiết bị xuất chuẩn của nó là gì (hiện là đường ống)
    • thiết bị xuất chuẩn của ffmpeg được đặt thành / dev / null

Mô tả này là khó hiểu với tôi. Hai viên đạn cuối cùng khiến tôi nghĩ "chuyển hướng stderr sang stdout", sau đó "redirect stdout (với stderr, now) thành / dev / null". Tuy nhiên, đây không phải là điều này thực sự đang làm. Những tuyên bố đó dường như bị đảo ngược.
Steve

1
@Steve Không có "với stderr" trong viên đạn thứ hai. Bạn đã thấy unix.stackexchange.com/questions/37660/order-of-redirections chưa?
Mikel

Không, ý tôi là đó là cách giải thích của tôi về cách bạn mô tả nó bằng tiếng Anh. Các liên kết bạn cung cấp là rất hữu ích, mặc dù.
Steve

Tại sao cần phải chuyển hướng đến / dev / null?
Kanwaljeet Singh

7

Xem dưới đây cho các kịch bản được sử dụng trong các thử nghiệm này.

Grep chỉ có thể hoạt động trên stdin, do đó bạn phải chuyển đổi luồng stderr theo hình thức mà Grep có thể phân tích cú pháp.

Thông thường, stdout và stderr đều được in ra màn hình của bạn:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

Để ẩn stdout, nhưng vẫn in stderr làm điều này:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

Nhưng grep sẽ không hoạt động trên stderr! Bạn sẽ mong đợi lệnh sau để chặn các dòng có chứa 'err', nhưng không được.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

Đây là giải pháp.

Cú pháp Bash sau sẽ ẩn đầu ra thành thiết bị xuất chuẩn, nhưng vẫn sẽ hiển thị stderr. Đầu tiên chúng ta chuyển stdout thành / dev / null, sau đó chúng ta chuyển stderr thành stdout, bởi vì các ống Unix sẽ chỉ hoạt động trên thiết bị xuất chuẩn. Bạn vẫn có thể grep văn bản.

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(Lưu ý rằng lệnh trên là khác nhau sau đó ./command >/dev/null 2>&1, mà là một lệnh rất phổ biến).

Đây là kịch bản được sử dụng để thử nghiệm. Điều này in một dòng tới thiết bị xuất chuẩn và một dòng cho thiết bị xuất chuẩn:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0

Nếu bạn chuyển hướng chuyển hướng xung quanh, bạn không cần tất cả các dấu ngoặc nhọn. Cứ làm đi ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel

3

Khi bạn chuyển đầu ra của một lệnh này sang lệnh khác (sử dụng |), bạn chỉ chuyển hướng đầu ra tiêu chuẩn. Vì vậy, điều đó sẽ giải thích tại sao

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

không xuất ra những gì bạn muốn (mặc dù nó hoạt động).

Nếu bạn không muốn chuyển hướng đầu ra lỗi sang đầu ra tiêu chuẩn, bạn có thể chuyển hướng đầu ra lỗi sang một tệp, sau đó grep nó sau

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error

Cảm ơn. it does work, though, bạn có nghĩa là nó đang làm việc trên máy của bạn? Thứ hai, như bạn đã chỉ ra bằng cách sử dụng đường ống, chúng tôi chỉ có thể chuyển hướng thiết bị xuất chuẩn. Tôi quan tâm đến một số tính năng lệnh hoặc bash sẽ cho phép tôi chuyển hướng stderr. (nhưng không phải là lừa tệp tạm thời)
Andrew-Dufresne

@Andrew Ý tôi là, lệnh hoạt động theo cách nó đã được thiết kế để hoạt động. Nó chỉ không hoạt động theo cách bạn muốn :)
phunehehe

Tôi không biết cách nào có thể chuyển hướng đầu ra lỗi của lệnh sang đầu vào tiêu chuẩn của lệnh khác. Sẽ rất thú vị nếu ai đó có thể chỉ ra điều đó.
phunehehe

2

Bạn có thể trao đổi các luồng. Điều này sẽ cho phép bạn đến grepluồng lỗi tiêu chuẩn ban đầu trong khi vẫn nhận được đầu ra ban đầu đi đến đầu ra tiêu chuẩn trong thiết bị đầu cuối:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

Điều này hoạt động bằng cách trước tiên tạo một bộ mô tả tệp mới (3) mở cho đầu ra và đặt nó vào luồng lỗi tiêu chuẩn ( 3>&2). Sau đó, chúng tôi chuyển hướng lỗi tiêu chuẩn sang đầu ra tiêu chuẩn ( 2>&1). Cuối cùng, đầu ra tiêu chuẩn được chuyển hướng đến lỗi tiêu chuẩn ban đầu và bộ mô tả tệp mới được đóng ( 1>&3-).

Trong trường hợp của bạn:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

Kiểm tra nó:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output

1

Tôi thích sử dụng rcvỏ trong trường hợp này.

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

Đây là một ví dụ về cách bạn sẽ loại bỏ stdoutstderrchuyển sang grep trong 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, cú pháp rất đơn giản, điều này làm cho giải pháp ưa thích của tôi.

Bạn có thể chỉ định mô tả tệp nào bạn muốn được đặt trong ngoặc, ngay sau ký tự ống.

Các mô tả tệp tiêu chuẩn được đánh số như sau:

  • 0: Đầu vào tiêu chuẩn
  • 1: Tiêu chuẩn
  • 2: Lỗi tiêu chuẩn

0

Một biến thể trên ví dụ về quy trình con bash:

lặp lại hai dòng để stderr và tee stderr vào một tập tin, grep tee và pipe trở lại stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

xuất sắc:

fff

some.load.errors (vd: stderr):

asdf
fff

-1

thử lệnh này

  • tạo tập tin với tên ngẫu nhiên
  • gửi đầu ra cho tập tin
  • gửi nội dung của tập tin đến đường ống
  • grep

ceva -> chỉ là một tên biến (tiếng Anh = cái gì đó)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;

Tôi sẽ sử dụng /tmp/$ceva, tốt hơn là xả rác thư mục hiện tại với các tệp tạm thời - và bạn cho rằng thư mục hiện tại có thể ghi được.
Rolf
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.