Làm thế nào để kiểm tra xem một đường ống có trống không và chạy lệnh trên dữ liệu nếu nó không?


42

Tôi đã đặt một dòng trong tập lệnh bash và muốn kiểm tra xem đường ống có dữ liệu hay không, trước khi đưa nó vào chương trình.

Tìm kiếm tôi tìm thấy test -t 0nhưng nó không hoạt động ở đây. Luôn trả về sai. Vậy làm thế nào để chắc chắn rằng đường ống có dữ liệu?

Thí dụ:

echo "string" | [ -t 0 ] && echo "empty" || echo "fill"

Đầu ra: fill

echo "string" | tail -n+2 | [ -t 0 ] && echo "empty" || echo "fill"

Đầu ra: fill

Không giống như cách tiêu chuẩn / chính tắc để kiểm tra xem liệu đầu ra đường ống sản xuất ra? đầu vào cần được bảo tồn để chuyển nó vào chương trình. Điều này khái quát Làm thế nào để đầu ra đường ống từ quá trình này sang quá trình khác nhưng chỉ thực hiện nếu đầu tiên có đầu ra? trong đó tập trung vào việc gửi email.


Câu trả lời:


37

Không có cách nào để xem nội dung của một đường ống bằng cách sử dụng các tiện ích vỏ thường có sẵn, cũng không có cách nào để đọc một ký tự cho đường ống sau đó đặt lại. Cách duy nhất để biết rằng một đường ống có dữ liệu là đọc một byte và sau đó bạn phải đưa byte đó đến đích của nó.

Vì vậy, làm điều đó: đọc một byte; nếu bạn phát hiện phần cuối của tệp, thì hãy làm những gì bạn muốn làm khi đầu vào trống; nếu bạn đọc một byte rồi rẽ nhánh những gì bạn muốn làm khi đầu vào không trống, hãy đặt byte đó vào đó và đặt phần còn lại của dữ liệu.

first_byte=$(dd bs=1 count=1 2>/dev/null | od -t o1 -A n | tr -dc 0-9)
if [ -z "$first_byte" ]; then
  # stuff to do if the input is empty
else
  {
    printf "\\$first_byte"
    cat
  } | {
    # stuff to do if the input is not empty
  }      
fi

Các ifnetiện ích từ moreutils Joey Hess của chạy một lệnh nếu đầu vào của nó là không có sản phẩm nào. Nó thường không được cài đặt theo mặc định, nhưng nó nên có sẵn hoặc dễ dàng xây dựng trên hầu hết các biến thể unix. Nếu đầu vào trống, ifnekhông có gì và trả về trạng thái 0, không thể phân biệt với lệnh đang chạy thành công. Nếu bạn muốn làm gì đó nếu đầu vào trống, bạn cần sắp xếp để lệnh không trả về 0, điều này có thể được thực hiện bằng cách có trường hợp thành công trả về trạng thái lỗi có thể phân biệt:

ifne sh -c 'do_stuff_with_input && exit 255'
case $? in
  0) echo empty;;
  255) echo success;;
  *) echo failure;;
esac

test -t 0không có gì để làm với điều này; nó kiểm tra xem đầu vào tiêu chuẩn là một thiết bị đầu cuối. Nó không nói bất cứ điều gì theo cách này hay cách khác về việc liệu có bất kỳ đầu vào nào có sẵn hay không.


Trên các hệ thống có đường ống dựa trên STREAM (Solaris HP / UX), tôi tin rằng bạn có thể sử dụng I_PEEK ioctl để nhìn trộm những gì trên đường ống mà không tiêu thụ nó.
Stéphane Chazelas

@ StéphaneChazelas thật không may, không có cách nào để xem dữ liệu từ một ống / fifo trên * BSD, vì vậy không có triển vọng nào để thực hiện một tiện ích di động peek có thể trả về dữ liệu thực tế từ một đường ống, không chỉ là có bao nhiêu. (trong 4,4 BSD, các đường ống 386BSD, v.v. đã được triển khai như các cặp ổ cắm , nhưng điều đó đã được rút ra trong các phiên bản sau của * BSD - mặc dù chúng giữ chúng hai chiều).
mosvy

bash có một thói quen để kiểm tra đầu vào tiếp xúc thông qua một read -t 0(t trong trường hợp này có nghĩa là hết thời gian, nếu bạn tự hỏi).
Isaac

11

Một giải pháp đơn giản là sử dụng ifnelệnh (nếu đầu vào không trống). Trong một số bản phân phối, nó không được cài đặt theo mặc định. Nó là một phần của gói moreutilstrong hầu hết các bản phát hành.

ifne chạy một lệnh đã cho khi và chỉ khi đầu vào tiêu chuẩn không trống

Lưu ý rằng nếu đầu vào tiêu chuẩn không trống, nó được chuyển qua ifnelệnh đã cho


2
Kể từ năm 2017, nó không có ở đó theo mặc định trong Mac hoặc Ubuntu.
Sridhar Sarnobat

7

Câu hỏi cũ, nhưng trong trường hợp ai đó đi qua nó như tôi đã làm: Giải pháp của tôi là đọc với thời gian chờ.

while read -t 5 line; do
    echo "$line"
done

Nếu stdintrống, điều này sẽ trở lại sau 5 giây. Nếu không, nó sẽ đọc tất cả các đầu vào và bạn có thể xử lý nó khi cần thiết.


Mặc dù tôi thích ý tưởng này, nhưng -tthật đáng buồn, không phải là một phần của POSIX: pubs.opengroup.org/onlinepub/9699919799/utilities/read.html
JepZ

6

kiểm tra xem mô tả tập tin của stdin (0) là mở hoặc đóng:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"

Khi bạn vượt qua một số dữ liệu và bạn muốn kiểm tra xem có dữ liệu nào không, bạn vẫn vượt qua FD vì vậy đây cũng không phải là một bài kiểm tra tốt.
Jakuje

1
[ -t 0 ]kiểm tra xem fd 0 có được mở thành tty không, xem nó được đóng hay mở.
mosvy

@mosvy bạn có thể vui lòng giải thích về việc điều đó sẽ ảnh hưởng như thế nào đến việc sử dụng giải pháp đó trong một kịch bản không? Có trường hợp nào khi nó không hoạt động?
JepZ

@JepZ hả? ./that_script </dev/null=> "stdin có dữ liệu". Hoặc ./that_script <&-để có stdin thực sự đóng cửa .
mosvy

5

Bạn cũng có thể sử dụng test -s /dev/stdin(trong một lớp con rõ ràng).

# test if a pipe is empty or not
echo "string" | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

echo "string" | tail -n+2 | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

: | (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

8
Không làm việc cho tôi. Luôn luôn nói đường ống là trống rỗng.
amphetamachine

2
Hoạt động trên máy Mac của tôi, nhưng không phải trên hộp Linux của tôi.
cao điểm

3

Trong bash:

read -t 0 

Phát hiện nếu một đầu vào có dữ liệu (không đọc bất cứ thứ gì). Sau đó, bạn có thể đọc đầu vào (nếu đầu vào khả dụng tại thời điểm đọc được thực thi):

if     read -t 0
then   read -r input
       echo "got input: $input"
else   echo "No data to read"
fi

Lưu ý: Hiểu rằng điều này phụ thuộc vào thời gian. Điều này phát hiện nếu đầu vào đã có dữ liệu chỉ tại thời điểm read -tchạy.

Ví dụ với

{ sleep 0.1; echo "abc"; } | read -t 0; echo "$?"

đầu ra là 1(đọc thất bại, tức là: đầu vào trống). Tiếng vang ghi một số dữ liệu nhưng không nhanh lắm để bắt đầu và ghi byte đầu tiên của nó, do đó, read -t 0sẽ báo cáo rằng đầu vào của nó trống, vì chương trình chưa viết gì cả.


github.com/bminor/bash/blob/ - - đây là nguồn của cách bash phát hiện ra rằng có gì đó trong mô tả tệp.
Pavel Patrin

Cảm ơn @PavelPatrin
Isaac

1
@PavelPatrin Điều đó không hoạt động . Như được thấy rõ từ liên kết của bạn, bashsẽ làm một select()hoặc một ioctl(FIONREAD)hoặc cả hai, nhưng không phải cả hai, vì nó sẽ làm cho nó hoạt động. read -t0bị phá vỡ. Không sử dụng nó
mosvy

Oooh, hôm nay tôi cố gắng hiểu những gì là sai với nó trong hai giờ! Cảm ơn bạn, @mosvy!
Pavel Patrin

3

Một cách dễ dàng để kiểm tra xem có dữ liệu có sẵn để đọc trong Unix hay không là với FIONREADioctl.

Tôi không thể nghĩ ra bất kỳ tiện ích tiêu chuẩn nào làm việc đó, vì vậy đây là một chương trình tầm thường làm điều đó (tốt hơn so với ifnetừ IMut ;-)).

fionread [ prog args ... ]

Nếu không có dữ liệu trên stdin, nó sẽ thoát với trạng thái 1. Nếu có dữ liệu, nó sẽ chạy prog. Nếu không progđược đưa ra, nó sẽ thoát với trạng thái 0.

Bạn có thể xóa pollcuộc gọi nếu bạn chỉ quan tâm đến dữ liệu có sẵn ngay lập tức . Điều này sẽ làm việc với hầu hết các loại fds, không chỉ ống.

fionread.c

#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#include <err.h>

int main(int ac, char **av){
        int r; struct pollfd pd = { 0, POLLIN };
        if(poll(&pd, 1, -1) < 0) err(1, "poll");
        if(ioctl(0, FIONREAD, &r)) err(1, "ioctl(FIONREAD)");
        if(!r) return 1;
        if(++av, --ac < 1) return 0;
        execvp(*av, av);
        err(1, "execvp %s", *av);
}

Chương trình này có thực sự hoạt động không? Bạn không cần phải chờ đợi một POLLHUPsự kiện cũng để xử lý trường hợp trống? Nó có hoạt động không nếu có nhiều mô tả tập tin ở đầu kia của ống?
Gilles 'SO- ngừng trở nên xấu xa'

Có nó hoạt động. POLLHUP chỉ được trả về bởi cuộc thăm dò ý kiến, bạn nên sử dụng POLLIN để chờ POLLHUP. Không quan trọng có bao nhiêu tay cầm mở ở bất kỳ đầu ống nào.
mosvy

Xem unix.stackexchange.com/search?q=FIONREAD+user%3A22565 để biết cách chạy FIONREAD từ perl (thường có sẵn hơn trình biên dịch)
Stéphane Chazelas

Thói quen sử dụng FIONREAD (hoặc Lawr_SELECT) được triển khai trong bash tại đây .
Isaac

2

Nếu bạn thích một lớp lót ngắn và khó hiểu:

$ echo "string" | grep . && echo "fill" || echo "empty"
string
fill
$ echo "string" | tail -n+2 | grep . && echo "fill" || echo "empty"
empty

Tôi đã sử dụng các ví dụ từ câu hỏi ban đầu. Nếu bạn không muốn -qtùy chọn sử dụng dữ liệu đường ống với grep


0

Đây có vẻ là một triển khai ifne hợp lý trong bash nếu bạn ổn với việc đọc toàn bộ dòng đầu tiên

ifne () {
        read line || return 1
        (echo "$line"; cat) | eval "$@"
}


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo

5
readcũng sẽ trả về false nếu đầu vào không trống nhưng không chứa ký tự dòng mới, readthực hiện một số xử lý trên đầu vào của nó và có thể đọc nhiều hơn một dòng trừ khi bạn gọi nó là IFS= read -r line. echokhông thể được sử dụng cho dữ liệu tùy ý.
Stéphane Chazelas

0

Cái này hiệu quả với tôi read -rt 0

ví dụ từ câu hỏi ban đầu, không có dữ liệu:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi

không, nó không hoạt động thử với { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(âm tính giả) và true | { sleep .1; read -rt0 && echo YES; }(dương tính giả). Trên thực tế, bash's readsẽ bị đánh lừa ngay cả khi các fds được mở ở chế độ chỉ ghi : { read -rt0 && echo YES; cat; } 0>/tmp/foo. Điều duy nhất nó dường như làm là select(2)trên fd đó.
mosvy

... và selectsẽ trả về một fd là "sẵn sàng" nếu a read(2)trên nó không chặn, bất kể nó sẽ trả về EOFhay có lỗi. Kết luận: read -t0được chia trong bash. Đừng sử dụng nó.
mosvy

@mosvy Bạn đã báo cáo nó với bashorms?
Isaac

@mosvy { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }Không phải là phủ định sai bởi vì (tại thời điểm đọc được thực thi) không có đầu vào. Sau đó (ngủ .1) đầu vào đó có sẵn (đối với mèo).
Isaac

@mosvy Tại sao tùy chọn r ảnh hưởng đến việc phát hiện?: echo "" | { read -t0 && echo YES; }in CÓ nhưng echo "" | { read -rt0 && echo YES; }không.
Isaac
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.