Принудительная линейная буферизация стандартного вывода при подключении к тройнику

118

Обычно stdoutэто строчная буферизация. Другими словами, если ваш printfаргумент заканчивается новой строкой, вы можете ожидать, что строка будет напечатана мгновенно. Похоже, это не выполняется при использовании канала для перенаправления tee.

У меня есть программа на C ++ a, которая выводит строки, которые всегда \nзавершаются, в stdout.

Когда он запускается сам по себе ( ./a), все печатается правильно и в нужное время, как и ожидалось. Однако, если я передаю его в tee( ./a | tee output.txt), он ничего не печатает, пока не завершит работу, что противоречит цели использования tee.

Я знаю, что могу исправить это, добавив fflush(stdout)после каждой операции печати в программе на C ++. Но есть ли более чистый и простой способ? Есть ли команда, которую я могу запустить, например, для принудительной stdoutстрочной буферизации даже при использовании канала?

houbysoft
источник

Ответы:

67

Попробуйте, unbufferчто входит в expectкомплект. Возможно, он уже есть в вашей системе.

В вашем случае вы бы использовали это так:

./a | unbuffer -p tee output.txt

( -pдля режима конвейера, в котором unbuffer читает из stdin и передает его команде в остальных аргументах)

Приостановлено до дальнейшего уведомления.
источник
Спасибо, это сработало, хотя мне пришлось скомпилировать expectсебя, поскольку unbuffer, похоже, он не включен по умолчанию в OS X.
houbysoft
@houbysoft: Я рад, что это сработало для вас. unbufferэто всего лишь небольшой сценарий, поэтому вам не нужно перекомпилировать весь пакет.
Приостановлено до дальнейшего уведомления.
Да, наверное , нет, но ./configure && makeзанимает около 10 секунд , а затем я просто переехал unbufferв /usr/local/bin:)
houbysoft
3
Я установил его на свой Mac (10.8.5) через brew: brew install expect --with-brewed-tk
Нильс
2
FWIW, поскольку unbuffer несколько сбивает с толку, соответствующая структура unbuffer {commands with pipes/tee}.
Fake Name
128

можешь попробовать stdbuf

$ stdbuf -o 0 ./a | tee output.txt

(большая) часть справочной страницы:

  -i, --input=MODE   adjust standard input stream buffering
  -o, --output=MODE  adjust standard output stream buffering
  -e, --error=MODE   adjust standard error stream buffering

If MODE is 'L' the corresponding stream will be line buffered.
This option is invalid with standard input.

If MODE is '0' the corresponding stream will be unbuffered.

Otherwise MODE is a number which may be followed by one of the following:
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.
In this case the corresponding stream will be fully buffered with the buffer
size set to MODE bytes.

имейте это в виду:

NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does
for e.g.) then that will override corresponding settings changed by 'stdbuf'.
Also some filters (like 'dd' and 'cat' etc.) dont use streams for I/O,
and are thus unaffected by 'stdbuf' settings.

вы не работаете stdbufна tee, вы работаете его a, так что это не должно повлиять на вас, если вы не установите буферизацию a«потоков s в a» s источника.

Кроме того, stdbufэто не POSIX, а часть GNU-coreutils.

c00kiemon5ter
источник
3
Спасибо, но похоже, что это недоступно в OS X (вопрос помечен как osx-lion).
houbysoft 05
2
@houbysoft - я почти уверен, что инструменты GNU можно установить на OS X
Иордания,
1
@jordanm: возможно, но установка всех инструментов GNU кажется излишним для этого ...
houbysoft 05
1
Проголосовали за этот ответ, потому что stdbufон уже доступен в используемых нами дистрибутивах Centos Linux, а unbufferего нет. Спасибо!
Хью Уолтерс
6
Для скрипта python stdbuf не будет работать, но вы можете использовать его -uдля отключения буферизации на стороне python:python3 -u a.py | tee output.txt
Honza
27

Вы также можете попытаться выполнить свою команду в псевдотерминале, используя scriptкоманду (которая должна обеспечить вывод в конвейер с строчной буферизацией)!

script -q /dev/null ./a | tee output.txt     # Mac OS X, FreeBSD
script -c "./a" /dev/null | tee output.txt   # Linux

Имейте в виду, что эта scriptкоманда не возвращает обратно статус завершения обернутой команды.

Джон
источник
3
script -t 1 /path/to/outputfile.txt ./aотлично работал для моего варианта использования. Он транслирует весь вывод в реальном времени, outputfile.txtа также выводит его на стандартный вывод вашей оболочки. Не нужно было использоватьtee
Питер Берг
26

Вы можете использовать setlinebuf из stdio.h.

setlinebuf(stdout);

Это должно изменить буферизацию на «буферизованную строку».

Если вам нужна большая гибкость, вы можете использовать setvbuf.

Денис Ртвелиашвили
источник
8
Интересно, почему у этого решения так мало голосов. Это единственное решение, не обременяющее абонента.
oxygene
1
Обратите внимание, что это не стандартный C (или даже POSIX). Наверное, лучше использовать setvbuf(stdout, NULL, _IOLBF, 0), что в точности эквивалентно.
rvighne
Это устранило мою проблему в OS X Catalina с программой на C ++, которая использовала printf (), и я работал по конвейеру, но видел результат только тогда, когда программа была завершена.
jbaxter
2

Если вместо этого вы используете классы потоков C ++, каждый std::endl будет неявным сбросом. Используя печать в стиле C, я думаю, что предложенный вами метод ( fflush()) - единственный способ.

Кевин Грант
источник
4
К сожалению, это не так. Вы можете наблюдать такое же поведение с c ++ std :: cout даже при использовании std :: endl или std :: flush. Буферизация происходит сверху, и самым простым решением в Linux кажется setlinebuf (stdout); в качестве самой первой строки в main (), когда вы являетесь автором программы, и используете другие вышеперечисленные решения, когда не можете изменить исходный код.
oxygene
1
@oxygene Это неправда. Я попробовал, и endl сбрасывает буфер при подключении к тройнику (в отличие от printf). Код: #include <iostream> #include <unistd.h> int main(void) { std::cout << "1" << std::endl; sleep(1); std::cout << "2" << std::endl; }. endl всегда очищает буфер, как определено здесь: en.cppreference.com/w/cpp/io/manip/endl
Curtis Yallop
0

Команда unbufferиз expectпакета в @Paused до дальнейшего уведомления не сработала для меня в том виде, в котором она была представлена.

Вместо того, чтобы использовать:

./a | unbuffer -p tee output.txt

Пришлось использовать:

unbuffer -p ./a | tee output.txt

( -pдля режима конвейера, в котором unbuffer читает из stdin и передает его команде в остальных аргументах)

expectПакет может быть установлен на:

  1. MSYS2 с pacman -S expected
  2. Mac OS с brew install expected
пользователь
источник