Shell基础入门
1、什么是Shell?
Shell是一个命令解释器,它的作用是解释执行用户输入的命令及程序,用户每输入一条命令,Shell就解释执行一条。Shell存在于操作系统的最外层,负责和用户直接对话,把用户的输入解释给操作系统,并处理各种各样的操作系统输出结果 ,然后输出到屏幕返回给用户。
而Shell脚本就是命令或程序语句不在命令行下执行,而是通过一个程序文件来执行。
2、Shell脚本的结构
在Linux系统中,Shell脚本通常是使用Vim编辑器进行编写,其内容大多数是命令和程序结构控制语句以及注释构成。其规范的格式如下:
- Shell脚本的第一行为指定脚本解释器,通常为:
#/bin/bash 或 #/bin/sh
- Shell脚本添加版本和个人信息
# Date: 2019-03-08 15:30
# Author: KIM
# Description: nginx_log_cut
# Version: 1.1
- Shell脚本中尽量不使用中文
- Shell脚本的命名应该以.sh为扩展名
- Shell脚本应该放在固定的路径下,如/server/scripts
- 使用合适的代码缩进规范
3、Shell的变量
变量的类型:环境变量(全局变量)和普通变量(局部变量)
环境变量:也可称为全局变量,可在在创建他们的shell及其派生出来的任意子进程shell中使用,环境变量又可氛围自定义环境变量和bash的内置环境变量
普通变量:也可称为局部变量,只能在创建他们的shell函数或者shell脚本中使用,普通变量一般由开发者在开发脚本程序时创建。
3.1.自定义环境变量
如果像要设置环境变量,就要在给变量赋值之后或在设置变量时用export命令。除了export命令,带-x选项的declare内置命令也可以完成同样的功能(注意:此处不要再变量名前加$)
exprot命令和declare命令的格式如下:
- ①export 变量名=value
- ②变量名=value; export 变量名
- ③declare -x 变量名=value
[root@localhost ~]# export NAME=long
[root@localhost ~]# echo $NAME
long
[root@localhost ~]# declare -x NAME=li
[root@localhost ~]# echo $NAME
li
[root@localhost ~]# NAME=ing;export NAME
[root@localhost ~]# echo $NAME
ing
3.2.普通变量
定义方式有3种:
变量名=value #赋值时不加引号
变量名='value' #赋值时加单引号
变量名="value" #赋值时加双引号
定义脚本退出状态码
exit: 退出脚本
exit #
如果脚本没有明确定义退出状态码,那么,最后执行的一条命令的退出码即为脚本的退出状态码;
测试脚本是否有语法错误:
bash -n 脚本
bash -x 脚本:单步执行
3.3.位置参数变量
在Shell中存在一些特殊的位置参数变量,当我们在命令行、函数或脚本执行时传递参数时,此时就需要使用位置参数变量。如下表:
位置变量 | 作用 |
---|---|
$0 | 获取当前执行的Shell脚本文件名,如果执行脚本包含路径,那么就包含脚本路径 |
$n | 获取当前执行Shell脚本的第N个参数,n=1..9,当n=0时,表示脚本文件名;n>9用大括号括起来{10},接的参数要 以空格隔开 |
$# | 获取当前执行的Shell脚本后面接的参数的总个数 |
$* | 获取当前Shell脚本所有传参的参数,不加引号和$@相同;加了引号,如"$*"表示所有参数视为单个字符串,相当于"$1 $2 $3" |
$@ | 获取当前Shell脚本所有传参的参数,不加引号和$*相同 |
3.4、状态变量
位置变量 | 作用 |
---|---|
$? | 获取上一个执行指令的状态返回值,0为成功,非0为失败 |
$$ | 获取当前执行的Shell脚本的进程号 |
$! | 获取上一个在后台运行的进程的进程号 |
$_ | 获取在之前执行命令或脚本的最后一个参数 |
4、条件测试和比较
4.1、条件测试常用的语法形式:
test <测试表达式>
[ 测试表达式 ]:[]两端需要有空格
[[ 测试表达式 ]]:两端需要有空格,与[]和test的区别在于,在[[]]中可以使用通配符等进行模式匹配;并且与&&、||、>、<等操作符可用于[[]]中,但不能用于[]中,在[]中一般使用 -a、-o、-gt(用于整数)、-lt。除了使用通配符的功能之外,建议放弃该用法。
((测试表达式)):一般用于if语句里,两端不需要空格
示例:
[root@localhost ~]# test -f file && echo ture || echo false
false
表示如果file文件存在,则输出true,否则输出false。
[root@localhost ~]# [ -f /etc/passwd ] && echo 0 || echo 1
0
表示如果/etc/passwd文件存在,则输出0,否则输出1
[root@localhost ~]# [[ -f /etc/passwd && -d /123/ ]] && echo 0 || echo 1
1
表示如果/etc/passwd文件存在,并且/123/目录也存在,则输出0,否则输出1
4.2、文件测试表达式的用法:
常用的文件测试操作符:
-d:文件存在且为目录则为真,即测试表达式成立
-f:文件存在且为普通文件则为真,即测试表达式成立
-e:文件存在则为真,即测试表达式成立
-r:文件存在且可读则为真,即测试表达式成立
-w:文件存在且可写则为真,即测试表达式成立
-x:文件存在且可执行为真,即测试表达式成立
-s:文件存在且文件大小不为0,即测试表达式成立
tips:测试文件的读、写、执行属性,不光是看文件属性rwx的表示进行判断,还需要看当前执行测试的用户是否真的可以按照对应的权限操作该文件。
特殊条件测试表达式案例:
以下写法适用于所有的条件测试表达式,是工作中比较常用替代if语句的方法。判断条件测试表达式的条件成立与否,还需要继续执行多条命令语句的语法形式如下:
[ 条件1 ] && {
COMMAND 1
COMMAND 2
COMMAND 3
}
[[ 条件1 ]] && {
COMMAND 1
COMMAND 2
COMMAND 3
}
test 条件1 && {
COMMAND 1
COMMAND 2
COMMAND 3
}
相当于if语句:
if [ 条件1 ]
then
COMMAND 1
COMMAND 2
COMMAND 3
fi
4.3、字符串测试表达式
字符串测试操作符:
-n "字符串":若字符串的长度不为0,则为真,即测试表达式成立。
-z "字符串":若字符串的长度为0,则为真,即测试表达式成立。
"串1" = "串2":若串1等于串2,则为真,即测试表达式成立,可用==代替=
"串1" != "串2":若串1不等于串2,则为真,即测试表达式成立,可用!==代替!=
tips:字符串比较时等号两边需要空格,没有空格会导致逻辑错误。
4.4、整数二元比较操作符
在[]和test中使用 在(())和[[]]中使用
-eq == 或 =
-ne !=
-gt >
-ge >=
-lt <
-le <=
4.5、逻辑操作符
在[]和test中使用 在(())和[[]]中使用
-a &&
-o ||
! !
5、If条件语句
条件语句语法:
单分支if语句
if 判断条件; then
statement1
statement2
...
fi
双分支的if语句:
if 判断条件; then
statement1
statement2
...
else
statement3
statement4
...
fi
多分支的if语句:
if 判断条件1; then
statement1
...
elif 判断条件2; then
statement2
...
elif 判断条件3; then
statement3
...
else
statement4
...
fi
6、case语句
case条件语句相当于多分支的if/elif/else条件语句,但是它比这些条件语句看起来更规范更公正,常被应用于事先系统服务启动脚本等企业应用场景中。
在case语句中,程序会将case获取的变量的值与表达式部分的值1、值2、值3等逐个进行比较,如果获取的变量值和某个值(例如值1)相匹配,就会执行值后面对应的指令,直到执行到双分号;;才会停止,然后再跳出语句主体,执行case语句后面的其他指令。
如果没有找到匹配变量的任何值,则执行"*)"后面的指令(通常是给使用提示),直到遇到双分号;;或esac结束,这部分相当于if多分支语句中最后的else语句部分。
case语句语法:选择结构
SWITCH为变量的值,对变量的值进行引用,并进行判断选择
case SWITCH in
value1)
statement
...
;;
value2)
statement
...
;;
*)
statement
...
;;
esac
范例1:根据用户的输入判断用户输入的是哪个数字,如果是1-9的任意数字,则输出对应的数字;如果是其他数字及字符,则返回输入不正确的提示并退出程序。
[root@localhost ]# vim num.sh
#!/bin/bash
while :;do
read -p "please input your number:" A
case $A in
1)
echo "You input the number is 1."
;;
[2-9]*)
echo "You input then number is $A ."
;;
q|Q)
echo "quiting ..."
exit 0
;;
*)
echo "Your input is error.please input your number again."
;;
esac
done
范例2:给出水果列表,并输出客户选择的水果,且用不同的颜色显示不同的水果。
#!/bin/bash
cat << EOF
1. apple
2. pear
3. banana
4.cheery
EOF
while :;do
read -p "Do you have some fruits." FRUITS
case $FRUITS in
1)
echo -e "\033[31mapple\033[0m"
;;
2)
echo -e "\033[32mpear\033[0m"
;;
3)
echo -e "\033[37mbanana\033[0m"
;;
4)
echo -e "\033[36mcherry\033[0m"
;;
*)
echo "Your choice is error."
exit 1
;;
esac
done
范例3:给输出的字符加颜色基础知识
在linux脚本中,可以通过echo的-e参数,结合特殊的数字给不同的字符加上颜色并显示。
内容的颜色可以用数字表示,范围为30-37,每个数字代表一种颜色,代码如下:
echo -e "\033[30m 黑色字体 long \033[0m"
echo -e "\033[31m 红色字体 long \033[0m"
echo -e "\033[32m 绿色字体 long \033[0m"
echo -e "\033[33m 棕色字体 long \033[0m"
echo -e "\033[34m 蓝色字体 long \033[0m"
echo -e "\033[35m 洋红色字体 long \033[0m"
echo -e "\033[36m 蓝绿色字体 long \033[0m"
echo -e "\033[37m 白色字体 long \033[0m"
7、for循环语句
for循环语句和while循环语句类似,但for循环主要用于执行次数有限的循环,而不是用于守护进程及无限循环。
1.for循环语法结构:
for 变量名 in 变量取值列表
do
指令...
done
tips:在此结构中,“in 变量取值列表”可以省略,省略时相当于in "$@",也就是使用for i就相当于使用for i in "$@"
在这种for循环语句结构语法中,for关键字后面有一个变量名,变量名依次获取in关键字后面的变量取值列表内容(以空格隔开),每次取一个,然后进入循环执行循环内的指令,当执行到done时结束本次循环。之后,“变量名”再继续获取列表里的下一个变量值,继续执行循环体内的指令,当执行到done时结束返回,以此类推,直到取完列表里的最后一个值并进入循环执行到done结束为止。
第二种语法结构:
for((expr1;expr2;expr3))
do
指令...
done
范例1:计算1-100整数和
#!/bin/bash
declare -i SUM=0
for I in {1..100};do
let SUM+=$I
done
echo $SUM
declare -i SUM2=0
for ((I=1;I<=100;I++));do
let SUM2+=$I
done
echo $SUM2
范例2:竖向打印5、4、3、2、1这5个数字
#!/bin/bash
for I in `seq 5 -1 1`
do
echo $I
done
8、while循环语句
循环语句命令常用于重复执行一条指令或一组指令,知道条件不满足为止,shell脚本语言的循环语句常见的有while、until、for、select循环语句。
while循环语句主要用来重复执行一组命令或语句,在企业实际应用中,常用于守护进程或持续运行的程序,除此以外,大多数循环都会用到for循环语句。
1.while循环语句语法:
while <条件表达式>
do
指令...
done
while循环语句会对紧跟while命令后的条件表达式进行判断,如果该条件表达式成立,则执行while循环体中的命令或语句,每一次执行到done时,就会重新判断while条件表达式是否成立,直到条件表达式不成立时才会跳出while循环体。如果一开始条件表达式就不成立,那么程序就不会进入循环体。
while的特殊用法一,死循环:
while :;do
done
举例输入文件路径,进行判断:
#!/bin/bash
while :;do
read -p "File path:" FILEPATH
[ $FILEPATH == 'quit'] && break
if [ -e $FILEPATH ];then
echo "$FILEPATH exists"
else
echo "No $FILEPATH."
fi
done
while的特殊用法二:
while read LINE; do
done < /PATH/TO/SOMEFILE
举例:判断/etc/passwd中哪个用户的shell为bash shell,如果是bash shell就显示该用户的用户名
#!/bin/bash
#
FILE=/etc/passwd
while read LINE;do
[ `echo $LINE |awk -F: '{print $7}'` == '/bin/bash' ] && echo $LINE |awk -F: '{print $1}'
done < $FILE
2.until循环语句语法:
until <条件表达式>
do
指令...
done
until的用户和while类似,区别是until会在条件表达式不成立时,进入循环执行指令;条件表达式成立时,终止循环。
范例1:
1、判断一个指定的bash脚本是否有语法错误;如果有错误,则提醒用户键入Q或者q无视错误并退出,其它任何键可以通过vim打开这个指定的脚本;
2、如果用户通过vim打开编辑后保存退出时仍然有错误,则重复第1步中的内容;否则,就正常关闭退出。
./syntax.sh a.sh
until bash -n $1 &> /dev/null; do
read -p "Syntax error, [Qq] to quit, others for editing: " CHOICE
case $CHOICE in
q|Q)
echo "Something wrong, quiting."
exit 5
;;
*)
vim + $1
;;
esac
done
echo "0K"
break: 提前退出循环
continue:提前结束本轮循环,而进入下一轮循环;
计算1-100的奇数和
#!/bin/bash
let SUM=0
let I=1
while [ $I -le 100 ];do
if [ $[$I%2] -eq 1 ];then
let SUM+=$I
fi
let I++
done
echo $SUM
continue举例:
#!/bin/bash
let SUM=0
let I=0
while [ $I -lt 100 ];do
let I++
if [ $[$I%2] -eq 0 ];then
continue---当判断取余为0时,直接提前结束本轮循环体
fi
let SUM+=$I
done
echo $SUM
break举例:
#!/bin/bash
#
let SUM=0
for I in {1..1000};do
let SUM+=$I
if [ $SUM -gt 5000 ];then
break---直接退出循环
fi
done
echo $I
echo $SUM
企业生产范例:
写一个shell脚本解决类DDOS攻击的生产案例。请根据web日志或系统网络连接数,监控某个ip的并发连接数,若短时间内PV达到100,即调用防火墙命令封掉对应的ip,防火墙命令为:iptables -I INPUT -s IP地址 -j DROP
#!/bin/bash
file=$1
while true;do
awk '{print $1}' $1 |grep -v "^$"|sort |uniq -c > /tmp/tmp.log
exec </tmp/tmp.log
while read line
do
ip=`echo $line |awk '{print $2}'`
count=`echo $line |awk 'print $1'`
if [ $count -gt 500 ] && [ `iptables -L -n|grep "$ip"|wc -l` -lt 1 ]
then
iptables -I INPUT -s $ip -j DROP
echo "$line is droped" >> /tmp/droplist_$(date +%F).log
fi
done
sleep 3600
done
9、函数
函数的功能:实现代码的重用
9.1、shell函数的常见语法
标准写法:
function 函数名 () {
指令...
return n
}
简化写法1:
fucntion 函数名 {
指令...
return n
}
简化写法2:
函数名 () {
指令...
return n
}
tips:在shell函数语法中,function表示声明一个函数,这部分可以省略不写。
9.2、shell函数的执行
shell的函数分为最基本的函数和可以传参的函数两种,其执行的方式分别如下:
- ①执行不带参数的函数时,直接输入函数名即可(注意不带小括号)。格式如下:
函数名
说明:执行函数时,函数名前的function和函数后的小括号都不用带;
函数的定义必须要在执行的程序前面定义或加载;
return命令和exit命令类似,return的作用是退出函数,exit是退出脚本文件;
如果将函数放在独立的文件中,被脚本加载时,需要使用source或"."来加载;
函数内,一般使用local定义的局部变量,这些变量离开函数后就会消失;
- ②带参数的函数执行方法,格式如下:
函数名 参数1 参数2
说明:shell的位置参数($1、$2...$#、$*、$@、$?)都可以作为函数的参数来使用;
此时父脚本的参数临时地被函数参数所掩盖或隐藏;
$0比较特殊,它仍是父脚本的名称;
当函数执行完成时,原来的命令行脚本的参数即可恢复;
函数的参数变量是在函数体里面定义的;
举例:使用函数多次显示一个菜单
#!/bin/bash
#
function SHOWMENU {
cat << EOF
d|D) show disk usages
m|M) show memory usages
s|S) show swap usages
q|Q) quit
EOF
}
SHOWMENU
SHOWMENU
SHOWMENU
范例1:将函数的传参转换成脚本文件命令行传参,判断任意指定的URL是否存在异常.
1)实现脚本传参,检查web URL是否正常。
#!/bin/bash
#判断传参数量是否为1
if [ $# -ne 1 ];then
echo "Usage:`basename $0` url."
exit 1
fi
#利用wget进行访问
wget --spider -q -o /dev/null --tries=1 -T 5 $1 #T为超时时间,$1为脚本参数
if [ $? -eq 0 ];then
echo "$1 is yes."
else
echo "$1 is no."
fi
2)将上述检测的功能写成函数,并将函数传参转换成脚本命令行传参,判断任意指定的URL是否存在异常。
#!/bin/bash
function usage() {
echo "Usage:`basename $0` url."
exit 1
}
function check_url() {
wget --spider -q -o /dev/null --tries=1 -T 5 $1
if [ $? -eq 0 ];then
echo "$1 is yes."
else
echo "$1 is no."
fi
}
function main() {
if [ $# -ne 1 ];then
useage
fi
check_url $1
}
main $* --这里的$*就是把命令行接收的所有参数作为函数参数穿给函数内部,是一种常用手法
10、数组
1.shell数组的定义:
方法1:用小括号将变量值括起来赋值给数组变量,每个变量之间要用空格进行分隔。
语法如下:
array=(value1 value2 value3...)
[root@localhost ~]# array=(1 2 3)
[root@localhost ~]# echo ${array[*]}
1 2 3
方法2:动态定义数组变量,使用命令的输出结果作为数组的内容。
语法为:
array=($(命令)) 或 array=(`命令`)
示例如下:
[root@localhost ~]# mkdir /array/ -p
[root@localhost ~]# touch /array/{1..3}.txt
[root@localhost ~]# ll /array/
total 0
-rw-r--r-- 1 root root 0 Aug 23 21:45 1.txt
-rw-r--r-- 1 root root 0 Aug 23 21:45 2.txt
-rw-r--r-- 1 root root 0 Aug 23 21:45 3.txt
[root@localhost ~]# array=($(ls /array/))
[root@localhost ~]# echo ${array[*]}
1.txt 2.txt 3.txt
2.shell数组的打印及输出
①打印数组元素
[root@localhost ~]# array=(1 2 3)
[root@localhost ~]# echo ${array[0]}
1
[root@localhost ~]# echo ${array[1]}
2
[root@localhost ~]# echo ${array[2]}
3
②打印数组元素个数
[root@localhost ~]# echo ${array[*]} --使用*或@可以得到整个数组的内容
1 2 3
[root@localhost ~]# echo ${#array[*]} -使用${#数组名[*]}可以得到数组的长度
3
[root@localhost ~]# echo ${array[@]}
1 2 3
[root@localhost ~]# echo ${#array[@]}
3
③数组的删除
因为数组的本质还是变量,因此可以通过unset 数组[下标] 清除相应的数组元素,如果不带下标,则表示清除整个数组的所有数据。
④数组内容的截取与替换
[root@localhost ~]# array=(1 2 3 4 5)
[root@localhost ~]# echo ${array[@]:1:3} -截取1号到3号数组元素
2 3 4
[root@localhost ~]# echo ${array[@]/1/a} -把数组中的1替换成a
a 2 3 4 5
[root@localhost ~]# echo ${array[@]}
1 2 3 4 5
tips:调用方法为${数组名[@或*]/查找字符/替换字符},该操作不会改变原数组的内容。
范例1:使用循环批量输出数组元素
#!/bin/bash
array=(1 2 3 4 5)
for((i=0;i<${#array[@]};i++))
do
echo ${array[i]}
done
范例2:利用bash for循环打印下面这句话中不大于6的单词
I am oldboy teacher welcome to oldboy training class
#!/bin/bash
array=(I am oldboy teacher welcom to oldboy training class)
for((i<0;i<${#array[@]};i++))
do
length=`echo ${array[i]} |wc -l`
if [ $length -lt 6 ];then
echo ${array[i]}
fi
done