Как сохранить статус последнего выхода после теста

8

Можно ли сохранить статус завершения последней команды ( $?) без изменений после теста?

Например, я хотел бы сделать:

command -p sudo ...
[ $? -ne 1 ] && exit $?

Последний exit $?должен возвращать статус выхода sudo, но вместо этого он всегда возвращает 0(код завершения теста).

Возможно ли это сделать без временной переменной?

Еще один пример для уточнения:

 spd-say "$@"
 [ $? -ne 127 ] && exit $?

В этом случае я хочу выйти, только если найдена первая команда (код выхода! = 127). И я хочу выйти с фактическим spd-sayкодом выхода (это не может быть 0).

РЕДАКТИРОВАТЬ: я забыл упомянуть, что я предпочитаю решение жалоб POSIX для лучшей переносимости.

Я использую эту конструкцию в сценариях, где я хочу предоставить альтернативы для той же команды. Например, см. Мой скрипт crc32 .

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

eadmaster
источник
Нет, но вы можете просто сделать if ! command -p sudo; then exit; fiто же самое для вашего примера.
Иордания
хорошо, что если я хочу проверить код 127 вместо этого? (например. [ $? -ne 127 ] && exit $?)
Eadmaster
1
@jordanm: Правда? Если OP представил код / ​​логику, которую он имел в виду, и если я правильно его читаю, он хочет, чтобы скрипт завершился, если sudoкоманда выполнена успешно (то есть, если sudoзавершается со статусом 0). Но в вашем коде сценарий продолжает работать (неsudo завершается ), если это удается
G-Man говорит: «Восстановите Монику»
@ G-Man - я уверен, что условия аскера на самом деле основаны на возвращении, commandа не sudoна всех. Это то, что подразумевается под тем, что я хочу выйти, только если первая команда найдена (код выхода! = 127) и является указанным возвращением,command когда вызываемая команда не найдена. Я предполагаю, что проблема в том, что вызов в sudoкачестве части теста позволяет sudoподавить возврат commandв первую очередь и таким образом исказить тест.
mikeserv

Ответы:

3

$_будет работать в (по крайней мере) интерактивный dash, bash, zsh, ksh (хотя , по- видимому , не в условном операторе по запросу) и mkshснарядов. Из них - насколько мне известно - только bashи zshбудет также заполнять его в скриптовой оболочке. Это не параметр POSIX - он достаточно переносим для любой современной интерактивной оболочки.

Для более портативного решения вы можете сделать:

command -p sudo ...
eval '[ "$?" = 127 ] || exit '"$?"

Который в основном позволяет вам расширить начальное значение для $?хвоста скрипта, прежде чем даже проверять его значение в его начале .

Но в любом случае, поскольку вы, похоже, проверяете, sudoможно ли найти команду в строке встроенного -p переносимого пути оболочки с помощью command, я думаю, вы могли бы перейти к ней более прямо. Кроме того , просто чтобы быть ясно, command не будет проверять на месте каких - либо аргументов в sudo- так это толькоsudo - и ничто не вызывает - что отношение к этому возвращаемого значения.

И так или иначе, если это то, что вы пытаетесь сделать:

command -pv sudo >/dev/null || handle_it
command -p  sudo something or another

... отлично работает как тест, без каких-либо шансов на ошибки при выполнении команд, sudoвозвращающихся таким образом, что это может исказить результаты вашего теста.

mikeserv
источник
9

Существуют различные варианты надежной обработки состояния выхода без накладных расходов, в зависимости от фактических требований.

Вы можете сохранить статус выхода, используя переменную:

command -p sudo ...
rc=$?
[ "$rc" -ne 1 ] && echo "$rc"

Вы можете напрямую проверить успех или неудачу:

if  command -p sudo ...
then  echo success
else  echo failure
fi

Или используйте caseконструкцию, чтобы дифференцировать статус выхода:

command -p sudo ...
case $? in
(1) ... ;;
(127) ... ;;
(*) echo $? ;;
esac

с особым случаем, заданным в вопросе:

command -p sudo ...
case $? in (1) :;; (*) echo $?;; esac

Все эти опции имеют то преимущество, что они соответствуют стандарту POSIX.

(Примечание. Для иллюстрации я использовал echoприведенные выше команды; замените их соответствующими exitоператорами, чтобы они соответствовали запрошенной функции.)

Janis
источник
Комментарии не для расширенного обсуждения; этот разговор был перемещен в чат .
Тердон
1
(Интересно, почему последний комментарий не переместился в чат.) - Как уже тщательно объяснил и поддержал цитаты POSIX в обсуждении чата (подробности см. Там); 1.) ("$(get_errnos)")является искусственным добавлением подстановки команд, где в вопросе запрашивается сравнение с фактическими кодами ошибок, 2.) В стандарте ясно, что меняется $?(и, следовательно, что не меняется), и 3.) там нет никаких побочных эффектов с этой конструкцией (если, как вы, вы вводите их без необходимости). - OTOH, так как вы не возражаете, другой ответ, который вы предпочитаете, явно нестандартный.
Янис
1
@mikeserv; Это раздражает (и вводит в заблуждение!) Не только потому, что вы применяете двойные стандарты ! Если вы примените тот же искусственный $(get_errnos)код к любым другим решениям ( ( exit 42 ); test "$(get_errnos)" -ne $? && echo $_), они также не будут работать. (Вы предпочли ввести мое стандартное решение в несоответствие, а не другие нестандартные хаки .) - Конечно, вы можете добавить произвольный код к любым ответам и испортить его. - и WRT до смены $?; только то, что вы не можете найти это, не означает, что это неправда. (Я уже предоставил в наших обсуждениях даже ключевые слова для поиска в POSIX.)
Janis
1
@mikeserv; Но это, опять же, склонно к продолжению (бесплодной и бесконечной) дискуссии, которую не следует размещать здесь, как правильно заметил @terdon.
Янис
1
@mikeserv; Я сказал в самом начале, где вы впервые упомянули zsh(сначала упомянули в одиночку; позже вы также добавили dash), что я думаю, что это должно быть ошибкой, учитывая, что состояние caseвыхода и настройки, $?кажется, хорошо определены в POSIX. - А также здесь, опять же, вы применяете двойные стандарты; другой хак, например, не является ни стандартным, ни надежно работает в других стандартных оболочках (например, не в ksh). - Мои предложения стандартны и работают bash(в основном используются в Linux) и ksh(преобладающая оболочка в коммерческих Unixes).
Янис
7
command -p ...
test 1 -ne $? && exit $_

Использование $_, которое расширяется до последнего аргумента предыдущей команды.

llua
источник
1
Допустимо для этого конкретного примера, но может использоваться только в том случае, если между ссылками $?и и нет другой команды $_. ИМХО, лучше придерживаться согласованного метода, который работает в других случаях (а также может помочь с читабельностью кода).
Дан Корнилеску
4
Оболочка была специально названа дважды, один раз в заголовке и один раз как тег. Также нигде в вопросе не упоминалась переносимость, поэтому я дал ответ, который работает в указанной оболочке. Какие еще странные ограничения будут выдуманы?
Луа
1
@mikeserv; В случае, если вы пропустили это: я сказал: «Здесь был самый первый комментарий о POSIX». который должен был быть исправлен. Не больше, не меньше. - Как подробно обсуждалось с вами и объяснялось там, все три предложения в другом ответе хорошо определены POSIX. Не нужно повторять здесь свое (неправильное IMO) мнение или начинать еще одну итерацию спора.
Янис
1
@mikeserv; Побочные эффекты расширения в case паттернах , единственное место, которое имело бы значение в теории (но не в данном вопросе), является сконструированным случаем. - В любом случае, ваша подстановка команд оболочки будет распространяться на результат встроенной команды, обычно другие расширения не влияют на возвращаемое состояние в любом случае, если нет задействованных команд, которые могут создавать ошибки (случайные x=${a!b}ситуации, но не относящиеся к делу). - Что вы подразумеваете под «команда без имени команды» ?
Янис
1
@mikeserv; Это может быть захвачено только специальным составленным ненужным кодом. Там нет абсолютно серой области, если вы принимаете предложение без излишнего введения искусственной чепухи. В этом случае требования были абсолютно ясны: 1. выполнить команду, 2. проверить код выхода, 3. код возврата возврата. - Ты понял? - Вы изменили это требование произвольно, чтобы просто выдвинуть аргумент.
Янис
6

Вы можете определить (и использовать) функцию оболочки:

check_exit_status()
{
    [ "$1" -ne 1 ] && exit "$1"
}

затем

command -p sudo ...
check_exit_status "$?"

Возможно, это «обман», так как он делает копию $?в check_exit_statusсписке аргументов.

Это может показаться немного неловким, и это так. (Это иногда случается, когда вы накладываете произвольные ограничения на проблемы.) Это может показаться негибким, но это не так. Вы можете сделать check_exit_statusболее сложным, добавив аргументы, которые сообщают ему, какие тесты нужно выполнить для значения состояния выхода.

Скотт
источник
0

Чтобы ответить на ваш прямой вопрос, нет, невозможно сохранить $?без изменений. И это один из тех случаев, когда я подозреваю, что вы сосредотачиваетесь не на той проблеме. Временная переменная является стандартным и предпочтительным способом получения эффекта, который вы ищете. Это настолько стандартно, что я бы предложил отказаться (или переосмыслить) любую причину, по которой, по вашему мнению, вы не хотите ее использовать; Я очень сомневаюсь, что это стоит дополнительной сложности, добавленной любым другим методом.

Если вы просто спрашиваете из простого любопытства, тогда ответ - нет.

Дэвид З
источник
@ mikeserv Я не уверен, что понимаю - ты говоришь о ручном назначении $?или что-то в этом роде?
Дэвид З
0

Вот мой фрагмент кода, чтобы сохранить предыдущий статус выхода без выхода из текущего скрипта / оболочки

EXIT_STATUS=1
if [[ $EXIT_STATUS == 0 ]]
then
  echo yes
else
  (exit $EXIT_STATUS)
fi
Junaid
источник