Лучший способ обработки труб и их статус выхода в make файле
Если команда make
не работает, например gcc
, она завершает работу...
gcc
gcc: fatal error: no input files
compilation terminated.
make: *** [main.o] Error 4
Однако, если у меня есть труба, берется статус выхода последней команды в трубе. В качестве примера gcc | cat
не сбой, поскольку cat
преуспевает.
Я знаю, что коды выхода для всего канала хранятся в массиве PIPESTATUS
, и я могу получить код ошибки 4 с помощью ${PIPESTATUS[0]}
. Как я должен структурировать мой make файл для обработки команды с каналами и выхода из строя как обычно?
Как и в комментариях, еще один пример: gcc | grep something
. Здесь я предполагаю, что наиболее желаемое поведение по-прежнему остается для gcc
и только gcc
, чтобы вызвать сбой, а не grep
, если он ничего не нашел.
Ответы
Ответ 1
Вы можете сказать make использовать bash
вместо sh
и получить bash
для установки set -o pipefail
, чтобы он выходил с первым сбоем в конвейере.
В GNU Make
3,81 (и предположительно ранее, хотя я точно не знаю) вы должны сделать это с помощью SHELL = /bin/bash -o pipefail
.
В GNU Make
3.82 (и новее) вы сможете сделать это с помощью SHELL = /bin/bash
и .SHELLFLAGS = -o pipefail -c
(хотя я не знаю, нужно ли добавлять -c
в конец, как это необходимо, или если make будет добавьте это для вас, даже если вы укажете .SHELLFLAGS
.
На странице bash
man:
Возвратный статус конвейера - это статус выхода последнего, если параметр pipefail не включен. Если enabled, статус возврата конвейера - это значение последнего (самый правый) для выхода с ненулевым статусом или ноль, если все команды успешно завершены. Если зарезервированное слово! предшествует конвейер, статус выхода этого конвейера является логическим отрицанием статус выхода, как описано выше. Оболочка ждет всех команд в конвейере для завершения, прежде чем возвращать значение.
Ответ 2
Я бы пошел за pipefail
. Но если вы действительно не хотите (или если вы хотите сбой только при первом процессе - не в случае отказа от остальной части трубы):
SHELL=bash
all:
gcc | cat ; exit "$${PIPESTATUS[0]}"
Единственное преимущество, по сравнению с самим ответом @jozxyqk, заключается в том, что вы не теряете код статуса выхода.
Ответ 3
Просто добавьте в начало своей makefile
команду:
SHELL=/bin/bash -o pipefail
Теперь вы можете, например, сгенерировать файл errors.err
из объектов (1-е правило), не беспокоясь, что он будет перезаписан исполняемым файлом (2-е правило).
%.o : %.c
gcc $(CFLAGS) $(CPPFLAGS) $^ -o [email protected] 2>&1 | tee errors.err
%.x : %.o $(OBJECTS)
gcc $(LDLIBS) $^ -o [email protected] 2>&1 | tee errors.err
Без него make
не получить ошибок из правила 1 и запустить правило 2, перезаписав его. У вас будет только одна строка в errors.err
, указав, что для запуска нет
gcc: error: program.o: No such file or directory
Ответ 4
Разумным и переносимым подходом является реорганизация ваших заданий сборки для использования файлов вместо труб. Например:
foo:
gcc >[email protected]
grep success [email protected]
cat [email protected]
rm [email protected]
Удаление файла журнала после печати, очевидно, не требуется; это всего лишь общий шаблон. Говядина - это перенаправление для замены трубопровода. Вы даже можете реорганизовать его на несколько рецептов:
foo: foo.tmp foo.log
grep success [email protected]
mv $< [email protected]
%.tmp %.log:
gcc -o $*.tmp >$*.log
Правильная очистка временных артефактов и, как правило, управление ими является очевидным недостатком такого подхода.