1. 前言
1.1 为什么学习shell编程
Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具,Linux/UNIX系统的底层及基础应用软件的核心大部分涉及Shell脚本的内容。
每一个合格的Linux系统管理员或运维工程师,都需要熟练的编写Shell脚本语言,并能够阅读系统及各类软件附带的Shell脚本内容。只有这样才能提升运维人员的工作效率,适应日益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础。
1.2 学好Shell编程所需的基础知识
- 能够熟练使用vim编辑器,熟悉SSH终端。
- 有一定的Linux命令基础,至少需要掌握80个以上Linux常用命令,并能够熟练使用它。
- 要熟练掌握Linux正则表达式及三剑客命令(grep,sed,awk)
- 常见的Linux网络服务部署、优化以及排错。
- 例如:crond, nfs, rsync, inotify, lanmp, sersync, ssh, Memcached, MySQL等。
2. Shell脚本入门
2.1 什么是shell
- Shell是一个命令解释器,它在操作系统的最外层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,输出屏幕返回给用户。
这种对话方式可以是:
- 交互的方式:从键盘输入命令,通过/bin/bash的解释器,可以立即得到shell的回应
- 非交互的方式:脚本
Shell的英文意思是贝壳的意思,命令解释器Shell像一个贝壳一样包住系统核心。
Shell执行命令分为两种方式:
- 内置命令:如讲过的cd,pwd,exit和echo等命令,当用户登录系统后,shell以及内置命令就被系统载入内存,并且一直运行。
- 一般命令:如ls,磁盘上的程序文件-->调入-->执行命令
2.2 什么是shell脚本
当Linux命令或语句不在命令行下执行(严格说,命令行也是shell),而是通过一个程序文件执行时,该程序就被称为Shell脚本或Shell程序。
用户可以在Shell脚本中敲入一系列的命令及语句组合。
这些命令,变量和流程控制语句等有机的结合起来就形成一个功能强大的Shell脚本。
shell程序很类似DOS系统下的批处理程序(扩展名*.bat)。
2.3 一个简单的shell脚本
首先先带领大家写一个清空/var/log/messages日志的脚本
我们需要先想明白几个问题:
- 日志文件在哪?
- /var/log/messages
- 用什么命令可以清空文件?
- 重定向>
- 写一个简单的shell脚本。
- 将所有命令放在一个文件里堆积起来就形成了脚本,下面就是一个最简单的命令堆积形成的shell脚本。
- 要使用root身份来运行这个脚本
- 清除日志脚本
[root@oldboy scripts]# cat clear_log_messages.sh #!/bin/sh cd /var/log cat /dev/null > messages echo "Logs cleaned up."
上述脚本的问题:
- 如果不是root用户就无法执行清理日志
- 没有任何流程控制语句,简单的说,就是顺序操作,没有成功判断和逻辑严密性。
# 如果不是root用户,就无法修改日志文件/var/log/messages [root@oldboy scripts]# ll /var/log/messages -rw------- 1 root root 81192 Oct 15 15:24 /var/log/messages # 如果不是root用户就无法执行清理日志脚本 [root@oldboy scripts]# ls -l clear_log_messages.sh -rw-r--r-- 1 root root 72 Oct 15 15:34 clear_log_messages.sh
- 有没有脚本放在统一的目录下
- /server/scripts
- 授权:用那个用户执行文件
- 需要对用户做判断
- 清空错文件怎么办?
- 错误提示:有没有成功?
- 脚本的通用性
范例:包含命令,变量和流程控制的清空/var/log/messages日志的shell脚本
#!/bin/bash # 清除日志脚本,版本2 LOG_DIR = /var/log ROOT_UID = 0 # $UID 为0的时候,用户才具有root用户的权限 # 优雅提示:要使用root用户来运行 # $UID 系统变量,不需要定义 if ["$UID" -ne "$ROOT_UID"] then echo "Must be root to run this script." exit 1 # 退出脚本,返回值1 fi cd $LOG_DIR || { echo "Cannot change to necessary directory.">&2 exit 1 } cat /dev/null > messages && echo "Logs cleaned up." exit 0 # 退出之前返回0 表示成功,返回1 表示失败。
编写shell的步骤:
- 必须是root才能执行脚本,否则退出
- 成功切换目录 cd /var/log,否则退出
- 清理日志 cat /dev/null > messages,清理成功,在输出
- 上述条件都确认ok,echo输出。
清空日志的三种方法:
- echo >test.log
- >test.log
- cat /dev/null > test.log
shell脚本在运维工作中的作用和地位:
shell脚本擅长处理文本类型的数据,而Linux中几乎所有的配置文件,日志文件等都是纯文本类型文件。 多数启动文件都是纯文本类型的文件。
因此,学好shell脚本语言,就可以利用它在linux系统中发挥巨大的作用。
运维的服务,通过shell管理和配置:
- 基础命令
- 定时任务
- NFS服务
- Rsync服务
- ssh key服务
- Nagios监控服务
- Apache服务
- MySQL服务
- Php服务
- Nginx服务
- Lvs+keepalive服务
- Cacti/Mrtg流量及监控
- iptables防火墙
2.4 shell脚本语言的种类
在Unix和Linux中主要有两大类shell:
- Bourne shell,包括sh,ksh,and bash
- Bourne shell,sh,已经被bash取代
- Korn shell,ksh
- Bourne Again shell,bash
- Posix shell,sh
- C shell,包括csh和 tcsh
- C shell,csh
- TENEX/TOPS C shell, tcsh
shell脚本语言是弱类型语言
- 较为通用的shell有标准的Bourne shell(sh)和C shell(csh)。
- 其中Bourne shell(sh)已经被bash shell取代,
- bourne Again shell(bash)是从sh发展而来的
- bash和sh稍有不同,它还包含了csh和ksh的特色,但大多数脚本都可以不加修改地在bash上运行。
[root@oldboy scripts]# cat /etc/shells /bin/sh # 最常用的 /bin/bash # 常用的,第二个的功能要比sh的功能更强 /sbin/nologin # 常用, /bin/dash /bin/tcsh /bin/csh
其他语言:
- php
- php是网页程序,也是脚本语言。更专注于web页面开发,如:dedecms, discuz.
- perl
- perl脚本语言,比shell强大的多,语法灵活,复杂,实现方式很多,不易读,团队协作困难
- python
- 可以做脚本开发,也可以实现web开发。中等以上的公司都要求会python
shell脚本与php,perl,python语言的区别?
- shell的优势在于处理操作系统底层的业务(大量的命令为它做支撑,2000多个命令都是shell支持,grep,awk,sed)
-
一键安装,报警脚本,常规的业务应用,shell开发更简单快速。
-
php,python的优势在于开发运维工具,web界面的管理工具
常用操作系统的默认shell
- linux 是 Bourne Again shell(bash)
- solaris 是freeBSD 缺省的是 Bourne shell(sh)
- AIX 下是 Korn Shell(ksh)
- HP-UX 缺省的是POSIX shell(sh)
3. shell脚本的建立和执行
3.1 shell脚本的建立
shell脚本(bash shell程序)通常是在编辑器(如:vi/vim)中编写,由 Unix/Linux 命令、bash shell命令、程序结构控制语言和注释等内容组成。
3.1.1 脚本开头(第一行)
一个规范的shell脚本的第一行会指出由哪个程序(解释器)来执行脚本中的内容,在Linux bash办成中一般为:
#!/bin/bash 或 #!/bin/sh
查看服务脚本的开头:
[root@oldboy scripts]# head -1 /etc/init.d/mysqld #!/bin/sh
#!
"#!" 又称为幻数,在执行bash脚本的时候,内核会根据它来确定该用哪个程序来解释脚本中的内容。
这一行必须在脚本顶端的第一行,如果不是第一行则为注释。
3.1.2 bash和sh的区别
[root@chensiqi1 scripts]# ll /bin/sh lrwxrwxrwx. 1 root root 4 Dec 23 20:25 /bin/sh -> bash
sh是bash的软链接,推荐标准写法#!/bin/bash
不同语言脚本的开头写法
下面是linux中常用脚本语言开头的编码写法,不同语言脚本的开头一般都要加上如下相应语言的开头标识内容。
#!/bin/bash #!/bin/sh #!/usr/bin/awk #!/bin/sed #!/usr/bin/tcl #!/usr/bin/expect #!/usr/bin/perl #!/usr/bin/env python
ash 是GNU/Linux 默认的shell,和Bourne shell(sh)兼容,Bash采用了Korn shell(ksh)和C Shell(csh)的特色。符合 IEEE POSIX P1003.2/ISO 9945.2 Shell and Tools 标准。
CentOS和RedHat Linux下默认的Shell 均为shell。
因此,在写shell脚本的时候,我们的脚本的开头也可以不加#!/bin/bash。
但如果当前的shell非你默认的shell时,比如tcsh,那么就必须要写#!。
否则脚本文件就只能执行一些命令的集合,不能够使用shell内建的指令。
所以,不管什么脚本最好都加上开头语言标识。
如果脚本的开头不指定解释器,那么,就要用对应的解释器来执行脚本。
例如:
- bash test.sh
- python test.py
下面是linux中启动文件的脚本的第一行的示例:
[root@oldboy scripts]# head -1 /etc/init.d/* ==> /etc/init.d/abrt-ccpp <== #!/bin/bash ==> /etc/init.d/abrt-oops <== #!/bin/bash ==> /etc/init.d/atd <== #!/bin/sh ==> /etc/init.d/auditd <== #!/bin/bash ==> /etc/init.d/blk-availability <== #!/bin/bash ==> /etc/init.d/chktestd <== # chkconfig:23456 66 33 ... ==> /etc/init.d/udev-post <== #!/bin/bash
bash版本:
[root@oldboy /]# bash --version GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu) Copyright (C) 2009 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software; you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
bash漏洞【破壳漏洞】
如果是比较老的系统,需要注意shell的版本太低,有漏洞,需要升级shell
[root@chensiqi1 scripts]# yum -y update bash #验证方法 [root@chensiqi1 scripts]# env x='(){ :;};echo be careful' bash -c "echo this is a test" this is a test 如果返回2行 be careful this is a test 这样的结果的话,请尽快升级。
3.1.3 脚本注释 "#"
在shell脚本中,跟在"#"后面的内容表示注释,用来对脚本进行注释说明。
3.2 shell脚本的执行
当shell脚本以非交互的方式运行时,它会先查找环境变量ENV,该变量指定了一个环境文件(通常是.bashrc),然后从该环境变量文件开始执行,当读取了ENV文件后,SHELL才开始执行shell脚本中的内容。
Shell脚本的执行通常可以采用以下三种方式:
- bash script-name 或 sh script-name(推荐使用)
- 这种方法是当脚本本身没有可执行权限"x"时常使用的方法。
- 或者文件开头没有指定解释器
- path/script-name 或 ./script-name (当前路径下执行脚本)
- 这种方法首先需要给脚本文件可执行权限。
- chmod +x script-name
- 这种方法首先需要给脚本文件可执行权限。
- source script-name 或 . script-name
- source 或.在执行这个脚本的同时,可以将脚本中的函数和变量加载到当前Shell。
- 不会产生子shell。
- 又有点像nginx的include功能。
第三种方法通常是使用source 或"."点号读入或加载指定的shell脚本文件,然后,依次执行指定shell脚本文件 san.sh 中的所有语句。
这些语句将作为当前 父shell脚本 father.sh 进程的一部分运行。
因此,使用source 或者"."点号可以将 san.sh 自身脚本中的变量的值或函数等的返回值传递到当前的父shell脚本father.sh中使用。
这是第三种方法和前两种办法的最大区别。
source或者"."点号命令的功能:
- 在当前shell中执行source或者"."点号,加载并执行相关脚本文件中的命令及语句
- 而不是产生一个子shell来执行命令中的文件。
示例:
# 文件准备 [oldboy@oldboy scripts]$ cat test.sh echo 'I am oldboy' # 使用第一种方法,指定解释器去执行shell脚本 [oldboy@oldboy scripts]$ sh test.sh I am oldboy [oldboy@oldboy scripts]$ bash test.sh I am oldboy # 第二种方法,需要给文件添加执行权限后,才能成功执行 [oldboy@oldboy scripts]$ ./test.sh -bash: ./test.sh: Permission denied [oldboy@oldboy scripts]$ sudo chmod +x test.sh [oldboy@oldboy scripts]$ ./test.sh I am oldboy # 第三种方法:source和. [oldboy@oldboy scripts]$ source test.sh I am oldboy [oldboy@oldboy scripts]$ . test.sh I am oldboy
下面用示例说明source或 . 执行shell脚本文件和前两种执行方式的区别:
# 文件准备 [root@oldboy scripts]# echo 'userdir=`pwd`' >testsource.sh [root@oldboy scripts]# cat testsource.sh userdir=`pwd` # 第一种方法执行,执行echo $userdir 输出结果为空 [root@oldboy scripts]# sh testsource.sh [root@oldboy scripts]# echo $userdir # 第三种方法执行,执行echo $userdir 输出结果为 /server/scripts [root@oldboy scripts]# source testsource.sh [root@oldboy scripts]# echo $userdir /server/scripts
3.3 Shell脚本开发的规范和习惯
- 开头指定脚本解释器
- 开头加版本版权等信息,可配置~/.vimrc文件自动添加
- 脚本不要用中文注释,尽量用英文注释
- 脚本以.sh为扩展名
- 放在统一的目录
- 代码书写优秀习惯
- 成对的内容一次性写出来,防止遗漏,如[],'',""等
- []两端要有空格,先输入[]退格,输入2个空格,再退格写。
- 流程控制语句一次书写完,再添加内容。
- 通过缩进让代码易读
- 脚本中的引号都是英文状态下的引号,其他字符也是英文状态。
1. 开头指定脚本解释器
[root@oldboy scripts]# head -1 /etc/init.d/mysqld #!/bin/sh [root@oldboy scripts]# head -1 /etc/init.d/svnserve #!/bin/bash
2. 开头加版本版权等信息,可配置~/.vimrc文件自动添加
可配置vim编辑文件时自动加上以上信息,方法是修改~/.vimrc配置文件
#!/bin/bash # Date 2019-10-16 # Author: Created by Zoe # Mail: 740466595@qq.com # Func: This script is for MySQL backup # V2.1
3. 脚本中不用中文注释,尽量用英文注释
4. 脚本以.sh扩展名
5. 放在统一的目录
6. 代码书写优秀习惯
- 成对的内容一次性写出来,防止遗漏,如[],'',""等
- (), [], '',"", ``
- []两端要有空格,先输入[]退格,输入2个空格,再退格写。
- [ contents ]
- 流程控制语句一次书写完,再添加内容。
-
if 条件语句 then 内容 fi
for do 内容 done
- 通过缩进让代码易读
- 脚本中的引号都是英文状态下的引号,其他字符也是英文状态。