Shell基础知识
本文主要讲解基础的Shell编程知识
一、Shell基本知识
内置命令、外置命令
可以通过type 命令名字
查看是否为内置命令,还可以通过compgen -b
来查看系统全部的内置命令
内置命令:在系统启动时就加载入内存,常驻内存,执行效率高,但占用资源
外置命令:系统需要从硬盘读取程序文件,在读入内存加载
外置命令
也被称为,用户单独下载的文件系统命令,处于bash shell之外的程序
#一般存放在/bin;/usr/bin;/sbin;/usr/sbin
外置命令一定会开启子进程执行
内置命令和shell是一体的,是shell的一部分,不需要单独去读取某个文件,系统启动后就执行在内存中了
Shell分为局部作用域和全局作用域。在局部作用域中定义的变量不能通过全局作用域访问到
我们首先输入pstree
查看当前的Shell环境是zsh(当然你们的可能是Bash)。
>>>sshd───sshd───zsh───pstree
在这之后我们定义name="shell123"
,并通过echo $name
查看输出结果。现在你应该可以看到结果是“shell123”。这时我们输入zsh
进入子shell中(通常我们把之前的Shell环境叫做父Shell,在父Shell中在进入一层的Shell环境,我们称为子Shell),我们同样输入ehco $name
,可以看出此时运行的结果为空。
这是为什么?
这就要涉及到另外的一个概念:局部作用域。
局部作用域与全局作用域
局部作用域:主要是指在当前环境下,所产生的命名空间。如果你接触过python。局部作用域可以理解为函数题内的变量,而全局作用域可以理解为函数体外的变量。我们通过下面的代码来帮助你理解
a = 1
def test():
a = 2
print(a)
print(a)//1
test() //2
print(a)//1
我们可以看出来,局部变量不会影响到全局变量
同样我们如果想要知道那些局部作用域和全局作用域可以通过以下命令查看
set //输出所有变量,包括全局变量、局部变量
env //只显示全局变量
declare //输出所有遍历,等同于set
export //显示和设置环境变量
如果我们不想用某一个变量了怎么办?Linux中给我们提供了一个unset
方可以删除变量或者函数
readonly
方法是设置一个只读变量,后续不允许修改此变量的值,同样当shell结束时,会自动失效
bash多命令执行
ls /data;ls -alh;pwd
我们通过;
来区分语句
Shell有趣的特殊变量(**)
$0 #获取脚本文件名,以及脚本路径
$n #获取shell脚本的第n个参数,n在1-9之间,大于9需要写,${10},参数空格隔开
$# #获取执行的shell脚本的参数总个数
$* #获取shell脚本参数,不加引号等同于$@作用,加上引号"$*"作用是接收所有参数为单个字符串
$@ #不加引号同上,加引号,是接收所有参数为独立字符串
上述官方定义可能不太好理解,简单来说就是”@”是将每一个参数视为独立的字符串。
即,我们在位置参数处传递 bash zhen hao wan
这四个参数,"$*"
的结果为“shell zhen hao wan”
,而"$@"
的结果为"shell"、"zhen"、"hao"、"wan"
好了让我们回过头来接着看一下特殊状态变量
$? #上一次命令的返回状态,0表示正确
$$ #当前shell脚本的进程号
$! #上一次后台进程的pid
$_ #获取上次命令的最后一个参数
#脚本控制返回值的玩法
exit 数字#终止程序运行,返回状态码119,提供给当前shell的$?
父子Shell
父shell
- source和点,执行脚本,正在当前的shell环境中生效
- 指定bash解释器运行脚本,是开启subshell,开启子shell运行脚本命令
- ./script,都会知道shebang,指定解释器去运行
ps -ef --forest
树型显示所有进程
子shell
shell的进程列表理念,需要使用()小括号,如下执行方式为,进程列表
开启子shell执行下列命令
(cd ~;pwd;ls ;cd /tmp/;pwd;ls)
检测是否在子shell中
echo $BASH_SUBSHELL #检测是否开启了子shell的运行命令;0表示没有开启
习题
# 过滤出,格式化所有的linux环境变量名字
export | awk -F '[ :=]' '{print $3}'
shell子串
bash一些基础的内置命令
echo #打印 -n [不换号] -e [解析字符串中的特殊符号]
eval #表示执行多个命令 e.g. eval ls;pwd
exec #不创建子进程,执行后续命令,自动退出
export #定义变量
read #读取用户输入 -p [设置提示信息] -t[等待用户输入超时]
shell子串的花式用法
a*c #匹配以a开头,以c结尾的所有子串
${变量} #返回变量值
${#变量} #返回变量值的长度
${变量:start} #返回start数值之后的字符,且包含start的数字
${变量:start:length} #提取start之后length限制的字符
${变量#word} #从变量开头删除最短匹配的word子串
${变量##word} #从变量开头删除最长匹配的word子串
${变量%word} #从变量结尾删除最短匹配的word子串
${变量%%word} #从变量结尾删除最长匹配的word子串
#-------------------------替换---------------------------------#
${变量/pattern/string} #用string代替第一个匹配的pattern
${变量//pattern/string} #用string代替全部匹配的pattern
特殊的Shell扩展变量(*)
#如果prameter变量值为空,返回word字符串,赋值给result
result=${prameter:-word}
#如果prameter变量值为空,将word代替变量值,且返回其值
${prameter:=word}
#如果prameter变量值为空,word当做stderr输出,否则输出变量值
${prameter:?word}
#如果prameter变量值为空,什么都不做,否则返回word
${prameter:+word}
案例
统计命令执行时长
#简单案例
for number in {1..100}
do
echo $num
done
#方法1
time for n in {1...10000}
do
char=`seq -s "chaoge" 100`
echo ${#char} &>/dev/null
done
批量删除文件名
#1.删除单个文件名
mv 文件名 修改后的文件名
#2.批量删除文件名
for filename in `ls *fin*txt`
do
mv $filename `echo ${filename//_finished/}`
done
扩展变量的案例
$chaoge #先设置默认为空
result1=${chaoge:-apple}#>>$chaoge None;>>$result1 apple
result2=${chaoge:=apple}#>>$chaoge apple;>>$result1 apple
result3=${chaoge:?apple}#>>$chaoge None;>>$result1 apple
result4=${chaoge:+apple}#>>$chaoge None;>>$result1 None
shell数值计算
#运算符
+
-
* 需要转义
/
% #取余
== #相等
!= #不等
-eq #相等
-ne #不等
-gt #大于
-lt #小于
-ge #大于等于
-le #小于等于
-z #长度为0
-n #长度不为0
#shell扩展计算的方法
(())#用于整数的运算的常用运算符
++a #先计算+1,然后赋值给a
a++ #先赋值a,然后计算+1
let #用于整数的运算,类似于(())
let num=num+4
expr命令
expr 字符串 ":" "re表达式"
统计输入的字符串符合re表达式的长度
判断文件名后缀是否合法
if expr $1 ":" ".*\.jpg$" &> /dev/null
then
echo "的确以jpg结尾"
else
echo "不是以jpg结尾"
fi
bc命令
交互式操作、支持小数
数值比较
在[]和test中使用的比较符号 | 在(())和[[]]中使用的比较符号 | 说明 |
---|---|---|
-eq | ==或= | 相等 |
-ne | != | 不相等 |
-gt | > | 大于 |
-ge | >= | 大于等于 |
-lt | < | 小于 |
-le | <= | 小于等于 |
Shell条件测试
语法1:test <测试表达式>
#语法2: [[ <测试表达式> ]]
test的命令参数 e.g. test -e file1
-e 判断该文件是否存在,(普通文件,目录),存在为真,否则为假
-f 判断是否为文件
-d 判断是否为目录
-b 判断是否为二进制文件
-s 判断是否为sock
-p 判断是否为管道
-L 判断是否为连接档
----------------------------------------------------------
2.关于文件的权限检测 e.g. test -r file1
-r 可读
-w 可写
-x 可执行
-u 是否具有suid
-g 是否具有sgid
-k 是否具有sticky bit属性
-s 是否为非空白文件
----------------------------------------------------------
3.两个文件之间的比较 e.g. test file1 -nt file2
-nt 判断 file1 是否比file2新
-out 判断 file1 是否比file2旧
-ef 判断file1和file2是否为同一个文件上,基于inode判断,即链接指针
----------------------------------------------------------
4.判断两个整数
----------------------------------------------------------
5.判断字符串数据 e.g. test -z str1
-z 是否空,是返回true
-n 是否非空,是返回true
!-n可省略
----------------------------------------------------------
6.多重条件判定 e.g. test -r file1 -a -x file2
-a 两状况同时成立
-o 或操作
! 反相操作
语法2:[ ]表达式,单中括号
# []
#test 和 []的作用是一样的
# 进行比较判断的时候>、<需要加上转义字符
#!注意,在条件测试中使用变量,必须添加双引号
[ -n "$file1" ]
字符串比较测试
# 比较两个字符串是否相等
-n "字符串" #字符串长度不为0
-z "字符串" #字符串长度为0
"串1" = "串2" #字符串1=字符串2
"串1" != "串2" #字符串1!=字符串2
语法3:[[ ]]表达式,双中括号
对单中括号的补充,且还支持正则处理
[[ "$变量名" =~ 正则表达式 ]]
不需要转义字符
逻辑判断
&& -a 与运算,两边都为真,结果才为真
|| -o 或运算,两边有一个为真,结果为真
流程控制
if 语句
#语法1
if <条件表达式1>
then
<command1>
elif <条件表达式2>
<command2>
else
<command3>
fi
#语法2
if <条件表达式>;then
<command1>
elif <条件表达式2>
<command2>
else
<command3>
fi
#测试语句结合if语句
if [ -f /etc/hosts ];then
echo "[] is ok "
fi
if [[ -f /etc/hosts ];then
echo "[[] is ok "
fi
if test -f /etc/hosts ;then
echo "test is ok "
fi
for 循环
#语法
for 变量名 in 范围;do
函数体代码
done
while循环
#语法
while 条件;do
函数体代码
done
函数
#----------------------定义--------------
#语法1
#function 函数体名(){
# 函数体代码
# return 返回值
#}
#语法2
#function 函数名{
# 函数体代码
#}
#语法3
#函数名(){
# 函数体代码
#}
#-------------------调用-------------------
#直接写函数名即可
多文件函数配合开发
场景:
函数写在一个单独的文件只定义,不执行
在另一文件中,调用其函数
#通过source加载函数文件的变量
#通过set |grep ^函数名获取函数变量
#加载脚本
[ -f /script/test.sh ] && . /script/test.sh || exit
#执行函数
chao
shell脚本开发
- 1.想好脚本的功能,作用以及需求
- 2.转换为shell代码
1.简单的计算器脚本——运算符实战
#!/bin/bash
#脚本开发
#函数的作用,就是把你写的功能代码,进行打包,封装成一个函数名,然后调用该函数名,函数就会执行
#函数的名字
print_usage(){
printf "Print enter an integer!!!\n"
个脚本赋予一个退出码
exit 1
}
#接收用户输入的命令
read -p "Please input your number:" firstnum
#对用户输入判断if语句
#语法:中括号里面前后必须有一个空格,是固定的语法格式
if [ -n "`echo $firstname|sed 's/[0-9]//g'`" ]
then
print_usage
fi
#此时对运算符进行输入
read -p "Please input your operator:" operator
if [ "${operator}" !="+" ] && [ "${operator}" !="-" ] && [ "${operator}" !="*" ] && [ "${operator}" !="/" ]
then
echo "只允许输入+|-|*|/"
exit 2
fi
read -p "Please input your number:" secondnum
if [ -n "`echo $secondnum|sed 's/[0-9]//g'`" ]
then
print_usage
fi
#对数值进行运算
echo "${firstnum}${operator}${secondnum}的结果是:"$((${firstnum}${operator}${secondnum}))
2.检测网站是否存活的脚本——test测试实战
#!/bin/bash
CheckUrl(){
timeout=5
fails=0 #相当于定义一个计数器
success=0
while true
do
wget --timeout=${timeout} --tries=1 http://pythonav.cn -q -O /dev/null
if [ $? -ne 0 ]
then
let fails=fails+1 #失败次数加1
else
let success+=1
fi
#判断当成功次数>=1:该网站可以正确访问
if [ ${success} -ge 1 ]
then
echo "恭喜你,该网站正在健康的执行"
exit 0
fi
if [ ${fails} -ge 2 ]
then
echo "该网站一定是挂了"
exit 2
fi
done
}
CheckUrl
3.安装LNMP/LAMP脚本开发—-test测试实战
# LNMP/LAMP实际不会开发,仅仅是简单进行用户选择
#!/bin/bash
path=/shell_program/shell_scripts/
[ ! -d "$path" ] && mkdir $path -p
cat <<END
1.[install lamp]
2.[install lnmp]
3.[exit]
pls input the num you want:
END
read num
#判断输入的是否为数字
expr $num + 1 &> /dev/null
[ "$?" -ne "0" ] && {
echo "The num you input must be {1|2|3}"
exit 1
}
#对输入的数字进行判断
[ "$num" -eq 1 ] &&{
echo "Starting install lamp...waiting...."
sleep 2;
[ -x "$path/lamp.sh" ] || {
echo "The file does not exist or can't be exec."
exit 1
}
$path/lamp.sh
exit $?
}
[ "$num" -eq 2 ] &&{
echo "Starting install lnmp...waiting...."
sleep 2;
[ -x "$path/lnmp.sh" ] || {
echo "The file does not exist or can't be exec."
exit 1
}
$path/lnmp.sh
exit $?
}
[ "$num" -eq 3 ] &&{
echo "ByeBye."
exit 0
}
4.开发内存检测脚本—-if实战
开发shell脚本
1.检测Linux剩余可用内存,当可用内存小于100M,就发邮件给运维
2.配置邮件告警
#!/bin/shell
FreeMem=`free -m |awk 'NR==2 {print $NF}'` # 获取当前内存情况
CHARS="Current memory is $FreeMem"
if [ "$FreeMem" -lt 100 ];then
echo $CHARS| tee /tmp/messages.txt
mail -s "`date +%F-%T`$CHARS" 1801405093@qq.com </tmp/messages.txt
echo "内存不足,抓紧维护服务器"
fi
5.监测MySQL运行状态
通过端口netstat -tunlp
或者ss -tunlp|grep mysql|wc -l
或lsof -i tcp:3380
或者nmap 127.0.0.1 -p 3380|grep open |wc -l
或者telnet 127.0.01 3380|grep Connect|wc -l
来进行监测mysql运行的状态
#准备python脚本,用于检测数据库连接状态
import pymysql
db =pymysql.connect(
host="localhost",
port=3306,
user='root',
password="123456",
charset='utf8',
db='usr'
)
cursor = db.cursor()
cursor.execute('select version()')
data = cursor.fetchone()
print('当前数据库连接正确,该数据库版本是%s'%data)
db.close()
#!/bin/bash
python3 /script/mysql.sh &> /dev/null
if [ "$?" -eq "0" ];then
echo "MySQL is running."
else
echo "MySQL has been stopped."
echo "MySQL is restarting."
systemctl restart mysql
fi
6.Rsync启停脚本开发—if实战
首先检查是否存在Rsync服务rpm -qa|grep rsync;ls /etc/rsyncd.conf;netstat -tunlp|grep 873
启动Rsync服务/usr/bin/rsync --damon
#!/bin/bash
#author:HigginsLee
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!"
elif [ "$1" = "stop" ];then
killall rsync &> /dev/null
sleep 2
if [ `netstat -tunlp |grep rsync|wc -l` -eq 1 ];then
echo "Rsync is stopped!"
elif [ "$1" = "restart" ];then
killall rsync &> /dev/null
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 "Rsync is restarted!"
else
echo "Usage:$0 {start|stop|restart}"
exit 1
fi
7.检测网站是否存活—函数实战
#!/bin/bash
#原始版本
if [ "$#" -ne 1 ];then
echo "Usage:$0 url"
exit 1
fi
wget --spider -q -o /dev/null --tries=1 -T 5 $1
if [ "$?" -eq 0 ];then
echo "$1 is running"
else
echo "$1 is down."
#函数版本
function usage(){
echo "Usage:$0 url"
exit 1
}
check_url(){
wget --spider -q -o /dev/null --tries=1 -T 5 $1
if [ "$?" -eq 0 ];then
echo "$1 is running"
else
echo "$1 is down."
}
function main(){
if [ "$#" -ne 1 ];then
usage
fi
check_url $1
}
main $*