[toc]
shell编程
一、shell 脚本介绍
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。 shell是一种脚本语言; 可以使用逻辑判断、循环等语法; 可自定义函数; shell是系统命令的集合; shell脚本可以实现自动化运维,能大大增加我们的运维效率;
说明了shell 脚本在工作的重要性,shell脚本就是一些命令的集合,是自动化运维的重要部分
二、 Shell脚本结构和执行
下面跟着步骤来一步步学习如何写shell脚本
2.1 创建shell脚本实验环境
[root@localhost ~]# mkdir shell
[root@localhost ~]# cd shell/
[root@localhost shell]# ls
[root@localhost shell]# vi 01.sh
shell脚本通常都是.sh为后缀名,但不是说不加.sh的脚本就不能执行,只是大家的一个习惯而已,所以如果发新了以.sh为后缀的文件,那么它可能就是一个shell脚本.
本例中,脚本文件01.sh,进入编辑状态后,第一行要以#!/bin/bash开头,表示该文件使用的是bash语法.而/bin/bash是Bash的解释器命令路径.
2.2 执行该脚本 #sh.01.sh
[root@localhost shell]# sh 01.sh
123
22:43:14 up 25 min, 1 user, load average: 0.00, 0.01, 0.01
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 192.168.72.1 22:26 2.00s 0.08s 0.00s w
01.sh
把#!/bin/bash这一行删除,运行命令后也能执行但这样不符合规范.
2.3 另一种方法执行该脚本 #./01.sh
使用该方法运行shell脚本的前提是脚本本身有执行权限,需要给脚本加x权限
[root@localhost shell]# chmod +x 01.sh
[root@localhost shell]# ./01.sh
123
23:18:34 up 1:01, 1 user, load average: 0.00, 0.01, 0.03
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 192.168.72.1 22:26 2.00s 0.11s 0.01s w
01.sh
2.4 查看解释路径/bin/bash和/bin/sh
[root@localhost shell]# bash 01.sh
123
23:37:15 up 1:19, 1 user, load average: 0.00, 0.01, 0.03
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 192.168.72.1 22:26 3.00s 0.20s 0.01s w
01.sh
这里是否可以理解有三种方法都可以执行shell脚本,但是本机验证成功,不代表其他机器虚拟机是否成功,后续测试
2.5 举例脚本的编辑方式,注释等
[root@localhost shell]# vi /etc/init.d/network
#! /bin/bash
#
# network Bring up/down networking
#
# chkconfig: 2345 10 90
# description: Activates/Deactivates all network interfaces configured to \
# start at boot time.
#
2.6 sh -x 查看shell脚本执行过程,方便后续调试
[root@localhost shell]# sh -x 01.sh
+ echo 123
123
+ w
23:40:21 up 1:22, 1 user, load average: 0.00, 0.01, 0.03
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 192.168.72.1 22:26 5.00s 0.22s 0.01s w
+ ls
01.sh
[root@localhost shell]# bash -x 01.sh
+ echo 123
123
+ w
23:40:29 up 1:23, 1 user, load average: 0.00, 0.01, 0.03
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 192.168.72.1 22:26 5.00s 0.21s 0.00s w
+ ls
01.sh
2.7 sh -n 检测shell脚本是否有语法错误
三、date命令用法
date命令用于显示或设置系统时间与日期。 命令选项:
[root@xavi ~]# date //当前日期
2018年 04月 17日 星期二 22:06:22 CST
[root@xavi ~]# cal //当前日历
四月 2018
日 一 二 三 四 五 六
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
[root@xavi ~]# date +%Y //年
2018
[root@xavi ~]# date +%y //年缩写
18
[root@xavi ~]# date +%m //月
04
[root@xavi ~]# date +%Y%m%d //年月日
20180417
[root@xavi ~]# date +%F
2018-04-17
[root@xavi ~]# date +%H //小时
22
[root@xavi ~]# date +%M //分钟
23
[root@xavi ~]# date +%S //秒
10
[root@xavi ~]# date +%s //时间戳,时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒
1523975099
[root@xavi ~]# date +%T //时间的完整显示
22:27:04
[root@xavi ~]# date "+%Y-%m-%d %H:%M:%S %w" // 年月日,时分秒,星期
2018-04-17 22:20:50 2
[root@xavi ~]# date -d "-1 day"
2018年 04月 16日 星期一 22:31:20 CST
[root@xavi ~]# date -d "-1 day" +%F
2018-04-16
[root@xavi ~]# date -d "-1 month" +%F
2018-03-17
[root@xavi ~]# date -d "-1 year" +%F
2017-04-17
[root@xavi ~]# date -d "-1 hour" +%T
21:44:30
[root@xavi ~]# date +%s -d "2018-04-17 22:46" // 日期转化为时间戳
1523976360
四、shell脚本中的变量
当脚本中使用某个字符串较频繁并且字符串长度很长时就应该使用变量代替
使用条件语句时,常使用变量 if [ $a -gt 1 ]; then ... ; fi
引用某个命令的结果时,用变量替代 n=`wc -l 1.txt`
写和用户交互的脚本时,变量也是必不可少的 read -p "Input a number: " n; echo $n 如果没写这个n,可以直接使用$REPLY
内置变量 $0, $1, $2… $0表示脚本本身,$1 第一个参数,$2 第二个 .... $#表示参数个数
数学运算a=1;b=2; c=$(($a+$b))或者$[$a+$b]
4.1 shell变量
4.1 系统变量
在命令行提示符直接执行env、set查看系统或环境变量。env显示用户环境变量,set显示Shell预先定义好的变量以及用户变量。可以通过export导出成用户变量。 一些写Shell脚本时常用的系统变量:
4.2 普通变量与临时环境变量
普通变量定义:VAR=value 临时环境变量定义:export VAR=value 变量引用:$VAR
下面看下他们之间区别:
Shell进程的环境变量作用域是Shell进程,当export导入到系统变量时,则作用域是Shell进程及其Shell子进程。
[root@xaviyunserver sbin]# ps axjf |grep pts
1229 1773 1773 1773 ? -1 Ss 0 0:00 \_ sshd: root@pts/0
1773 1775 1775 1775 pts/0 2631 Ss 0 0:00 \_ -bash
1775 1825 1825 1775 pts/0 2631 T 0 0:00 \_ sh test2.sh
1775 2631 2631 1775 pts/0 2631 R+ 0 0:00 \_ ps axjf
1775 2632 2631 1775 pts/0 2631 S+ 0 0:00 \_ grep --color=auto pts
[root@xaviyunserver sbin]# echo $$
1775
[root@xaviyunserver sbin]# VAR=123
[root@xaviyunserver sbin]# echo $VAR
123
[root@xaviyunserver sbin]# bash
[root@xaviyunserver sbin]# echo $$
2633
[root@xaviyunserver sbin]# ps axjf |grep pts
1229 1773 1773 1773 ? -1 Ss 0 0:00 \_ sshd: root@pts/0
1773 1775 1775 1775 pts/0 2649 Ss 0 0:00 \_ -bash
1775 1825 1825 1775 pts/0 2649 T 0 0:00 \_ sh test2.sh
1775 2633 2633 1775 pts/0 2649 S 0 0:00 \_ bash
2633 2649 2649 1775 pts/0 2649 R+ 0 0:00 \_ ps axjf
2633 2650 2649 1775 pts/0 2649 S+ 0 0:00 \_ grep --color=auto pts
[root@xaviyunserver sbin]# echo $VAR
[root@xaviyunserver sbin]# exit
exit
[root@xaviyunserver sbin]# echo $VAR
123
[root@xaviyunserver sbin]# ps -ef |grep ssh
root 1229 1 0 07:58 ? 00:00:00 /usr/sbin/sshd -D
ps axjf输出的第一列是PPID(父进程ID),第二列是PID(子进程ID) 当SSH连接Shell时,当前终端PPID(-bash)是sshd守护程序的PID(root@pts/0),因此在当前终端下的所有进程的PPID都是-bash的PID,比如执行命令、运行脚本。 所以当在-bash下设置的变量,只在-bash进程下有效,而-bash下的子进程bash是无效的,当export后才有效。
- [ ] 进一步说明:再重新连接SSH,去除上面定义的变量测试下
[root@xaviyunserver sbin]# sh test.sh
1229 1773 1773 1773 ? -1 Ss 0 0:00 \_ sshd: root@pts/0
1773 1775 1775 1775 pts/0 2693 Ss 0 0:00 \_ -bash
1775 1825 1825 1775 pts/0 2693 T 0 0:00 \_ sh test2.sh
1775 2693 2693 1775 pts/0 2693 S+ 0 0:00 \_ sh test.sh
2693 2694 2693 1775 pts/0 2693 R+ 0 0:00 \_ ps -axjf
2693 2695 2693 1775 pts/0 2693 R+ 0 0:00 \_ sh test.sh
2693
[root@xaviyunserver sbin]# cat test.sh
#!/bin/bash
ps -axjf |grep pts
echo $$
echo $VAR
[root@xaviyunserver sbin]# sh test.sh
1229 1773 1773 1773 ? -1 Ss 0 0:00 \_ sshd: root@pts/0
1773 1775 1775 1775 pts/0 2693 Ss 0 0:00 \_ -bash
1775 1825 1825 1775 pts/0 2693 T 0 0:00 \_ sh test2.sh
1775 2693 2693 1775 pts/0 2693 S+ 0 0:00 \_ sh test.sh
2693 2694 2693 1775 pts/0 2693 R+ 0 0:00 \_ ps -axjf
2693 2695 2693 1775 pts/0 2693 R+ 0 0:00 \_ sh test.sh
2693
[root@xaviyunserver sbin]# export VAR
[root@xaviyunserver sbin]# sh test.sh
1229 1773 1773 1773 ? -1 Ss 0 0:00 \_ sshd: root@pts/0
1773 1775 1775 1775 pts/0 2696 Ss 0 0:00 \_ -bash
1775 1825 1825 1775 pts/0 2696 T 0 0:00 \_ sh test2.sh
1775 2696 2696 1775 pts/0 2696 S+ 0 0:00 \_ sh test.sh
2696 2697 2696 1775 pts/0 2696 R+ 0 0:00 \_ ps -axjf
2696 2698 2696 1775 pts/0 2696 R+ 0 0:00 \_ sh test.sh
2696
123
- [ ] 所以在当前shell定义的变量一定要export,否则在写脚本时,会引用不到。 还需要注意的是退出终端后,所有用户定义的变量都会清除。
在/etc/profile下定义的变量就是这个原理,后面有章节会讲解Linux常用变量文件。
4.3 位置变量,位置变量指的是函数或脚本后跟的第n个参数。
$1-$n,需要注意的是从第10个开始要用花括号调用,例如${10}
shift可对位置变量控制,例如:
#!/bin/bash
echo "1: $1"
shift
echo "2: $2"
shift
echo "3: $3"
shift
[root@xaviyunserver sbin]# sh postion.sh
1:
2:
3:
[root@xaviyunserver sbin]# sh postion.sh a b c
1: a
2: c
3:
- [ ] 每执行一次shift命令,位置变量个数就会减一,而变量值则提前一位。shift n,可设置向前移动n位。
4.4 特殊变量
4.5 变量引用
4.5.1 自定义变量与引用
Shell中所有变量引用使用$符,后跟变量名。 有时个别特殊字符会影响正常引用,那么需要使用${VAR},例如:
# VAR=123
# echo $VAR
123
# echo $VAR_ # Shell允许VAR_为变量名,所以此引用认为这是一个有效的变量名,故此返回空
# echo ${VAR}
123
4.5.2 还有时候变量名与其他字符串紧碍着,也会误认为是整个变量:
# echo $VAR456
# echo ${VAR}456
123456
4.5.3 将命令结果作为变量值
# VAR=`echo 123`
# echo $VAR
123
# VAR=$(echo 123)
# echo $VAR
123
这里的反撇号等效于$(),都是用于执行Shell命令。
4.6 双引号和单引号
在变量赋值时,如果值有空格,Shell会把空格后面的字符串解释为命令:
# VAR=1 2 3
-bash: 2: command not found
# VAR="1 2 3"
# echo $VAR
1 2 3
# VAR='1 2 3'
# echo $VAR
1 2 3
看不出什么区别,再举个说明:
# N=3
# VAR="1 2 $N"
# echo $VAR
1 2 3
# VAR='1 2 $N'
# echo $VAR
1 2 $N
单引号是告诉Shell忽略特殊字符,而双引号则解释特殊符号原有的意义,比如$、!。
4.7 注释
Shell注释也很简单,只要在每行前面加个#号,即表示Shell忽略解释。
五、Shell字符串处理之${}及颜色
用${}引用变量,${}还有一个重要的功能,就是文本处理,单行文本基本上可以满足你所有需求。
5.1 获取字符串长度
# VAR='hello world!'
# echo $VAR
hello world!
# echo ${#VAR}
12
5.2 字符串切片
格式:
${parameter:offset}
${parameter:offset:length}
截取从offset个字符开始,向后length个字符。
截取hello字符串:
# VAR='hello world!'
# echo ${VAR:0:5}
hello
截取wo字符:
# echo ${VAR:6:2}
wo
截取world!字符串:
# echo ${VAR:5}
world!
截取最后一个字符:
# echo ${VAR:(-1)}
!
截取最后二个字符:
# echo ${VAR:(-2)}
d!
截取从倒数第3个字符后的2个字符:
# echo ${VAR:(-3):2}
ld
5.3 替换字符串
格式:${parameter/pattern/string}
# VAR='hello world world!'
将第一个world字符串替换为WORLD:
# echo ${VAR/world/WORLD}
hello WORLD world!
将全部world字符串替换为WORLD:
# echo ${VAR//world/WORLD}
hello WORLD WORLD!
5.4 字符串截取
格式:
${parameter#word} # 删除匹配前缀
${parameter##word}
${parameter%word} # 删除匹配后缀
${parameter%%word}
- [ ] # 去掉左边,最短匹配模式,##最长匹配模式。
- [ ] % 去掉右边,最短匹配模式,%%最长匹配模式。
# URL="http://www.baidu.com/baike/user.html"
以//为分隔符截取右边字符串:
# echo ${URL#*//}
www.baidu.com/baike/user.html
以/为分隔符截取右边字符串:
# echo ${URL##*/}
user.html
以//为分隔符截取左边字符串:
# echo ${URL%%//*}
http:
以/为分隔符截取左边字符串:
# echo ${URL%/*}
http://www.baidu.com/baike
以.为分隔符截取左边:
# echo ${URL%.*}
http://www.baidu.com/baike/user
以.为分隔符截取右边:
# echo ${URL##*.}
html
- [ ] # 去掉左边,从左边匹配第一个,##从右边匹配第一个。
- [ ] % 去掉右边,从右边匹配第一个,%%从左边匹配第一个。 有*号情况下才这样。
5.5 变量状态赋值
${VAR:-string} 如果VAR变量为空则返回string
${VAR:+string} 如果VAR变量不为空则返回string
${VAR:=string} 如果VAR变量为空则重新赋值VAR变量值为string
${VAR:?string} 如果VAR变量为空则将string输出到stderr
如果变量为空就返回hello world!:
# VAR=
# echo ${VAR:-'hello world!'}
hello world! 如果变量不为空就返回hello world!:
# VAR="hello"
# echo ${VAR:+'hello world!'}
hello world! 如果变量为空就重新赋值:
# VAR=
# echo ${VAR:=hello}
hello
# echo $VAR
hello
如果变量为空就将信息输出stderr:
# VAR=
# echo ${VAR:?value is null}
-bash: VAR: value is null
${}主要用途大概就这么多了,另外还可以获取数组元素。
5.6 字符串颜色
再介绍下字符串输出颜色,有时候关键地方需要醒目,颜色是最好的方式:
格式:
-
[ ] \033[1;31;40m # 1是显示方式,可选。31是字体颜色。40m是字体背景颜色。
-
[ ] \033[0m # 恢复终端默认颜色,即取消颜色设置。
#!/bin/bash
# 字体颜色,显示方式默认为空
for i in {31..37}; do
echo -e "\033[$i;40mHello world!\033[0m"
done
# 背景颜色,显示方式默认为空
for i in {41..47}; do
echo -e "\033[47;${i}mHello world!\033[0m"
done
# 显示方式
for i in {1..8}; do
echo -e "\033[$i;31;40mHello world!\033[0m"
done