Несколько каналов в конце журнала — последний канал никогда не получает stdin

Несколько каналов в конце журнала — последний канал никогда не получает stdin

Я попал в интересное состояние на Ubuntu. Нижеприведенные шаги описывают это лучше всего.

С одной трубой я вижу то, что и ожидал

# In shell A
tail -f foo.log | grep aaa

# In shell B
echo aaa >> foo.log

# Shell A prints out `aaa`

Но с несколькими трубами я вообще ничего не вижу.

# In shell A
tail -f foo.log | grep aaa | grep bbb

# In shell B
echo aaa bbb >> foo.log

# Nothing ever prints in shell A

Но если я просто повторяю, то всё работает нормально.

echo 'aaa bbb' | grep aaa | grep bbb

Это моя попытка создать минимальное воспроизведение -- изначально я столкнулся с проблемой, пытаясь получить логи из adb logcat (Android dev tools). Я также пробовал в zsh, bash и fish.

Я предполагал, что это как-то связано с моим лимитом наблюдателей inotify, но его увеличение ничего не изменило.

решение1

Это происходит из-за буферизации в канале, который, как правило, не заботится об очередях и может накапливать данные.

Я думаю, tail -fчто использует буферизацию строк сама по себе; и последний grepпишет в tty, так что он также использует буферизацию строк. Поэтому ваш первый пример работает.

Но grepв середине отличается, и вам нужно настроить его поведение, принудительно включив буферизацию строк или отключив буферизацию. Следующие команды будут работать так, как вы ожидали.

  • Если у вас grepподдерживается --line-buffered(в Ubuntu так и есть):

      tail -f foo.log | grep --line-buffered aaa | grep bbb
    
  • Более общие решения (они будут работать со многими фильтрами, кроме grep):

      tail -f foo.log | unbuffer -p grep aaa | grep bbb
      tail -f foo.log | stdbuf -oL grep aaa | grep bbb
      tail -f foo.log | stdbuf -o0 grep aaa | grep bbb
    

Подробности и особенности см . здесь man 1 grep.man 1 unbufferman 1 stdbuf

Примечания:

  • Ни одно из решений не является переносимым ( grep --line-bufferedи не специфицировано в POSIX).unbufferstdbuf
  • Если вы можете сделать это с, grep --line-bufferedто это должен быть ваш выбор. Нет смысла использовать дополнительные инструменты.
  • Сопутствующий вопрос по Unix и Linux SE:Отключить буферизацию в канале.
  • unbufferиstdbuf работают совершенно по-разному.
  • В данном случае stdbufбуферизация строк ( -oL) должна быть предпочтительнее отсутствия буферизации ( -o0), поскольку
    • он, скорее всего, работает лучше,
    • и другие части вашего канала в любом случае используют буферизацию линий.
  • Если ваш последний grepзаписал в еще один файл, он будет вести себя как другой grep. В таком случае, если вы хотите, чтобы строки немедленно появлялись в конечном файле, то вам следует также изменить поведение последнего grep.
  • В fish, если grepэто функция-обертка, вы можете не получить желаемого поведения с --line-buffered. Используйте command grep --line-buffered. См. этот вопрос:Выходной канал ожидает EOF вfish.

Примечание: tail foo.log | grep aaa | grep bbb(т.е. tailбез -f) не вызывает проблему, потому что tailвыходит. При tailвыходе первый grepобнаруживает EOF, очищает свой буфер и выходит, затем второй grepделает то же самое.

Связанный контент