O que 'set -e' faz e por que pode ser considerado perigoso?

147

Esta pergunta apareceu em um questionário pré-entrevista e está me deixando louco. Alguém pode responder isso e me deixar à vontade? O questionário não tem referência a um shell específico, mas a descrição do trabalho é para um unix sa. novamente a questão é simplesmente ...

O que 'set -e' faz e por que pode ser considerado perigoso?

faminto
fonte
Aqui está uma lista de discussão relacionada: stackoverflow.com/questions/64786/error-handling-in-bash/...
Stefan Lasiewski

Respostas:

154

set -e faz com que o shell saia se algum subcomando ou pipeline retornar um status diferente de zero.

A resposta que o entrevistador provavelmente estava procurando é:

Seria perigoso usar "set -e" ao criar scripts init.d:

De http://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2 -

Cuidado ao usar set -e nos scripts init.d. A gravação correta de scripts init.d requer a aceitação de vários status de saída de erro quando os daemons já estiverem em execução ou parados sem interromper o script init.d e as bibliotecas de funções comuns init.d não são seguras para chamar com set -e em vigor. Para scripts init.d, geralmente é mais fácil não usar set -e e, em vez disso, verificar o resultado de cada comando separadamente.

Esta é uma pergunta válida do ponto de vista do entrevistador, porque avalia os candidatos que trabalham com conhecimento de scripts e automação no nível do servidor

Rico
fonte
bom ponto. que iria interromper o processo de inicialização sobre algo que pode ser um erro tecnicamente, mas não deve causar todo o roteiro para parar
Matt
Embora eu concorde com o stackoverflow.com/questions/78497/… , acho que esse estilo encontra um bom ajuste com o bash para verificações e logs breves de init ou outro patch de macaco de ambiente antes de executar a carga de trabalho de destino. scripts de inicialização.
Joshperry #
33

De bash(1):

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see SHELL GRAMMAR
                  above) exits with a non-zero status.  The shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the shell exits.  This option
                  applies to the shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

Infelizmente, não sou criativo o suficiente para pensar por que isso seria perigoso, além de "o restante do script não será executado" ou "talvez possa mascarar problemas reais".

Ignacio Vazquez-Abrams
fonte
1
mascarar reais / outros problemas, sim, eu acho que eu podia ver isso ... Eu estava lutando para chegar a um exemplo de ser perigoso, bem ...
cpbills
Aqui está a página de manual set! Eu sempre acabo em builtin(1). Obrigado.
Jared Beck
como eu saberia que a documentação para seté encontrada em man 1 bash?? e como alguém seria capaz de encontrar a -eopção para a setpalavra - chave em uma página de manual tão grande? você não pode simplesmente /-eprocurar aqui
phil294 5/17/17
@Blauhirn usandohelp set
phil294
19

Note-se que set -epode ser ativado e desativado para várias seções de um script. Ele não precisa estar ativado para a execução do script inteiro. Pode até ser habilitado condicionalmente. Dito isto, eu nunca o uso, pois faço meu próprio tratamento de erros (ou não).

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

Também é digno de nota isso na seção da página de manual do Bash no trapcomando, que também descreve como set -efunciona sob determinadas circunstâncias.

A interceptação de ERR não será executada se o comando com falha fizer parte da lista de comandos imediatamente após um tempo ou até a palavra-chave, parte do teste em uma instrução if, parte de um comando executado em uma lista && ou ⎪⎪ ou se o comando o valor de retorno está sendo invertido via!. Essas são as mesmas condições obedecidas pela opção errexit.

Portanto, existem algumas condições sob as quais um status diferente de zero não causará uma saída.

Eu acho que o perigo é não entender quando set -eentra em jogo e quando não e confiar nele incorretamente sob alguma suposição inválida.

Consulte também o BashFAQ / 105 Por que set -e (ou set -o errexit ou armadilha ERR) não faz o que eu esperava?

Dennis Williamson
fonte
Embora me desvie um pouco da pergunta, esta resposta é exatamente o que estou procurando: desativá-la apenas por uma pequena parte de um script!
precisa saber é o seguinte
13

Lembre-se de que este é um questionário para uma entrevista de emprego. As perguntas podem ter sido escritas pela equipe atual e podem estar erradas. Isso não é necessariamente ruim, e todo mundo comete erros, e as perguntas da entrevista geralmente ficam em um canto escuro sem revisão e só saem durante uma entrevista.

É perfeitamente possível que 'set -e' não faça nada que consideraríamos "perigoso". Mas o autor dessa pergunta pode acreditar erroneamente que 'set -e' é perigoso, devido à sua própria ignorância ou viés. Talvez eles tenham escrito um script de buggy, ele foi horrivelmente bombardeado, e eles pensaram erroneamente que 'set -e' era o culpado, quando na verdade eles deixaram de escrever uma verificação de erro adequada.

Eu participei de provavelmente 40 entrevistas de emprego nos últimos 2 anos, e os entrevistadores às vezes fazem perguntas erradas ou têm respostas erradas.

Ou talvez seja uma pergunta complicada, que seria ridícula, mas não totalmente inesperada.

Ou talvez seja uma boa explicação: http://www.mail-archive.com/[email protected]/msg473314.html

Stefan Lasiewski
fonte
1
+1 Estou no mesmo barco e muitas das entrevistas 'técnicas' com as quais tenho lidado foram pelo menos um pouco equivocadas.
Cpbills
1
Uau. Boa localização na lista debian. +1 pela ignorância das perguntas da entrevista. Lembro-me de ter discutido uma resposta netback uma vez, pois tinha 100% de certeza de que estava certo. Eles disseram que não. Fui para casa e pesquisei no Google, estava certo.
egorgry 19/05/10
9

set -e diz ao bash, em um script, para sair sempre que algo retornar um valor de retorno diferente de zero.

pude ver como isso seria irritante e com bugs, não tenho certeza sobre perigos, a menos que você tenha aberto permissões para alguma coisa e antes que você possa restringi-las novamente, seu script morreu.

cpbills
fonte
Isso é interessante. Não pensei nas permissões abertas em um script que morrem prematuramente, mas pude ver isso sendo considerado perigoso. A parte divertida deste questionário é que você não pode usar nenhum material de referência, como man ou google, e se não puder responder totalmente, não responda.
egorgry
6
isso é bobagem, eu reconsideraria esse empregador ... brincando (mais ou menos). é bom ter uma base sólida de conhecimento, mas metade do trabalho de TI é saber / onde / encontrar as informações ... espero que tenham sido inteligentes o suficiente para levar isso em consideração ao pontuar candidatos. em uma nota lateral, eu vejo / não / uso set -e, pois , para mim, fala de preguiça de ignorar a verificação de erros em seus scripts ... então, lembre-se disso, também com esse empregador ... se eles o usarem muito, o que seus scripts parecer, que você terá, inevitavelmente, para manter ...
cpbills
excelente ponto @cpbills. Também não vejo utilidade para set -e em um script e provavelmente é por isso que me deixou perplexo. Gostaria de postar todas as perguntas, já que algumas são realmente boas e outras são realmente malucas e aleatórias como esta.
egorgry
Eu já vi isso em muitos trabalhos do cron sistema ...
Andrew
8

set -efinaliza o script se um código de saída diferente de zero for encontrado, exceto sob certas condições. Para resumir os perigos de seu uso em poucas palavras: não se comporta como as pessoas pensam.

Na minha opinião, deve ser considerado um hack perigoso que continua a existir apenas para fins de compatibilidade. A set -einstrução não transforma o shell de uma linguagem que usa códigos de erro em uma linguagem que usa o fluxo de controle do tipo exceção, apenas tenta emular mal esse comportamento.

Greg Wooledge tem muito a dizer sobre os perigos de set -e:

No segundo link, existem vários exemplos do comportamento não intuitivo e imprevisível de set -e.

Alguns exemplos do comportamento não intuitivo de set -e(alguns extraídos do link wiki acima):

  • set -e
    x = 0
    deixe x ++
    eco "x é $ x"
    O exemplo acima fará com que o script shell saia prematuramente, porque let x++retorna 0, que é tratado pela letpalavra - chave como um valor falso e transformado em um código de saída diferente de zero. set -enota isso e termina silenciosamente o script.
  • set -e
    [-d / opt / foo] && echo "Aviso: o foo já está instalado. Será substituído." > & 2
    eco "Instalando foo ..."
    

    O procedimento acima funciona como esperado, imprimindo um aviso, se /opt/foojá existir.

    set -e
    check_previous_install () {
        [-d / opt / foo] && echo "Aviso: o foo já está instalado. Será substituído." > & 2
    }
    
    check_previous_install
    eco "Instalando foo ..."
    

    O que foi dito acima, apesar da única diferença de que uma única linha foi refatorada em uma função, terminará se /opt/foonão existir. Isso ocorre porque o fato de ter funcionado originalmente é uma exceção especial ao set -ecomportamento de. Quando a && bretorna diferente de zero, é ignorado por set -e. No entanto, agora que é uma função, o código de saída da função é igual ao código de saída desse comando e a função que retorna diferente de zero encerra silenciosamente o script.

  • set -e
    IFS = $ '\ n' lê-d '' -r -a config_vars <config
    

    O acima irá ler a matriz config_varsdo arquivo config. Como o autor pretende, ele termina com um erro se configestiver faltando. Como o autor pode não pretender, ele termina silenciosamente se confignão terminar em uma nova linha. Se set -enão fosse usado aqui, config_varsconteria todas as linhas do arquivo, terminando ou não em uma nova linha.

    Usuários de texto sublime (e outros editores de texto que lidam com novas linhas incorretamente), tenha cuidado.

  • set -e
    
    should_audit_user () {
        grupos de grupos locais = "$ (groups" $ 1 ")"
        para grupo em $ groups; Faz
            if ["$ group" = auditoria]; então retorne 0; fi
        feito
        retorno 1
    }
    
    if should_audit_user "$ user"; então
        madeireiro 'Blah'
    fi
    

    O autor aqui pode esperar razoavelmente que, se por algum motivo o usuário $usernão existir, o groupscomando falhará e o script será encerrado em vez de permitir que o usuário execute alguma tarefa não auditada. No entanto, nesse caso, a set -erescisão nunca entra em vigor. Se $usernão puder ser encontrado por algum motivo, em vez de encerrar o script, a should_audit_userfunção retornará dados incorretos como se set -enão estivessem em vigor.

    Isso se aplica a qualquer função chamada da parte da condição de uma ifinstrução, não importa quão profundamente aninhada, não importa onde seja definida, não importa mesmo se você executar set -edentro dela novamente. O uso ifa qualquer momento desativa completamente o efeito de set -eaté que o bloco de condições seja totalmente executado. Se o autor não está ciente dessa armadilha ou não conhece toda a pilha de chamadas em todas as situações possíveis em que uma função pode ser chamada, ele escreverá um código de buggy e a falsa sensação de segurança fornecida por set -eserá pelo menos parcialmente culpa.

    Mesmo que o autor esteja totalmente ciente dessa armadilha, a solução alternativa é escrever o código da mesma maneira que se escreveria sem set -e, tornando efetivamente essa troca menos que inútil; não apenas o autor precisa escrever o código de tratamento manual de erros como se set -enão estivesse em vigor, mas a presença de set -epode ter enganado-os a pensar que não precisam.

Algumas desvantagens adicionais de set -e:

  • Ele incentiva código desleixado. Os manipuladores de erros são completamente esquecidos, na esperança de que tudo que falhou reportará o erro de alguma maneira sensata. No entanto, com exemplos como let x++acima, este não é o caso. Se o script morre inesperadamente, geralmente é silencioso, o que dificulta a depuração. Se o script não morrer e você esperava (veja o ponto anterior), você tem um bug mais sutil e insidioso em suas mãos.
  • Isso leva as pessoas a uma falsa sensação de segurança. Veja novamente o ifmarcador de condição.
  • Os locais onde o shell termina não são consistentes entre os shells ou as versões do shell. É possível escrever acidentalmente um script que se comporte de maneira diferente em uma versão mais antiga do bash devido ao comportamento de set -eter sido ajustado entre essas versões.

set -eé uma questão controversa, e algumas pessoas cientes dos problemas que a cercam recomendam, enquanto outras recomendam tomar cuidado enquanto estiver ativo para conhecer as armadilhas. Existem muitos iniciantes em scripts de shell que recomendam set -eem todos os scripts um exemplo geral para condições de erro, mas na vida real isso não funciona dessa maneira.

set -e não substitui a educação.

Score_Under
fonte
2

Eu diria que é perigoso, porque você não controla mais o fluxo do seu script. O script pode terminar desde que qualquer um dos comandos que o script invoque retorne um valor diferente de zero. Portanto, tudo o que você precisa fazer é fazer algo que altere o comportamento ou a saída de qualquer um dos componentes e você poderá finalizar o script principal. Pode ser mais um problema de estilo, mas definitivamente tem consequências. E se o seu script principal deveria definir alguma sinalização e não o fez porque terminou mais cedo? Você acabaria culpando o resto do sistema se ele assumir que o sinalizador deveria estar lá ou trabalhar com um valor padrão ou antigo inesperado.

Marcin
fonte
Concordo totalmente com esta resposta. Não é inerentemente perigoso, mas é uma oportunidade de ter uma saída antecipada inesperada.
pboin 20/05/10
1
O que isso me lembrou é um professor de C ++ que estava sempre obtendo pontos de minhas atribuições por não ter um único ponto de entrada / único ponto de saída em um programa. Eu pensei que era uma coisa puramente de 'princípio', mas esse negócio de demos definitivamente demonstra como e por que as coisas podem sair completamente de controle. Em última análise, os programas tratam de controle e determinismo e, com o término prematuro, você desiste de ambos.
Marcin
0

Como um exemplo mais concreto da resposta de @ Marcin que pessoalmente me mordeu, imagine que houvesse uma rm foo/bar.txtlinha em algum lugar do seu script. Normalmente não é grande coisa se foo/bar.txtnão existe realmente. No entanto, com o set -epresente agora, seu script será encerrado mais cedo! Opa

Suan
fonte