本文将介绍Bash中 set -euxo pipefail,它们可以帮助你写出更容易维护也更安全的脚本。这也是Bash脚本的终极调试手段,希望你以后在自己的脚本中加上这么一行,头顶也能少秃一点。

set -e

set -e 选项可以让你的脚本在出现异常时马上退出,后续命令不再执行。默认情况下Shell脚本不会因为错误而结束执行,但大多数情况是,我们希望出现异常时就不要再往下走了。假如你的if判断条件里会出现异常,这时脚本也会直接退出,但可能这并不是你期望的情况,这时你可以在判断语句后加上 || true 来阻止退出。

Before

  1. #!/bin/bash
  2. # 'foo' is a non-existing command
  3. foo
  4. echo "bar"
  5. # output
  6. # ------
  7. # line 4: foo: command not found
  8. # bar

After

  1. #!/bin/bash
  2. set -e
  3. # 'foo' is a non-existing command
  4. foo
  5. echo "bar"
  6. # output
  7. # ------
  8. # line 5: foo: command not found

阻止立即退出的例子。

  1. #!/bin/bash
  2. set -e
  3. # 'foo' is a non-existing command
  4. foo || true
  5. echo "bar"
  6. # output
  7. # ------
  8. # line 5: foo: command not found
  9. # bar

set -o pipefail

默认情况下Bash只会检查管道(pipeline)操作最后一个命令的返回值,假如最右边的命令成功那么它就认为这个语句没问题。这个行为其实是很不安全的,所以就有了set -o pipefail。这个特别的选项表示在管道连接的命令中,只要有任何一个命令失败(返回值非0),则整个管道操作被视为失败。只有管道中所有命令都成功执行了这个管道才算成功执行。

Before

  1. #!/bin/bash
  2. set -e
  3. # 'foo' is a non-existing command
  4. foo | echo "a"
  5. echo "bar"
  6. # output
  7. # ------
  8. # a
  9. # line 5: foo: command not found
  10. # bar

After

  1. #!/bin/bash
  2. set -eo pipefail
  3. # 'foo' is a non-existing command
  4. foo | echo "a"
  5. echo "bar"
  6. # output
  7. # ------
  8. # a
  9. # line 5: foo: command not found

set -u

set -u 比较容易理解,Bash会把所有未定义的变量视为错误。默认情况下Bash会将未定义的变量视为空,不会报错,这也是很多坑的来源。也许由于变量名的细微差别让你查半天最后骂骂咧咧。

Before

  1. #!/bin/bash
  2. set -eo pipefail
  3. echo $a
  4. echo "bar"
  5. # output
  6. # ------
  7. #
  8. # bar

After

  1. #!/bin/bash
  2. set -euo pipefail
  3. echo $a
  4. echo "bar"
  5. # output
  6. # ------
  7. # line 5: a: unbound variable

set -x

set -x 可以让Bash把每个命令在执行前先打印出来,你可以认为这就是Bash的Debug开关。它的好处当然显而易见,方便你快速找到有问题的脚本位置,但是也坏处也有吧,就是Bash的log会格外的乱。另外,它在打印命令前会把变量先解析出来,所以你可以知道当前执行的语句的变量值是什么。纵然log可能会乱一些,总比头发乱一些好,所以建议还是打开这个开关。

  1. #!/bin/bash
  2. set -euxo pipefail
  3. a=5
  4. echo $a
  5. echo "bar"
  6. # output
  7. # ------
  8. # + a=5
  9. # + echo 5
  10. # 5
  11. # + echo bar
  12. # bar

以上就是关于 set -euxo pipefail 的介绍,从Shell脚本的编写角度看,我十分建议所有人都应该在自己的Shell脚本里加上这么一行。但从实际情况看,如果你的Shell脚本已经超过200行,我更建议你换成高级语言来实现。比如Python或者Ruby甚至Perl,这些高级语言在Linux系统都是内置的,注意版本兼容性就好,写起来比Shell舒服太多了。

set +H

禁用历史扩展功能,禁用之后可以使用 set -H 开启
详见 man bash
HISTORY EXPANSION

Before

  1. $ foo="!@#$%^&*()_+-={}|[]\:\";'<>?,./~\`"
  2. bash: !@#: event not found
  3. $ echo foo
  4. $ foo="@!#$%^&*()_+-={}|[]\:\";'<>?,./~\`"
  5. foo="@foo="@%^&*()_+-={}|[]\:\";'<>?,./~\`"
  6. > ^c
  7. $ echo foo

After

  1. set +H
  2. $ foo="@!#$%^&*()_+-={}|[]\:\";'<>?,./~\`"
  3. $ echo foo
  4. @!#$%^&*()_+-={}|[]\:";'<>?,./~\`