Невозможно выйти и странный порядок исполнения с тройником

Я получаю странное поведение при передаче вывода функции команде tee. Первая проблема заключается в том, что я не могу выйти из программы при использовании команды exit при вызове из функции, переданной в tee. Например:

myfunction(){
    # Some stuff here
    exit 1
}

myfunction | tee -a $UPGRADE_LOG

Когда я запускаю приведенный выше код, программа не завершается и работает до завершения.

Другая проблема, с которой я столкнулся, заключается в том, что tee, похоже, заставил некоторый код работать таким образом, что последовательный порядок отменяется. У меня такой вывод:

SHOWING SYSTEM-WIDE AND INSTATNCE MEMORY USAGE:
Are you sure you would like to back up the instance given current memory contraints? [y/n]: Filesystem                           Size   Used  Avail  Use%  Mounted on
/dev/mapper/system-root              15G    13G   1.5G   90%   /
Log File Size: 24K   Total Size to Package: 248K Available Space: 1.5G

Когда он должен работать как:

SHOWING SYSTEM-WIDE AND INSTATNCE MEMORY USAGE:
Filesystem                           Size   Used  Avail  Use%  Mounted on
/dev/mapper/system-root              15G    13G   1.5G   90%   /
Log File Size: 24K   Total Size to Package: 248K Available Space: 1.5G
Are you sure you would like to back up the instance given current memory contraints? [y/n]: 

Когда tee не используется, все работает правильно. Кажется, что проблемы связаны друг с другом. Есть идеи, почему это так и что мне с этим делать?


person willh99    schedule 16.10.2018    source источник
comment
Пожалуйста, не задавайте несколько вопросов, вместо этого разделите их. Также обратите внимание, что вы увеличиваете свои шансы на получение хороших ответов, если подойдете как можно ближе к MCVE: stackoverflow.com/help/mcve   -  person Micha Wiedenmann    schedule 16.10.2018
comment
Эти вопросы, похоже, связаны с одной и той же проблемой, поэтому я собрал их вместе. Извините, если это вызвало путаницу.   -  person willh99    schedule 16.10.2018
comment
Нет, код не выполняется непоследовательно - происходит то, что ваш stdout и stderr проходят разные процессы, поэтому они поступают в разное время. Однако первоначальные записи все еще происходят в то же время.   -  person Charles Duffy    schedule 16.10.2018
comment
Да, я знал, что код запускается последовательно. Я просто указывал, что он вел себя как таковой. Я предположил, что это было потому, что они работали отдельно, я просто не знал, почему. Спасибо за разъяснения!   -  person willh99    schedule 16.10.2018


Ответы (2)


В ответе kvantour рассказывается, почему это происходит. Здесь рассказывается, как это исправить. :)

with_logs_piped() {
  local logfile=$1; shift
  "$@" > >(tee -a -- "$logfile") 2>&1  # 2>&1 redirects stderr through the same tee so it's
}                                      # ...also logged, and shows up sync'd with stdout.

myfunction() {
    # Some stuff here
    exit 1
}

with_logs_piped "$UPGRADE_LOG" myfunction

Здесь важно то, что вместо обычного конвейера мы используем подстановку процесса < / a> для tee - поэтому myfunction работает в самой оболочке, а не в подоболочке, поэтому exit применяется правильно.


Что касается того, почему перенаправление stdout через tee десинхронизирует stderr, см. Отдельное перенаправление и рекомбинирование stderr / stdout без потери порядка

person Charles Duffy    schedule 16.10.2018
comment
Это решение отлично работает. Спасибо и за объяснение! - person willh99; 16.10.2018

Оператор exit завершает (под) оболочку, в которой выполняется процесс. Вот и сюрприз:

Конвейеры

Конвейер - это последовательность из одной или нескольких команд, разделенных одним из операторов управления | или |&. Формат конвейера:

[time [-p]] [ ! ] command [ [|⎪|&] command2 ... ]

‹Snip›

Каждая команда в конвейере выполняется как отдельный процесс (т. е. в подоболочке).

источник: man bash

Таким образом, оператор exit из функции просто уничтожает подоболочку конвейера. На самом деле это означает, что exit ничего не делает в конвейере

$ exit | exit | echo foo
foo
$ exit | exit
$ # shell not terminated

Примечание. это явно зависит от оболочки, поскольку zsh ведет себя иначе.

person kvantour    schedule 16.10.2018
comment
Ничего не делает не вполне точно; exit 2 | exit 3 | true устанавливает ${PIPESTATUS[0]} на 2 и ${PIPESTATUS[1]} на 3, и, если бы set -o pipefail был активен, конвейер в целом имел бы статус выхода 3. Однако допустимо, что эти эффекты не то, что хотел OP. :) - person Charles Duffy; 16.10.2018
comment
@CharlesDuffy Верно. Также, если установлен параметр lastpipe, он повлияет на последний оператор exit (например, в exit | exit) - person kvantour; 16.10.2018