前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[翻译]15分钟bash进阶

[翻译]15分钟bash进阶

作者头像
陆道峰
发布2020-06-17 22:05:18
1.5K0
发布2020-06-17 22:05:18
举报

说明

更安全的脚本

每个脚本中我都以下面的内从开始:

代码语言:javascript
复制
#!/bin/bash
set -o nounset
set -o errexit

这会处理两个常见的错误:

  1. 引用未定义的变量(默认是””)
  2. 忽略执行失败的命令

这两个设置是有对应快捷写法的(”-u”和”-e”),但是原始写法更佳易读。

如果你要忽略可能执行错误的命令,可以使用下面的写法:

代码语言:javascript
复制
if ! <possible failing command> ; then
   echo "failure ignored"
fi

需要注意的是,有些Linux命令可以使用一些选项来强制忽略错误,比如rm -fmkdir -p

还需要注意的是,在“errexit”模式下,虽然能有效捕捉错误,但不能捕捉全部错误。在特定情况下,有些失败的命令没办法检测。(更多信息可以参考这篇文章

一位读者还推荐另一个用法set -o pipefail

函数

在Bash中你可以定义其它函数,它们和其它命令一样—你可以随意调用它们;这也会让你的脚本更具可读性。

代码语言:javascript
复制
ExtractBashComments() {
   egrep "^#"
}
cat myscript.sh | ExtractBashComments | wc
comments=$(ExtractBashComments < myscript.sh)

更多例子:

代码语言:javascript
复制
SumLines() {  # iterating over stdin - similar to awk      
   local sum=0
   local line=””
   while read line ; do
       sum=$((${sum} + ${line}))
   done
   echo ${sum}
}
SumLines < data_one_number_per_line.txt
log() {  # classic logger
  local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]: "
  echo "${prefix} $@" >&2
}
log "INFO" "a message"

尝试把所有bash代码移植到函数中,只留下全局变量/常量,然后在main函数中统一调用它们。

变量注解

Bash允许一种有限制的变量注解形式,最终要的有:

  • local(在函数内定义局部变量)
  • readonly(只读变量)# a useful idiom: DEFAULT_VAL can be overwritten # with an environment variable of the same name readonly DEFAULT_VAL=${DEFAULT_VAL:-7} myfunc() { # initialize a local variable with the global default local some_var=${DEFAULT_VAL} ... }

这样,你就可以把以前不是只读的变量声明成只读变量:

代码语言:javascript
复制
x=5
x=6
readonly x
x=7   # failure

尽量把bash中的所有变量注解成readonly或者local

用$() 代替 (`)

反单引号在一些字体中难以辨识,很容易和单引号混淆。

$()允许内嵌,而且避免了转义的麻烦

代码语言:javascript
复制
# both commands below print out: A-B-C-D
echo "A-`echo B-\`echo C-\\\`echo D\\\`\``"
echo "A-$(echo B-$(echo C-$(echo D)))"

[[]]代替[]

[[]]能够避免文件扩展名异常,提供一些语法上的改进,还增加了一些新的特性:

Operator(操作符)

Meaning(含义)

逻辑或

&&

逻辑与

<

字符比较(双中括号中不需要转义)

-lt

数字比较

=

字符串比较

==

以globbing的方式比较字符串,见下文 (仅双中括号有效)

=~

正则方式比较字符串,见下文(仅双中括号有效)

-n

字符串非空

-z

字符串为空

-eq

数字相等

-ne

数字不相等

单中括号:

代码语言:javascript
复制
[ "${name}" \> "a" -o ${name} \< "m" ]

双中括号:

代码语言:javascript
复制
 [[ "${name}" > "a" && "${name}" < "m"  ]]

正则和Globbing

以下几个例子能够体现出双中括号的强大能力:

代码语言:javascript
复制
t="abc123"
[[ "$t" == abc* ]]         # true (globbing)
[[ "$t" == "abc*" ]]       # false (literal matching)
[[ "$t" =~ [abc]+[123]+ ]] # true (regular expression)
[[ "$t" =~ "abc*" ]]       # false (literal matching)

注意,bash3.2以后正则表达式或Globbing表达式不能被引号包裹。如果表达式中含有空格,你可以存到变量中:

代码语言:javascript
复制
r="a b+"
[[ "a bbb" =~ $r ]]        # true

基于Globbing的字符串比较也可以用到case中:

代码语言:javascript
复制
case $t in
abc*)  <action> ;;
esac

字符串操作

bash有很多操作字符串的方法。

  • {#f}" # = 20 (string length) # slicing: {f: -8}" # = "file.ext"(Note: space before "-") pos=6 len=5 slice4="
  • {f//path?/x}" # = "x/x/file.ext" # string splitting readonly DIR_SEP="/" array=(
  • Deletion at beginning/end (with globbing) f="path1/path2/file.ext" # deletion at string beginning extension="${f#*.}" # = "ext" # greedy deletion at string beginning filename="${f##*/}" # = "file.ext" # deletion at string end dirname="${f%/*}" # = "path1/path2" # greedy deletion at end root="${f%%/*}" # = "path1"

避免使用临时文件

一些命令使用文件名作为参数,所以管道就无法使用了。这时<()就派上用场了,它可以接受一个命令,然后把命令转换成可以作为文件名的东西:

代码语言:javascript
复制
# download and diff two webpages
diff <(wget -O - url1) <(wget -O - url2)

“here document”也很有用,它允许把任意行字符串传给标准输入。

下面的MARKER可以换成任何文本:

代码语言:javascript
复制
# DELIMITER is an arbitrary string
command  << MARKER
...
${var}
$(cmd)
...
MARKER

内置变量

  • ! PID of the last command executed (and run in the background) ? exit status of the last command ({PIPESTATUS} for pipelined commands)
  • @ handles empty parameter list and white-space within parameters correctly @ should usually be quoted like so "

调试

对脚本进行语法检查

代码语言:javascript
复制
bash  -n myscript.sh

跟踪脚本里每个命令的执行:

代码语言:javascript
复制
bash -v myscript.sh

跟踪脚本里每个命令的执行并附加扩充信息:

代码语言:javascript
复制
bash -x myscript.sh

你可以在脚本头部添加set -o verboseset -o xtrace来永久指定-x-v

如果脚本运行在远程机器上这会很有效,用它来输出远程信息。

什么时候不该用脚本

  • 你的脚本很长,不下于几百行
  • 除了简单的数组外你还需要数据结构
  • 出现复杂的转义问题
  • 需要很多字符串操作
  • 不太需要调用其它程序或者通过管道和其它程序交互
  • 你比较在意性能

你需要考虑Python或者Ruby这样的脚本语言

参考

译者说

本文介绍了Bash中很多好的编程习惯和经验,字符串操作和比较是容易忽视以及不易掌握的。 注意bash中的正则和globbing的区别。

另外,本文有很多国人翻译了,译者在翻译本文时有一些翻译参考了《Bash脚本15分钟进阶教程》(http://www.vaikan.com/bash-scripting/),这篇翻译质量很高,我个人学习和借鉴了很多。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器学习与系统 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说明
  • 更安全的脚本
  • 函数
  • 变量注解
  • 用$() 代替 (`)
  • [[]]代替[]
  • 正则和Globbing
  • 字符串操作
  • 避免使用临时文件
  • 内置变量
  • 调试
  • 什么时候不该用脚本
  • 参考
  • 译者说
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档