LINUX.ORG.RU

Получить exit code, с которым завершилась программа в середине скрипта

 , , ,


0

1

Это уже мой третий тред за неделю с глупыми вопросами по скриптингу.

На этот раз мне нужно получить exit code от command1 в конструкции { command1 2>&3 | command2; } 3>&1 1>&2 | command3, чтобы вывести сообщение об ошибке и завершить выполнение скрипта.

В первых двух тредах мне не без помощи скрипт-гуру удалось сделать следующее:

sim.sh:

#!/bin/sh

filter() {
  local l n
  while read -r l; do
    n=${l#\[ninja\]}
    [ "x$l" != "x$n" ] && printf "%s\n" "$n" || printf "%s\n" "$l" >&2
  done
}

cat log | filter

exit 1

test.sh:

#!/bin/sh
set -e

process() {
  while read line; do
    printf "%s\n" "${line}" >> "std$1.txt"
    printf "$2"
  done
}

{ ./sim.sh 2>&3 | process out .; } 3>&1 1>&2 | process err x

printf '\nthis should never be printed\n'

log содержит следующие строки, взятые из вывода настоящего ninja:

[ninja][72/154] Building C object src/CMakeFiles/git2internal.dir/oid.c.o
[ninja][73/154] Building C object src/CMakeFiles/git2internal.dir/parse.c.o
[ninja][74/154] Building C object src/CMakeFiles/git2internal.dir/patch.c.o
[ninja][75/154] Building C object src/CMakeFiles/git2internal.dir/pack.c.o
../src/pack.c:790:22: warning: incompatible pointer types assigning to 'mz_alloc_func' (aka 'void *(*)(void *, unsigned long, unsigned long)') from 'void *(void *, unsigned int, unsigned int)' [-Wincompatible-pointer-types]
        obj->zstream.zalloc = use_git_alloc;
                            ^ ~~~~~~~~~~~~~
../src/pack.c:868:16: warning: incompatible pointer types assigning to 'mz_alloc_func' (aka 'void *(*)(void *, unsigned long, unsigned long)') from 'void *(void *, unsigned int, unsigned int)' [-Wincompatible-pointer-types]
        stream.zalloc = use_git_alloc;
                      ^ ~~~~~~~~~~~~~
2 warnings generated.
[ninja][76/154] Building C object src/CMakeFiles/git2internal.dir/posix.c.o
[ninja][77/154] Building C object src/CMakeFiles/git2internal.dir/patch_generate.c.o
[ninja][78/154] Building C object src/CMakeFiles/git2internal.dir/pqueue.c.o
[ninja][79/154] Building C object src/CMakeFiles/git2internal.dir/patch_parse.c.o

К сожалению, скрипт не завершается с ошибкой, а выводить на экран «this should never be printed». Буду благодарен любым подсказкам, как реализовать завершение скрипта при ошибке.

★★★★★

Ответ на: комментарий от debugger

Забыл в посте указать, что пытаюсь сделать скрипт совместимым с POSIX shell (тестирую на dash и busybox ash).

CYB3R ★★★★★
() автор топика

В первую очередь, exit 1 — это просто код возврата, он у некоторых утилит означает некоторую информацию, а не «всё плохо», ну, например expr 1 != 1, вернёт на stdout 0, а в коде завершения — 1, но это не проблема, а результат выражения, может это наоборот — хорошо. Отсюда вытекает, что запускающий скрипт и не будет «завершаться при ошибке».

Самый простой способ передать код возврата в результат пайпа в shell-ах, не умеющих pipestatus, это собственно передать как завершающее сообщение через этот же pipe. Можно и в другой дескриптор.

vodz ★★★★★
()

{ … 2>&3 …. } 3>&1

Хах, фокус простой, но чрезвычайно малоизвестный. Большинство людей, если им придётся саппортить ваш код, будут очень O_o :)

legolegs ★★★★★
()
{ command1 2>&3 | command2; } 3>&1 1>&2 | command3

Это как же надо упороться, чтобы до такого извратиться.

Будь проще, пиши код на шелле там где надо (в контексте того, для чего ты это пишешь, судя по уже созданным темам)

touch stdout.txt
touch stderr.txt
tail -f stdout.txt | показываем_для_каждой_строки 'точка' &
tail -f stderr.txt | показываем_для_каждой_строки 'косой_андреевский_крест' &
command1 1>>stdout.txt 2>>stderr.txt &
command1_pid=$!
wait $command1_pid
echo "`command1` завершился с кодом возврата $?"
anonymous
()
Ответ на: комментарий от anonymous

Если не важен posix, то можно работать напрямую с /dev/std{out,err} и отвазятся от захардкоженных std{out,err}.txt

on_char(){                                                                                                                                                                
    tr -dc "$1" | tr "$1" "$2"
}
alias on_newline="on_char '\n'"

cat /dev/stdout | on_newline '.' &
cat /dev/stderr | on_newline 'x' &
command1 &
wait $!
exit $?

# одной строкой
# cat /dev/stdout | on_newline '.' & cat /dev/stderr | on_newline 'x' & command1 & wait $! exit $?

anonymous
()
Ответ на: комментарий от anonymous

Почти в таком виде, только с FIFO, у меня этот код и был изначально. Теперь переписал в это «извращение» в последнем топике.

CYB3R ★★★★★
() автор топика
Ответ на: комментарий от CYB3R

У тебя там не только фифо. Еще было перекачивание вывода через баш, который любит до неузнаваемости перепарсить строки. И требования exit code тоже небыло, что резко меняет основную логику, возможно придется использовать wait [pid]. До сих пор не понимаю, почему ты через пайпы перекачиваешь stdout и stderr, вместо того чтобы сразу писать в конечные файлы. Да еще читаешь строки в баш и нарываешься перепарсивание этих строк, что во-первых тормозит, во-вторых портит строку. Можно же отдельно погрепать на интересующие символы. К остальному я не придирался.

anonymous
()
Ответ на: комментарий от anonymous

до неузнаваемости перепарсить строки.

Ню-ню. Если вы про первые пробелы, то ТС почему-то проигнорировал данный ему мной код с IFS=

возможно придется использовать wait [pid].

Это не вы выше написали command &; wait $! ? Это же масло-масленное, то есть ненужное.

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

до неузнаваемости перепарсить строки. Ню-ню. Если вы про первые пробелы, то ТС почему-то проигнорировал данный ему мной код с IFS=

Я, вообще, про шелл. В попытках упростить работу со строками в часто используемых случаях, испортили(извратили) работу со строками в остальных случаях. Поэтому есть read -r, IFS, "$@" и тп. И прочитав 1000 страниц, должно было отложится в памяти опасность нестандартной работы со строками.

Это не вы выше написали command &; wait $! ? Это же масло-масленное, то есть ненужное.

Щас бы писать про нужность набросков, которые пишуться прям в текстбоксе лора.

Идея была в том, что можно получить pid фонового процесса, дожаться и получить код его возврата, даже если между командой и wait есть дополнительная логика с запусками других процессов (например process_x)

anonymous
()
Ответ на: комментарий от anonymous

Идея была в том

Наброски тоже должны иметь осмысленные. Либо у вас фильтр не в фоне, либо основной процесс, а иначе - черте-что. tail -f /dev/sdtout - тоже фигня полная. Это не только обычный cat получается, но и тот cat, который ненужен.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Либо у вас фильтр не в фоне, либо основной процесс, а иначе - черте-что.

# это середина скрипта, код возврата которого надо получить по условию задачи
cmd 1>>out.log 2>>err.log &
pid=$!

process out.log &
process err.log &

# ТС хочет интерактивности, хочет бесконечно смотреть на появляющиеся символы
# развлечем его
tetris &

wait $pid
exitcode=$?

# дождемся, пока не наиграется
wait
exit $exitcode

спорим докопаешься до 1>>

tail -f /dev/sdtout - тоже фигня полная.

такого я не писал. Я писал другую фигню, которая работает только в терминале cat /dev/stdout, в скрипте это не будет работать.

обычный cat получается, но и тот cat, который ненужен.

Я привык первым выделять обрабатываемый файл. Я даже грепаю так, если не нужна эффективность cat $file | grep $patern.

И да, выруби shellcheck, shellcheck меня бесит больше, чем помогает

anonymous
()
Ответ на: комментарий от anonymous

# ТС хочет интерактивности,

Вот это откуда взяли? У ТС-а сборка проекта, он хочет спрятать многословный вывод от этого процесса за точки-иксы.

vodz ★★★★★
()
Ответ на: комментарий от vodz

У ТС-а сборка проекта, он хочет спрятать многословный вывод от этого процесса за точки-иксы.

Он легко может спрятать вывод, просто отправивь в файл ‘.log’. А точки-иксы показать потом: грепом, седом, моим способом через tr. Тогда не нужен wait.

# запуск не в фоне, дожидаемся конца работы, тишина на экране
cmd > out.log 2>err.log
exitcode=$?

# тоже не в фоне, и тут пачка иксов и точек после бесконечного молчания
process out.log
process err.log

exit $exitcode

Но ему нужна именно интерактивность, чтоб точки-иксы появлялись сразу, чтобы он мог среагировать, например, прервать процесс. Его не устроит полчаса молчания и пачка точек-иксов на экране через полчаса. Хотя результат неотличим.

anonymous
()
Ответ на: комментарий от anonymous

Но ему нужна именно интерактивность,

Вы вообще стартовое сообщение читали? Он уже придумал пайп, а спрашивает, как узнать код возврата первой команды в пайпе.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Именно из этой конструкции он не получит нужный код возврата. Ответ однозначен.

А если менят эту конструкцию, то лучше менят на более читабельный вариант. Или пусть пишет на perl нечитабельные однострочники, там хоть со строками намного приятнее работать.

anonymous
()
Ответ на: комментарий от anonymous

Именно из этой конструкции он не получит нужный код возврата. Ответ однозначен.

В смысле? Без телодвижений ничего на этом свете не делается. Методы я рассказал в первом своём ответе этого топика. Короче, можете дальше не продолжать. Надоело.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Методы я рассказал в первом своём ответе этого топика.

Ну давай показывавай идею-набросок с exitcode через пайп. чтобы в конце был exit что-то

anonymous
()
Ответ на: комментарий от anonymous

Ну давай показывавай идею-набросок с

Открываете в гугле ссылки на запрос pipestatus и видите не наброски, а готовые решения.

чтобы в конце был exit что-то

Вы не поверите, но exit что-то — это есть всегда!

vodz ★★★★★
()
Ответ на: комментарий от CYB3R

Теперь я понял, на какую проблему намекал самый первый анон. Из сабшелла нужно каким-то образом получить информацию о том, что вернул процесс внутри сабшелла, а это проблема.

Для получения кода возврата первого процесса я использовал pipestatus, а вот каким образом завершить родительский скрипт у меня так и не получилось.

{ runpipe ./sim.sh 2>&3 \| process out .; [ $pipestatus_1 -gt 0 ] && echo exit 1; } 3>&1 1>&2 | process err x

Конечно, логично было бы заменить «echo exit 1» на «exit 1» и обернуть в еще один runpipe, а потом таким же образом проверять значение в переменной. Но у меня так ничего не получилось.

CYB3R ★★★★★
() автор топика
Ответ на: комментарий от vodz

pipestatus

man dash | grep -i pipestatus ничего не показывает

Самый простой способ передать код возврата в результат пайпа в shell-ах, не умеющих pipestatus, это собственно передать как завершающее сообщение через этот же pipe. Можно и в другой дескриптор.

Я жду эти варианты

Вы не поверите, но exit что-то — это есть всегда!

Ну тогда вот такой план действий

# тут измененный исходный пайпец, где устанавливается переменная exitcode
...

exit $exitcode
anonymous
()
Ответ на: комментарий от CYB3R

а вот каким образом завершить родительский скрипт

Потому что вы опять всё свалили в кучу. Уже почти готовое решение с одинм фильтром по разбору сообщений в одном дискрипторе по начальной маске вы опять преобразовали в два пайпа и теперь думаете как прервать наружный, когда код возврата получает внутренний.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Было бы 5 звзед, безнаказанно послал бы открытым текстом.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.