shell脚本基础
文章目录
0x00 什么是shell
shell脚本就是将完成一个任务的所有命令按照执行的先后顺序,自上而下的写入到一个文本文件中,然后给予执行权限
0x01 shell脚本格式
开头必须指定脚本运行环境,以 #! 这个特殊符号组合来组成。如,#!/bin/bash 指定脚本运行解析由/bin/bash来完成。shell脚本中,最好加入脚本说明字段。# 代表注释。 #!是特例。脚本中最好不要带中文
#!/bin/bash
#Author: binbin_erices
#Created Time: 2020/2/22
#Release: 1.0
#Script Description: nginx install script
1.1 脚本组成
#脚本环境 #!/usr/bin/env bash|python|perl
#注释说明
#执行代码
1.2 运行脚本
(1)给执行权限, ./1.sh
(2) 解释器直接运行 不需要给权限. sh 1.sh
查看支持的shell 和 当前使用的shell
zbb@ubuntu:~$ echo $SHELL
/bin/bash
zbb@ubuntu:~$ echo $0
bash
zbb@ubuntu:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
@ubuntu:~$ echo -n "DATE IS: "; date +%F
DATE IS: 2020-02-22
zbb@ubuntu:~$ echo "DATE IS: "; date +%F
DATE IS:
2020-02-22
zbb@ubuntu:~$ echo "DATE IS: `date +%F`"
DATE IS: 2020-02-22
zbb@ubuntu:~$ echo '$USER'
$USER
zbb@ubuntu:~$ echo "$USER"
zbb
1.3 shell中的特殊符号
~ : 家目录
! : 执行历史命令 !!执行上一次命令
$ : 变量中取内容符
+ — * / % : 加减乘除取余
& : 后台运行
* : 星号是shell中的通配符, 匹配所有
? : 问号是shell中的通配符,匹配除回车以外的一个字符
; : 分号可以在shell中一行执行多个命令,命令之间用分号分隔
| : 管道符,上一个命令的输出作为下一个命令的输入 cat filename | grep "root"
\ : 转义字符
``: 反引号 命令中执行命令 echo "DATE IS: `date +%F`"
'': 单引号,版本中字符串要用单引号引起来, 但是不同于双引号的是,单引号不能解析变量
"": 双引号,版本中出现的字符串可以用双引号引起来
1.4 安装Nginx的脚本
#chmod 700 nginx_install.sh
#!/bin/bash
#Author: binbin_erices
#Created Time: 2020/2/22
#Release: 1.0
#Script Description: nginx install script
yum -y install wget gcc pcre-devel zlib-devel
wget http://nginx.org/download/nginx-1.16.0.tar.gz
tar xf nginx-1.16.0.tar.gz
cd nginx-1.16.0
./configure --prefix=/usr/local/nginx
make -j 4
make install
1.5 shell中的管道运用
上一个命令的输出作为下一个命令的输入 cat filename | grep “name”
zbb@ubuntu:~$ cat /etc/passwd | grep "zbb"
zbb:x:1000:1000:MyUbuntu,,,:/home/zbb:/bin/bash
1.6 shell重定向
> 重定向输入 覆盖原始数据
>> 重定向追加输入,在原始数据的末尾添加
< 重定向输出 wc -l < /etc/passwd
<< 重定向追加输出
zbb@ubuntu:~$ echo "123" >> Test.txt
zbb@ubuntu:~$ echo "123" >> Test.txt
zbb@ubuntu:~$ echo "123" >> Test.txt
zbb@ubuntu:~$ cat Test.txt
123
123
123
zbb@ubuntu:~$ wc < Test.txt
3 3 12 #3行 3个单词 12字节
zbb@ubuntu:~$ echo "12345" >> Test.txt
zbb@ubuntu:~$ wc < Test.txt
4 4 18
#!/bin/bash
#Author: binbin_erices
#Created Time: 2020/2/22
#Release: 1.0
#Script Description: harddisk partition script
fdisk /dev/sdb << EOF
n
p
3
+512M
W
EOF
EOF也可以用END替换
1.7 shell数学运算
zbb@ubuntu:~$ expr 1+2
1+2
zbb@ubuntu:~$ expr 1 + 2
3
zbb@ubuntu:~$ expr 1 - 2
-1
zbb@ubuntu:~$ expr 1 * 2
expr: 语法错误
zbb@ubuntu:~$ expr 3 \* 2
6
zbb@ubuntu:~$ expr 3 / 2
1
zbb@ubuntu:~$ expr 3 / 1
3
zbb@ubuntu:~$ expr 3 % 4
3
zbb@ubuntu:~$ expr 7 + 1 &>/dev/null ; echo $?
0
zbb@ubuntu:~$ expr 7 +1 &>/dev/null ; echo $?
2
zbb@ubuntu:~$ echo "scale=2; 1436*100/1982"|bc
72.45
zbb@ubuntu:~$ echo "`echo "scale=2; 1436*100/1982"|bc`%"
72.45%
echo &? 命令执行成功返回0, 否者返回错误码
$[] $(())表达式
$[] $(())表达式
[root@station mnt]# echo $[a+2]
6
[root@station mnt]# echo $[a-2]
2
[root@station mnt]# echo $[a*2]
8
[root@station mnt]# echo $[a/2]
2
[root@station mnt]# echo $[a%2]
0
[root@station mnt]# echo $((a+2))
6
[root@station mnt]# echo $((a-2))
2
1.8 整型变量自增
1. i=`expr $i + 1`;
2. let i+=1;
3. ((i++));
4. i=$[$i+1];
5. i=$(( $i + 1 ))
可以实践一下,简单的实例如下:
#!/bin/bash
i=0;
while [ $i -lt 4 ];
do
echo $i;
i=`expr $i + 1`;
# let i+=1;
# ((i++));
# i=$[$i+1];
# i=$(( $i + 1 ))
done
# 对于固定次数的循环,可以通过seq命令来实现,就不需要变量的自增:
for j in $(seq 1 5)
do
echo $j
done
0x02 shell格式化输出
2.1 echo命令
将内容输出到默认显示设备
语法: echo [-ne] [字符串]
命令选项:
-n 不要再最后加自动换行
-e 若字符串中出现以下字符,则特别加以处理,而不会将它当做一般文字输出
转义字符
\a发出警告声
\b删除前一个字符
\c最后不加上换行符号
\f换行但光标仍旧是停留在原来的位置
\n换行且光标移到行首
\r光标移动到行首 不换行
\t插入TAB
\v与\f相同
#\b必须在一行才可以删除前一个字符
zbb@ubuntu:~$ echo -e "ans\b"
ans
zbb@ubuntu:~$ echo -e -n "ans\b"
anzbb@ubuntu:~$
#!/bin/bash
for time in `seq 9 -1 0`;do
echo -n -e "\b$time"
sleep 1
done
echo
2.2 颜色代码
脚本中echo显示内容带颜色显示,echo显示带颜色需要带参数-e
格式:
echo -e “\033[文字背景颜色; 文字颜色m字符串\033[0m”
WARNING:文字背景颜色; 文字颜色之间不带空格
echo -e “\033[41; 36m hello world \033[0m”
其中41的位置代表底色,36位置代表字的颜色
- 字背景颜色与文字颜色之间是英文“ ;”
- 文字背景色后面有个m
- 字符串前后可以没有空格,如果有的话,输出也是同样有空格
0x03 shell基本输入
3.1 read命令
默认接受键盘的输入,回车表示输入结束
read命令选项
-p 打印信息
-t 限定时间
-s 不回显
-n 输入字符个数
#!/bin/bash
clear
echo -n -e "Login: "
read acc
echo -n -e "PassWord: "
read -s -t10 -n6 pass
echo
echo -e "accont: $acc\tPassword: $pass"
#!/bin/bash
clear
read -p "Login: " acc
echo -n -e "PassWord: "
read -s -t10 -n6 pass
echo -e "accont: $acc\tPassword: $pass"
0x04 shell变量
4.1 变量分类
本地变量:用户私有变量,只有本用户可以使用,保存在家目录下的 .bash_profile(.profile) .bashrc文件中
全局变量:所有用户都可以使用,保存在/etc/profile, /etc/bashrc文件中
用户自定义变量:用户自定义,比如脚本中的变量
全局变量是在用户登录之前加载进内存,本地变量登录成功后加载进内存
4.2 定义变量
(1)变量格式:变量名=值
在shell编程中的变量名和等号之间不能有空格
变量命名规则:
命名只能使用英文字符 数字和下划线 首字符不能以数字开头
中间不能有空格 可以使用下划线
不能使用标点符号
不能使用bash里的关键字(可以用help命令查看保留关键字)
VAR1=18
AGE=24
SCORE_1=100.5
NAME='shell'
字符串要用单引号或者双引号引起来
(2)读取变量内容
读取变量内容符号:$
读取方法: $变量名
(3)取消变量
unset NAME
echo $NAME
(4)定义全局变量export
export NAME=“erices”
上述设置的变量其实都是一次性变量,系统重启就会丢弃
如果希望本地变量或者全局变量可以永久使用,可以将需要设置的变量写入变量文件中即可
(5)定义永久变量
本地变量:用户私有变量,只有本用户可以使用,保存在家目录下的 .bash_profile .bashrc文件中
全局变量:所有用户都可以使用,保存在/etc/profile, /etc/bashrc文件中
export AGE=15 写入到~/.bashrc中就是永久变量
0x05 shell数组
5.1 基本数组
数组语法:
数组名称=(元素1 元素2 … 元素n)
数组读出
${数组名称[索引]}
索引从0开始
数组赋值
#一次赋一个数值
array[0]=“000”
array[1]=“111”
array[2]=“2222”
#一次赋多个数值
array2=(‘111’ ‘222’ ‘333’)
array3=(cat /etc/passwd) 希望是将该文件中的每一行作为一个元素赋值给数组array3
array4=(`ls /var/ftp/shell/for`)
array5=(tom jack alice “bash shell”)
查看数组
#查看系统中存在的数组
zbb@ubuntu:~/shell$ declare -a
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -ar BASH_REMATCH='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="3" [2]="48" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="0")'
访问数组中的元素
echo ${ARRAY[0]} 访问数组中的第一个元素
echo ${ARRAY[@]} 访问数组中的所有元素 等同于 echo ${ARRAY[*]}
echo ${#ARRAY[@]} 统计数组中元素的个数
echo ${!ARRAY[@]} 获取数组元素的索引
echo ${ARRAY[@]:1} 从数组下标为1开始遍历
echo ${ATTAY[@]:1:2} 从数组下标1开始,向后遍历两个元素
#!/bin/bash
ARRAY=('111' '2222' '3333' '444')
ARRAY[2]='hello'
echo ${ARRAY[0]}
echo ${ARRAY[1]}
echo ${ARRAY[2]}
echo ${ARRAY[3]}
ARRAY5=(tom jack alice "bash shell")
echo ${ARRAY5[0]}
echo ${ARRAY5[1]}
echo ${ARRAY5[2]}
echo ${ARRAY5[3]}
echo ${ARRAY[@]}
echo ${#ARRAY[@]}
echo ${!ARRAY[@]}
echo ${ARRAY[@]:1}
echo ${ARRAY[@]:1:2}
#执行结果
zbb@ubuntu:~/shell$ bash arr.sh
111
2222
hello
444
tom
jack
alice
bash shell
111 2222 hello 444
4
0 1 2 3
2222 hello 444
2222 hello
遍历数组
默认数组通过数组的元素个数进行遍历
关联数组可以通过数组元素的索引进行遍历
5.2 关联数组
关联数组可以允许用户自定义数组的索引,这样试用起来更加方便,高效
定义关联数组
申明关联数组变量
declare -A ass_arr
declare -A ass_arr2
关联数组赋值
#一次赋一个数值
数组名称[索引]=变量数值
ass_arr[name]="zhangsan"
arr_arr[age]=20
#一次赋多个数值
ass_arr2=([name]="zhangsan" [age]=20)
查看数组
#declare -A
访问数组中元素
echo ${ass_arr[index2]} 访问数组中的第二个元素
echo ${ass_arr[@]} 访问数组中所有元素 等同于echo ${ass_arr[*]}
echo ${#ass_arr[@]} 获取数组中元素个数
echo ${!ass_arr[@]} 获得数组元素的索引
#!/bin/bash
#声明一个关联数组
declare -A ass_arr
declare -A ass_arr2
ass_arr2=([name]='zhangsan' [age]='20')
ass_arr[name]='zhangsan'
ass_arr[age]='20'
echo ${ass_arr[name]}
echo ${ass_arr2[name]}
echo ${ass_arr[@]}
echo ${#ass_arr[@]}
echo ${!ass_arr[@]}
#执行结果
zbb@ubuntu:~/shell$ bash ass_arr.sh
zhangsan
zhangsan
zhangsan 20
2
name age
5.3 数组遍历
#!/bin/bash
array=( A B C D 1 2 3 4)
echo "case1.标准的for循环"
#${#array[@]}获取数组长度用于循环
for(( i=0;i<${#array[@]};i++))
do
echo ${array[i]};
done;
echo "case2.for … in"
echo "case2.1 遍历(不带数组下标)"
for element in ${array[@]}
#也可以写成for element in ${array[*]}
do
echo $element
done
echo "case2.2 遍历(带数组下标)"
for i in "${!array[@]}";
do
printf "%s\t%s\n" "$i" "${array[$i]}"
done
echo "case3.While循环法"
i=0
#当变量(下标)小于数组长度时进入循环体
while [ $i -lt ${#array[@]} ]
do
echo ${array[$i]}
#按下标打印数组元素
let i++
done
0x06 shell流程控制
6.1 if判断语句
shell中的比较运算
(1)数学比较运算
-eq 等于
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
-ne 不等于
#man test
zbb@ubuntu:~/shell$ test 1 -eq 1; echo $?
0
zbb@ubuntu:~/shell$ test 1 -gt 1; echo $?
1
zbb@ubuntu:~/shell$ test 1 -lt 1; echo $?
1
zbb@ubuntu:~/shell$ test 1 -ge 1; echo $?
0
zbb@ubuntu:~/shell$ test 1 -le 1; echo $?
0
zbb@ubuntu:~/shell$ test 1 -ne 1; echo $?
1
#浮点数的比较,shell对浮点数支持很差,需要进行转换
zbb@ubuntu:~/shell$ test `echo "1.5*10"|bc|cut -d '.' -f1` -gt 20; echo $?
1
#bash -x test.sh -x选项查询debug过程
(2) 字符串比较
注意字符串一定不能忘记使用引号引起
== 等于
!= 不等于
-n 检查字符串的长度是否大于0
-z 检查字符串的长度是否为0
(3)文件比较与检查
-d 检查文件是否存在且为目录
-e 检查文件是否存在
-f 检查文件是否存在 且为文件
-r 检查文件是否存在 且可读
-s 检查文件是否存在 且不为空
-w 检查文件是否存在 且可写
-x 检查文件是否存在 且可执行
-O 检查文件是否存在 且被当前用户拥有
-G 检查文件是否存在 且默认组为当前用户组
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧
(4) 逻辑运算
与 &&
或 ||
非 !
(5) 赋值运算
=
a=1,等号前后没有空格,否则会报错
if 语法
单if语句格式:
if [ condition ]
then
command
fi
#!/bin/bash
if [ ! -d /tmp/abc ]
then
mkdir -v /tmp/abc
echo "Create /tmp/abc"
fi
if-then-else语句
适用范围:两步判,条件为真干什么,条件为假干什么
if [ condition ]
then
command1
else
command2
fi
#!/bin/bash
if [ $USER == "root" ]
then
echo "Hello Administrator!"
else
echo "hello other user!"
fi
if-then-elif语句
适用范围: 多于两个以上的判断结果,也就是多于一个以上的判断条件
if [ condition1 ]
then
command1
elif [ condition2 ]
then
command2
...
fi
#!/bin/bash
if [ $1 -eq $2 ]
then
echo "$1==$1"
elif [ $1 -gt $2 ]
then
echo "$1 > $2"
else
echo "$1 < $2"
fi
多条件表示
逻辑与
if [ condition1 -a condition2 ]
if [ condition1 ] && [ condition2 ]
逻辑或
if [ condition1 -o condition2 ]
if [ condition1 ] || [ condition2 ]
逻辑非(取反)
!
高级应用
if ((100%3+1 > 1))
then
echo "YES"
else
echo "NO"
fi
if ((100%3+1 > 1));then
echo "YES"
else
echo "NO"
fi
6.2 for循环语句
for语法一
for var in value1 value2 ...
do
command
done
#!/bin/bash
for i in `seq 10 -1 1`
do
echo $i
done
#需要使用转义字符,赋值是字符串
for var in zhangsan\'s is school, he is handsome
do
echo "word:$var"
done
for语法二
for ((变量;条件;自增减运算))
do
代码块
done
for ((i=1; i < 10; i++))
do
echo $i
done
#多变量C格式
for (( n=10,m=0;n>0,m<10;n--,m++ ))
do
echo -e "$n\t$m"
done
#无限循环
for(;;)
do
echo 'hello'
done
6.3 循环控制语句
sleep N 脚本执行到该步休眠N秒
#!/bin/bash
#监控主机存活的脚本
for ((;;))
do
ping -c1 $1 &>/dev/null
if [ $? -eq 0 ]
then
echo "`date +"%F %H:%M:%S"`: $1 is up"
else
echo "`date +"%F %H:%M:%S"`: $1 is down"
fi
# 生产环境中建议1min以上
sleep 5
done
continue跳过循环中的某次循环
#!/bin/bash
for ((i = 0; i < 10; i++))
do
if (($i%2 == 0 ))
then
continue
fi
echo $i
done
for ((i = 0; i < 10; i++))
do
if [ $(($i%2)) -eq 0 ]
then
continue
fi
echo $i
done
break跳出循环
#!/bin/bash
for ((;;))
do
read -p "char: " ch
if [ $ch == 'Q' ]
then
break
else
echo "your enter char is: $ch"
fi
done
break跳出多层循环
有时需要跳出多层循环,使用: break n, n表示需要跳出的循环层数,默认情况下n = 1, 代表只跳出当前循环
#!/bin/bash
for ((i=0; i < 10; i++))
do
echo "#OUT LOOP: $i"
for ((;;))
do
echo "ahhh"
break 2
done
done
6.4 while循环语句
while循环语法
知道循环次数要用for,不知道循环次数用while
while [ condition ]
do
command
done
#!/bin/bash
read -p "Ch: " ch
while [ $ch != "Q" ]
do
echo "your enter ch is $ch"
read -p "Ch: " ch
done
#!/bin/bash
i=1
while [ $i -lt 10 ]
do
for ((j = 1;j <= $i; j++))
do
echo -n "$j * $i =$((i*j)) "
done
echo
i=$((i+1))
done
6.5 until语句
和while正好相反, until是条件为假开始执行,条件为真停止执行
until [ condition ]
do
command
done
#!/bin/bash
num=10
until [ $num -ge 21 ]
do
echo "num: $num"
num=$((num+1))
done
6.6 case多条件分支语句
case语法
语法:
case 变量 in
条件1)
执行代码111
;;
条件2)
执行代码222
;;
esac
#!/bin/bash
read -p "enter a num: " num
case $num in
1)
echo "ahhh"
;;
2)
echo "bbbbb"
;;
*)
echo "byebye"
;;
esac
6.7 shell特殊变量
$* 代表所有参数
$@ 与$*相同
$#: 传入参数个数
$?: 命令执行返回值
$$: 脚本执行的进程号
$n: 第n个传入参数 $0 表示脚本的名称
$_: 最后执行命令
6.8 多行shell语句写在一行
6.8.1 间隔5秒查询tomcat进程是否存在,如果存在跳出循环,最多循环20次,等待100秒
for i in $(seq 1 20); do ps aux| grep tomcat | grep -v grep && break;sleep 5;done
6.8.2 while循环实例
间隔5秒查询tomcat进程是否存在,如果存在跳出循环,如果不存在将一直等待
while true; do ps aux | grep tomcat | grep -v grep && break;sleep 5; done
注意 分号后的空格 可加 可不加,以下代码也是可以正常运行的
while true;do ps aux | grep tomcat | grep -v grep && break;sleep 5;done
0x07 shell函数
7.1 shell函数
函数语法
#语法1
函数名(){
代码块
return N
}
#语法2:
function 函数名称() {
代码块
return N
}
#语法3:
function 函数名称 {
代码块
return N
}
#!/bin/bash
start(){
echo "func exec ..... [OK]"
#return 0
}
stop(){
echo "func stop .... [FAIL]"
}
#call func
start
start
stop
stop
vim /lib/lsb/init-functions ubuntu下的shell函数库
/etc/init.d/functions CentOS下的函数库
0x08 正则表达式
shell支持正则表达式,但不是所有的命令都支持正则表达式。 常见的命令中有grep sed awk等命令支持正则表达式
特殊字符
8.1 定位符
定位符使用技巧: 同时锚定开头和结尾,做精匹配,单一锚定开头和结尾,做模糊匹配
定位符 | 说明 |
---|---|
^ | 锚定开头^a 以a开头 默认锚定一个字符 |
$ | 锚定结尾a$ 以a结尾 默认锚定一个字符 |
zbb@ubuntu:~/shell$ egrep "^d" monitor.sh
do
done
zbb@ubuntu:~/shell$ egrep "5$" monitor.sh
sleep 5
8.2 匹配符
匹配符 | 说明 |
---|---|
. | 匹配除回车以外的任意字符 |
() | 字符串分组 |
[] | 定义字符类,匹配括号中的一个字符 |
[^] | 表示否定括号中出现字符类中的字符,去反 |
\ | 转义字符 |
| | 管道 |
egrep "^a[0-9]c$" file
a3c
egrep "^a[^0-9]c$" file
acc
abc
a_c
aZc
a c
egrep "^a\*c$" file
a*c
egrep "^(a|b)c$" file
ac
bc
8.3 限定符
限定符 | 说明 |
---|---|
* | 某个字符之后加星号表示该字符不出现或者出现多次 |
? | 与星号类似,略有不同,表示该字符出现一次或者不出现 |
+ | 与星号类似 表示其前面的字符出现一个或者多次 但是必须出现一次 |
{n, m} | 某个字符之后出现 表示该字符至少出现n次 最多m次 |
{m} | 正好出现m次 |
egrep "^ab*c$" file
ac
abbbc
abbc
abc
egrep "^ab?c$" file
ac
abc
egrep "^ab+c$" file
abbbc
abbc
abc
egrep "^ab{1,2}c$" file
abbc
abc
8.4 分割字符串
# awk:
echo "1:3:5" | awk -F ":" '{print $NF}'
# sed:
echo "1:3:5" | sed 's/.*:\([^:]*\)$/\1/'
# 输出 5