Como manter o status da última saída após o teste

8

É possível manter o último comando exit status ( $?) inalterado após um teste?

Por exemplo, eu gostaria de fazer:

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

O último exit $?deve retornar o status de saída do sudo, mas sempre retorna 0(o código de saída do teste).

É possível fazer isso sem uma variável temporária?

Outro exemplo para esclarecer mais:

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

Nesse caso, quero sair apenas se o primeiro comando for encontrado (código de saída! = 127). E eu quero sair com o spd-saycódigo de saída real (pode não ser 0).

EDIT: Eu esqueci de mencionar que eu prefiro uma solução de reclamação POSIX para melhor portabilidade.

Eu uso essa construção em scripts onde eu quero fornecer alternativas para o mesmo comando. Por exemplo, veja meu script crc32 .

O problema com variáveis ​​temporárias é que elas podem sombrear outras variáveis ​​e para evitar que você precise usar nomes longos, o que não é bom para a legibilidade do código.

eadmaster
fonte
Não, mas você pode fazer o if ! command -p sudo; then exit; fique teria os mesmos resultados para o seu exemplo.
jordanm
ok, e se eu quiser testar o código 127? (ex. [ $? -ne 127 ] && exit $?)
eadmaster
1
@jordanm: Sério? Se o OP tiver apresentado o código / lógica que ele pretendia, e se eu estiver lendo corretamente, ele deseja que o script saia se o sudocomando for bem-sucedido (ou seja, se sudosair com o status 0). Mas, no seu código, o script continua em execução (não sai) se sudofor bem
G-Man diz 'Reinstate Monica'
@ G-Man - Tenho certeza de que as condições do autor da pergunta se baseiam no retorno commande sudona verdade não são . É isso o que quero dizer com sair apenas se o primeiro comando for encontrado (código de saída! = 127) e for um retorno especificado paracommand quando o comando que ele chama não for encontrado. Eu acho que o problema é que invocar sudocomo parte do teste permite sudoesmagar o retorno commandem primeiro lugar e assim distorcer o teste.
mikeserv

Respostas:

3

$_vai trabalhar em (pelo menos) interativo dash, bash, zsh, ksh (embora , aparentemente, não em uma declaração condicional, conforme solicitado) e mkshconchas. Desses - que eu saiba - apenas bashe zshtambém o preencherá em um shell com script. Não é um parâmetro POSIX - mas é bastante portátil para qualquer shell moderno e interativo.

Para uma solução mais portátil, você pode:

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

O que basicamente permite expandir o valor inicial para $?o final do script antes mesmo de testar seu valor.

Mas de qualquer maneira, já que você parece estar testando se o comando sudopode ou não ser encontrado na cadeia de -p caminho portátil incorporada do shell command, eu acho que você poderia abordá-lo um pouco mais diretamente. Além disso, só para esclarecer, command não testará a localização de nenhum argumento para sudo- por isso é apenas sudo- e nada que ele invoque - o que é relevante para esse valor de retorno.

De qualquer forma, se é isso que você está tentando fazer:

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

... funcionaria muito bem como um teste sem chance de erros no comando sudoexecutado retornando de maneira que possa distorcer os resultados do seu teste.

mikeserv
fonte
9

Existem várias opções para lidar com o status de saída de forma confiável e sem custos indiretos, dependendo dos requisitos reais.

Você pode salvar o status de saída usando uma variável:

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

Você pode verificar diretamente o sucesso ou o fracasso:

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

Ou use uma caseconstrução para diferenciar o status de saída:

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

com o caso especial solicitado na pergunta:

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

Todas essas opções têm a vantagem de estar em conformidade com o padrão POSIX.

(Nota: Para ilustração, usei os echocomandos acima; substitua por exitinstruções apropriadas para corresponder à função solicitada.)

Janis
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
terdon
1
(Eu me pergunto por que o último comentário não mudou para o bate-papo.) - Como já foi explicado e apoiado por citações do POSIX na discussão do bate-papo (veja detalhes aqui); 1.) ("$(get_errnos)")é uma adição artificial de uma substituição de comando, na qual são solicitadas comparações com códigos de erro reais na pergunta, 2.) No padrão, é claro o que muda $?(e, portanto, o que não muda) e 3.) não há efeitos colaterais com essa construção (a menos que, como você fez, você os introduz desnecessariamente). - OTOH, como você se importa, a outra resposta que você prefere é claramente fora do padrão.
Janis
1
@mikeserv; É irritante (e enganador!) Não apenas porque você aplica padrões duplos ! Se você aplicar o mesmo $(get_errnos)código artificial a outras soluções ( ( exit 42 ); test "$(get_errnos)" -ne $? && echo $_), elas também não funcionarão. (Você preferiu colocar minha solução padrão em descrédito incorreto, não os outros hackes não padrão .) - É claro que você pode adicionar código arbitrário a qualquer resposta e estragá-lo. - E WRT para a mudança de $?; só porque você não consegue encontrá-lo, não significa que não seja verdade. (Eu já forneci em nossas discussões até as palavras-chave para pesquisar no POSIX.) #
191 Janis
1
@mikeserv; Mas isso é, novamente, propenso a continuar a discussão (infrutífera e interminável), que não deve ser colocada aqui como o @terdon observou corretamente.
Janis
1
@mikeserv; Eu disse bem no começo, onde você mencionou pela primeira vez zsh(mencionado apenas sozinho; depois você adicionou também dash) que acho que deve ser um bug, já que o casestatus de saída e a configuração de $?parecem bem definidos no POSIX. - E também aqui, novamente, você aplica padrões duplos; o outro hack, por exemplo, não é padrão, nem funciona de maneira confiável em outros shells padrão (por exemplo, não em ksh). - Minhas propostas são padrão e funcionam bash(principalmente usadas no Linux) e ksh(o shell predominante nos Unixes comerciais).
Janis
7
command -p ...
test 1 -ne $? && exit $_

Use $_, que se expande para o último argumento do comando anterior.

llua
fonte
1
Válido para este exemplo particular, mas apenas utilizável se não houver nenhum outro comando, entre o $?e $_referências. IMHO, é melhor seguir um método consistente que funcione em outros casos (e também pode ajudar na legibilidade do código).
Dan Cornilescu
4
O shell foi nomeado especificamente duas vezes, uma vez no título e outra como tag. A portabilidade também não foi mencionada em nenhum lugar da pergunta, portanto, dei uma resposta que funciona no referido shell. Que outras restrições estranhas serão criadas?
llua
1
@mikeserv; Caso você tenha perdido: eu disse: "Houve um primeiro comentário aqui sobre o POSIX". que teve que ser corrigido. Nem mais nem menos. - Como completamente debatido com você e explicado lá, todas as três sugestões na outra resposta são bem definidas pelo POSIX. Não há necessidade de repetir sua opinião (incorreta da IMO) aqui ou iniciar outra iteração da disputa.
Janis
1
@mikeserv; Os efeitos colaterais da expansão nos case padrões , o único lugar que importaria na teoria (mas não na questão dada), é um caso construído. - De qualquer forma, sua substituição de comando do shell propagaria o resultado do comando incorporado, normalmente outras expansões não afetarão o estado de retorno de qualquer maneira, se não houver comandos envolvidos que possam criar erros ( x=${a!b}casos mentais , mas irrelevantes aqui). - O que você quer dizer com "comando sem nome do comando" ?
Janis
1
@mikeserv; Ele pode ser derrotado apenas por código desnecessário composto ad hoc. Não há absolutamente nenhuma área cinzenta se você seguir a sugestão sem introduzir desnecessariamente disparates artificiais. Os requisitos eram absolutamente claros neste caso: 1. execute um comando, 2. verifique o código de saída, 3. código de saída de retorno. - Você entendeu? - Você alterou esse requisito arbitrariamente para apenas criar um argumento.
Janis
6

Você pode definir (e usar) uma função shell:

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

Então

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

Indiscutivelmente, isso é "trapaça", uma vez que faz uma cópia $?na check_exit_statuslista de argumentos.

Isso pode parecer um pouco estranho, e é. (Isso às vezes acontece quando você impõe restrições arbitrárias aos problemas.) Isso pode parecer inflexível, mas não é. Você pode tornar check_exit_statusmais complexo, adicionando argumentos que informam quais testes devem ser feitos no valor do status de saída.

Scott
fonte
0

Para responder sua pergunta direta, não, não é possível manter-se $?inalterado. E esse é um daqueles casos em que suspeito que você esteja se concentrando no problema errado. Uma variável temporária é a maneira padrão e preferida de obter o efeito que você está procurando. É tão padrão que eu sugeriria abandonar (ou repensar) qualquer que seja o motivo que você acha que tem por não querer usar um; Duvido muito que valha a complexidade extra adicionada por qualquer outro método.

Se você está apenas perguntando por simples curiosidade, a resposta é não.

David Z
fonte
@mikeserv Não sei se entendi - você está falando sobre atribuir manualmente $?ou algo assim?
David Z
0

Aqui está meu snippet de código para manter um status de saída anterior sem sair do script / shell atual

EXIT_STATUS=1
if [[ $EXIT_STATUS == 0 ]]
then
  echo yes
else
  (exit $EXIT_STATUS)
fi
Junaid
fonte