这玩意写的好真的可以事半功倍,有的大佬写的脚本真的强,各种一键操作。语法简单,只是一个小工具。但是如果不系统了解一下,用起来真的会哭的。
第六章-Linux下shell篇 shell编程 为什么要学习Shell编程? 1 2 3 1) Linux运维工程师 在进行服务器集群管理时,需要编写Shell程序来进行服务器管理。 2) 对于 JavaEE和Python程序员 来说,工作的需要,你的老大会要求你编写一些Shell脚本进行程序或者是服务器的维护,比如编写一个定时备份数据库的脚本。 3) 对于 大数据程序员 来说,需要编写Shell程序来管理集群。
Shell是什么? 1 Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。直白的说就是可以调用Linux的命令.
Linux 的 Shell 种类众多,一个系统可以存在多个 shell,可以通过 cat /etc/shells
命令查看系统中安装的 shell。
Bash 由于易用和免费,在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。
快速入门 以下所有操作都在/export/shell
目录下进行,请提前创建该目录:
新建 /export/shell/hello.sh
文件
1 2 3 4 5 6 # !/bin/bash echo 'hello world' # !/bin/sh是指此脚本使用/bin/sh来解释执行, # ! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。注意,在shell脚本中除了第一行的#表示特殊格式外,其他地方的#一般表示注释。 echo命令用于向窗口输出文本。
解释器 Java需要虚拟机解释器, 同理 shell脚本也需要解析器,查看Linux系统支持的解释器:
1 2 3 4 5 6 7 [root@node1 shell]#cat /etc/shells /bin/sh /bin/bash /sbin/nologin /bin/dash /bin/tcsh /bin/csh
常见Shell解释器:
sh
: 的全称是 Bourne shell,由 AT&T 公司的 Steve Bourne开发,为了纪念他,就用他的名字命名了。sh 是UNIX 上的标准 shell,很多 UNIX 版本都配有 sh。sh 是第一个流行的 Shell。
Bash
:从名称可以看出是Bsh的升级版本,是著名的开源软件项目,目前大多数的Linux版本都使用Bash 作为默认的Shell程序 ,当运行Shell程序时,实际运行的是Bash程序
Csh
:是因使用C语言的语法风格而得名,在用户的命令行交互界面上进行了很多改进,并增加了历史,别名,文件名替换,作业掏等功能,相比Bsh,Csh在更加适用为用户提供命令交互操作
在现代的 Linux上,sh 已经被 bash 代替,/bin/sh
往往是指向/bin/bash
的符号链接。
如果你希望查看当前 Linux 的默认 Shell,那么可以输出 SHELL 环境变量:
1 2 [root@node1 shell]# echo $SHELL /bin/bash
输出结果表明默认的 Shell 是 bash。
3种执行方式 方式一:sh执行脚本
sh执行,进入脚本的工作目录,然后使用对应的sh或bash来执行脚本,这种执行方式,脚本文件不需要具有可执行权限 。
1 2 3 [root@node1 ~]# cd /export/shell/ [root@node1 shell]#sh hello.sh hello world
方式2:工作目录执行
执行脚本时,先进入到脚本所在的目录,然后使用 ./脚本方式执行,这种执行方式,必须保证脚本文件具有可执行权限 。
1 2 3 4 5 6 7 [root@node1 shell]# chmod +x hello.sh [root@node1 shell]# ll 总用量 4 -rwxr-xr-x. 1 root root 33 12月 31 20:18 hello.sh [root@node1 shell]# ./hello.sh hello world
方式三:绝对路径执行
绝对路径中执行,指的是直接从根目录/到脚本目录的绝对路径,这种执行方式,必须保证脚本文件具有可执行权限 。
1 2 [root@node1 ~]# /export/shell/hello.sh hello world
sh文件后缀无关,只是为了便于区分
3种方式区别:方式1和2一般是个人测试用,生产环境用方式3绝对路径
数据类型 字符串: 字符串是shell编程中最常用最有用的数据类型,字符串可以用单引号,也可以用双引号 ,也可以不用引号。建议使用双引号 ,因为双引号里可以有变量和特殊字符,可以按照变量和特殊字符去使用。 声明字符串类型的变量:
整数型: 在Shell中所有的变量默认都是字符串型。默认情况下,所有的数值都是不能进行运算的,如果想要进行数学运算,可以使用$((运算式))
或$[运算式]
方式运算。
Shell的变量 变量的简介 shell变量是一种很“弱”的变量,默认情况下,一个变量保存一个串,shell不关心这个串是什么含义。所以若要进行数学运算,必须使用一些命令例如let、declare、expr、双括号等。
在shell中有3种变量:用户变量、环境变量、特殊变量 ,其中用户变量在编程过程中使用量最多,环境变量主要是在程序运行时需要设置,特殊变量在对参数判断和命令返回值判断时会使用,。
定义变量:变量=值
a) 变量名称可以由字母、数字和下划线组成,但是不能以数字开头。
b) 等号两侧不能有空格
c) 变量名称一般习惯为大写
d) 变量值中如果有空格,则需要使用单引号或双引号包含,如 test="hello world!"
。双引号括起来的内容”$”和反引号者都拥有特殊含义 ,而单引号括起来的内容都是普通字符 。
e) 在变量值中,可以使用转义符”"。
f) 不能使用bash里的关键字(可用help命令查看保留关键字)。
h) 在 Bash中,变量的默认类型都是字符串型,如果要进行数值运算,则必须使用特殊命令。
用户变量 定义变量 在对变量赋于字符串值时,建议使用引号将其包裹。如果字符串中存在空格,请一定要使用单引号或双引号将整个内容包裹。注意:单引号里的内容原封不动的输出,双引号里有变量的调用则会调用变量。
1 [root@node1 shell]# username="nbchen"
访问变量 要对变量进行调用时,在变量名前加美元符号$
。
1 2 [root@node1 shell]# echo $username nbchen
如果需要增加变量的值,那么可以进行变量值的叠加。不够变量需要用双引号包含"$变量名"
或${变量名}
1 2 3 4 5 6 7 [root@node1 ~]# usernmae="nbchen" [root@node1 ~]# echo $usernamedb.log #这种方式不可以 .log [root@node1 ~]# echo "$username"db.log #可以 nbchendb.log [root@node1 ~]# echo ${username}db.log #可以 nbchendb.log
测试脚本:test1.sh
1 2 3 4 # !/bin/bash string="I am shell" num=5 echo "a=${num},string=${string}"
执行脚本,结果如下:
1 2 [root@node1 shell]# sh test1.sh a=,string=I am shell
变量的其他赋值方式
可以使用read关键字从键盘获取内容赋值给变量
并将命令的执行结果赋值给变量
1 2 1)A=`ls -la` 反引号,运行里面的命令,并把结果返回给变量A 2)A=$(ls -la) 等价于反引号
测试脚本:test2.sh
1 2 3 4 5 6 7 8 # !/bin/bash echo "who are you?" read name #从键盘获取变量的值 pwd_string=$(pwd) #将当前的绝对路径赋值给pwd_string变量 date_string=`date` #将当前时间赋值给date_string变量,注意这里使用的是反引号 echo "hello, $name" echo $pwd_string echo $date_string
执行脚本,结果如下:
1 2 3 4 5 6 [root@node1 shell]# sh test2.sh who are you? nbchen hello, nbchen /export/data/shell 2020年 05月 10日 星期日 16:50:21 CST
只读变量 使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变(包括不能unset删除)。
下面的例子尝试更改只读变量,结果报错:
测试脚本:test3.sh
1 2 3 4 #!/bin/bash myUrl="http://www.google.com" readonly myUrlmyUrl="http://www.runoob.com"
运行脚本,结果如下:
1 /bin/sh: NAME: This variable is read only.
删除变量 使用 unset 命令可以删除变量。语法:
变量被删除后不能再次使用。unset 命令不能删除只读变量。
测试脚本:test4.sh
1 2 3 4 #!/bin/sh myUrl="http://www.runoob.com" unset myUrl echo $myUrl
以上实例执行将没有任何输出。
测试案例 1 2 3 4 案例1:定义变量A 案例2:撤销变量A 案例3:声明静态的变量B=2,不能unset 案例4:可把变量提升为全局环境变量,可供其他shell程序使用
将结果返回
环境变量 当shell程序启动时,都自动设置一组变量,这组变量就是环境变量。shell中的所有命令都可以使用这些变量,环境变量可以在/etc/profile
中设置,环境变量的名字习惯上使用大写字母 。
系统环境变量:/etc/profile 用户环境变量: 用户家目录/bash_profile
1 2 系统变量:$HOME、$PWD、$SHELL、$USER等等 比如: echo $HOME 等等..
常见的环境变量 可以使用env
命令查看所有的系统环境变量
PATH 决定了shell将到哪些目录中寻找命令或程序
HOME 当前用户主目录
HISTSIZE 历史记录数
LOGNAME 当前用户的登录名
HOSTNAME 指主机的名称
SHELL 当前用户Shell类型
LANGUGE 语言相关的环境变量,多语言可以修改此环境变量
MAIL 当前用户的邮件存放目录
PS1 基本提示符,对于root用户是#
,对于普通用户是$
1 2 3 4 5 6 7 8 9 1) export 变量名=变量值 (功能描述:将shell变量输出为环境变量) 2) source 配置文件 (功能描述:让修改后的配置信息立即生效) 注意:在输出JAVA_HOME 环境变量前,需要让其生效: source /etc/profile 3) echo $变量名 (功能描述:查询环境变量的值) 1) 在/etc/profile文件中定义TOMCAT_HOME环境变量 2) 查看环境变量TOMCAT_HOME的值 3) 在另外一个shell程序中使用 TOMCAT_HOME 参考JAVA_HOME PATH
自定义环境变量 1)vi /etc/profile
,在文件末尾加上要定义的环境变量,语法如下:export 变量名=变量值
2)wq
退出
3)source /etc/profile
4)输入 env
查看环境变量,是否有自己定义的环境变量。
5)环境变量的使用方式和普通变量是一样的:$环境变量名
特殊变量 我们可以在执行 Shell 脚本时,向脚本传递参数,这时候可以使用特变变量来获取参数,Shell常用的特殊变量如下:
变量
解释
$#
命令行参数的个数
$n
n为数字,$0
代表命令本身,$1-$9
代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10}
$0
当前程序的名称
$?
前一个命令或函数的返回码,如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了
$*
以”参数1 参数2 。。。”形式保存所有参数
$@
以”参数1” “参数2”。。。形式保存所有参数
$$
本程序的(进程ID号)PID
$!
上一个命令的PID
$?
返回[0-255]的状态码
测试脚本: test5.sh
以下实例我们向脚本传递三个参数,并分别输出,其中 $0
为执行的文件名:
1 2 3 4 5 6 7 8 # !/bin/bash echo "Shell 传递参数实例!"; echo "执行的文件名:$0"; echo "第一个参数为:$1"; echo "第二个参数为:$2"; echo "第三个参数为:$3"; echo "参数个数为:$#"; echo "传递的参数作为一个字符串显示*:$*";
执行脚本,结果如下:
1 2 3 4 5 6 7 8 9 [root@node1 shell]# chmod +x test5.sh [root@node1 shell]# ./test5.sh aaa bbb ccc Shell 传递参数实例! 执行的文件名:./demo4.sh 第一个参数为:aaa 第二个参数为:bbb 第三个参数为:ccc 参数个数为:3 传递的参数作为一个字符串显示*:aaa bbb ccc
$*
与 $@
区别:
相同点:都是引用所有参数。 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " $* "
等价于 "1 2 3"
(传递了一个参数),而 "$@"
等价于 "1" "2" "3"
(传递了三个参数)。
案例:编写一个shell脚本 positionPara.sh ,在脚本中获取到命令行的各个参数信息。
案例:在一个shell脚本中简单使用一下预定义变量
字符串 字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。
单引号 1 2 3 name='乔布斯' str='我很崇拜$name' echo $str
输出结果为:
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号 1 2 3 name="乔布斯" str="我很崇拜$name" echo $str
输出结果为:
双引号里可以有变量
双引号里可以出现转义字符
拼接字符串 测试脚本:test.sh
1 2 3 4 5 6 # !/bin/bash yourname="吴恩达" wenhou_1="你好,$yourname ." wenhou_2="你好,"$yourname" ." wenhou_3="你好,\"$yourname\" ." echo $wenhou_1 $wenhou_2 $wenhou_3
输出结果为:
1 你好,吴恩达 . 你好,吴恩达 . 你好,"吴恩达"
获取字符串长度 测试脚本:test.sh
1 2 3 4 # !/bin/bash string="jobs" echo ${string} # 输出结果: jobs echo ${#string} # 输出结果: 4
提取子字符串 测试脚本:test.sh
1 2 3 # !/bin/bash string="敢于亮剑决不后退" echo ${string:2:3} # 输出结果为: 亮剑决
追加 1 2 3 命令 > 文件 : 将命令成功结果覆盖指定文件 命令 >> 文件 :将命令成功结果追加指定文件 命令 &>> 文件:将命令失败结果追加指定文件
运算符 Shell 和其他编程一样,支持包括:算术、关系、布尔、字符串等运算符。原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如expr。expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
1 2 3 4 1) "$((运算式))"或"$[运算式]" 不要有空格 2) expr m + n 注意expr运算符间要有空格 3) expr m - n 4) expr \* 乘, / 除, % 取余
案例1:计算(2+3)X4
的值 案例2:请求出命令行的两个参数[整数]的和
条件判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [ condition ](注意condition前后要有空格) #非空返回true,可使用$?验证(0为true,>1为false) [ atguigu ] 返回true [] 返回false [condition] && echo OK || echo notok 条件满足,执行后面的语句 常用判断条件 1) 两个整数的比较 = 字符串比较 -lt 小于 -le 小于等于 -eq 等于 -gt 大于 -ge 大于等于 -ne 不等于 2) 按照文件权限进行判断 -r 有读的权限 -w 有写的权限 -x 有执行的权限 3) 按照文件类型进行判断 -f 文件存在并且是一个常规的文件 -e 文件存在 -d 文件存在并是一个目录
流程控制 if 判断 1 2 3 4 5 6 7 8 9 10 11 12 if [ 条件判断式 ];then 程序 fi 或者 if [ 条件判断式 ] then 程序 elif [ 条件判断式 ] then 程序 fi 注意事项:[ 条件判断式 ],中括号和条件判断式之间必须有空格;推荐使用第二种方式,第一种形式要多个分号
案例:请编写一个shell程序,如果输入的参数,大于等于60,则输出 “及格了”,如果小于60,则输出 “不及格”
案例:判断/media/cdrom
文件是否存在,若不存在就去创建这个目录
1 2 3 4 5 6 # !/bin/bash DIR="/media/cdrom" if [ ! -e $DIR ] then mkdir -p $DIR fi
案例:判断是否成功了
1 2 3 4 5 6 7 8 9 10 # !/bin/bash read -p "Enter your age(1-100):" age if [ $age -ge 18 ] then echo '已经成年!' else echo '未成年!' fi # read -p 可以不换行获取输入,更优雅
if和test结合使用 test 命令允许你做各种测试并在测试成功或失败时返回它的退出状态码(为0表示为真,为1表示为假)。使用这个状态码,可以让 Bash 对测试的结果做出反应。
1 2 3 4 test 命令的语法为: test EXPRESSION 或 [ EXPRESSION ]
案例:
1 2 3 4 5 6 7 8 9 # !/bin/bash num1=$[2*3] num2=$[1+5] if test $num1 -eq $num2 then echo '两个数字相等!' else echo '两个数字不相等!' fi
if多分支 案例:判断两个变量是否相等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # !/bin/bash echo "请输入a的值:" read a echo "请输入b的值:" read b if [ $a -eq $b ] then echo "a 等于 b" elif [ $a -gt $b ] then echo "a 大于 b" elif [ $a -lt $b ] then echo "a 小于 b" else echo "没有符合的条件" fi
if多条件 案例:输入成绩,判断成绩“优”“良”“中”
1 2 3 4 5 6 7 8 9 10 11 # !/bin/bash read -p "Enter your score(0-100):" n #-p参数表示给出提示信息 if [ $n -ge 85 ] && [ $n -le 100 ] ; then echo "优" elif [ $n -ge 70 ] && [ $n -le 84 ] ; then echo "良" elif [ $n -ge 60 ] && [ $n -le 69 ] ; then echo "中" else echo "差" fi
case选择 1 2 3 4 5 6 7 8 9 10 11 12 case $变量名 in "值1") 如果变量的值等于值1,则执行程序1 ;; "值2") 如果变量的值等于值2,则执行程序2 ;; …省略其他分支… *) 如果变量的值都不是以上的值,则执行此程序 ;; esac
案例1 :当命令行参数是 1 时,输出 “周一”, 是2 时,就输出”周二”, 其它情况输出 “other”
案例2:提示输入1到4,与每一种模式进行匹配
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 # !/bin/bash echo '输入 1 到 4 之间的数字:' echo '你输入的数字为:' read aNum case $aNum in 1) echo '你选择了 1' ;; 2) echo '你选择了 2' ;; 3) echo '你选择了 3' ;; 4) echo '你选择了 4' ;; *) echo '你没有输入 1 到 4 之间的数字' ;; esac # 穿透写法: echo -n '你输入的数字为:' => -n 表示不换行类似 read -p read aNum case $aNum in 1|2|3|4) echo '你选择了 1~4的数字' ;; *) echo '你没有输入 1 到 4 之间的数字' ;; esac
for循环 1 2 3 4 5 6 7 8 9 10 方式一: for 变量 in 值1 值2 值3… => 注意,是空格,如果是逗号,会被当成一个整体 do 程序 done 方式二: for (( 初始值;循环控制条件;变量变化 )) do 程序 done
案例1 :打印命令行输入的参数 案例2 :从1加到100的值输出显示 [这里可以看出$*
和 $@
的区别] 一般用$@
S=$((s+i))
可以写做:
let s = s+i
还可以自增: let num++
案例3:打印/root目录下所有文件的名字
1 2 3 4 5 # !/bin/bash for file in $(ls /root) do echo $file done
while循环 1 2 3 4 5 6 while [ 条件判断式 ] do 程序 done 案例1 :从命令行输入一个数n,统计从 1+..+ n 的值是多少?
无限循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 while : do command done 或者 while true do command done 或者 for (( ; ; ))
循环中可以用 sleep 1 睡眠1s
跳出循环 在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。
break break命令允许跳出所有循环(终止执行后面的所有循环)。
下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # !/bin/bash while : do echo -n "输入 1 到 5 之间的数字:" read aNum case $aNum in 1|2|3|4|5) echo "你输入的数字为 $aNum!" ;; *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束" break ;; esac done
执行以上代码,输出结果为:
1 2 3 4 输入 1 到 5 之间的数字:3 你输入的数字为 3! 输入 1 到 5 之间的数字:7 你输入的数字不是 1 到 5 之间的! 游戏结束
exit是终止进程
continue continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
对上面的例子进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # !/bin/bash while : do echo -n "输入 1 到 5 之间的数字: " read aNum case $aNum in 1|2|3|4|5) echo "你输入的数字为 $aNum!" ;; *) echo "你输入的数字不是 1 到 5 之间的!" continue echo "游戏结束" ;; esac done
运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句 echo “游戏结束” 永远不会被执行。
read读取控制台输入 1 2 3 4 5 6 read(选项)(参数) 选项 -p:指定读取值时的提示 -t:指定读取值时等待的时间(秒),如果没有在指定的时间内输入,就不再等待了。。 参数 变量:指定读取值的变量名
函数 1 2 3 4 5 6 7 8 shell编程和其它编程语言一样,有系统函数,也可以自定义函数。系统函数中,我们这里就介绍两个。 # 系统函数 basename :返回完整路径最后 / 的部分,常用于获取文件名 basename [pathname] [suffix] basename [string] [suffix] basename命令会删掉所有的前缀包括最后一个('/')字符,然后将字符串显示出来。 suffix为后缀,如果suffix被指定了,basename会将pathname或string中的suffix去掉。
1 2 dirname :返回完整路径最后 / 的前面的部分,常用于返回路径部分 dirname 文件绝对路径 (功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))
1 2 3 4 5 6 7 8 9 # 自定义函数 [ function ] funname[()] { Action; [return int;] => return的是状态码,并不是执行的结果,输出变量可以用echo $var } 调用直接写函数名:funname [值] 案例1:计算输入两个参数的和, getSum
可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
案例1:定义一个函数并进行调用
1 2 3 4 5 6 7 8 # !/bin/bash demoFun(){ echo "这是我的第一个 shell 函数!" } echo "-----函数开始执行-----" demoFun echo "-----函数执行完毕-----"
输出:
1 2 3 -----函数开始执行----- 这是我的第一个 shell 函数! -----函数执行完毕-----
注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
案例2:定义一个带有return语句的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 funWithReturn(){ echo "这个函数会对输入的两个数字进行相加运算..." echo "输入第一个数字: " read aNum echo "输入第二个数字: " read anotherNum echo "两个数字分别为 $aNum 和 $anotherNum !" echo "两个数的和为:$(($aNum+$anotherNum))" return 100 } funWithReturn echo "退出状态码: $? !"
输出:
1 2 3 4 5 6 7 8 这个函数会对输入的两个数字进行相加运算... 输入第一个数字: 1000 输入第二个数字: 2000 两个数字分别为 1000 和 2000 ! 两个数的和为:3000 退出状态码: 100 !
案例3:获取函数的运算结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # !/bin/bash funWithReturn(){ s=0 # 求1-n的和,$1 为传入的第一个参数 for(( i=1;i<=$1;i=i+1)) do s=$(($s+$i)) done # 返回计算结果 echo $s return 0; } # 调用函数,并传入参数,求1-100的和 # result用户获取函数的返回结果,也就是echo 后边的内容 result=$(funWithReturn 100) echo "1-$1的和:$result" # 状态码为0表示正常退出,非0表示不正常退出(状态码范围:0-255) echo "退出状态码: $? !"
函数带参数
在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 的形式来获取参数的值,例如,1表示第一个参数,$2
表示第二个参数…
带参数的函数示例:
1 2 3 4 5 6 7 8 9 10 11 12 # !/bin/bash funWithParam(){ echo "第一个参数为 $1 !" echo "第二个参数为 $2 !" echo "第十个参数为 $10 !" echo "第十个参数为 ${10} !" echo "第十一个参数为 ${11} !" echo "参数总数有 $# 个!" echo "作为一个字符串输出所有参数 $* !" } funWithParam 1 2 3 4 5 6 7 8 9 34 73
输出为:
1 2 3 4 5 6 7 第一个参数为 1 ! 第二个参数为 2 ! 第十个参数为 10 ! 第十个参数为 34 ! 第十一个参数为 73 ! 参数总数有 11 个! 作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !
数组 数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似)。
与大部分编程语言类似,数组元素的下标由0开始。
Shell 数组用括号来表示,元素用”空格”符号分割开,语法格式如下:
1 array_name=(value1 ... valuen)
测试脚本:test.sh
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 31 32 33 34 35 36 37 38 39 40 41 42 # !/bin/bash my_array=("A" "B" "C" "D") # 我们也可以使用下标来定义数组: array_name[0]=value0 array_name[1]=value1 array_name[2]=value2 # 读取数组 echo "第一个元素为: ${my_array[0]}" echo "第二个元素为: ${my_array[1]}" echo "第三个元素为: ${my_array[2]}" echo "第四个元素为: ${my_array[3]}" # 获取数组所有元素 echo "数组的元素为: ${my_array[*]}" echo "数组的元素为: ${my_array[@]}" *和@的区别:如果遍历的变量用""括起来是,@会正常输出,而*会当成整体输出 # 获取数组的长度 echo "数组元素个数为: ${#my_array[*]}" echo "数组元素个数为: ${#my_array[@]}" # 遍历数组 方式1: my_arr=(AA BB CC) for var in ${my_arr[*]} do echo $var done 方式2: my_arr=(AA BB CC) my_arr_num=${#my_arr[*]} for((i=0;i<my_arr_num;i++)); do echo "-----------------------------" echo ${my_arr[$i]} done
select select表达式是bash的一种扩展应用,擅长于交互式场合。用户可以从一组不同的值中进行选择:
1 2 3 4 5 select var in ... ; do commond done .... now $var can be used ...
注意:select 是个无限循环,因此要记住用 break 命令退出循环,或用exit 命令终止脚本
测试脚本:test.sh
1 2 3 4 5 6 7 8 # !/bin/bash echo "你喜欢那个操作系统?" PS3="请输入你的选择:" select var in "Linux" "Mac" "Windows" "Other" do break; done echo "你选择了 $var 系统"
这里PS3作为select语句的shell界面提示符,注意:PS3一定要定义在select语句的前面
该脚本的运行结果如下:
1 2 3 4 5 6 7 What is your favourite OS? 1) Linux 2) Gnu Hurd 3) Free BSD 4) Other Please enter your choice:3 You have selected Free BSD
图片效果:
select和case的综合练习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # !/bin/bash echo "你想学习什么语言?" PS3="请输入你的选择:" # 设置提示字符串 select var in java c++ shell python do # 嵌套具体的操作,匹配你的选择 case $var in "java") echo "恭喜你选择成功.java最牛" ;; "c++") echo "驱动开发 网络优化 go 语言" ;; "shell") echo "运维必会" ;; python) echo "人工智能" esac break # 如果这里没有break 将会进行无限循环 done echo "你选择的是:$var"
加载其它文件的变量 和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
1 2 3 4 . filename # 注意点号(.)和文件名中间有一空格 或 source filename # 路径最好用绝对路径,不建议用相对路径
案例:定义两个文件 demo1.sh和demo2.sh,在test1中定义一个变量arr=(java c++ shell)
,在demo2中对arr进行循环打印输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 vim demo1.sh # !/bin/bash my_arr=(AA BB CC) vim demo2.sh # !/bin/bash source ./test1.sh # 加载test1.sh 的文件内容 导入多个,以最后的为准 for var in ${my_arr[*]} do echo $var done # 执行demo2 sh demo2.sh
好处 :
1. 数据源和业务处理分离
2. 复用代码扩展性更强
Shell编程综合案例 需求分析 1)每天凌晨 2:10 备份 数据库 atguiguDB 到 /data/backup/db
2)备份开始和备份结束能够给出相应的提示信息
3)备份后的文件要求以备份时间为文件名,并打包成 .tar.gz 的形式,比如:
2018-03-12_230201.tar.gz
4) 在备份的同时,检查是否有10天前备份的数据库文件,如果有就将其删除。
思路:
1 2 # cd /usr/sbin # vim mysql_db_backup.sh
脚本内容 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 31 32 33 34 #!/bin/bash #完成数据库的定时备份 #备份的路径 BACKUP=/data/backup/db #当前的时间作为文件名 DATETIME=$(date +%Y_%m_%d_%H%M%S) #可以输出变量调试 #echo $DATETIME #echo ${DATETIME} # 用中括号包裹起来也可以输出 echo "=========开始备份=======" echo "=========备份的路径是: $BACKUP/$DATETIME.tar.gz=======" #主机 HOST=localhost #用户名 DB_USER=root #密码 DB_PWD=root #备份数据库名 DATABASE=atguiguDB #创建备份的路径 #如果备份的路径文件夹存在,就使用,否则就创建 [ ! -d "$BACKUP/$DATETIME" ] && mkdir -p "$BACKUP/$DATETIME" #执行mysql的备份数据库的指令 mysqldump -u${DB_USER} -p${DB_PWD} --host=$HOST $DATABASE | gzip > $BACKUP/$DATETIME/$DATETIME.sql.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 "=========备份文件成功==========="
添加到定时任务调度 1 2 3 $ crontab -e 编写任务命令: 10 2 * * * /usr/sbin/mysql_db_backup.sh
shell脚本部署jar 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 31 # !/bin/bash # jar path APP_NAME=/apps/xxl-job/xxl-job-admin-2.3.1-SNAPSHOT.jar # log pathLOG_NAME=/apps/logs/xxl-job/xxl-job-admin.log # kill processpid=`ps -ef|grep $APP_NAME | grep -v grep | awk '{print $2}'` kill -9 $pid echo "$pid kill success" sleep 2 # check jar exist, yes ? start and open log file if test -e $APP_NAME then echo "app exist, start" # start jar nohup java -Xms512m -Xmx512m -Xmn128m -Xss256K -XX:PermSize=64m -jar $APP_NAME >$LOG_NAME 2>&1 & # start success, open log file tail -f $LOG_NAME # file not exist, prompt! else echo "$APP_NAME is not exist, please check it out" fi
清理日志 清除当前目录下的所有日志文件
1 find . -name "*.txt" -type f -print -exec rm -rf {} \;
更新于2020年12月11日14:02:40
1 2 3 4 这几天脚本一直跑失败了。手动执行可以,自动就不行,一直不知道为什么 群里老大和我说,服务器磁盘满了。。。我心里咯噔一下 莫非是因为磁盘满了。所以任务执行失败了。 裂开
查询FullLink目录及子目录的大文件
1 2 3 4 5 6 7 find . -type f -size +800M 我们仅仅能看到超过800M大小的文件的文件名称,但是对文件的信息(例如,文件大小、文件属性)一无所知,那么能否更详细显示一些文件属性或信息呢 find . -type f -size +800M -print0 | xargs -0 ls -l 当我们只需要查找超过800M大小文件,并显示查找出来文件的具体大小,可以使用下面命令 find . -type f -size +800M -print0 | xargs -0 du -h 如果你还需要对查找结果按照文件大小做一个排序,那么可以使用下面命令 find . -type f -size +800M -print0 | xargs -0 du -h | sort -nr
CROND任务调度 任务调度原理:
任务调度:是指系统在某个时间执行的特定的命令或程序。
任务调度分类:
1.系统工作:有些重要的工作必须周而复始地执行。如病毒扫描等
2.个别用户工作:个别用户可能希望执行某些程序,比如对mysql数据库的备份。
crontab 进行 定时任务的设置。
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 31 32 crontab -e: 编辑crontab定时任务 crontab –r:终止任务调度(删除当前用户所有的crontab任务)。 crontab –l:列出当前有那些任务调度(查询crontab任务) service crond restart [重启任务调度] 简单任务入门: 设置任务调度文件:/etc/crontab 设置个人任务调度。执行crontab –e命令。 接着输入任务到调度文件 如:*/1 * * * * ls –l /etc/ > /tmp/to.txt 意思说每小时的每分钟执行ls –l /etc/ > /tmp/to.txt命令 占位符详解: 第一个"*" 一小时当中的第几分钟 0-59 第二个"*" 一天当中的第几小时 0-23 第三个"*" 一个月当中的第几天 1-31 第四个"*" 一年当中的第几月 1-12 第五个"*" 一周当中的星期几 0-7(0和7都代表星期日) 特殊符号说明 * 代表任何时间。 比如第一个“*”就代表一小时中每分钟都执行一次的意思。 , 代表不连续的时间。 比如“0 8,12,16 * * * 命令”,就代表在每天的8点0分,12点0分,16点0分都执行一次命令 - 代表连续的时间范围。 比如“0 5 * * 1-6命令”,代表在周一到周六的凌晨5点0分执行命令 */n 代表每隔多久执行一次。 比如“*/10 * * * * 命令”,代表每隔10分钟就执行一遍命令 特定时间执行任务案例 45 22 * * * 命令 在22点45分执行命令 0 17 * * 1 命令 每周1 的17点0分执行命令 0 5 1,15 * * 命令 每月1号和15号的凌晨5点0分执行命令 40 4 * * 1-5 命令 每周一到周五的凌晨4点40分执行命令 */10 4 * * * 命令 每天的凌晨4点,每隔10分钟执行一次命令 0 0 1,15 * 1 命令 每月1号和15号,每周1的0点0分都会执行命令。注意:星期几和几号最好不要同时出现,因为他们定义的都是天。非常容易让管理员混乱。
案例1:每隔1分钟,就将当前的日期信息,追加到 /tmp/mydate 文件中
1 2 3 4 5 # 1. 在/home/test下创建脚本 myTask.sh。 编写内容: date >> /tmp/mydate # 2. 授权脚本: chmod 744 myTask.sh (颜色由普通白色变可执行文件绿色) # 3. 任务调度: crontab -e 编写任务调度命令: */1 * * * * /home/test/myTask.sh
案例2:每隔1分钟, 将当前日期和日历都追加到 /tmp/mycal 文件中
1 2 3 4 5 6 7 # 1. 在/home/test下创建脚本 myTask2.sh。 编写内容: date >> /tmp/mycal cal >> /tmp/mycal # 2. 授权脚本: chmod 744 myTask2.sh (颜色由普通白色变可执行文件绿色) # 3. 任务调度: crontab -e 编写任务调度命令: */1 * * * * /home/test/myTask2.sh
案例3: 每天凌晨2:00 将mysql数据库 testdb ,备份到文件中。
1 2 3 4 5 # 1. 在/home/test下创建脚本 myTask3.sh。 编写内容: /usr/local/mysql/bin/mysqldump -u root -proot testdb > /tmp/mydb.bak # 2. 授权脚本: chmod 744 myTask3.sh (颜色由普通白色变可执行文件绿色) # 3. 任务调度: crontab -e 编写任务调度命令: 0 2 * * * /home/test/myTask3.sh
猜字游戏 游戏规则为:程序内置一个1到100 之间的数字作为猜测的结果,由用户猜测此数字。用户每猜测一次,由系统提示猜测结果:大了、小了或者猜对了;直到用户猜对结果,则提示游戏结束。
创建脚本:guess_number.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # !/bin/bash # 生成100以内的随机数 提示用户猜测 猜对为止 # random 系统自带,值为0-32767任意数 # 随机数1-100 num=$[RANDOM%100+1] # read 提示用户猜数字# if 判断while : do read -p "计算机生成了一个 1‐100 的随机数,你猜: " cai if [ $cai -eq $num ] then echo "恭喜,猜对了" exit elif [ $cai -gt $num ] then echo "不巧,猜大了" else echo "不巧,猜小了" fi done
一键安装jdk 在做这个案例之前,将之前安装的JDK全部删除:
1.删除jdk安装目录
1 rm -rf /export/server/jdk1.8.0_241/
2.删除/etc/profile中的JDK环境变量配置内容
3.让修改生效
4.安装脚本jdk_install.sh
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 31 32 33 34 35 36 37 38 39 40 41 # !/bin/bash # 1.提示安装jdk echo "现在开始安装jdk" sleep 1 # 休眠1秒钟 # 2.删除centos自带的jdk oldjava=$(rpm -qa | grep java ) for old in ${oldjava} do # echo $old rpm -e --nodeps $old done # 3.创建安装目录/export/server, 进入安装目录 java_path="/export/server" if [ ! -d $java_path ] then mkdir -p $java_path fi # 4:解压jdk安装包 tar -xvf /export/software/jdk-8u241-linux-x64.tar.gz -C $java_path # 6.设置环境变量 JAVA_HOME="/export/server/jdk1.8.0_241" grep "JAVA_HOME" /etc/profile if [ $? -ne 0 ] then # JAVA_HOME echo "--------------JAVA_HOME------------------" echo 'export JAVA_HOME=/export/server/jdk1.8.0_241' >> /etc/profile # PATH echo "--------------PATH------------------------" echo 'export PATH=:$JAVA_HOME/bin:$PATH' >> /etc/profile fi # 7.加载环境变量 source /etc/profile # 8.提示用户安装成功,查看jdk安装版本 echo "恭喜您,jdk安装成功!" java -version
执行该脚本
1 2 chmod +x jdk_install.sh ./jdk_install.sh
数据库定时备份 1)每天凌晨1:15备份 数据库 itcast_shop到 /export/data/db目录
2)备份开始和备份结束能够给出相应的提示信息
3)备份后的文件要求以备份时间为文件名,并打包成 .tar.gz 的形式,比如:
2020-05-12_021501.tar.gz
4)在备份的同时,检查是否有10天前备份的数据库文件,如果有就将其删除
脚本mysqldump_demo.sh
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 31 32 33 34 35 36 # !/bin/bash # 完成数据库的定时备份 # 备份的路径 BACKUP=/export/data/db # 当前时间作为文件名 DATETIME=$(date +%Y_%m_%d_%H%M%S) # 可以通过输出变量来调试 # echo ${DATETIME} echo "---------------------开始备份数据库---------------------" echo "---------------------备份的路径是$BACKUP/$DATETIME.tar.gz---------------------" # 主机ip地址 HOST=192.168.88.161 # 数据库用户名 DB_USER=root # 数据库密码 DB_PWD=123456 # 数据库名 DATABASE=itcast_shop # 创建备份路径 # 如果备份的文件夹路径存在的话,就直接使用,否则就创建 [ ! -d "${BACKUP}/${DATETIME}" ] && mkdir -p "${BACKUP}/${DATETIME}" # 执行mysql的备份数据库的指令 /export/server/mysql-5.7.29/bin/mysqldump -u${DB_USER} -p${DB_PWD} --host=${HOST} ${DATABASE} > ${BACKUP}/${DATETIME}/${DATETIME}.sql # 打包备份文件 cd ${BACKUP} tar -czvf ${DATETIME}.tar.gz ${DATETIME} # 删除临时目录 rm -rf ${BACKUP}/${DATETIME} # 删除10天前的备份文件 find ${BACKUP} -mtime +10 -name "*.tar.gz" -exec rm -rf {} \; echo "-------------------------备份成功-------------------------"
代码说明:
代码中HOS、DB_USER、DB_PWD这三个变量的值要根据自己的情况来设置
mysqldump就是系统内置的用来备份数据库的工具
语句\find ${BACKUP} -mtime +10 -name "*.tar.gz" -exec rm -rf {} \;
解释:
1)find命令只是来查找匹配的文件,如果查到文件之后还需要进一步的操作,则需要添加-exec
参数,{}
表示find查询出来的文件名字。
2)-exec
参数后面跟的是要执行的command命令,它的终止是以;为结束标志的,所以这句命令后面的分号是不可缺少的,考虑到各个系统中分号会有不同的意义,所以前面加反斜杠。
3)-mtime
文件状态修改时间 ,-mtime +10
:表示10天前的文件
导入脚本:
将itcast_shop.sql提前导入到数据库中
1.选择导入sql标本
2.指定要导入的脚本文件
执行完之后,点击完成
3.刷新
运行脚本:
1 2 3 4 5 6 7 8 9 10 11 [root@node1 shell]# vim mysqldump_demo.sh [root@node1 shell]# chmod +x mysqldump_demo.sh [root@node1 shell]# ./mysqldump_demo.sh 2020_05_12_102918 2020_05_12_102918 ---------------------开始备份数据库--------------------- ---------------------备份的路径是/export/data/db/2020_05_12_102918.tar.gz--------------------- mysqldump: [Warning] Using a password on the command line interface can be insecure. 2020_05_12_102918/ 2020_05_12_102918/2020_05_12_102918.sql.gz -------------------------备份成功-------------------------
结果查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1:进入数据库备份目录 [root@node1 shell]# cd /export/data/db/ [root@node1 db]# ls 2020_05_12_102918.tar.gz 2:解压备份压缩包 [root@node1 db]# tar -zxvf 2020_05_12_102918.tar.gz 2020_05_12_102918/ 3:进入解压后目录 [root@node1 db]# cd 2020_05_12_102918/ [root@node1 2020_05_12_102918]# ls 2020_05_12_102918.sql 4:查看sql脚本内容 [root@node1 2020_05_12_102918]# vim 2020_05_12_102918.sql
查看sql脚本内容,发现备份成功!
定时执行:
这里配置定时任务,需要使用Linux的定时工具crontab,crontab语法如下:
在以上各个字段中,还可以使用以下特殊字符:
星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 分(0~59) 时(0~23) 日(0~31,但是你需要考虑你月的天数) 月(1~12) 周(0~6 0=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT) "* * * * *" #每隔一分钟触发 "15 * ? * *" #每小时的第15分触发 "15 10 ? * *" #每天上午10:15触发 "15 10 * * ?" #每天上午10:15触发 "* 14 * * ?" #在每天下午2点到下午2:59期间的每1分钟触发 "0/5 14 * * ?" #在每天下午2点到下午2:55期间的每5分钟触发 "0/5 14,18 * * ?" #在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0-5 14 * * ?" #在每天下午2点到下午2:05期间的每1分钟触发 "10,44 14 ? 3 WED" #每年三月的星期三的下午2:10和2:44触发
命令行输入crontab -e
进入编辑模式
编辑,写入以下内容
1 15 1 * * * /export/data/shell/mysqldump_demo.sh
wq保存退出即可
配置定时任务调度成功!这样的话,每天的凌晨 1:15就会自动的备份数据库