Як перевірити, чи труба порожня, і запустити команду над даними, якщо її немає?


42

Я проклав рядок в bash script і хочу перевірити, чи є в трубі дані, перш ніж подавати їх програмі.

Пошук, про який я знайшов, 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 Джої Гесса запускає команду , якщо її ввід не порожній. Зазвичай він не встановлений за замовчуванням, але він повинен бути доступним або простим для побудови на більшості варіантів Unix. Якщо вхід порожній, 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 (Solaris HP / UX) я вважаю, що ви можете використовувати ioctl I_PEEK, щоб заглянути в те, що знаходиться в трубі, не витрачаючи її.
Стефан Шазелас

@ StéphaneChazelas, на жаль, немає ніякого способу зазирнути дані з труби / fifo на * BSD, тому немає перспективи впровадити портативну peek утиліту, яка могла б повернути фактичні дані з труби, а не лише скільки їх є. (у 4.4 BSD, 386BSD тощо труби реалізовувались як пари розеток , але це було поглиблено в пізніших версіях * BSD - хоча вони тримали їх двонаправленими).
mosvy

bash має звичайну перевірку вхідних даних, що знаходяться через read -t 0(t в цьому випадку означає тайм-аут, якщо вам цікаво).
Ісаак

11

Просте рішення - використовувати ifneкоманду (якщо вхід не порожній). У деяких дистрибутивах він не встановлений за замовчуванням. Він є частиною пакету moreutilsу більшості дистрибутивів.

ifne виконує задану команду, якщо і лише тоді, коли стандартний ввід не порожній

Зауважте, що якщо стандартний вхід не порожній, він передається ifneданій команді


2
Станом на 2017 рік, його немає за замовчуванням у Mac чи Ubuntu.
Шрідхар Сарнобат

7

Старе питання, але якщо хтось натрапив на нього так, як я: Моє рішення - прочитати з таймаутом.

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

Якщо stdinпорожній, це повернеться через 5 секунд. В іншому випадку він прочитає всі дані, і ви можете обробити це за потребою.


Хоча ідея мені подобається, -tна жаль, не є частиною POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ

6

перевірте, чи дескриптор файлу 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(в явній підзарядці).

# 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

В bash:

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 виявляє, що щось є в дескрипторі файлів.
Павло Патрин

Дякуємо @PavelPatrin
Ісаак

1
@PavelPatrin Це не працює . Як чітко видно з вашого посилання, bashчи буде це робити, select()чи то ioctl(FIONREAD), або жодне з них, але не те і інше, як слід для нього. read -t0зламаний. Не користуйтеся ним
mosvy

Ооо, сьогодні я намагаюся зрозуміти, що з цим не так вже дві години! Дякую, @mosvy!
Павло Патрин

3

Один простий спосіб перевірити, чи є дані, доступні для читання в Unix, - це FIONREADioctl.

Я не можу придумати будь-яку стандартну утиліту, що робить саме це, тому ось тривіальна програма, що робить це (краще, ніж ifneз більш корисних IMHO ;-)).

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події, а також обробляти порожню справу? Це працює, якщо на іншому кінці труби є кілька описів файлів?
Жил "ТАК - перестань бути злим"

Так, це працює. POLLHUP повертається лише опитуванням, ви повинні використовувати POLLIN, щоб дочекатися POLLHUP. Не має значення, скільки відкритих ручок на будь-якому кінці труби.
mosvy

Див. Unix.stackexchange.com/search?q=FIONREAD+user%3A22565 про те, як запустити FIONREAD з perl (частіше, ніж компілятори)
Stéphane Chazelas

Підпрограма, яка використовує FIONREAD (або HAVE_SELECT), реалізована в bash тут .
Ісаак

2

Якщо вам подобаються короткі та виразні однолінійки:

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

Я використав приклади з початкового питання. Якщо ви не хочете, щоб -qопція " pipeped data" використовувала опцію "grep"


0

Здається, це розумна реалізація ifne в програванні bash, якщо ви все добре, читаючи весь перший рядок

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 line. echoне можна використовувати для довільних даних.
Стефан Шазелас

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; }(помилково негативно) та true | { sleep .1; read -rt0 && echo YES; }(хибнопозитивно). Насправді, в Bash readбуде обдурити навіть FDS , відкритий в запису-тільки режим: { read -rt0 && echo YES; cat; } 0>/tmp/foo. Єдине, що, здається, це - це select(2)на цьому фд.
mosvy

... і selectповерне fd як "готовий", якщо a read(2)на ньому не блокується, незалежно від того, повернеться він EOFабо помилка. Висновок: read -t0це зламано в bash. Не використовуйте його.
mosvy

@mosvy Ви повідомили про це з bashbug?
Ісаак

@mosvy { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }Не є помилковим негативом, оскільки (під час виконання читання) немає вводу. Пізніше (сон 0,1) , що вхід в наявності (для кішки).
Ісаак

@mosvy Чому параметр r впливає на виявлення?: echo "" | { read -t0 && echo YES; }друкує ТАК, але echo "" | { read -rt0 && echo YES; }ні.
Ісаак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.