SHELL 编程

来源:互联网 发布:对大数据的认识 编辑:程序博客网 时间:2024/05/22 06:43
一、Shell编程简介
之前课程中已经介绍过,在操作系统的内核kernel外部有一个Shell层,Shell的作用是保护内核kernel不受损害。同时,Shell接收用户对Linux系统的操作指令并传递给内核,之后由内核执行。
之前学习过的Shell命令都是运行在Shell层上的,每次我们通过terminal输入一条命令,再通过Shell交给内核执行。有时,我们的需求过于复杂,需要执行多条命令,此时一条一条的在terminal内输入命令会十分的繁琐。Shell层提供给我们一个脚本工具,我们可以将想要依次执行的命令写成脚本文件,再将脚本文件发送给Shell。这样Shell就会按照脚本文件内记载的命令依次执行,从而实现了自动化。
//Shell脚本在Windows系统内称为“批处理文件”(.bat文件),本质相同
也就是说,Shell脚本的实质就是Shell命令的有序集合。
1、编译型语言与解释性语言
本质上来说,编程并让计算机执行实际上是一个将其中一种语言(编程语言)翻译成另外一种语言(机器语言)的过程。那么翻译语言肯定需要一定的翻译策略。编程语言从源代码变成计算机识别的可执行程序有“编译”和“解释”两种方式。
1)编译
-需要编译器
-对代码进行整体检查
-主要进行词法检查、语法分析、语义检查和中间代码生成、代码优化、目标代码生成5部分
-若代码有错误,则停止编译并报错;若无错误,则会生成目标代码
-执行效率较高
-代表语言:C/C++
2)解释
-需要解释器
-依次执行代码内每条命令,每句执行一次
-只会运行到当前语句时才会翻译该命令
-若代码有错误,则在出错语句处停止,而该语句上面的语句已经被执行
-执行效率较低
-代表语言:Python、JavaScript、Shell脚本
我们在进行Shell编程的时候,本质上是将多条Shell命令写在一个文件内(脚本),然后terminal按照该文件内每条命令开始执行命令。因此Shell脚本是一种典型的解释性编程语言。
2、Shell编程的基本步骤
我们进行Shell编程有以下几个步骤:
1.建立Shell脚本文件
2.给Shell脚本文件可执行的权限
3.执行Shell脚本
示例:使用Shell编程
第一步:建立Shell脚本文件,文件名为myshell.sh。文件内容:
#!/bin/bash
date
cat myshell.sh
ping -c 3 114.114.114.114
echo "hello world"


脚本内的命令分别代表:
1.#!/bin/bash是Shell脚本文件的固定开头,该命令说明了这个Shell脚本在哪个程序上执行。在这里表示使用/bin/bash来执行这个脚本。
注:在Shell脚本中,#开头的行表示注释。
2.打印当前系统时间
3.查看myshell.sh文件内容
4.ping3次114.114.114.114检查网络通断
5.在终端打印hello world


第二步:给Shell脚本文件可执行的权限
我们刚刚写的Shell脚本文件只是一个普通的文本文件,没有可执行的权限,因此我们需要给该文件添加可执行权限。
chmod 0775 myshell.sh
这样myshell.sh就具有可执行权限了。我们可以使用ls -l命令查看该文件的权限


第三步:执行该Shell脚本
./myshell.sh
执行该Shell脚本即可查看效果


二、Shell编程的变量
和C语言一样,Shell编程也有变量。在Shell脚本内,所有的变量都是字符串类型但是不支持数据类型(整型、字符型、浮点型等),并且无需对变量进行声明。
在sh/bash中有4种变量:
-用户自定义变量
-位置参数(命令行参数)
-预定义变量
-环境变量
1、用户自定义变量
Shell编程允许用户自定义变量来存储数据,但不支持数据类型(整型、字符型、浮点型等),任何变量的值都被解释成一个字符串。变量名命名规则如下:
1.首字符必须是字母或者下划线
2.中间不能有空格,可以使用下划线_表示空格
3.不能使用除下划线外其他的标点符号
我们可以使用=给变量赋值,格式为:
变量名=变量值
注意等号两边没有空格。在Shell编程中变量名一般使用全大写字母,为了与Shell命令区分。
COUNT=1
若想调用一个变量的值,则需要在该变量名前面加上$。例如:
#!/bin/bash
COUNT=1
VAR="hello world"
echo $COUNT
echo COUNT
echo $VAR
注意第一个echo命令与第二个echo命令输出的区别。第一个echo命令会输出COUNT变量的值1,而第二个echo命令会将COUNT当做待输出的字符串处理,会输出COUNT。
Shell编程支持变量互相赋值,赋值方向为从右向左。例如:
Y=2
X=$Y
则变量X内就得到了变量Y的值2。
某些情况下,我们定义的变量会与其他文字混淆。例如:
num=2
echo "this is the $numnd"
此时执行该Shell脚本不会输出"this is the 2nd"而会输出"this is the",因为echo命令无法找到一个名为"numnd"的变量。我们可以使用花括号来包裹变量名,被花括号包裹的字符串会被Shell当做是一个变量来处理。
num=2
echo "this is the ${num}nd"
这样就可以输出"this is the 2nd"。
我们可以使用unset命令删除已定义过的变量。例如:
Z=hello
echo $Z
unset Z
echo $Z
则第二次执行的时候就不会输出变量Z的值。


2、位置变量(命令行参数)
由系统提供的参数称为位置参数,其作用等价于C语言中main函数传参的“命令行参数”。位置参数可以使用$+数字的形式获得。
$0 与键入命令一样
$1~$9 第一个到第九个命令行参数
例如:
#!/bin/bash
echo "this is 0" $0
echo "this is 1" $1
在执行该Shell脚本的时候我们携带一个命令行参数:
./myshell 123
则会输出:
this is 0 ./myshell.sh
this is 1 123


3、预定义变量
Shell编程内事先定义了一些变量,用户只能使用这些变量而不能重新定义它们。所有的预定义变量都由$符号和另一个符号构成,常用的预定义变量如下:
$# 命令行参数的个数
$@ 所有命令行参数(不计$0,同$*)
$? 前一个命令的退出状态
$* 所有命令行参数(不计$0,同$@)
$$ 正在执行的进程ID号
示例:演示各个位置变量的值
#!/bin/bash
echo "this is 0" $0
echo "this is 1" $1
echo "this is #" $#
echo "this is @" $@
echo "this is ?" $?
echo "this is *" $*
echo "this is $" $$


执行:./myshell.sh 123 456 789
输出:
this is 0 ./myshell.sh
this is 1 123
this is # 3
this is @ 123 456 789
this is ? 0
this is * 123 456 789
this is $ 2710


4、环境变量
环境变量适用于所有用户进程,环境变量均为大写。常用的环境变量如下:
HOME 用户工作目录所在地址,在文件/etc/passwd文件内存储
IFS(Internal Field Separator) 内部字段分隔符,默认是空格、tab以及换行符
PATH Shell搜索路径
PS1 命令提示符格式($及$前的字符)(PS是Prompt Sign的缩写)
PS2 换行提示符>
TERM 终端类型,常见的值有vt100、vt200、ansi、xterm等
HISTSIZE 保存历史记录的条目数
LOGNAME 当前登录用户名
HOSTNAME 主机名称
SHELL 当前使用的Shell类型
LANG/LANGUAGE与语言相关的环境变量
MAIL 用户的邮件存放目录
TMOUT 设置的脚本过期时间。例如TMOUT=3则表示该脚本3秒后过期
UID 登录用户的ID
USER 显示当前用户名
SECONDS 记录脚本从开始运行到结束耗费的时间
示例:演示各个环境变量的值
#!/bin/bash
echo "HOME IS " $HOME
echo "IFS IS " $IFS
echo "PATH IS " $PATH
echo "TERM IS " $TERM
echo "LANGUAGE IS " $LANG
echo "LOGNAME IS " $LOGNAME
echo "HOSTNAME IS " $HOSTNAME
echo "SHELL IS " $SHELL
echo "MAIL IS " $MAIL
echo "UID IS " $UID
echo "USER IS " $USER 
我们可以使用env命令查看更多的环境变量信息。
我们可以使用export命令来自定义环境变量,使用unset命令清除环境变量。例如:
export HELLO="Hello"
echo $HELLO


三、Shell编程语句
Shell脚本由0条或多条Shell语句构成,Shell语句可以分为三类:
1.说明性语句:以#开始到该行结束,对Shell语句说明,不会被执行。类似C语言的注释
2.功能性语句:任意的Shell命令、用户程序或其他
3.结构性语句:条件语句、分支语句、循环语句、循环控制语句等
1、说明性语句(注释行语句)
说明性语句可以出现在程序的任意位置,既可以独立一行,也可以接在其他语句后面。说明性语句都是以#开头的语句,在Shell脚本执行的时候不会被解释执行。
2、功能性语句
1)读入键盘的值read
read从标准输入内读数据,并赋值给后面的变量。语法格式为:
read 待赋值的变量
一个read命令会读取一行。例如:
read WORD#从输入读取一个字符串值赋值给变量WORD
read VAR1 VAR2 VAR3#从输入内读取三个字符串值,分别赋值给变量VAR1、VAR2和VAR3


思考:以下两种写法的操作一样吗?
写法1:
read VAR1 VAR2 VAR3
写法2:
read VAR1
read VAR2
read VAR3
示例1:用户输入一个目录,然后显示输入的目录内的文件
#!/bin/bash
echo "please input a directory"
read VAR1
ls -l $VAR1
示例2:用户输入年、月、日,然后按指定格式输出。注意输入的数据要以空格或tab分隔,不要用回车
#!/bin/bash
echo "please input time with format: yyyy mm dd:"
read VAR1 VAR2 VAR3
echo "today is ${VAR1}/${VAR2}/${VAR3}"
2)算数运算命令expr
命令expr用于变量间简单的整数四则运算,包括加、减、乘、除、取余。注意如果使用乘法需要使用\*的写法来取消*(通配符)的元字符含义。
例如,在终端输入:
expr 12 + 5 \* 3 #注意乘号的写法
结果为27
示例:从键盘读入两个数字,分别计算这两个数字的加、减、乘、除、取余的结果并输出
#!/bin/bash
echo "please input 2 numbers:"
read VAR1
read VAR2
ADD=`expr $VAR1 + $VAR2` #反引号`的意思是使用后面命令的结果
SUB=`expr $VAR1 - $VAR2`
MUL=`expr $VAR1 \* $VAR2`
DIV=`expr $VAR1 / $VAR2`
MOD=`expr $VAR1 % $VAR2`


echo $VAR1 + $VAR2 = $ADD
echo $VAR1 - $VAR2 = $SUB
echo $VAR1 \* $VAR2 = $MUL #注意乘号写法,表示该符号是字符而不是通配符*
echo $VAR1 / $VAR2 = $DIV
echo $VAR1 % $VAR2 = $MOD
在示例脚本中,反引号`(位于键盘左上角)的作用是引用该命令的结果。ADD=`expr $VAR1 + $VAR2`的意思是首先执行expr命令得到计算结果,然后将该结果作为值赋值给变量ADD。
3)测试命令test
test命令可以测试三种对象:字符串、整数、文件属性,每种测试对象都有若干测试操作符。
注意:在Shell编程中,0代表真,1代表假。这点和C语言相反。
1.测试字符串
用法:
test 字符串1 = 字符串2
若两字符串相等则结果为0,若不相等则结果为1。注意两个字符串中间的等号左右分别有空格。若没有空格则会将输入作为一个字符串。
test 字符串1 != 字符串2
若两字符串不相等则结果为0,若相等则结果为1。注意两个字符串中间的不等号左右分别有空格。若没有空格则会将输入作为一个字符串。
test -z 字符串
测试该字符串长度是否为0,若为0则结果为0,若不为0则结果为1。
test -n 字符串
测试该字符串长度是否不为0,若不为0则结果为0,若为0则结果为1。
例如:
test "answer" = "yes"
echo $? #$?表示上一个命令的退出状态
test命令还有一种写法:使用方括号。例如:
test "answer" = "yes" 等价于 [ "answer" = "yes" ]。
test -z "hello" 等价于 [ -z "hello" ]。
注意左右方括号的两边都有空格。
示例:从键盘读入两个字符串,判断这两个字符串是否相同。并测试字符串1是否是空
#写法1
#!/bin/bash
echo "please input 2 strings:"
read VAR1
read VAR2
test $VAR1 = $VAR2
echo $?
test -z $VAR1
echo $?


#写法2
#!/bin/bash
echo "please input 2 strings:"
read VAR1
read VAR2
[ $VAR1 = $VAR2 ]
echo $?
[ -z $VAR1 ]
echo $?


2.测试整数关系
a -eq b 测试a与b是否相等
a -ne b 测试a与b是否不相等
a -gt b 测试a是否大于b
a -ge b 测试a是否大于等于b
a -lt b 测试a是否小于b
a -le b 测试a是否小于等于b
示例:从键盘读入两个数,并判断两个数字是否相等
#!/bin/bash
read X Y
if test $X -eq $Y
then
echo "X equal Y"
else
echo "X not equal Y"
fi


3.测试文件属性
文件测试操作表达式通常是为了测试文件信息,一般由脚本来决定是否应该备份、复制、删除。test关于文件测试的操作符很多,这里只做简单的介绍。
-e name 测试一个文件是否存在
-d name 测试name是否为一个目录
-f name 测试name是否为普通文件
-L/h name 测试name是否为符号链接
-r name 测试name文件是否存在且可读
-w name 测试name文件是否存在且可写
-x name 测试name文件是否存在且可执行
-s name 测试文件是否存在且文件长度不为0
f1 -nt f2 测试文件f1是否比文件f2更新
f1 -ot f2 测试文件f1是否比文件f2更旧
例如:
test -e /home/linux/myshell.sh
echo $?
3、结构性语句
结构性语句主要根据程序的运行状态、输入数据、变量取值、控制信号、运行时间等因素来控制程序的流程。主要包括:条件测试语句(二路分支)、多路分支语句、循环语句、控制循环语句和后台执行语句。
1)条件测试语句
1.if……then……fi语句
类似C语言的if语句,格式为:
if 表达式
then 命令
fi
如果表达式为真,则执行命令表中的命令,否则退出if语句。fi表示if语句的结束,类似C语言的右括号}。fi和if必须成对出现。
2.if……then……else……fi语句
类似C语言的if-else语句,格式为:
if 表达式
then 命令1
else
命令2
fi
如果表达式为真,则执行命令表中的命令,否则执行else下命令。
3.if……then……elif……fi语句
类似C语言的if-else语句多重并列使用,格式为:
if 表达式1
then 命令1
elif 表达式2
then 命令2
……
else
命令n
fi
如果表达式1为真,则执行命令表中的命令1,否则判断表达式2;若表达式2为真则执行命令2……若所有表达式都不为真,则执行else下命令。
示例:从键盘读入一个文件判断该文件是否存在,并判断该文件类型(普通文件/目录/未知文件)
#!/bin/bash
echo "please input a filename"
read FILE
if [ ! -e $FILE ] # 对test的结果取“非”
then
echo "file not exist"
elif [ -L $FILE ]
then
echo "file is a symbollink"
elif [ -d $FILE ]
then
echo "file is a directory"
elif [ -f $FILE ]
then
echo "file is a regular file"
else
echo "Unknown"
fi
练习:将示例程序改成使用命令行传参的形式。注意考虑命令行参数未传参的情况。
#!/bin/bash
if [ $# -eq 0 ]
then
echo "No arguments"
exit #直接退出脚本
fi
if [ ! -e $1 ]
then
echo "file not exist"
elif [ -L $1 ]
then
echo "file is a symbollink"
elif [ -d $1 ]
then
echo "file is a directory"
elif [ -f $1 ]
then
echo "file is a regular file"
else
echo "Unknown"
fi


2)多路分支语句
多路分支语句case可以用于实现多路分支,类似C语言内switch()语句下的case语句。格式为:
case 字符串变量 in #case语句只能检测字符串变量
模式1)
命令表1
;; #退出case语句用双分号
模式2|模式3) #若多个模式共用则使用|分隔
命令表2
;;
模式4)
命令表3
;;
……
模式n) #模式n常用通配符*表示所有其他模式
命令表n
;; #最后一个模式的双分号可以省略
esac
示例:从命令行传参file1或file2或file3,并输出传参结果
#!/bin/bash
if [ $# -eq 0 ]
then
echo "No arguments"
exit
fi
case $1 in
file1)
echo "You choose file1"
;;
file2)
echo "You choose file2"
;;
file3|4)
echo "You choose file3/4"
;;
*)
echo "You MUST choose a file"
;;
esac


3)循环语句
循环语句有两类:当循环次数已经确定时,则使用for循环语句;当循环次数未定的时候,则使用while循环语句。循环语句的语句括号用do和done来确定。
1.for循环语句
for循环语句一般用于循环次数确定的时候。格式为:
for 变量名 in 单词表
do
命令表
done
变量依次取出单词表内的所有数值,每取出一个数值,就执行一次循环,因此for循环语句的循环次数由单词表内数值个数决定
示例:从命令行传参一个目录名,然后将该目录内所有文件复制到~/backup目录下
#!/bin/bash
if [ $# -eq 0 ]
then
echo "No arguments"
exit
fi
if [ ! -d $1 ] #如果传参命令行不是目录
then
echo "$1 not a directory"
exit
fi
if [ ! -d $HOME/backup ] #如果backup目录不存在则需要创建
then
mkdir $HOME/backup
fi
if [ ! `ls $HOME/backup | wc -l` -eq 0 ] #如果backup目录非空则需要清空
then
echo "$HOME/backup is not empty, need clean……"
rm -rf $HOME/backup
mkdir $HOME/backup
echo "clean all files in $HOME/backup"
fi
FLIST=`ls $1` #获取参数
for FILE in $FLIST #让FILE变量从单词表中取数据
do
cp $1/$FILE $HOME/backup
echo "$FILE copied"
done
echo "Backup Completed"


2.while循环语句
若无法事先确定循环次数,则我们不推荐使用for循环语句因为无法事先设置好单词表。此时我们可以使用while循环语句。while循环语句适用于无法事先预估循环次数的情况。while语句的格式为:
while 命令或表达式
do
命令表
done
while语句首先会执行后面的命令或者判断表达式的值,如果为真则执行循环,然后再次判断;若为假则退出循环。
示例:批量生成名字为"文件名+数字"的空白文件,其中文件名和数字都从命令行传参。例如输入
./proj.sh nihao 5
则会生成nihao1、nihao2、nihao3、nihao4、nihao5
#!/bin/bash
if [ $# -eq 0 ]
then
echo "No arguments"
exit
elif [ $# -lt 2 ]
then
echo "Arguments are too few"
exit
fi
echo "files will store in directory: $HOME/blankfile" #提示用户生成的文件在~/blankfile内
if [ ! -d $HOME/blankfile ] #如果blankfile目录不存在则需要创建
then
mkdir $HOME/blankfile
fi
if [ ! `ls $HOME/blankfile | wc -l` -eq 0 ] #如果blankfile目录非空则需要清空
then
rm -rf $HOME/blankfile
mkdir $HOME/blankfile
fi
LOOP=$2 #循环次数
i=1 #控制循环状态变量
while [ $i -le $LOOP ]
do
touch $HOME/blankfile/$1$i
i=`expr $i + 1` #等价于i++
done
echo "CreateBlankFiles Completed"


4)循环控制语句
Shell脚本中可以使用break语句和continue语句来控制循环停止。continue语句的作用是结束本次循环进入下次循环。而break语句的作用则是跳出循环。
示例1:终端会输出10个数,在数字5处使用continue,则不会输出数字5
#!/bin/bash
for i in 1 2 3 4 5 6 7 8 9 10
do
if [ $i -eq 5 ]
then
continue
fi
echo "$i"
done
示例2:将示例1的脚本修改,将continue修改为break,则输出到数字4之后循环结束
#!/bin/bash
for i in 1 2 3 4 5 6 7 8 9 10
do
if [ $i -eq 5 ]
then
break
fi
echo "$i"
done
练习1:编写一个脚本,输出9*9乘法表
#!/bin/bash
for i in 1 2 3 4 5 6 7 8 9
do
for j in 1 2 3 4 5 6 7 8 9
do
MUL=`expr $i \* $j` #注意乘号写法
echo -e "$j*$i=$MUL\t\c"
#echo -e:开启转义字符
#t:制表符  c:不换行
#"echo -e"和'\c'连用可以关闭换行
if [ $i -eq $j ] #j>i的部分无需计算
then
break
fi
done
echo "" #换行
done
练习2(选做):编写一个脚本,测试当前子网内有多少主机可以连通(使用ping命令)
提示:IP地址范围为192.168.0.1~192.168.0.254 或 192.168.1.1~192.168.1.254(根据当前子网确定范围)
答案:
#!/bin/bash
i=1
COUNT=0
while [ $i -le 254 ]
do
echo "---------------------------"
echo "will ping host:$i"
ping -c 1 192.168.0.$i
if [ $? -eq 0 ]
then
echo "host$i can be connected"
COUNT=`expr $COUNT + 1`
else
echo "host$i cannot be connected"
fi
i=`expr $i + 1`
done
echo "---------------------------"
echo "There are $COUNT host can be connected"
四、Shell编程函数
Shell脚本支持自定义函数功能。我们可以将固定功能、需要多次调用的一组命令封装在一个函数内,这样我们需要使用该功能的时候只需调用该函数即可。
函数在调用前必须先定义,且在顺序上必须放在调用函数前面。
调用函数时可以使用参数传递,函数内使用return命令将运行结果返回给调用程序。
1、函数定义
函数定义的格式为:
格式1:
function_name(){
命令
……
}
格式2:
function function_name(){
命令
……
}
2、函数调用
//实际上,在Shell编程中,函数被看成“许多Shell命令整合成一个大的Shell命令”,因此调用函数与执行一个Shell命令无本质区别
1)无参数无返回值
若调用的函数无需返回值也无需传参,则像普通命令一样即可
示例:写一个打印hello world的子函数并调用
#!/bin/bash
hello(){
echo "hello world"
}
hello #调用函数
2)有参数有返回值
若函数需要传递参数,也需要得到返回值,则有两种调用方式:
方式1:
value=`function_name $arg1 $arg2……`
方式2:
function_name $arg1 $arg2……
echo $?
示例:编写一个函数add,计算两个数的和,两个数使用传参的形式获得
#!/bin/bash
add(){
a=$1
b=$2
z=`expr $a + $b`
echo "the num is $z"
}
add $1 $2
0 0
原创粉丝点击