Я запускаю скрипт через ssh следующим образом:
ssh user@host 'bash -s' < ./script.sh
проблема в том, что иногда я получаю неверный вывод, линии смешиваются.
В моем случае скрипт не выполняет ничего нового, нормальный вывод выглядит примерно так:
...
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
Processed 93 total files in almost no time.
No new mail.
но иногда вывод получается примерно таким:
...
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
Processed 93 total files in almost no time.
No new mail.
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
и конечно же это не настоящий вывод notmuch new
, команда заканчивается на , No new mail
но похоже, что вывод получается через ssh, а не построчно.
Почему это происходит?
решение1
Буферизация. Если мы поищем в исходном кодеnotmuch
$ find . -name \*.c -exec grep 'Ignoring non-mail file' {} +
./notmuch-new.c: fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
$ find . -name \*.c -exec grep 'No new mail' {} +
./notmuch-new.c: printf ("No new mail.");
$
Некоторые из этих сообщений используют стандартную ошибку (которая по умолчанию не буферизуется), а некоторые используют стандартный вывод (который по умолчанию буферизуется строками или блоками, в зависимости от того, идет ли стандартный вывод на терминал или в файл). Такое поведение исходит из стандартной библиотеки C, см.setvbuf(3)
для подробностей. Таким образом, stderr
сообщения пишутся немедленно, а printf
вызовы stdout
будут отображаться... ну, это как.
Буферизация обычно настраивается каждым приложением индивидуально, хотя ее можно обойти с помощью таких утилит, как stdbuf
(хотя некоторые считают LD_PRELOAD
трюки, используемые , stdbuf
довольно ужасными...).
Разницу легко воспроизвести локально, например, записав на терминал (построчная буферизация для stdout
):
$ perl -E 'for (1..4) { say "out"; warn "err\n" }'
out
err
out
err
out
err
out
err
$
в то время как вместо этого, если этот же самый код перенаправить в файл (буферизация на основе блоков для stdout
):
$ perl -E 'for (1..4) { say "out"; warn "err\n" }' >x 2>&1
$ cat x
err
err
err
err
out
out
out
out
$
ssh
добавляет дополнительный уровень сложности, поскольку, возможно, придется также выяснить, как он собирает, буферизует и отправляет байты, а также notmuch
то, ssh
к чему он подключен в клиентской системе...