前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >shell脚本还能这么写?

shell脚本还能这么写?

作者头像
yxxhero
发布2022-05-31 19:28:07
1.4K0
发布2022-05-31 19:28:07
举报
文章被收录于专栏:DevOps充电宝

工作学习中,shell脚本是日常基本需求,你印象中的脚本应该是这样的:命令的堆砌、从上到下依次执行、杂乱无章、实现功能就行,导致自己写的脚本自己都不想看,今天我就教你怎么写脚本,学完之后,一定会说一句,脚本还能这么写!

现以nginx控制脚本为例,看一下脚本的美容过程:

代码语言:javascript
复制
#!/bin/bash
# set用法在文章末尾
set -eu

# nginx配置重载


nginx -c /etc/nginx/nginx.conf -t
kill -1 `ps auxf | grep -E "nginx:[[:space:]]+master"| awk '{print $2}'`

1. 变量替换

在平常的开发中,脚本会依赖很多的配置,例如监听端口、配置文件之类的可变参数,如果我们将其硬编码到脚本中,那么改起来就是牵一发动全身了,很麻烦,不可靠。测试我们就需要变量来帮忙了,这样只需要修改一处,脚本整体生效,高效很多。

代码语言:javascript
复制
#!/bin/bash
# set用法在文章末尾
set -eu
# nginx配置文件在不同环境中可能不同,所以需要将其抽离成可配置变量,后面来引用
NGINX_CONFIG_FILE=/etc/nginx/nginx.conf

# nginx重载配置
nginx -c $NGINX_CONFIG_FILE -t
kill -HUP `ps auxf | grep -E "nginx:[[:space:]]+master"| awk '{print $2}'`

2.模块化

运维毕竟开发,写脚本时就是从上到下依次执行,命令的堆砌,这就导致脚本复用性差,不易维护,解决这问题的关键在于函数化、模块化思想,shell虽然是一种比较简单的语言,但语言基本的逻辑控制、函数功能都有,这就让我们编写高质量shell脚本充满了想象。接下来,上菜:

代码语言:javascript
复制
# 由于nginx配置文件检查是执行其他操作的第一步,所以我们将其独立成一个单独函数
#!/bin/bash
# set命令的奇妙用途留在文章末尾
set -eu
# nginx配置文件在不同环境中,位置可能不同,所以需要将其抽离成可配置变量,脚本来引用
NGINX_CONFIG_FILE=/etc/nginx/nginx.conf

# 抽离配置文件检查为单独的函数
config_test() {
    nginx -c $NGINX_CONFIG_FILE -t
}
get_nginx_master_pid(){
 echo `ps auxf | grep -E "nginx:[[:space:]]+master"| awk '{print $2}'`
}
# 抽离配置重载为独立函数
reload() {
 kill -HUP `get_nginx_master_pid`
}

# nginx重载配置文件
config_test
reload


3.main函数

脚本的可维护性在于脚本的结构的好坏,为了拥有更好的结构,通常需要在脚本中定义入口函数,即main函数,让我在维护脚本时,可以更好的把握脚本的组织架构,找到切入点:

代码语言:javascript
复制
# 由于nginx配置文件检查是执行其他操作的第一步,所以我们将其独立成一个单独函数
#!/bin/bash
# set命令的奇妙用途留在文章末尾
set -eu
# nginx配置文件在不同环境中,位置可能不同,所以需要将其抽离成可配置变量,脚本来引用
NGINX_CONFIG_FILE=/etc/nginx/nginx.conf

# 抽离检查配置文件为函数
config_test() {
    nginx -c $NGINX_CONFIG_FILE -t
}

get_nginx_master_pid(){
 echo `ps auxf | grep -E "nginx:[[:space:]]+master"| awk '{print $2}'`
}
# 抽离配置重载为函数
reload() {
 kill -HUP `get_nginx_master_pid`
}

# 脚本入口
main() {
    config_test
    reload
}

# main在此需要获取脚本本身的参数, 故将$@传递给main函数
main $@

4.函数返回值

在其他编程语言,可以通过return获取函数的返回值,但是return语句在shell中含义不同,return在默认会返回上一次命令的执行状态码。那如何实现类似其他编程语言的return效果呢?可以使用echo命令:

代码语言:javascript
复制
#!/bin/bash
set -eu
NGINX_CONFIG_FILE=/etc/nginx/nginx.conf

config_test() {
    nginx -c $NGINX_CONFIG_FILE -t
}

# 在此处直接把nginx的master pid可以通过反引号来获取echo后的值
get_nginx_master_pid(){
 echo `ps auxf | grep -E "nginx:[[:space:]]+master"| awk '{print $2}'`
}

reload() {
    # `get_nginx_master_pid` 获得nginx master pid
    nginx_pid=`get_nginx_master_pid`
 kill -HUP $nginx_pid 
}

# 脚本入口
main() {
    config_test
    reload
}

# main需要获取脚本本身的所有参数, 故将$@传递给main函数
main $@

5.set命令

内置的set命令,可以改变我们脚本的执行行为,让我们对脚本的把握和调试更强,下面是常用的几种set指令,相信你会喜欢的:

  • set -e: bash脚本遇到错误立即退出
  • set -n: 检查脚本语法但不执行
  • set -u: 遇到未设置的变量立即退出
  • set -o pipefail: 控制在管道符执行过程中有错误立即退出
  • set -x: 分步调试命令

在写脚本时,我们可以直接在脚本开头添加如下内容:

代码语言:javascript
复制
#!/bin/bash
set -euxo pipefail

检查bash脚本的语法时,可以这样写:

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

6.组命令

有的时候我们有这样的需求,对文本内容的修改,不是简单一条命令来实现,需要两条命令,在一定条件下,一起执行,类似于事务的概念,这就要通过()来实现,括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。

代码语言:javascript
复制
ip a | grep docker0 || (ip link add name docker0 type bridge && ip addr add dev docker0 172.17.0.1/16)

7.stderr重定向至stdout

脚本开发过程中,在我们使用管道时,默认只是传递stdout,在某种场景下,比如需要根据stderr的内容进行判断,默认情况下就不支持,需要我们进行特殊处理,stderr重定向至stdout,那具体怎么做呢?看下面的示例:

代码语言:javascript
复制
 kubectl create clusterrolebinding kube-apiserver:kubelet-apis --clusterrole=system:kubelet-api-admin --user kubernetes 2>&1 | grep -E -i "created|exists" > /dev/null

重点在于,2>&1。

- END -

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

本文分享自 DevOps充电宝 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 变量替换
  • 2.模块化
  • 3.main函数
  • 4.函数返回值
  • 5.set命令
  • 6.组命令
  • 7.stderr重定向至stdout
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档