Shell快速入门心得与自动化脚本
2016-12-12 / modified at 2023-08-12 / 2k words / 8 mins

本文结合博主这两个月(实际使用肯定有6~7年了)的Shell系统化学习经历,介绍如何在已有Shell的基础上开始解决业务问题。

现在很多大小公司都有一个趋势,就是将开发与运维合并到一个人身上去做,即DevOps,保证最终产品运行的环境与开发环境部署一致。开发者不再是只编码业务,还要自己维护甚至开发工具。这样做的好处很明显,第一个就是发生故障后可以一个人立刻定位,不需要拉通一堆人开莫名其妙的会议;第二个就是在工具选择上不再受制于人,不怕被工具组的人吊;第三个就是使用自动化脚本,节约手工时间,提高生活质量。

如何简单入门

如果你还没有听过Shell,你需要入门一下。这里的入门指最简单的命令,比如: ls, cd, pwd 等等,此外还需要掌握正则表达式。

请直接看下面

上面的内容大概需要3~8小时初步跳读即可完成,看完了就对Shell有个概念。

如何为Shell进行调试

看完入门读物了,接下来可能需要自己尝试写脚本了。学一门语言很重要的一点就是要掌握调试,如果你不想在Shell中频繁的写echo的话,可以使用set -x参数,它将会把解释器的执行过程都打印出来,比如下面的+号就是输出的调试内容。

1
2
3
4
5
➜  ~ set -x
➜ ~ echo "Hello $USER, today is `date`"
++ date
+ echo 'Hello Haruhi, today is Sat Jan 21 12:26:07 CST 2017'
Hello Haruhi, today is Sat Jan 21 12:26:07 CST 2017

调试完成后,想关闭调试输出的话,输入set +x 即可。

How to copy and paste from StackOverflow

掌握了基本命令与调试方法后,那么如何通过Shell解决实际业务问题呢?那当然是使用搜索引擎啦,它将可靠地降低编码时间。

比如老大现在出了一个需求,要求让某台机器每天定时去取某个Windows共享目录的SQL,接着到数据库中。

方案一:人工搜索

分解需求

  1. 获取共享目录的文件
  2. 执行SQL刷入
  3. 设计定时任务

搜索需求

1. 获取共享目录的文件

首先将本句翻译为英文关键词 shell get windows shared folder,接着通过谷歌去搜索,在结果中可以发现Stackoverflow中有个大神教了一个smbclient的命令,通过阅读此答案,我就实现了此功能

1
smbclient '//windowsserver/c$' -c 'lcd /tmp; cd $PWD; get *.sql' -U user%password

这样,我们第一个需求就完成了

2. 执行SQL刷入

同样,将此句翻译为英文关键词,以oracle数据库为例,关键词是 oracle sql shell import sql,同样通过谷歌搜索,可以抄到一个命令

1
sqlplus user/password@connect @/Nisarg/NEult/softpoint.sql

但是,老大要求刷入所有的sql,这里需要有一个文件夹遍历操作,我们接着通过谷歌搜索 shell dictionary loop file, 可以发现Shell可以通过for循环或者find实现,最终脚本如下

1
2
3
4
for f in *.sql
do
sqlplus user/password@connect @$f.sql
done
3. 执行定时任务

这个就很简单了,你甚至可以用百度中文进行搜索,本文略,这样,你的需求就完成了。

Linux上面的命令是学不完的,不要像背六级单词那样去背下来,或者去自己造轮子,而是尽可能尝试谷歌英文搜索shell + 功能描述,你的需求基本上都有现成的库。

方案二:实用AI生成

考验英文concise的能力

1
I want an example shell script that can read SQL files from a shared SMB disk and execute them in MySQL.

Shell的高级自动化

在使用Shell过程中,一般会调用其他的工具,比如Redis、Zookeeper、SSH等,它们均自己又打开了一个终端窗口,需要手动输入非Shell的命令。对于这类需求,有两种实现方法,第一种是去找它们自带的--help中是否有支持,第二种使用万能的Expect命令。

软件自带解析

很多软件经过迭代发展,自己搞了一套出来,一般用--help可以搜索出来,比如Redis支持eval执行lua脚本

1
2
3
4
➜  ~ redis-cli --help
....
--eval <file> Send an EVAL command using the Lua script at <file>.
....

甚至支持直接将参数放到后面

1
2
3
4
➜  ~ redis-cli SET 'key' 'val'
OK
➜ ~ redis-cli GET 'key'
"val"

很多常见的文字处理工具,比如perl, awk, sed, ex 等,都是支持自有语法的,它们明显比Shell用的更爽,跨平台、跨Shell也没有兼容性问题。

expect 自动化

Expect 可以实现互动,直接输入man expect即可看到教程。

由于Expect命令一般不单独使用,而是通过Bash中调用expect -c使用,因此下面的脚本默认均在Bash中运行

举个例子,清空Redis的db0缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
host="127.0.0.1"
port="6379"
expect -c '
spawn redis-cli -h '${host}' -p '${port}'
expect ">"
send "select 0
"
expect "OK*>"
send "flushall
"
expect "OK*>"
send "exit
"
expect eof
exit
'

再举个例子,登陆SSH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
host=192.168.1.100
user="root"
pwd="12345"
expect -c '
spawn ssh '${user}'@'${host}'
expect {
"*yes/no*" {send "yes
";exp_continue}
"*assword" {send '${pwd}'
}
}
# 这里要具体看你的sh怎么配置了
# interact 表示开始手工操作
interact
'

Shell自动化

  • Ansible:这个对入门文章有点超纲了,它主要是运维侧的工作,你要知道有这个东西

  • PaaS自动化:通过部署Kubernetes/Nomad等PaaS平台,基于Yaml进行自动化,同样超纲了。

创建自己的dotfile

dotfile是Shell中的工具类,类似于Java中的Utils.java,你可以把下面的内容放入dotfile:

  1. 网上搜索到的工具类函数
  2. 某个业务提炼出来的函数
  3. 常见的alias、变量、路径

最后不妨把它放到Git中进行管理,以进行统一管理,并在调用时使用source/ln引用,这样你就不用写重复的命令,代码维护也更加简单。

1
2
source /dev/stdin <<< "$(curl -s http://raw.github.com/.profile)"
# todo: 这时可以调用上面sh中封装的方法了,有点类似C中的头文件

总结

个人认为,Shell是一个历史遗留问题过多的语言,其语法并不友好,Trick过多导致需要“经验”大于"流程",没有必要为了精通Shell而本末倒置

  1. 能使用awk、perl,gradle等跨平台DSL的工具,尽量用跨平台工具去实现,一般来说它们的坑更少一些。
  2. 写Shell时切忌一上来就考虑复用,Shell是用来解决问题的,是要计算时间投入收益比的。比如某个Shell只在一台机器上跑,把路径硬编码也是无所谓的。如果以后出现复用需求后,后续再迭代改进,最后再封装下沉为自己的工具类。
  3. 写Shell切忌反客为主,它只是一个自动化工具,不像JAVA等饭碗工具,没有必要花费太多的时间

附录

关于单双引号

单引号'

除了'中的',否则将所有的字符串都看作文本,不进行dereference操作。

1
2
3
4
➜  ~ echo '`whoami` is using java at "$JAVA_HOME"'
`whoami` is using java at "$JAVA_HOME"
➜ ~ echo '`whoami` is using java at '$JAVA_HOME''
`whoami` is using java at /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home

双引号"

只对$与 ` 进行dereference

1
2
echo "`whoami` is using java at $JAVA_HOME"
> swift is using java at /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home