Como detectar um erro em um script bash do Linux?

Fiz o seguinte script:

# !/bin/bash # OUTPUT-COLORING red="\e[0;31m" green="\e[0;32m" NC="\e[0m" # No Color # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { cd $1 if [ $? = 0 ] then echo -e "${green}$1${NC}" else echo -e "${red}$1${NC}" fi } # EXE directoryExists "~/foobar" directoryExists "/www/html/drupal" 

O script funciona, mas ao lado do meu ecoa, também há a saída quando

cd $1 

falha na execução.

testscripts//test_labo3: line 11: cd: ~/foobar: No such file or directory 

É possível pegar isso?

Comentários

  • Apenas para sua informação, você também pode fazer isso de forma muito mais simples; test -d /path/to/directory (ou [[ -d /path/to/directory ]] em bash) dirá se um determinado destino é um diretório ou não, e fará isso silenciosamente.
  • @Patrick, que apenas testa se é ‘ um diretório, não se você pode cd nele.
  • @StephaneChazelas sim. O nome da função é directoryExists.
  • Veja uma resposta detalhada aqui: Gerar erro em um script Bash .

Resposta

Seu script muda de diretório conforme é executado, o que significa que não funcionará com uma série de nomes de caminhos relativos. Mais tarde, você comentou que só queria verificar a existência do diretório, não a capacidade de usar cd, portanto, as respostas não precisam usar cd em tudo. Revisado. Usando tput e cores de man terminfo:

#!/bin/bash -u # OUTPUT-COLORING red=$( tput setaf 1 ) green=$( tput setaf 2 ) NC=$( tput setaf 0 ) # or perhaps: tput sgr0 # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { # was: do the cd in a sub-shell so it doesn"t change our own PWD # was: if errmsg=$( cd -- "$1" 2>&1 ) ; then if [ -d "$1" ] ; then # was: echo "${green}$1${NC}" printf "%s\n" "${green}$1${NC}" else # was: echo "${red}$1${NC}" printf "%s\n" "${red}$1${NC}" # was: optional: printf "%s\n" "${red}$1 -- $errmsg${NC}" fi } 

(Editado para usar o mais invulnerável printf em vez do problemático echo que pode atuar nas sequências de escape no texto.)

Comentários

  • Isso também corrige (a menos que xpg_echo esteja ativado) os problemas quando os nomes de arquivo contêm caracteres de barra invertida.

Resposta

Use set -e para definir o modo exit-on-error: se um comando simples retornar um status diferente de zero (indicando falha), o shell sai.

Esteja ciente de que set -e nem sempre entra em ação. Comandos em posições de teste podem falhar (por exemplo, if failing_command, failing_command || fallback). Os comandos no subshell só levam à saída do subshell, não do pai: set -e; (false); echo foo exibe foo.

Alternativamente, ou além, em bash (e ksh e zsh, mas não sh simples), você pode especificar um comando que “é executado no caso de um comando retornar um status diferente de zero, com a armadilha ERR, por exemplo trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Observe que em casos como (false); …, a armadilha ERR é executada no subshell, portanto, não pode fazer com que o pai saia.

Comentários

  • Recentemente, eu experimentei um pouco e descobri uma maneira conveniente de corrigir o || comportamento, que permite lidar facilmente com os erros sem usar armadilhas. Consulte minha resposta . O que você acha desse método?
  • @ sam.kozin eu não ‘ t tenha tempo para revisar sua resposta em detalhes, parece bom em princípio. Além da portabilidade, quais são os benefícios sobre ksh / bash / zsh ‘ s ERR trap? ii id = “d72b74b94c”>

reescrevendo alguma função comum que você irá fornecer e usar posteriormente de outros scripts. Outro benefício pode ser totalmente compatível com POSIX, embora não seja tão importante quantoERRpseudo-sinal é suportado em todos os shells principais. Obrigado pela revisão! =)

  • @ sam.kozin Esqueci de escrever meu comentário anterior: você pode postar isso em Revisão de código e postar uma link na sala de bate-papo .
  • Obrigado pela sugestão, ‘ tentarei seguir isto. Não ‘ sabia sobre Revisão de código.
  • Resposta

    Para expandir a @Gilles “resposta :

    Na verdade, set -e não funciona dentro dos comandos se você usar o operador || depois deles, mesmo se você os executar em um subshell; por exemplo, isso não funcionaria:

     #!/bin/sh # prints: # # --> outer # --> inner # ./so_1.sh: line 16: some_failed_command: command not found # <-- inner # <-- outer set -e outer() { echo "--> outer" (inner) || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

    Mas || operador é necessário para evitar o retorno da função externa antes da limpeza.

    Há um pequeno truque que pode ser usado para corrigir isso: execute o comando interno em segundo plano e, em seguida, imediatamente espere por isso.O wait integrado retornará o código de saída do comando interno e agora você “está usando || após wait, não a função interna, então set -e funciona corretamente dentro da última:

     #!/bin/sh # prints: # # --> outer # --> inner # ./so_2.sh: line 27: some_failed_command: command not found # --> cleanup set -e outer() { echo "--> outer" inner & wait $! || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

    Aqui está a função genérica que se baseia nessa ideia. Ela deve funcionar em todos os shells compatíveis com POSIX se você remover local palavras-chave, ou seja, substitua todas as local x=y por apenas x=y:

     # [CLEANUP=cleanup_cmd] run cmd [args...] # # `cmd` and `args...` A command to run and its arguments. # # `cleanup_cmd` A command that is called after cmd has exited, # and gets passed the same arguments as cmd. Additionally, the # following environment variables are available to that command: # # - `RUN_CMD` contains the `cmd` that was passed to `run`; # - `RUN_EXIT_CODE` contains the exit code of the command. # # If `cleanup_cmd` is set, `run` will return the exit code of that # command. Otherwise, it will return the exit code of `cmd`. # run() { local cmd="$1"; shift local exit_code=0 local e_was_set=1; if ! is_shell_attribute_set e; then set -e e_was_set=0 fi "$cmd" "$@" & wait $! || { exit_code=$? } if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then set +e fi if [ -n "$CLEANUP" ]; then RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@" return $? fi return $exit_code } is_shell_attribute_set() { # attribute, like "x" case "$-" in *"$1"*) return 0 ;; *) return 1 ;; esac }  

    Exemplo de uso:

     #!/bin/sh set -e # Source the file with the definition of `run` (previous code snippet). # Alternatively, you may paste that code directly here and comment the next line. . ./utils.sh main() { echo "--> main: $@" CLEANUP=cleanup run inner "$@" echo "<-- main" } inner() { echo "--> inner: $@" sleep 0.5; if [ "$1" = "fail" ]; then oh_my_god_look_at_this fi echo "<-- inner" } cleanup() { echo "--> cleanup: $@" echo " RUN_CMD = "$RUN_CMD"" echo " RUN_EXIT_CODE = $RUN_EXIT_CODE" sleep 0.3 echo "<-- cleanup" return $RUN_EXIT_CODE } main "$@"  

    Executando o exemplo:

     $ ./so_3 fail; echo "exit code: $?" --> main: fail --> inner: fail ./so_3: line 15: oh_my_god_look_at_this: command not found --> cleanup: fail RUN_CMD = "inner" RUN_EXIT_CODE = 127 <-- cleanup exit code: 127 $ ./so_3 pass; echo "exit code: $?" --> main: pass --> inner: pass <-- inner --> cleanup: pass RUN_CMD = "inner" RUN_EXIT_CODE = 0 <-- cleanup <-- main exit code: 0  

    O a única coisa que você precisa estar ciente ao usar este método é que todas as modificações das variáveis Shell feitas a partir do com o comando que você passar para run não se propagará para a função de chamada, porque o comando é executado em um subshell.

    Resposta

    Você não diz exatamente o que quer dizer com catch — relatar e continuar; abortar o processamento adicional?

    Visto que cd retorna um status diferente de zero em caso de falha, você pode fazer:

    cd -- "$1" && echo OK || echo NOT_OK 

    Você pode simplesmente sair em caso de falha:

    cd -- "$1" || exit 1 

    Ou, ecoar sua própria mensagem e sair:

    cd -- "$1" || { echo NOT_OK; exit 1; } 

    E / ou suprimir o erro fornecido por cd em caso de falha:

    cd -- "$1" 2>/dev/null || exit 1 

    Por padrões, os comandos devem colocar mensagens de erro no STDERR (descritor de arquivo 2). Assim, 2>/dev/null diz redirecionar STDERR para o “bit-bucket” conhecido por /dev/null.

    (não se esqueça para citar suas variáveis e marcar o fim das opções para cd).

    Comentários

    • @Stephane Chazelas cita e sinaliza o fim das opções bem entendido. Obrigado pela edição.

    Resposta

    Na verdade para o seu caso, eu diria que a lógica pode ser melhorada.

    Em vez de cd e depois verifique se existe, verifique se existe e vá para o diretório.

    if [ -d "$1" ] then printf "${green}${NC}\\n" "$1" cd -- "$1" else printf "${red}${NC}\\n" "$1" fi 

    Mas se o seu objetivo é silenciar os possíveis erros, cd -- "$1" 2>/dev/null, mas isso tornará a depuração mais difícil no futuro. Você pode verificar se o teste sinalizadores em: Bash se documentação :

    Comentários

    • Essa resposta falha cite a variável $1 e irá falhar se essa variável contém espaços em branco ou outros metacaracteres de shell. Também falha em verificar se o usuário tem permissão para cd nele.
    • Na verdade, eu estava tentando verificar se um determinado diretório existia, não necessariamente cd para ele . Mas porque eu não ‘ não sabia melhor, pensei que tentar fazer um cd para ele causaria um erro, se não existisse, então por que não detectá-lo? Eu não ‘ não sabia sobre se [-d $ 1] que ‘ é exatamente o que eu precisava. Então, muito obrigado! (Eu ‘ m usado para proram Java e verificar um diretório em uma instrução if não é exatamente comum em Java)

    Deixe uma resposta

    O seu endereço de email não será publicado. Campos obrigatórios marcados com *