Shell 脚本用于在服务器上维护各种服务
Shell是一个命令行解释器
创建一个Shell脚本,取名first.sh
#!/bin/bash cd /opt echo "hello,shell!"
脚本的常用执行方式#!后面跟的是解释器的路径(shell本身就是一种解释型语言),解释器的路径是/bin/bash;
第一种:因为shell程序写完之后并没有执行权限(x),所以该脚本想要执行可以使用chmod命令赋予该脚本可执行的权限,然后再执行。(推荐) 补充:因为直接./脚本 需要调用解释器的权限,所以确保当前用户有那个解释器的执行权限才能使用. chmod 744 first.sh [root@localhost shell]# ./first.sh hello,shell! 第二种:调用解释器使得脚本执行,例如:bash、csh、csh、ash、bsh、ksh等等;shell脚本只是作为解释器的一个参数传进去,解释器会读取脚本内容并执行,所以只需要读的权限即可. [root@localhost shell]# bash first.sh hello,shell! [root@localhost shell]# sh first.sh hello,shell! 第三种:使用source命令 [root@localhost shell]# source first.sh hello,shell! [root@localhost opt]# 在脚本的第一行有cd命令,这是一个改变工作目录的命令,可是使用第一种和第二种方法执行脚本后当前的工作目录并没有改变(脚本所在的目录是shell文件夹,脚本执行后,使用pwd命令显示出当前的工作目录还是shell),使用第三种方法执行后当前的工作目录就改变了,进入了opt中。出现这种情况是系统本身执行脚本的方式不同所造成的 前两种方法执行脚本时系统会创建一个子进程或者说子shell来执行脚本,原来的进程就是父进程或者说父shell,整个过程中父进程会等待子进程执行完毕,然后子进程退出,父进程也退出。cd命令确实被执行了,但是仅仅是在子进程中改变了工作目录,所以脚本执行完后使用pwd显示工作目录并没有改变。而第三种方法执行的shell脚本的话并不创建子进程,就是在原来的进程中执行,所以最后的工作目录改变了。sh和bash的关系
进入/bin目录下看到 lrwxrwxrwx. 1 root root 4 1月 22 15:17 sh -> bash 但sh并不简单的是bash的软链接 详解 在同一个目录下有两个脚本,a.sh和b.sh,脚本内容如下: a.sh: echo "test for a" source b.sh b.sh: echo "test for b" 使用bash a.sh 返回正确结果。 而使用sh a.sh返回结果如下: test for a a.sh: line 2: source: b.sh: file not found 明显脚本运行过程中,没有找到文件b.sh,将a.sh脚本内容修改为如下: echo "test for a" source ./b.sh 再次使用sh a.sh 又可以正常执行了,这是为什么呢? 问题就出在sh与bash执行脚本的区别。 首先要明确重要的一点:/bin/sh 虽然是/bin/bash的软连接,但这个软连接很特殊,它并不仅仅只是一个链接,sh不等于bash。如果你在CentOS下使用man sh可以找到这句话: Bash can be configured to be POSIX-conformant by default. 这说明**sh 等于 bash -posix 也就是说使用sh来执行一个脚本,所以使用sh a.sh的效果等同于bash -posix a.sh**,虽然大部分情况下等于使用bash来执行脚本,但启用了bash的posix模式,也可以理解为posix标准。那么就要遵循这个标准下的要求。 现在来man bash在关于source命令的文档中找到这么一句话: When bash is not in posix mode, the current directory is searched if no file is found in PATH. 如果bash不是运行在posix模式下时,脚本中有source命令时,系统变量中找不到这个文件时会在当前目录下查找这个文件。 所以在不使用posix模式的情况下 a.sh脚本中的source b.sh不会出错,因为即使在PATH中找不到,也会在当前目录中进行查找。但如果使用了posix模式,可以在google中找到关于bash posix mode的内容,其中有一条: The . and source builtins do not search the current directory for the filename argument if it is not found by searching PATH. 也就是source这种内建命令在使用文件名作为参数时,如果在PATH中无法找到,将不会在当前目录自动进行查找。 这就是问题的关键,使用了sh 执行a.sh,脚本中source b.sh中的b.sh既不会在PATH中被找到也不会在当前目录下被找到,那么bash只会返回 b.sh: file not found 但如果a.sh写成这样 source ./b.sh 这就不一样了,这等于给source提供了一个该文件的路径,虽然是相对路径,但bash是可以根据脚本本身执行的位置来找到b.sh的,所以就不会出错了。 总结:所以一般情况下,在Linux中执行一个bash的脚本,我们都会使用bash去执行,就是为了避免这种奇怪问题的产生。
shell 的变量
系统变量和用户自定义变量
系统变量:
$HOME、$PWD、$SHELL、$USER 等等比如: echo $HOME
用户自定义变量:变量=值
[root@localhost shell]# vim c.sh #! /bin/bash echo "PATH=$PATH" echo "user=$USER" [root@localhost shell]# bash c.sh PATH=/usr/java/jdk1.8.0_281-amd64/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin user=root
显示当前shell中所有变量
set
撤销变量:unset 变量
声明静态变量:readonly 变量,注意:不能 unset
快速入门
#定义变量A,撤销变量 A [root@localhost shell]# A=25 [root@localhost shell]# echo $A 25 [root@localhost shell]# unset A [root@localhost shell]# echo $A #声明静态的变量,不能 unset [root@localhost shell]# readonly A=99 [root@localhost shell]# echo $A 99 [root@localhost shell]# unset A -bash: unset: A: 无法反设定: 只读 variable [root@localhost shell]# echo $A 99 #可把变量提升为全局环境变量,可供其他shell程序使用.只读变量不能用unset删除,程序执行完或exit退出即结束(比如重新连接服务器)
定义变量的规则
变量名称可以由字母、数字和下划线组成,但是不能以数字开头 等号两侧不能有空格 变量名称一般习惯大写
将命令的返回值赋给变量(重点)
#反引号,运行里面的命令,并把结果返回给变量 A [root@localhost shell]# RESULT=`ls -l /home/xuebiao/shell` [root@localhost shell]# echo $RESULT 总用量 16 -rw-r--r--. 1 root root 32 2月 18 09:42 a.sh -rw-r--r--. 1 root root 18 2月 18 09:40 b.sh -rw-r--r--. 1 root root 49 2月 18 10:09 c.sh -rwxr--r--. 1 root root 40 2月 3 11:27 first.sh #$() 等价于反引号 [root@localhost shell]# MY_DATE=$(date) [root@localhost shell]# echo "date=$MY_DATE" date=2021年 02月 18日 星期四 10:46:20 CST [root@localhost shell]#
设置环境变量
export 变量名=变量值 #将shell变量输出为环境变量 source 配置文件 #让修改后的配置信息立即生效 echo $变量名 #输出环境变量的值 env 查看环境变量
快速入门
在/etc/profile 文件中定义 TOMCAT_HOME 环境变量 TOMCAT_HOME=/usr/apache-tomcat-9.0.41 export TOMCAT_HOME #退出后使用source命令使修改生效 [root@localhost bin]# source /etc/profile #查看环境变量 TOMCAT_HOME 的值 [root@localhost bin]# echo $TOMCAT_HOME /usr/apache-tomcat-9.0.41
shell中的单行注释和多行注释
单行注释 # echo "ni" 多行注释 方式一: :< 位置参数变量当我们执行一个 shell 脚本时,如果希望获取到命令行的参数信息,就可以使用到位置参数变量
./myshell.sh 100 200 $n #n为数字,$0 代表命令本身,$1-$9 代表第一到第九个参数,十以上的参数需要用大括号包含,如${10} $* #这个变量代表命令行中所有的参数,$*把所有参数看成一个整体 $@ #这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待 $# #这个变量代表命令行中所有参数个数(不包括命令本身)快速入门
[root@localhost shell]# vim position.sh #!/bin/bash #获取到各个参数 echo $0 $1 $2 echo $* echo $@ echo 参数的个数=$# [root@localhost shell]# chmod 744 position.sh [root@localhost shell]# ./position.sh 100 200 300 400 ./position.sh 100 200 100 200 300 400 100 200 300 400 参数的个数=4 #默认创建的文件没有执行权限预定义变量
就是 shell 设计者事先已经定义好的变量,可以直接在 shell 脚本中使用$$ #当前进程的进程号PID $! #后台运行的最后一个进程的进程号PID $? #最后一次执行的命令的返回状态。如果这个变量的值为 0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了快速入门
[root@localhost shell]# vim sup.sh #!/bin/bash echo =$$ #后台的方式运行myShell.sh ./a.sh & #&代表后台方式运行 echo 最后的进程号=$! echo 执行的值=$? [root@localhost shell]# ./sup.sh 当前进程号=15789 最后的进程号=15790 执行的值=0运算符
“$((运算式))”或“$[运算式]” expr m + n 注意 expr 运算符间要有空格 expr m - n expr *, /, % 乘,除,取余快速入门
#算(2+3)X4 的值 [root@localhost shell]# echo $(((2+3)*4)) 20 [root@localhost shell]# echo $[(2+3)*4] 20expr
expr 对表达式的格式有几点特殊的要求:
- 出现在表达式中的运算符、数字、变量和小括号的左右两边至少要有一个空格,否则会报错。
- 有些特殊符号必须用反斜杠进行转义(屏蔽其特殊含义),比如乘号*和小括号(),如果不用转义,那么 Shell 会把它们误解为正则表达式中的符号,*对应通配符,()对应分组。
- 使用变量时要加$前缀。
[root@localhost ~]# expr 1 * 2 2 [root@localhost ~]# expr ( 2 + 3 ) * 4 20字符串操作返回字符串长度:expr length 字串 [root@localhost ~]# expr length "hello world" 11 返回 String1 中包含 String2 中任意字符的第一个位置: expr index String1 String2 [root@localhost ~]# expr index "abcdefg" "gdc" 3 提取字符串的子串:expr substr 内容 起始位置 截几个, [root@localhost ~]# expr substr "get shell" 2 5 et sh条件判断1)两个整数的比较 = 字符串比较 -lt 小 于 -le 小于等于 -eq 等 于 -gt 大 于 -ge 大于等于 -ne 不等于 2) 按照文件权限进行判断 -r 有读的权限 -w 有写的权限 -x 有执行的权限 3)按照文件类型进行判断 -f 文件存在并且是一个常规的文件 -e 文件存在 -d 文件存在并是一个目录 [root@localhost shell]# vim condition.sh #!/bin/bash #案例一:"ok"是否等于"ok" if [ "ok185" = "ok" ] then echo "equal" fi #案例2:23是否大于等于22 if [ 23 -gt 22 ] then echo "大于" fi #案例3:/root/shell/aaa.txt 目录中的文件是否存在 if [ -e /home/xuebiao/shell ] then echo "存在" fi流程控制if [ 条件判断式 ];then 程序 fi 或者 if [ 条件判断式 ] then 程序 elif [条件判断式] then 程序 fi注意事项:
- [ 条件判断式 ],中括号和条件判断式之间必须有空格
- 推荐使用第二种方式
[root@localhost shell]# vim process.sh #!/bin/bash #案例:请编写一个shell程序.如果输入的参数大于等于60,输出"及格了";如果小于60,则输出"不及格". if [ $1 -ge 60 ] then echo "及格了" elif [ $1 -lt 60 ] then echo "不及格" ficase语句case $变量名 in "值 1") 如果变量的值等于值 1,则执行程序 1 ;; "值 2") 如果变量的值等于值 2,则执行程序 2 ;; …省略其他分支… *) 如果变量的值都不是以上的值,则执行此程序 ;; esac [root@localhost shell]# vim case.sh #!/bin/bash #案例1:当命令行参数是1时,输出"周一",是2时,就输出"周二",其它情况输出"other" case $1 in "1") echo "周一" ;; "2") echo "周二" ;; *) echo "other" ;; esac [root@localhost shell]# ./case.sh 1 周一 [root@localhost shell]# ./case.sh 2 周二 [root@localhost shell]# ./case.sh 3 otherfor#遍历 for 变量 in 值 1 值 2 值 3… do 程序 done [root@localhost shell]# vim for.sh #!/bin/bash #案例1:打印命令行输入的参数 #使用$* for i in "$*" do echo "the num is $i" done echo "=====================================" #使用$@ for j in $@ do echo "the num is $j" done [root@localhost shell]# ./for.sh 1 2 3 4 5 6 the num is 1 2 3 4 5 6 ===================================== the num is 1 the num is 2 the num is 3 the num is 4 the num is 5 the num is 6 #循环逻辑 for (( 初始值 ; 循环控制条件 ; 变量变化)) do 程序 done [root@localhost shell]# vim sum.sh #!/bin/bash #案例一:从1加到100的值输出显示 #定义一个变量 SUM=0 for((i=1;i<=100;i++)) do SUM=$SUM+$i] done echo "sum=$SUM" [root@localhost shell]# ./sum.sh sum=5050while 循环while [ 条件判断式 ] do 程序 done [root@localhost shell]# vim while.sh #!/bin/bash #案例1:从命令行输入一个数n,统计从1+...+n的值是多少 SUM=0 i=0 whlie [ $i -le $1 ] do SUM=$[$SUM+$i] i=$[$i+1] done echo "sum=$SUM" [root@localhost shell]# ./while.sh 100 sum=5050read 读取控制台输入read(选项)(参数) 选项: -p:指定读取值时的提示符; -t:指定读取值时等待的时间(秒),如果没有在指定的时间内输入,就不再等待了 [root@localhost shell]# vim control.sh #!/bin/bash #案例1:读取控制台输入一个num值 read -p "请输入一个数num1=" NUM1 echo "你输入的值是num1=$NUM1" #案例2:读取控制台输入一个num值,在5s内输入 read -t 5 -p "请输入一个数num2=" NUM2 echo "你输入的值是num2=$NUM2" [root@localhost shell]# ./control.sh 请输入一个数num1=7 你输入的值是num1=7 请输入一个数num2=你输入的值是num2= [root@localhost shell]# ./control.sh 请输入一个数num1=r 你输入的值是num1=r 请输入一个数num2=d 你输入的值是num2=dwc用于统计wc [参数] 文件 -c或--bytes或--chars 只显示Bytes数。 -l或--lines 显示行数。 -w或--words 只显示单词数。 --help 在线帮助。 --version 显示版本信息。 wc testfile # testfile文件的统计信息 3 92 598 testfile # testfile文件的行数为3、单词数92、字节数598 wc testfile testfile_1 testfile_2 #统计三个文件的信息 输出结果如下: $ wc testfile testfile_1 testfile_2 #统计三个文件的信息 3 92 598 testfile #第一个文件行数为3、单词数92、字节数598 9 18 78 testfile_1 #第二个文件的行数为9、单词数18、字节数78 3 6 32 testfile_2 #第三个文件的行数为3、单词数6、字节数32 15 116 708 总用量 #三个文件总共的行数为15、单词数116、字节数708nl列出内容并编号[root@localhost sh]# nl sed.sh 1 test aaaaaaaaaaa 2 test aaaaaaaaaaa 3 test aaaaaaaaaaa 4 test aaaaaaaaaaa 5 test aaaaaaaaaaa 6 test aaaaaaaaaaa 7 test aaaaaaaaaaa 8 test aaaaaaaaaaa 9 test aaaaaaaaaaa 10 test aaaaaaaaaaashell脚本while read line的使用计算文档a.txt中每一行中出现的数字个数并且要计算一下整个文档中一共出现了几个数字。例如a.txt内容如下:
12aa*lkjskdj
alskdflkskdjflkjj
我们脚本名字为 ncount.sh, 运行它时:
bash ncount.sh a.txt
输出结果应该为:
2
0
sum:2#!/bin/bash sum=0 while read line do line_n=`echo $line|sed 's/[^0-9]//g'|wc -L` echo $line_n sum=$[$sum+$line_n] done < $1 echo "sum:$sum" 当我们输入的字段比变量数目多时,最后一个变量的值将不只一个字段,而是所有剩余的内容;当输入字段比变量数少时,多余的变量将是空值,你可以自己试试。现在我们再来看 while read line do done < fileread通过输入重定向,把file的第一行所有的内容赋值给变量line,循环体内的命令一般包含对变量line的处理;然后循环处理file的第二行、第三行。。。一直到file的最后一行。还记得while根据其后的命令退出状态来判断是否执行循环体吗?是的,read命令也有退出状态,当它从文件file中读到内容时,退出状态为0,循环继续惊醒;当read从文件中读完最后一行后,下次便没有内容可读了,此时read的退出状态为非0,所以循环才会退出。
函数
另一种也很常见的用法:
command | while read line
do
…
done
如果你还记得管道的用法,这个结构应该不难理解吧。command命令的输出作为read循环的输入,这种结构长用于处理超过一行的输出,当然awk也很擅长做这种事shell 编程和其它编程语言一样,有系统函数,也可以自定义函数。系统函数中,我们这里就介绍两个。
系统函数
basename
返回完整路径最后 / 的部分,常用于获取文件名basename [pathname] [suffix] basename [string] [suffix] #basename 命令会删掉所有的前缀包括最后一个(‘/’)字符,然后将字符串显示出来。 选项: suffix 为后缀,如果 suffix 被指定了,basename 会将 pathname 或 string 中的 suffix 去掉。 [root@localhost shell]# basename /home/xuebiao/shell/control.sh control.sh [root@localhost shell]# basename /home/xuebiao/shell/control.sh .sh control dirname 返回完整路径最后 / 的前面的部分,常用于返回路径部分 dirname 文件绝对路径 (功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))自定义函数function funname(){ Action; return int; } [root@localhost shell]# vim custom.sh #!/bin/bash #案例1:计算输入两个参数的和(read),getSum function getSum(){ SUM=$[$n1+$n2] echo "和是=$SUM" } read -p "请输入第一个数n1" n1 read -p "请输入第二个数n2" n2 #调用getSum getSum $n1 $n2 [root@localhost shell]# chmod 744 custom.sh [root@localhost shell]# ./custom.sh 请输入第一个数n112 请输入第二个数n222 和是=34备份数据库案例需求分析
1)每天凌晨 2:10 备份 数据库 atguiguDB 到 /data/backup/db
2)备份开始和备份结束能够给出相应的提示信息
3)备份后的文件要求以备份时间为文件名,并打包成 .tar.gz 的形式,比如:2018-03-12_23-02-01.tar.gz
4) 在备份的同时,检查是否有 10 天前备份的数据库文件,如果有就将其删除。[root@localhost shell]# vim backup.sh #!/bin/bash #备份的路径 BACKUP=/data/backup/db #当前时间 DATETIME=$(date +%Y-%m-%d_%H-%M-%S) echo "===========开始备份============" #主机 HOST=localhost #用户名 DB_USER=root #密码 DB_PWD=123456 #备份数据库 DATABASE=test #如果备份的路径文件存在就使用,否则就创建 [ ! -d "$BACKUP/$DATETIME" ] && mkdir -p "$BACKUP/$DATETIME" #备份数据库到临时文件 mysqldump -u${DB_USER} -p${DB_PWD} --host=$HOST $DATABASE | gzip > $BACKUP/$DATETIME.tar.gz #打包备份文件 cd $BACKUP tar -zcvf $DATETIME.tar.gz $DATETIME #删除临时文件 rm -rf $BACKUP/$DATETIME #删除10天前的文件 find $BACKUP -mtime +10 -name "*.tar.gz" -exec rm -rf {} ; echo "===========备份文件成功============" #在crontab中配置执行脚本 [root@localhost shell]# crontab -e 10 2 * * * /home/xuebiao/shell/backup.sh



