如何捕获 Linux bash 脚本中的错误?

如何捕获 Linux bash 脚本中的错误?

我制作了以下脚本:

# !/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"

该脚本有效,但除了我的回声之外,还有以下输出:

cd $1

执行失败。

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

有可能抓住这个吗?

答案1

用于set -e设置错误退出模式:如果一个简单的命令返回非零状态(指示失败),则 shell 退出。

请注意,这set -e并不总是起作用。测试位置中的命令允许失败(例如if failing_commandfailing_command || fallback)。子 shell 中的命令仅导致退出子 shell,而不是父 shell:set -e; (false); echo foodisplays foo

或者,或者此外,在 bash(以及 ksh 和 zsh,但不是普通 sh)中,您可以使用陷阱指定在命令返回非零状态时执行的命令,ERR例如trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR。请注意,在类似的情况下(false); …,ERR 陷阱是在子 shell 中执行的,因此它不会导致父 shell 退出。

答案2

您的脚本在运行时会更改目录,这意味着它无法使用一系列相对路径名。然后您后来评论说您只想检查目录是否存在,而不是使用的能力 ,因此答案根本cd不需要使用。cd修改。使用tput 和颜色来自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
}

(编辑为使用更无懈可击的而不是可能对文本中的转义序列起作用的printf问题 。)echo

答案3

为了扩展@吉尔斯的回答:

事实上,set -e如果您在命令后使用运算符,则在命令内部不起作用||,即使您在子 shell 中运行它们;例如,这是行不通的:

#!/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

但是||需要运算符来防止在清理之前从外部函数返回。

有一个小技巧可以用来解决这个问题:在后台运行内部命令,然后立即等待它。内置函数wait将返回内部命令的退出代码,现在您使用的是||after wait,而不是内部函数,因此set -e在后者内部可以正常工作:

#!/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

这是建立在这个想法之上的通用函数。如果删除关键字,它应该在所有 POSIX 兼容的 shell 中工作local,即将所有关键字替换local x=yx=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
}

使用示例:

#!/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 "$@"

运行示例:

$ ./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

使用此方法时唯一需要注意的是,从传递给的命令中完成的 Shell 变量的所有修改run都不会传播到调用函数,因为该命令在子 shell 中运行。

答案4

实际上对于你的情况我会说逻辑可以改进。

而不是 cd 然后检查它是否存在,而是检查它是否存在然后进入该目录。

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

但是,如果您的目的是消除可能的错误,那么cd -- "$1" 2>/dev/null,但这将使您将来的调试更加困难。您可以在以下位置检查 if 测试标志:Bash if 文档

相关内容