Como capturar um erro em um script bash do Linux?

9

Eu 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 além dos meus ecos, há também a saída quando

cd $1

falha na execução.

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

É possível perceber isso?

    
por Thomas De Wilde 22.10.2013 / 12:29

5 respostas

4

Seu script altera os diretórios conforme são executados, o que significa que não funcionará com uma série de caminhos relativos. Você então comentou mais tarde que você só queria verificar a existência de diretório, não a capacidade de usar cd , então as respostas não precisam usar cd . 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 printf mais invulnerável em vez do problemático echo que pode atuar em seqüências de escape no texto.)

    
por 22.10.2013 / 15:54
8

Use set -e para definir o modo de saída no erro: se um comando simples retornar um status diferente de zero (indicando falha), o shell sairá.

Tenha em atenção que set -e nem sempre entra em acção. Os comandos nas posições de teste podem falhar (por exemplo, if failing_command , failing_command || fallback ). Comandos em subshell só levam a sair do subshell, não o pai: set -e; (false); echo foo exibe foo .

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

    
por 23.10.2013 / 18:19
3

Para expandir a resposta do @Gilles :

De fato, set -e não funciona dentro de comandos se você usar o operador || depois deles, mesmo se você executá-los 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 o operador || é necessário para evitar o retorno da função externa antes da limpeza.

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

#!/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. Ele deve funcionar em todos os shells compatíveis com POSIX se você remover local keywords, ou seja, substituir todos os 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

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

    
por 11.01.2016 / 17:33
0

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

Como cd retorna um status diferente de zero na falha, você pode fazer:

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

Você pode simplesmente sair com falha:

cd -- "$1" || exit 1

Ou faça eco da sua própria mensagem e saia:

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

E / ou suprimir o erro fornecido por cd na 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 de citar suas variáveis e marcar o final das opções para cd ).

    
por 22.10.2013 / 14:39
0

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

Em vez de cd e, em seguida, verifique se ele existe, verifique se ele 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 propósito é silenciar os possíveis erros, então cd -- "$1" 2>/dev/null , mas isso fará com que você depure no futuro mais difícil. Você pode verificar os sinalizadores de teste se: Bash se a documentação :

    
por 22.10.2013 / 12:52