bash:错误处理和功能

bash: error handling and functions

我尝试在循环中调用一个函数,并在函数抛出时优雅地处理并继续。

如果我省略了|| handle_error,它只会像人们预期的那样停止整个脚本。

如果我把|| handle_error留在那里,它会在出错后打印foo is fine,根本不会执行handle_error。这也是一种预期的行为,这就是它的工作原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash

set -e

things=(foo bar)

function do_something {
  echo"param: $1"

  # just throw on first loop run
  # this statement is just a way to selectively throw
  # not part of a real use case scenario where the command(s)
  # may or may not throw
  if [[ $1 =="foo" ]]; then
    throw_error
  fi

  # this line should not be executed when $1 is"foo"
  echo"$1 is fine."
}

function handle_error {
  echo"$1 failed."
}

for thing in ${things[@]}; do
  do_something $thing || handle_error $thing
done

echo"done"

产量

1
2
3
4
5
6
param: foo
./test.sh: line 12: throw_error: command not found
foo is fine.
param: bar
bar is fine.
done

我想要的是

1
2
3
4
5
6
param: foo
./test.sh: line 12: throw_error: command not found
foo failed.
param: bar
bar is fine.
done

编辑:

do_something实际上不必归还任何东西。这只是一个抛出函数的例子,我可能会将它从示例源代码中移除,因为我无法控制它的内容,也不想这样做,并且测试其中的每个命令是否失败是不可行的。

编辑:

不允许触摸do_something逻辑。我之前说过,它只是一个包含一组可能引发错误的指令的函数。它可能是一个打字错误,也可能是一个CI环境中的make故障,也可能是一个网络错误。


我找到的解决方案是将函数保存在单独的文件中,并在子shell中执行它。缺点是我们失去了所有的本地人。

做某事

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

set -e

echo"param: $1"

if [[ $1 =="foo" ]]; then
  throw_error
fi

echo"$1 is fine."

MSY脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

set -e

things=(foo bar)

function handle_error {
  echo"$1 failed."
}

for thing in"${things[@]}"; do
  ./do-something.sh"$thing" || handle_error"$thing"
done

echo"done"

产量

1
2
3
4
5
6
param: foo
./do-something.sh: line 8: throw_error: command not found
foo failed.
param: bar
bar is fine.
done

如果有更优雅的方式,我会把它标为正确答案。将在48小时后再次登记。

编辑

多亏了@petercordes的评论和这个其他答案,我找到了另一个不需要单独文件的解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash

set -e

things=(foo bar)

function do_something {
  echo"param: $1"

  if [[ $1 =="foo" ]]; then
    throw_error
  fi

  echo"$1 is fine."
}

function handle_error {
  echo"$1 failed with code: $2"
}

for thing in"${things[@]}"; do
  set +e; (set -e; do_something"$thing"); error=$?; set -e
  ((error)) && handle_error"$thing" $error
done

echo"done"

正确的产量

1
2
3
4
5
6
param:  foo
./test.sh: line 11: throw_error: command not found
foo failed with code: 127
param:  bar
bar is fine.
done


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash

set -e

things=(foo bar)

function do_something() {
  echo"param: $1"
  ret_value=0

  if [[ $1 =="foo" ]]; then
    ret_value=1
  elif [[ $1 =="fred" ]]; then
    ret_value=2
  fi

  echo"$1 is fine."
  return $ret_value
}

function handle_error() {
  echo"$1 failed."
}

for thing in ${things[@]}; do
  do_something $thing || handle_error $thing
done

echo"done"

解释见我上面的评论。如果不创建返回值,就不能测试返回值,这应该是比较明显的。和测试返回值,基本上是一个大于0的值。类似于0的测试。我认为这或多或少是对的。我相信bash返回值限制是254?我想说。必须是0到254之间的整数。不能是字符串、浮点数等。

http://tldp.org/ldp/abs/html/complexfunct.html

Functions return a value, called an exit status. This is analogous to
the exit status returned by a command. The exit status may be
explicitly specified by a return statement, otherwise it is the exit
status of the last command in the function (0 if successful, and a
non-zero error code if not). This exit status may be used in the
script by referencing it as $?. This mechanism effectively permits
script functions to have a"return value" similar to C functions.

所以实际上,foo会返回一个127命令未找到错误。我想。我得测试一下才能确定。

[更新]不,echo是最后一个命令,从输出中可以看到。最后一个回声的结果是0,所以函数返回0。所以你想抛弃这个概念,去做类似陷阱的事情,那是假设你不能触及函数的内部,这很奇怪。

1
2
3
echo fred; echo reval: $?
fred
reval: 0

在bash脚本中set-e是什么意思?

1
-e  Exit immediately if a command exits with a non-zero status.

But it's not very reliable and considered as a bad practice, better use :

1
trap 'do_something' ERR

http://tldp.org/ldp/bash-beginners-guide/html/sect_12_02.html请参阅trap,这可以满足您的需要。但不是,除非添加退货。


尝试

1
2
3
if [["$1" =="foo" ]]; then
  foo
fi

我想知道它是否试图在条件测试中执行命令foo

从bash引用:

-e
Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists),
or a compound command (see Compound Commands) returns 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 in an if statement, 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 status
is being inverted with !.

如您所见,如果错误发生在测试条件中,那么脚本将继续忽略并返回0。

——

编辑

因此,作为回应,我注意到文档继续:

If a compound command other than a subshell returns a non-zero status
because a command failed while -e was being ignored, the shell does
not exit.

好吧,因为你的for是由echo继承的,所以没有理由抛出错误!