本文发布于792天前,本文最后更新于781 天前,其中的信息可能已经过时,如有错误请留言或评论。
[lwptoc title="目录"]
什么是shell脚本
当命令或者程序语句写在文件中,我们执行文件,读取其中的代码,这个程序文件就称之为shell脚本。
在shell脚本里定义多条Linux命令以及循环控制语句,然后将这些Linux命令一次性执行完毕,执行脚本文件的方式称之为,非交互式方式。
- windows中存在*.bat 批处理脚本
- Linux中常用*.sh 脚本文
bash特性
[root@localhost ~] # echo $HISTSIZE 3000 #存放用户执行的历史命令,写入文件 [root@localhost ~]# echo $HISTFILE /root/.bash_history # history命令 #-c清除历史(/root/.bash_history 中仍然存在) #-r恢复历史 #!历史id,快速执行历史命令 #!!快速执行上次命令
- 文件路径tab键补全
- 命令补全
- 快捷键ctrl + a,e,u,k,l
- 通配符
- 命令历史
- 命令别名
- 命令行展开
shell变量
- 变量定义与赋值,注意变量与值之间不得有空格
- 变量名,变量类型,bash默认把所有变量都认为是字符串
- bash变量是弱类型,无需事先声明类型,是将声明和赋值同时进行
- 变量替换/引用
- 变量名规则
- 名称定义要做到见名知意,不得引用保留关键字(help检查保留字)
- 只能包含数字、字母、下划线
- 不能以数字开头
- 不能用标点符号
- 变量名严格区分大小写
- 变量的作用域
- 本地变量,只针对当前的shell进程
- 环境变量,也称为全局变量,针对当前shell以及其任意子进程,环境变量也分自定义、内置两种环境变量
- 局部变量,针对在shell函数或是shell脚本中定义
- 位置参数变量:用于shell脚本中传递的参数
- 单引号变量,不识别特殊语法
- 双引号变量,能识别特殊符号
- 反引号变量可当命令
#1.每次调用bash/sh解释器执行脚本,都会开启一个子shell,因此不保留当前的shell变量,通过pstree命令检查进程树 #2.调用source或者点.符号,在当前shell环境加载脚本,因此保留变量 #演示 #1.开启子shell的执行方式 [root@hocalhost ~]#name="奥利给" [root@hocalhost ~]# cat make_vars.sh name="大西瓜" [root@hocalhost ~]# echo $name 奥利给 [root@hocalhost ~]# bash make_vars.sh [root@hocalhost ~]# echo $name 奥利给 #2.不开启子shell的执行方式 [root@hocalhost ~]# source make_vars. sh [root@hocalhost ~]# echo $name 大西瓜
- 自定义变量
- 变量赋值︰varName=value
- 变量引用:${varName} 、 $varName
取出变量值
- 单引号,所见即所得,强引用
- 双引号,输出引号里所有内容,识别特殊符号,弱引用
- 无引号,连续的符号可以不加引号,有空格则有歧义,最好使用双引号
- 反引号,引用命令执行结果,等于$()用法
特殊参数变量
shell的特殊变量,用在如脚本,函数传递参数使用,有如下特殊的,位置参数变量
- $0 获取shell脚本文件名,以及脚本路径
- $n 获取shell脚本的第n个参数,n在1~9之间,如$1 ,$2,$9 ,大于9则需要写,${10},参数空格隔开
- $# 获取执行的shell脚本后面的参数总个数
- $* 获取shell脚本所有参数,不加引号等同于$@作用,加上引号""$*"作用是接收所有参数为单个字符串,"$1$2.."
- $@ 不加引号,效果同上,加引号,是接收所有参数为独立字符串,如"$1"“$2”"$3" ...,空格保留
- $*和$@的区别你了解吗?
- $*和$@都表示传递给函数或脚本的所有参数
- 当$*和$不被双引号”"包围时,它们之间没有任何区别,都是将接收到的每个参数看做―份数据,彼此之间以空格来分隔。
- 但是当它们被双引号"“包含时,就会有区别了:
“$*"会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。 "yu chao 180 180 180 180"
"$@"仍然将每个参数都看作一份数据,彼此之间是独立的。 "yu""chao""180""180""18o" 比如传递了5个参数,那么对于""$*"来说,这5个参数会合并到一起形成―份数据,它们之间是无法分割的;而对于"$@"来说,这5个参数是相互独立的,它们是5份数据。 - 如果使用echo直接输出"$*""和"$@"做对比,是看不出区别的;但如果使用for循环来逐个输出数据,立即就能看出区别来。
- $*和$@都表示传递给函数或脚本的所有参数
特殊状态变量
- $? 上一次命令执行状态返回值,0正确,非e失败
- $$ 当前shell脚本的进程号
- $! 上一次后台进程的PID
- $_ 取出上一次执行命令的最后一个参数
- 查找方式man bash
搜索special Parameters - 特殊变量: shell内置的特殊功效变量
- $?
0:成功
1-255︰错误码
- $?
#脚本控制返回值并获取当前脚本进程号 [root@localhost ~]# cat t1.sh # !/bin/bash # $#获取参数个数 -ne不等于的情况 &&并且 [ $# -ne 2 ] && { echo "must be two args" exit 119 #终止程序运行,且返回119状态码,提供给当前shell的$?变量,若是在函数里可以return 119用法 } echo "没毛病,就是2个参数" echo "当前的脚本id是:$$"
环境变量设置
环境变量一般指的是用export内置命令导出的变量,用于定义shell的运行环境、保证shell命令的正确执行。
shell通过环境变量确定登录的用户名、PATH路径、文件系统等各种应用。
环境变量可以在命令行中临时创建,但是用户退出shell终端,变量即丢失,如要永久生效,需要修改环境变量配置文件
- 用户个人配置文件~/ .bash_profile 、 ~/.bashrc远程登录用户特有文件
- 全局配置文件/etc/profile 、/etc/bashrc ,且系统建议最好创建在/etc/profile.d/ ,而非直接修改主文件,修改全局配置文件,影响所有登录系统的用户
- 每个用户都有自己的环境变量配置文件,~/.bash_profile ~/,bashrc,且以个人配置文件,优先加载变量,读取,以个人的优先生效
当你需要给所有用户都使用某个变量,写入全局即可/etc/profile
检查系统环境变量的命令
- set,输出所有变量,包括全局变量、局部变量
- set命令能够找到当前的shell环境中的所有变量,以及包括局部变量(sh脚本文件中定义的变量)
- env,只显示全局变量
- declare,输出所有的变量,如同set
- export,显示和设置环境变量值
撤销环境变量
- unset 变量名,删除变量或函数。
设置只读变量
- readonly ,只有shell结束,只读变量失效
- 直接readonly显示当前系统只读变量
[root@localhost ~]# readonly name="超哥"
[root@localhost ~]# name="chaochao"
-bash: name:只读变量
系统保留环境变量关键字
- bash内嵌了诸多环境变量,用于定义bash的工作环境
[root@localhost ~]# export |awk -F '[ :=]' '{print $3}'
#列出所有环境变量名
环境变量初始化与加载顺序
shell子串
bash一些基础的内置命令
- echo命令
- -n不换行输出 -e解析字符串中的特殊符号
- 特殊符号如 \n换行 \r回车 \t 制表符四个空格 \b 退格
- eval
执行多个命令 - exec
不创建子进程,执行后续命令,且执行完毕后,自动exit
子串用法
#指定字符内容截取(区分大小写) a*c匹配开头为a,中间任意个字符,结尾为c的字符串 a*C匹配开头为a,中间任意个字符,结尾为C的字符串 name="yuchao" #该变量有索引从0开始 ${变量} 返回变量值 ${#变量} 返回变量长度,字符长度 ${变量:start} 返回变量start数值之后的字符,且包含start ${变量:start:length} 提取start之后的length限制的字符 ${变量#word} 从变量开头删除最短匹配的word子串 ${变量##word} 从变量开头,删除最长匹配的word ${变量%word} 从变量结尾删除最短的word ${变量%%word} 从变量结尾开始删除最长匹配的word ${变量/pattern/string} 用string代替第一个匹配的pattern ${变量//pattern/string} 用string代替所有的patterna
案例
- Shell变量截取字符串通常有两种方式:
- 从指定位置开始截取和,从指定字符(子字符串)开始截取。
- 从指定位置开始截取
这种方式需要两个参数︰除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串。
既然需要指定起始位置,那么就涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数。答案是Shell 同时支持两种计数方式。
- 从字符串左边开始计数
如果想从字符串的左边开始计数,那么截取字符串的具体格式如下︰
${ string: start :length}
其中, string是要截取的字符串, start是起始位置(从左边开始,从0开始计数,length是要截取的长度(省略的话表示直到字符串的末尾)[root@localhost ~]# name=suyou180 [root@localhost ~]# echo $name suyou180 [root@localhost ~]# echo ${name} suyou180 [root@localhost ~]# echo ${#name} 8 [root@localhost ~]# echo ${name:3}#截取子串 ou180 [root@localhost ~]# echo ${name:2:4}#设置起点以及元素长度 you1
- 计算变量长度
#解释wc命令参数用法 -l统计行数 -L找出最长一行统计元素数 #利用数值计算expr命令 [root@localhost ~]#expr length "${name}" 8 # awk统计长度,length函数 [root@localhost ~]#echo "${name}" | awk '{print length($0)}' 8 #for循环的使用 #for n in {1..3} ;do str1=`seq -s ": " 10`;echo$str1;done #{x..y}从x到y #seq x 生成一个x序列 seq -s "字符" x 指定用某个字符分隔 #do 你要执行的命令 done #结合time命令${#变量}计算时间是13s [root@localhost ~]#time for n in {1..10000};do char= `seq -s "chaoge" 10`;echo ${#char} &>/dev/null; done real 0m13.956S 实际运行的时间 user 0m6.005S 用户态执行的时间 sys 0m5.868S 内核态执行的时间 #结合wc -L命令计算时间 [root@localhost ~]#time for n in {1..10000};do char= `seq -s "chaoge" 10`;echo ${char}|wc -L &>/dev/null; done real 0m49.262s user 0m18.843s sys 0m24.905s #结合expr命令length函数计算时间 [root@localhost ~]#time for n in {1..10000};do char= `seq -s "chaoge" 10`;expr length "${char}" &>/dev/null; done real 0m28.511s user 0m11.960s sys 0m12.046s [root@localhost ~]#time for n in {1..10000};do char= `seq -s "chaoge" 10`;echo ${char}|awk '{print length($0)}' &>/dev/null; done real 0m44.309s user 0m17.704s sys 0m21.282s #shell编程,尽量使用linux内置的命令,内置的操作,和内置的函数,效率最高,尽可能的减少管道符的操作
- 删除匹配到的子串
${变量#word} 从变量开头删除最短匹配的word子串
${变量##word} 从变量开头,删除最长匹配的word
${变量%word} 从变量结尾删除最短的word
${变量%%word} 从变量结尾开始删除最长匹配的word - 替换字符串
${变量/pattern/string} 用string代替第一个匹配的pattern
${变量//pattern/string} 用string代替所有的patterna- 批量修改文件名:
- ls 配合通配符获得需要修改的文件名
- 利用循环遍历每一个要修改的文件名,使用反引号配合修改
- 批量修改文件名:
- 特殊shell扩展变量
- 如果parameter变量值为空,返回word字符串,赋值给result变量
result=$iparameter:-word} - 如果para变量为空,则word替代变量值,且返回其值
result=${parameter:=word} - 如果para变量为空,word当作stderr输出,否则输出变量值用于设置变量为空导致错误时,返回的错误信息
${parameter:?word} - 如果para变量为空,什么都不做,否则word返回
${parameter:+word}
- 如果parameter变量值为空,返回word字符串,赋值给result变量
父子shell
- source和点,执行脚本,只在当前的shell环境中执行生效
- 指定bash sh解释器运行脚本,是开启subshell,开启子shell运行脚本命令
- ./script,都会指定shebang,通过解释器运行,也是开启subshell运行命令
父shell的概念
#pstree看到如下结果,就是父shell环境 -sshd——sshd—bash—pstree #ps进程管理命令,查看 ps -ef -f 显示UID,PID,PPID -e列出所有进程的信息,如同-A选项 --forest
子shell的概念
创建进程列表(创建子shell)
- shell的进程列表理念,需要使用()小括号,如下执行方式,就称之为,进程列表
(cd ~; pwd;ls ;cd /tmp/ ; pwd ; ls)
加上小括号就是开启子shell运行命令
检测是否在子shell环境中
- linux默认的有关shell的变量BASH_SUBSHELL
#该变量的值特点,如果是0,就是在当前shell环境中执行的,否则就是开辟子shell去运行的
检测是否开启了子shell运行命令
- cd ~; pwd;ls ;cd /tmp/ ; pwd;ls ;echo $BASH_SUBSHELL
明确开启子shell运行的命令
- 进程列表,并且开启子shell运行
(cd ~; pwd;ls ;cd /tmp/ ; pwd ; ls ;echo $BASH_SUBSHELL) - 一个小括号开启一个子shell运行命令,还可以嵌套多个
内置命令、外置命令
- 内置命令︰在系统启动时就加载入内存,常驻内存,执行效率更高,但是占用资源
- 外置命令︰系统需要从硬盘中读取程序文件,再读入内存加载,外置命令,也称之为,自己单独下载的文件系统命令,处于bash shell之外的程序。
通过linux的type命令,可以验证是否是内置、外置命令
#查看linux的内置shell命令 compgen -b
- 外置命令的特点是:—定会开启子进程执行
- 内置命令
内置命令不会产生子进程去执行
内置命令和shell是为一体的,是shell的一部分,不需要单独去读取某个文件,系统启动后,就执行在内存中了
shell脚本开发
变量在脚本中的使用
- 变量被引用的时候,会赋予其值,脚本中的变量,在shell执行完毕后,就会消失,根据执行的方式决定
- 当你用不同的方式,执行脚本,产生的后果也不—样
- source和.是在当前的shell环境中加载变量,执行脚本
- bash或sh 去执行脚本的时候,是开启子shell运行的,变量也是在子shell环境中加载,子shell退出后,变量也就消失了
linux,shell变量的替换引用
- shell一大特性,就是可以从命令的执行结果中,再提取结果,因此特别适合编写脚本:
- ${vars} 取出变量结果
- $() 在括号中执行命令,且拿到命令的执行结果
- `` 在反引号中执行命令,且拿到命令的执行结果
- () 开启子shell执行命令结果
- $vars 取出变量结果
数值计算
- 双小括号
- 案例:
- 接受用户输入的命令,-p参数后面写,给用户看到的提示信息
read -p "提示信息" 接受用户输入的变量
read -p "Please input your number : " firstnum - 中括号里面前后必须有一个空格,是固定的语法
-n 参数是if的语句,对字符串判断,如果字符串为空,条件就不成立,如果字符串不为空,条件成立
-ne不等于 -ge大于等于 -le小于等于 -lt小于
若输入为qwe123,sed的作用就是把字符串“qwe123”进行替换,把所有的数字都替换为空,那么就剩下其他非数字的内容了
if [ -n " `echo $firstnum| sed 's/[0-9]//g'`" ]
then
条件成立进行的操作
fi
- 接受用户输入的命令,-p参数后面写,给用户看到的提示信息
- bc命令(计算器,直接输入bc可进入交互模式)
常与管道符连用
shell条件测试
- shell变量除了直接赋值,或者脚本传参,还有就是read命令读取。read也是内置命令。
-p 设置提示信息 -t 等待用户输入超时, timeout
read -p "请输入:” vars - shell提供条件测试的语法 1.test命令 2.[]中括号
test条件测试
- test命令评估一个表达式,它的结果是真,还是假,如果条件为真,那么命令执行状态码结果就为e否则就是不为e,通过$?取值
- test命令的参数
1. 关于某个文件名的『类型』侦测(存在与否),如test -e filename -e 该『文件名』是否存在?(常用) -f 该『文件名』是否为文件(File)?(常用) -d 该『文件名』是否为目录(directory)?(常用) -b 该『文件名』是否为一个block device装置? -c 该『文件名』是否为一个 character device装置? -S 该『文件名』是否为一个Socket文件? -p 该『文件名』是否为一个FIFO (pipe)文件? -L 该『文件名』是否为一个连结档? 2.关于文件的权限侦测,如test -r filename -r 侦测该文件名是否具有『可读的属性? -w 侦测该文件名是否具有『可写』的属性? -x 侦测该文件名是否具有『可执行』的属性? -u 侦测该文件名是否具有『SuID的属性? -g 侦测该文件名是否具有『SGID』的属性? -k 侦测该文件名是否具有『sticky bit的属性? -s 侦测该文件名是否为『非空白文件』? 3.两个文件之间的比较,如:test file1 -nt file2 -nt (newer than)判断 file1是否比 file2新 -ot (older than)判断file1是否比 file2 旧 -ef 判断file2与file2是否为同一文件,可用在判断 hard link 的判定上。主要意义在判定,两个文件是否均指向同一个inode哩! 4.关于两个整数之间的判定,例如test n1 -eq n2#针对变量数值的大小比较判断 -eq 两数值相等(equal) -ne 两数值不等(not equal) -gt n1大于n2 (greater than) -lt n1小于n2 ( less than) -ge n1大于等于n2 (greater than or equal) -le n1小于等于n2 ( less than or equal) 5.判定字符串的数据 test -z string判定字符串是否为0?若string为空字符串,则为 true test -n string判定字符串是否非为0?若 string为空字符串,则为 false。注:-n亦可省略 test str1 = str2判定str1是否等于str2 ,若相等,则回传 truetest str1 != str2判定str1是否不等于str2 ,若相等,则回传 false 6.多重条件判定,例如:test -r filename -a -x filename -a(and)两状况同时成立!例如test -r file -a -x file,则file同时具有r与×权限时,才回传true。 -o(or)两状况任何一个成立!例如test -r file -o -x file,则 file具有r或×权限时,就可回传 true。 !反相状态,如test ! -x file ,当file 不具有×时,回传 true
[]条件测试
- 脚本中经常进行条件测试,用的最多的,都是中括号[]test和[]的作用是一样
注意的点:中括号,前后的空格必须有。 - 注意!!!在条件测试中使用变量,必须添加双引号
- 在中括号中,使用数学比较符号,请添加转义符号\
- 双中括号
对单中括号的补充,双中括号还支持正则处理,在双中括号中,就不需要转义符号了
if语句
if实践
- awk指定读取文件中的某一行的某个字段
awk可以设置条件来输出文件中m行到n行中每行的指定的k字段,使用格式如下
awk ‘NR==m,NR==n {print $k}’ path/filename
m,n,k表示实在的数值。如果要用变量来表示m,n的值,则变量需要用单引号将其引起来。
NR,{print }是awk命令在此用法下的规定字段;path/filename表示读取文件的路径及文件名。
NR是指awk正在处理的记录位于文件中的位置(行号)
NF是指awk正在处理的记录包含几个域(字段),这与域分隔符有关,默认为空
开发mysql监控脚本
- 服务器本地端口监控mysql状态
[root@localhost learnshell]# netstat -tunlp | grep 3306 tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 1499/mysqld [root@localhost learnshell]# netstat -tunlp | grep mysql tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 1499/mysqld [root@localhost learnshell]# ss -tunlp | grep mysql tcp LISTEN 0 50 *:3306 *:* users:(("mysqld",pid=1499,fd=14)) [root@localhost learnshell]# lsof -i tcp:3306 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME mysqld 1499 mysql 14u IPv4 41955 0t0 TCP *:mysql (LISTEN) [root@localhost learnshell]#
- 远程监控mysql端口
- nmap ip
- echo -e "\n" | telnet 127.0.0.1 mysql端口
- 进程检查
- [root@localhost learnshell]# ps aux | grep mysql | grep -v grep
php连接mysql
#1.准备好php的环境依赖 yum remove php-mysql yum install php-mysqlnd php #2.开发php连接mysql的代码 vim mysql_test.php <?php $mysql_id=mysql_connect( "localhost" , "root" , "xxx") or mysql_error(); if ($mysql_id){ echo "mysql connection successful, 666 ! "; }else{ echo mysql_error(); }
python连接mysql
#1.安装python3开发环境依赖 yum install python3 python3-devel python3-pip #2.通过python的包管理工具,安装连接mysql的模块 pip3 install pymysql #3.开发python连接mysql的代码,注意python的代码,空格数量是严格把控的 import pymysql db=pymysql.connect( host="localhost", user= 'root', password='xxxx', db= 'mysql', charset='utf8' ) #操控数据库 cursor=db.cursor() cursor.execute('select version()') data=cursor.fetchone() print("数据库连接正确,该数据库版本是:%s"%data) db.close()
rsync起停脚本
[root@localhost learnshell]# rpm -qa rsync rsync-3.1.2-11.el7_9.x86_64 [root@localhost learnshell]# ls /etc/rsyncd.conf /etc/rsyncd.conf [root@localhost learnshell]# netstat -tunlp | grep rsync [root@localhost learnshell]# /usr/bin/rsync --daemon [root@localhost learnshell]# netstat -tunlp | grep rsync tcp 0 0 0.0.0.0:873 0.0.0.0:* LISTEN 10087/rsync tcp6 0 0 :::873 :::* LISTEN 10087/rsync [root@localhost learnshell]# vim /etc/init.d/cc_rsync if [ "$#" -ne 1 ];then echo "usage:$0 {start|stop|restart}" exit 1 fi if [ "$1" = "start" ];then /usr/bin/rsync --daemon sleep 2 #验证端口是否启动了 if [ `netstat -tunlp|grep rsync|wc -l` -ge 1 ];then echo "Rsync is started ! ! " exit 0 fi elif [ "$1" = "stop"];then killall rsync &>/dev/null sleep 2 if [ `netstat -tunlp|grep rsync|wc -l` -eq 0 ];then echo "Rsync is stopped ! ! " exit 0 fi elif [ "$1" = "restart" ];then killall rsync sleep 1 killpro=`netstat -tunlp|grep rsync |wc -l` /usr/bin/rsync --daemon sleep 1 startpro=`netstat -tunlp|grep rsync|wc -l` if [ "$killpro" -eq 0 -a "$startpro" -ge 1 ];then echo "Rsyncd is restarted ! ! " exit 0 fi else echo "usage:$0 {start|stop|restart}" exit 1 fi
shell函数实际开发
- shell函数定义的语法,标准shell函数定义
function 函数名(){
函数体
你想执行的linux命令。
return 返回值(可写可不写)
} - 当使用function关键字时可省略括号
- 最简可省略function与return,但必须有括号
有关函数执行的基础概念
-
- 执行shell函数,直接写函数名字即可,无需添加其他内容
- 函数必须先定义,再执行, shell脚本自上而下加载
- 函数体内定义的变量,称之为局部变量
- 函数体内需要添加return语句,作用是退出函数,且赋予返回值给调用该函数的程序,也就是shell脚本(在shell脚本中,定义,使用函数, shell脚本执行结束后,通过$?获取其return的返回值)
- return语句和exit不同
- return是结束函数的执行,返回一个(退出值、返回值)
- exit是结束shell环境,返回一个(退出值、返回值)给当前的shell
- 函数如果单独写入一个文件里,需要用source读取(写入文件中,用于加载)
- 函数内,使用local关键字,定义局部变量。
函数定义和执行,分开在不同的文件中
- linux自带的诸多脚本,都是基于该形式使用的
函数写在一个文件中,只定义,不执行
另外一个脚本,读取该函数文件,且加载该函数 - 利用.或者source命令读取shell脚本,能够加载其变量(即函数)到当前的shell环境中
此时直接输入函数名即可执行函数。
函数脚本传入参数
- 通过$1,$2等 获取参数