Bagaimana cara memeriksa apakah pipa kosong dan menjalankan perintah pada data jika tidak?

42

Saya telah memipipkan baris dalam skrip bash dan ingin memeriksa apakah pipa tersebut memiliki data, sebelum memasukkannya ke suatu program.

Mencari yang saya temukan test -t 0tetapi tidak berhasil di sini. Selalu mengembalikan false. Jadi bagaimana memastikan bahwa pipa itu memiliki data?

Contoh:

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

Keluaran: fill

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

Keluaran: fill

Tidak seperti standar / cara kanonik untuk menguji apakah sebelumnya pipa menghasilkan output? input perlu dipertahankan untuk meneruskannya ke program. Ini menggeneralisasi Bagaimana menyalurkan output dari satu proses ke proses lainnya tetapi hanya mengeksekusi jika yang pertama memiliki output? yang berfokus pada pengiriman email.

zetah
sumber
Sangat mirip: Buat pipa bersyarat pada pengembalian yang tidak kosong (pada Pengguna Super )
G-Man Mengatakan 'Reinstate Monica'

Jawaban:

37

Tidak ada cara untuk mengintip isi pipa menggunakan utilitas shell yang tersedia secara umum, juga tidak ada cara untuk membaca karakter ke pipa lalu memasangnya kembali. Satu-satunya cara untuk mengetahui bahwa pipa memiliki data adalah dengan membaca byte, dan kemudian Anda harus mendapatkan byte itu ke tujuannya.

Jadi lakukan saja: baca satu byte; jika Anda mendeteksi akhir file, maka lakukan apa yang ingin Anda lakukan ketika input kosong; jika Anda membaca byte maka garpu apa yang ingin Anda lakukan ketika input tidak kosong, pipa byte itu ke dalamnya, dan pipa sisa data.

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

The ifneutilitas dari MoreUtils Joey Hess menjalankan perintah jika input tidak kosong. Biasanya tidak diinstal secara default, tetapi harus tersedia atau mudah untuk membangun sebagian besar varian unix. Jika input kosong, ifnetidak melakukan apa-apa dan mengembalikan status 0, yang tidak dapat dibedakan dari perintah yang berhasil dijalankan. Jika Anda ingin melakukan sesuatu jika inputnya kosong, Anda perlu mengatur agar perintah tidak mengembalikan 0, yang dapat dilakukan dengan meminta case sukses mengembalikan status kesalahan yang dapat dibedakan:

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

test -t 0tidak ada hubungannya dengan ini; ini menguji apakah input standar adalah terminal. Itu tidak mengatakan apa pun dengan satu atau lain cara, apakah ada input yang tersedia.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Pada sistem dengan pipa berbasis STREAMS (Solaris HP / UX), saya yakin Anda dapat menggunakan I_PEEK ioctl untuk mengintip apa yang ada di pipa tanpa mengonsumsinya.
Stéphane Chazelas
@ StéphaneChazelas sayangnya tidak ada cara untuk mengintip data dari pipa / fifo pada * BSD, jadi tidak ada perspektif untuk mengimplementasikan utilitas portabel peek yang dapat mengembalikan data aktual dari sebuah pipa, tidak hanya seberapa banyak itu ada. (dalam 4.4 BSD, 386BSD dll. pipa diimplementasikan sebagai pasangan soket , tetapi itu memusnahkan dalam versi * BSD kemudian - meskipun mereka membuat mereka dua arah).
Mosvy
bash memiliki rutin untuk memeriksa input yang diekspos melalui read -t 0(t dalam hal ini berarti batas waktu, jika Anda bertanya-tanya).
Isaac
11

Solusi sederhana adalah dengan menggunakan ifneperintah (jika input tidak kosong). Dalam beberapa distribusi, ini tidak diinstal secara default. Ini adalah bagian dari paket moreutilsdi sebagian besar distro.

ifne menjalankan perintah yang diberikan jika dan hanya jika input standar tidak kosong

Perhatikan bahwa jika input standar tidak kosong, itu diteruskan ifneke perintah yang diberikan

Nick Wirth
sumber
2
Pada 2017, itu tidak ada di sana secara default di Mac atau Ubuntu.
Sridhar Sarnobat
7

Pertanyaan lama, tetapi kalau-kalau ada orang yang menemukannya seperti saya: Solusi saya adalah membaca dengan batas waktu.

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

Jika stdinkosong, ini akan kembali setelah 5 detik. Kalau tidak, itu akan membaca semua input dan Anda dapat memprosesnya sesuai kebutuhan.

Ladd
sumber
Walaupun saya suka gagasan itu, -tsayangnya bukan bagian dari POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ
6

periksa apakah file deskriptor stdin (0) terbuka atau tertutup:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"
mviereck
sumber
Ketika Anda melewati beberapa data dan Anda ingin memeriksa apakah ada, Anda tetap lulus FD jadi ini juga bukan tes yang baik.
Jakuje
1
[ -t 0 ]memeriksa apakah fd 0 dibuka untuk tty , bukan apakah ditutup atau terbuka.
Mosvy
@mosvy bisa tolong jelaskan bagaimana itu akan mempengaruhi menggunakan solusi itu dalam skrip? Apakah ada kasus ketika tidak berfungsi?
JepZ
@ JepZ ya? ./that_script </dev/null=> "stdin punya data". Atau ./that_script <&-agar stdin benar-benar tertutup .
Mosvy
5

Anda dapat menggunakan test -s /dev/stdin(dalam subkulit eksplisit) juga.

# 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')
trent55
sumber
8
Tidak bekerja untuk saya. Selalu mengatakan pipa kosong.
amphetamachine
2
Bekerja pada Mac saya, tetapi tidak pada kotak Linux saya.
puncak
3

Dalam bash:

read -t 0 

Mendeteksi jika input memiliki data (tanpa membaca apa pun). Kemudian Anda dapat membaca input (jika input tersedia pada saat membaca dijalankan):

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

Catatan: Memahami bahwa ini tergantung pada waktu. Ini mendeteksi jika input sudah memiliki data hanya pada saat read -tberjalan.

Misalnya dengan

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

outputnya adalah 1(baca kegagalan, yaitu: input kosong). Gema menulis beberapa data tetapi tidak terlalu cepat untuk memulai dan menulis byte pertama, dengan demikian, read -t 0akan melaporkan bahwa inputnya kosong, karena program belum menulis apa pun.

Ishak
sumber
github.com/bminor/bash/blob/… - di sini adalah sumber bagaimana bash mendeteksi bahwa ada sesuatu dalam deskriptor file.
Pavel Patrin
Terima kasih @PavelPatrin
Isaac
1
@ PavelPatrin Itu tidak berfungsi . Seperti yang terlihat jelas dari tautan Anda, bashapakah akan melakukan select()atau tidak ioctl(FIONREAD), atau tidak keduanya, tetapi tidak keduanya, sebagaimana mestinya agar dapat berfungsi. read -t0rusak. Jangan menggunakannya
mosvy
Oooh, hari ini saya mencoba memahami apa yang salah dengannya selama dua jam! Terima kasih, @mosvy!
Pavel Patrin
3

Satu cara mudah untuk memeriksa apakah ada data yang tersedia untuk dibaca di Unix adalah dengan FIONREADioctl.

Saya tidak dapat memikirkan utilitas standar melakukan hal itu, jadi di sini adalah program sepele melakukannya (lebih baik daripada ifnedari moreutils IMHO ;-)).

fionread [ prog args ... ]

Jika tidak ada data yang tersedia di stdin, itu akan keluar dengan status 1. Jika ada data, itu akan berjalan prog. Jika tidak progdiberikan, itu akan keluar dengan status 0.

Anda dapat menghapus pollpanggilan jika Anda hanya tertarik pada data yang segera tersedia. Ini harus bekerja dengan sebagian besar jenis fds, bukan hanya pipa.

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);
}
mosvy
sumber
Apakah program ini benar-benar berfungsi? Apakah Anda tidak perlu menunggu untuk suatu POLLHUPacara juga menangani kasing kosong? Apakah itu berfungsi jika ada beberapa deskripsi file di ujung pipa yang lain?
Gilles 'SANGAT berhenti menjadi jahat'
Ya itu berhasil. POLLHUP hanya dikembalikan oleh polling, Anda harus menggunakan POLLIN untuk menunggu POLLHUP. Tidak masalah berapa banyak pegangan terbuka ke ujung pipa.
Mosvy
Lihat unix.stackexchange.com/search?q=FIONREAD+user%3A22565 untuk cara menjalankan FIONREAD dari perl (lebih umum tersedia daripada kompiler)
Stéphane Chazelas
Rutin yang menggunakan FIONREAD (atau HAVE_SELECT) diimplementasikan dalam bash di sini .
Isaac
2

Jika Anda suka one-liners pendek dan samar:

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

Saya menggunakan contoh-contoh dari pertanyaan awal. Jika Anda tidak ingin -qopsi penggunaan data yang disalurkan dengan grep

yashma
sumber
0

Ini tampaknya menjadi implementasi ifne yang wajar di bash jika Anda setuju dengan membaca seluruh baris pertama

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


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo
Eric Woodruff
sumber
5
readjuga akan mengembalikan false jika inputnya tidak kosong tetapi tidak mengandung karakter baris baru, readmelakukan beberapa pemrosesan pada inputnya dan dapat membaca lebih dari satu baris kecuali Anda menyebutnya sebagai IFS= read -r line. echotidak dapat digunakan untuk data sewenang-wenang.
Stéphane Chazelas
0

Ini berfungsi untuk saya gunakan read -rt 0

contoh dari pertanyaan awal, tanpa data:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi
pengguna333755
sumber
tidak, itu tidak berhasil. coba dengan { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(false negative) dan true | { sleep .1; read -rt0 && echo YES; }(false positive). Bahkan, bash readakan tertipu bahkan oleh fds dibuka di write-satunya modus: { read -rt0 && echo YES; cat; } 0>/tmp/foo. Satu-satunya hal yang tampaknya dilakukan adalah select(2)pada fd itu.
Mosvy
... dan selectakan mengembalikan fd sebagai "siap" jika a read(2)di atasnya tidak akan diblokir, tidak peduli apakah itu akan kembali EOFatau kesalahan. Kesimpulan: read -t0yang rusak di bash. Jangan gunakan itu.
Mosvy
@mosvy Sudahkah Anda melaporkannya dengan bashbug?
Isaac
@mosvy Ini { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }bukan negatif palsu karena (pada saat membaca dijalankan) tidak ada input. Kemudian (tidur 0,1) input itu tersedia (untuk kucing).
Isaac
@mosvy Mengapa opsi r memengaruhi deteksi ?: echo "" | { read -t0 && echo YES; }mencetak YA tetapi echo "" | { read -rt0 && echo YES; }tidak.
Isaac