почему выход exec + tee отображается не в порядке? - PullRequest
0 голосов
/ 21 сентября 2019

Это, в основном, дополнение к этому вопросу: перенаправить КОПИЮ stdout в файл журнала из самого скрипта bash

Итак, используя этот тестовый скрипт:

#!/bin/bash

echo 'bash version:'
bash --version|grep release
echo '';
echo '----------------'
rm /tmp/logfile 2>/dev/null;

function testCommands() {
    local mode="$1"
    echo "testCommands(): mode == $mode";

    # Link file descriptor #6 with stdout to save stdout, #7 for stderr
    exec 6>&1;
    exec 7>&2;

    if [[ 'both' == "${mode}" ]]; then
        # log to file and stdout
        exec > >(tee -ia /tmp/logfile);

    elif [[ 'file' == "${mode}" ]]; then
        # log to file only
        exec 1>> /tmp/logfile

    elif [[ 'quiet' == "${mode}" ]]; then
        # be quiet
        exec 1> /dev/null

    #else - use normal stdout
    fi
    if [[ 'true' != "${separate_stderr}" ]]; then
        #by default, merge stderr to stdout for simple logging
        exec 2>&1
    #else - keep stderr separate like it normally is
    fi

    echo "fee";
    echo "fye";
    echo "foh";
    echo "fum";

    # Restore stdout/stderr and close file descriptors #6/#7
    exec 1>&6 6>&-;
    exec 2>&7 7>&-;
}

testCommands 'file'
echo '----------------'
echo ''
echo 'check /tmp/logfile'
ls -acl /tmp/logfile
echo ''
echo 'check output'
cat /tmp/logfile
rm /tmp/logfile 2>/dev/null;
echo '----------------'
testCommands 'stdout'
echo '----------------'
echo ''
echo 'check /tmp/logfile'
ls -acl /tmp/logfile
echo ''
echo 'check output'
cat /tmp/logfile
rm /tmp/logfile 2>/dev/null;
echo '----------------'
testCommands 'both'
echo '----------------'
echo ''
echo 'check /tmp/logfile'
ls -acl /tmp/logfile
echo ''
echo 'check output'
cat /tmp/logfile
rm /tmp/logfile 2>/dev/null;
echo '----------------'
testCommands 'quiet'
echo '----------------'
echo ''
echo 'check /tmp/logfile'
ls -acl /tmp/logfile
echo ''
echo 'check output'
cat /tmp/logfile
echo '----------------'

Я получаю следующий вывод:

bash version:
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

----------------
testCommands(): mode == file
----------------

check /tmp/logfile
-rw-rw---- 1 testuser testuser 16 Sep 20 16:10 /tmp/logfile

check output
fee
fye
foh
fum
----------------
testCommands(): mode == stdout
fee
fye
foh
fum
----------------

check /tmp/logfile
ls: cannot access '/tmp/logfile': No such file or directory

check output
cat: /tmp/logfile: No such file or directory
----------------
testCommands(): mode == both
----------------

check /tmp/logfile
fee
fye
foh
fum
-rw-rw---- 1 testuser testuser 16 Sep 20 16:10 /tmp/logfile

check output
fee
fye
foh
fum
----------------
testCommands(): mode == quiet
----------------

check /tmp/logfile
ls: cannot access '/tmp/logfile': No such file or directory

check output
cat: /tmp/logfile: No such file or directory
----------------

Здесь есть немало выводов, так что, если вы не увидели его в первый раз, то часть, которая выглядит странно, находится в разделе testCommands (): mode == both .

То, что я ожидал увидеть здесь, это то, что сначала выводятся все выходные данные функции, а затем выходные данные echo / ls / cat, определенные после вызова.Вместо этого вывод функции отображается между выводом от 2 команд, которые должны быть запущены после завершения функции.Другими словами, вот что я ожидаю, что должно произойти (примечание: ниже не фактический вывод - я его придумал):

----------------
testCommands(): mode == both
fee
fye
foh
fum
----------------

check /tmp/logfile
-rw-rw---- 1 testuser testuser 16 Sep 20 16:10 /tmp/logfile

check output
fee
fye
foh
fum

Я запускаю это из Linux Mint v19.2 x64 Cinnamon (на основес Ubuntu 18.04) на моем домашнем компьютере.Я искал, чтобы адаптировать ответ exec из другого поста к общему сценарию functions.sh, который я планирую вызывать из других сценариев оболочки bash, моего .bash_aliases и т. Д. У меня есть несколько сценариев, в которых я хотел бы вызвать ту же функциюв различных сценариях с различными требованиями к ведению журнала: файл + стандартный вывод, только файл, только стандартный вывод и без вывода сообщений.Я предпочитаю вводить stderr в стандартный вывод для всех моих сценариев.

1) При вызове testCommands 'both' я вижу вывод после функции, отображаемой перед выводом из самой функции.Любопытно, есть ли способ подтвердить это на самом деле из-за буферизации в тройнике, из-за чего-то совершенно другого или из-за какой-то комбинации.

2) Есть ли способ исправить это из bash, кроме использования «unbuffered»версия tee 'упоминается в другом вопросе?например, даже если это буферизация, могу ли я отключить или даже принудительно распечатать буферизованный вывод до завершения функции с помощью команд bash / other posix?Я бы предпочел избегать внешних зависимостей, но не вижу, чтобы я переключался на любые дистрибутивы, которые не используют bash в качестве оболочки по умолчанию.

3) Есть ли более элегантный / гибкий / более простой в обслуживании способидти об управлении выводом при использовании Bash?(например, управление различными перестановками отображения / скрытия вывода stdout / stderr для консоли / файла журнала)

Ответы [ 2 ]

0 голосов
/ 21 сентября 2019

Вот более простой способ воспроизвести вашу проблему:

echo "Hello" > >(tee -a file)
echo "World" >> file

Файл будет содержать «Hello World» вместо «Hello World».

Этого не происходит, потому что teeбуферы (это не так), но поскольку вы сделали запись двухэтапного процесса, а оболочка ожидает только первого.

Когда вы выполняете подстановку процесса, вы пишете в канал,и echo будет только ждать, пока данные будут успешно записаны в канал.Он не попадет в файл, пока tee не получит возможность записать его позже.Если сценарий продолжает вызывать запись в файл до того, как tee может работать, выходные данные отображаются не по порядку.

Если вы в любой момент захотите «очистить» выходные данные, вам придется закрыть канали дождитесь завершения процесса замены:

mkfifo tmpfile
exec > >(echo "$BASHPID" > tmpfile; exec tee -a file)
echo "Hello"
exec >&-
wait "$(<tmpfile)"
echo "World" >> file

Так что на самом деле проще просто обеспечить, чтобы все данные проходили по одному и тому же пути, например, гарантируя, что все записи происходят через одну и ту же замену процесса.

0 голосов
/ 21 сентября 2019

Потому что команда буферизует IO.Если вы отключите буферизацию ввода-вывода, вы получите желаемые результаты.Вы можете использовать команду stdbuf или unbuffer, которая отключит буферизацию.

stdbuf -oL command   # This will buffer one line at time
unbuffer command

И если в вашей системе нет ни одной из этих команд, попробуйте скрипт, который является более старым и должен быть в большинстве дистрибутивов.

script -q /dev/null long_running_command
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...