จะตรวจสอบได้อย่างไรว่าไพพ์นั้นว่างเปล่าและเรียกใช้คำสั่งกับข้อมูลหากไม่มีหรือไม่


42

ฉันไพพ์บรรทัดในสคริปต์ทุบตีและต้องการตรวจสอบว่าไพพ์มีข้อมูลหรือไม่ก่อนป้อนเข้าโปรแกรม

กำลังค้นหาฉันพบtest -t 0แต่มันใช้งานไม่ได้ที่นี่ ส่งคืนค่าเท็จเสมอ ดังนั้นจะแน่ใจได้อย่างไรว่าท่อมีข้อมูลหรือไม่

ตัวอย่าง:

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

เอาท์พุท: fill

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

เอาท์พุท: fill

ซึ่งแตกต่างจากวิธีมาตรฐาน / มาตรฐานเพื่อทดสอบว่าก่อนหน้านี้ท่อที่ผลิตได้หรือไม่ อินพุตจะต้องเก็บรักษาไว้เพื่อส่งผ่านไปยังโปรแกรม นี่เป็นการสรุปวิธีการไพพ์เอาท์พุทจากโปรเซสหนึ่งไปยังอีกโปรเซสเท่านั้น ซึ่งมุ่งเน้นการส่งอีเมล


คำตอบ:


37

ไม่มีวิธีที่จะดูเนื้อหาของไพพ์โดยใช้ยูทิลิตี้เชลล์ที่มีอยู่ทั่วไปและไม่มีวิธีอ่านตัวอักษรไปยังไพพ์แล้วจึงนำกลับมาใช้ วิธีเดียวที่จะรู้ว่าไปป์นั้นมีข้อมูลคือการอ่านไบต์แล้วคุณจะต้องรับไบต์นั้นไปยังปลายทาง

ดังนั้นเพียงแค่นั้น: อ่านหนึ่งไบต์; หากคุณตรวจพบจุดสิ้นสุดไฟล์ให้ทำสิ่งที่คุณต้องการทำเมื่ออินพุตว่าง ถ้าคุณอ่านไบต์แล้วแยกสิ่งที่คุณต้องการทำเมื่ออินพุตไม่ว่างไพพ์นั้นเข้าไปและไพพ์ส่วนที่เหลือของข้อมูล

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

ifneยูทิลิตี้จากmoreutils โจอี้เดิมของรันคำสั่งถ้าใส่มันไม่ว่างเปล่า โดยปกติจะไม่ได้รับการติดตั้งตามค่าเริ่มต้น แต่ควรจะพร้อมใช้งานหรือสร้างได้ง่ายในระบบปฏิบัติการยูนิกซ์ส่วนใหญ่ หากอินพุตว่างifneไม่ทำอะไรเลยและส่งคืนสถานะ 0 ซึ่งไม่สามารถแยกความแตกต่างจากคำสั่งที่รันได้สำเร็จ หากคุณต้องการทำบางสิ่งถ้าอินพุตว่างคุณต้องจัดเรียงคำสั่งไม่ให้ส่งคืน 0 ซึ่งสามารถทำได้โดยให้เคสที่ประสบความสำเร็จส่งคืนสถานะข้อผิดพลาดที่สามารถแยกแยะได้:

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

test -t 0ไม่มีอะไรเกี่ยวข้องกับเรื่องนี้ มันทดสอบว่าอินพุตมาตรฐานเป็นเทอร์มินัลหรือไม่ ไม่พูดอะไรทางเดียวหรืออื่น ๆ ว่ามีข้อมูลใด ๆ


สำหรับระบบที่มี STREAMS based pipes (Solaris HP / UX) ฉันเชื่อว่าคุณสามารถใช้ I_PEEK ioctl เพื่อดูว่ามีอะไรอยู่ในท่อโดยไม่ต้องใช้มัน
Stéphane Chazelas

@ StéphaneChazelas แต่น่าเสียดายที่ไม่มีวิธีการดูข้อมูลจากไพพ์ / ฟีฟ่าใน * BSD ดังนั้นจึงไม่มีมุมมองในการใช้ยูทิลิตี้แบบพกพา peekซึ่งสามารถส่งคืนข้อมูลจริงจากไพพ์ได้ไม่ใช่แค่ปริมาณเท่าไหร่ (ใน 4.4 BSD, ท่อ ฯลฯ 386BSD ถูกนำมาใช้เป็นซ็อกเก็ตคู่แต่นั่นก็เสียใจมากใน * BSD รุ่นต่อมา - แม้ว่าพวกเขาจะเก็บไว้ในทิศทางสองทิศทาง)
mosvy

bash มีขั้นตอนการตรวจสอบอินพุตที่เปิดเผยผ่านทางread -t 0(ในกรณีนี้หมายถึงการหมดเวลาหากคุณสงสัย)
ไอแซก

11

ทางออกที่ง่ายคือการใช้ifneคำสั่ง (ถ้าอินพุตไม่ว่าง) ในการแจกแจงบางอย่างจะไม่มีการติดตั้งตามค่าเริ่มต้น มันเป็นส่วนหนึ่งของแพ็คเกจmoreutilsใน distros ส่วนใหญ่

ifne รันคำสั่งที่กำหนดถ้าหากอินพุตมาตรฐานไม่ว่างเปล่า

โปรดทราบว่าหากอินพุตมาตรฐานไม่ว่างเปล่าจะถูกส่งผ่านifneไปยังคำสั่งที่กำหนด


2
ตั้งแต่ปี 2560 จะไม่มีการเริ่มต้นใน Mac หรือ Ubuntu
Sridhar Sarnobat

7

คำถามเก่า แต่ในกรณีที่มีคนเจอมันอย่างที่ฉันทำ: ทางออกของฉันคือการอ่านด้วยการหมดเวลา

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

หากstdinว่างเปล่าสิ่งนี้จะกลับมาหลังจาก 5 วินาที มิฉะนั้นจะอ่านอินพุตทั้งหมดและคุณสามารถประมวลผลได้ตามต้องการ


ในขณะที่ฉันชอบความคิด แต่-tน่าเสียดายที่ไม่ได้เป็นส่วนหนึ่งของ POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ

6

ตรวจสอบว่า file descriptor ของ stdin (0) เปิดหรือปิด:

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

เมื่อคุณผ่านข้อมูลบางส่วนและคุณต้องการตรวจสอบว่ามีบางอย่างหรือไม่คุณผ่าน FD ต่อไปดังนั้นนี่จึงไม่ใช่การทดสอบที่ดี
Jakuje

1
[ -t 0 ]ตรวจสอบว่า fd 0 เปิดให้ttyหรือไม่ไม่ว่าจะปิดหรือเปิด
mosvy

@mosvy คุณช่วยอธิบายรายละเอียดเกี่ยวกับวิธีการที่จะมีผลต่อการใช้โซลูชันในสคริปต์ได้อย่างไร มีกรณีที่ไม่ได้ผล?
JepZ

@JepZ เหรอ? ./that_script </dev/null=> "stdin มีข้อมูล" หรือ./that_script <&-จะมี stdin จริงๆปิด
mosvy

5

คุณสามารถใช้test -s /dev/stdin(ใน subshell ชัดเจน) เช่นกัน

# 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
ใช้งานไม่ได้สำหรับฉัน มักจะกล่าวว่าท่อว่างเปล่า
แอมเฟตามาจิน

2
ใช้งานได้บน Mac ของฉัน แต่ไม่ใช่ในกล่อง Linux
จุดสูงสุด

3

ในทุบตี:

read -t 0 

ตรวจพบว่าอินพุตมีข้อมูล (โดยไม่ต้องอ่านอะไร) จากนั้นคุณสามารถอ่านอินพุต (หากอินพุตพร้อมใช้งานในเวลาที่ทำการอ่าน):

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

หมายเหตุ:เข้าใจว่าสิ่งนี้ขึ้นอยู่กับเวลา สิ่งนี้จะตรวจพบว่าอินพุตมีข้อมูลอยู่ในขณะนั้นread -tเท่านั้น

ตัวอย่างเช่นด้วย

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

เอาท์พุทคือ1(อ่านล้มเหลวคือ: อินพุตที่ว่างเปล่า) เสียงก้องเขียนข้อมูลบางอย่าง แต่ไม่เริ่มต้นอย่างรวดเร็วและเขียนไบต์แรกดังนั้นread -t 0จะรายงานว่าอินพุตว่างเปล่าเนื่องจากโปรแกรมยังไม่ได้เขียนอะไรเลย


github.com/bminor/bash/blob/… - นี่คือที่มาของวิธีการที่ bash ตรวจพบว่ามีบางสิ่งอยู่ในตัวอธิบายไฟล์
Pavel Patrin

ขอบคุณ @PavelPatrin
Isaac

1
@PavelPatrin ที่ไม่ได้ทำงาน เท่าที่เห็นได้อย่างชัดเจนจากการเชื่อมโยงของคุณbashอาจจะทำselect()หรือioctl(FIONREAD)หรือไม่ของพวกเขา แต่ไม่ใช่ทั้งสองตามที่ควรให้มันทำงาน read -t0เสีย อย่าใช้มัน
mosvy

Oooh วันนี้ฉันพยายามที่จะเข้าใจสิ่งที่ผิดกับมันเป็นเวลาสองชั่วโมง! ขอบคุณ @mosvy!
Pavel Patrin

3

วิธีง่ายๆในการตรวจสอบว่ามีข้อมูลสำหรับการอ่านใน Unix หรือไม่นั้นคือFIONREADioctl

ฉันไม่สามารถนึกถึงยูทิลิตี้มาตรฐานใด ๆ ที่ทำเช่นนั้นดังนั้นนี่เป็นโปรแกรมที่น่าสนใจทำ (ดีกว่าifneIMUT;

fionread [ prog args ... ]

หากมีข้อมูลใด ๆ ที่มีอยู่บน stdin ก็จะออกจากที่มีสถานะ 1. progหากมีข้อมูลก็จะทำงาน หากไม่progได้รับมันจะออกจากสถานะ 0

คุณสามารถลบpollโทรหากคุณสนใจเพียงในข้อมูลทันทีที่พร้อมใช้งาน สิ่งนี้ควรทำงานกับ fds เกือบทุกชนิดไม่ใช่แค่ท่อ

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);
}

โปรแกรมนี้ใช้งานได้จริงหรือ คุณไม่จำเป็นต้องรอPOLLHUPเหตุการณ์เช่นกันเพื่อจัดการกับกรณีที่ว่างเปล่า? ใช้งานได้หรือไม่หากมีคำอธิบายไฟล์หลายไฟล์ที่ปลายอีกด้านของไปป์?
Gilles 'หยุดความชั่วร้าย'

ใช่มันใช้งานได้ POLLHUP จะถูกส่งกลับโดยโพลเท่านั้นคุณควรใช้ POLLIN เพื่อรอ POLLHUP มันไม่สำคัญว่าจะมีมือจับเปิดกี่อันที่ปลายท่อ
mosvy

ดูunix.stackexchange.com/search?q=FIONREAD+user%3A22565สำหรับวิธีการเรียกใช้ FIONREAD จาก perl (มีให้มากกว่าปกติในคอมไพเลอร์)
Stéphane Chazelas

กิจวัตรประจำวันว่าการใช้ FIONREAD (หรือ HAVE_SELECT) ถูกนำมาใช้ในการทุบตีที่นี่
Isaac

2

หากคุณชอบซับในแบบสั้นและซับใน:

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

ฉันใช้ตัวอย่างจากคำถามเดิม หากคุณไม่ต้องการให้-qตัวเลือกข้อมูลการใช้piped กับ grep


0

นี่น่าจะเป็นการใช้ ifne อย่างสมเหตุสมผลในการทุบตีถ้าคุณโอเคกับการอ่านบรรทัดแรกทั้งหมด

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
readนอกจากนี้ยังจะกลับเท็จถ้าใส่ไม่ว่าง แต่ไม่มีตัวอักษรขึ้นบรรทัดใหม่ไม่ประมวลผลบางอย่างเกี่ยวกับการป้อนข้อมูลและอาจจะอ่านมากกว่าหนึ่งบรรทัดจนกว่าคุณจะเรียกว่าเป็นread ไม่สามารถใช้สำหรับข้อมูลที่กำหนดเองได้ IFS= read -r lineecho
Stéphane Chazelas

0

มันใช้งานได้สำหรับฉัน read -rt 0

ตัวอย่างจากคำถามเดิมไม่มีข้อมูล:

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

ไม่นั่นไม่ได้ผล ลองด้วย{ sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(false negative) และtrue | { sleep .1; read -rt0 && echo YES; }(false positive) ในความเป็นจริงทุบตีของ bash readจะถูกหลอกแม้โดย fds ที่เปิดในโหมดเขียนอย่างเดียว : { read -rt0 && echo YES; cat; } 0>/tmp/foo. สิ่งเดียวที่ดูเหมือนว่าจะทำคือselect(2)บน fd นั้น
mosvy

... และselectจะส่งคืน fd เป็น "พร้อม" หากread(2)ไม่มีการบล็อกไม่ว่าจะส่งคืนEOFหรือเกิดข้อผิดพลาด สรุป: read -t0จะเสียbashใน อย่าใช้มัน
mosvy

@mosvy คุณได้รายงานด้วย bashbug หรือไม่?
ไอแซก

@mosvy The { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }ไม่ใช่ค่าลบที่ผิดเนื่องจาก (ในขณะที่ทำการประมวลผลการอ่าน) ไม่มีอินพุต ต่อมา (การนอนหลับ 0.1) การป้อนข้อมูลที่เป็นใช้ได้ (สำหรับแมว)
ไอแซก

@mosvy ทำไมตัวเลือกrส่งผลกระทบต่อการตรวจจับ: echo "" | { read -t0 && echo YES; }พิมพ์ใช่ แต่echo "" | { read -rt0 && echo YES; }ไม่
ไอแซก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.