摘要:概述、script的编写、test命令、[]判断符号、默认变量($1...)、if...then条件判断式、
一、概述
【什么是shell script】
针对shell所写的脚本,将多个命令汇整起来一起执行
可以进行类似程序的编写,并且不需要经过编译就能够执行
利用shell的功能所写的一个“程序”,这个程序是使用纯文本文件,将一些shell的语法与命令写在里面,搭配正则表达式、管道命令与数据流重定向等功能,以达到我们所想要的处理目的。
【用途】
简化我们日常的工作管理
一些服务的启动都是通过shell script进行的
二、编写第一个Hello world程序
【预处理】
新建一个script文件:vi helloworld.sh
【代码】
#!bin/bash
# Program:
# This is my first script.
# History:
# -- liuyu First release echo "Hello World!"
exit
说明:
#!bin/bash:声明这个script使用的shell名称
声明这个文件内是使用bash的语法,即这个程序被执行时,它就能够加载bash的相关环境配置文件(一般来说就是non-login shell的~/.bashrc),并且执行bash使我们下面的命名能够执行。
exit 0:告知执行结果
我们可以利用exit这个命令来让程序中断,并且回传一个数值给系统。如“exit 0”代表离开script并且回传一个0给系统,即我们执行完这个script后,若接着执行echo $?则可得到0的值。
【执行script程序】
bash 文件名
三、简单的shell script练习
1. 交互式脚本:变量内容由用户决定
题目:请你以read命令的用途,编写一个script,它可以让用户输入firstname与lastname,最后并且在屏幕上显示“Your full name is: ”的内容。
#!bin/bash
# Program:
# an easy interactive program
# History:
# -- liuyu First release read -p "Please input your firstname: " firstname
read -p "please input your lastname: " lastname
echo "Your full name is $firstname $lastname!"
2. 把日期作文件名:利用日期进行文件的创建
题目:我希望将每天的数据备份在不同的文件里,所以就需要以每天的日期作为文件名。关键是日期怎么来!假设我要创建3个空的文件,文件名最开头由用户决定(我们假设是filename),那今天的日期是2017/9/30,我想以前天、昨天、今天的日期来创建这些文件,即filename20170928, filename20170929, dilename20170930,该如何是好?
#!bin/bash
# Program:
# filename is created by user's input.
# History:
# -- liuyu First release echo "I will use 'touch' to create three files."
read -p "Please input your filename: " fileuser #开始判断有否配置文件名,防止用户随意按[Enter],我们默认开头为filename
filename=${fileuser:-"filename"} date1=$(date --date='2 days ago' +%Y%m%d) #前两天的日期
date2=$(date --date='1 days ago' +%Y%m%d)
date3=$(date +%Y%m%d) #今天的日期
file1=${filename}${date1} #追加的方式配置文件名
file2=${filename}${date2}
file3=${filename}${date3} touch "$file1" #防止有空格
touch "$file2"
touch "$file3"
说明:此程序运用了许多bash中的变量知识,所以学好shell才能轻松地编写script!
3. 数值运算:简单的加减乘除
题目:让用户输入两个整数,然后计算出这两个数相乘的结果。
#!bin/bash
# Program:
# multiply two integer.
# History:
# liuyu -- First release echo "You should input 2 integers,i will cross them!"
read -p "first number: " firnum
read -p "second number: " secnum
total=$(($firnum*$secnum))
echo "The result of $firnum*$secnum is $total."
四、test命令的测试功能
1. 功能
检测系统上面某些文件、测试文件的相关属性。
2. 常用命令
命令 | 意义 |
test -e filename | 该文件名是否存在 |
test -f filename | 该文件名是否存在且为文件 |
test -e filename | 该文件名是否存在且为目录 |
test -S filename | 该文件名是否存在且为一个Socket文件 |
test -L filename | 该文件名是否存在且为一个连接文件 |
test -r filename | 该文件名是否存在且具有“可读”的权限 |
test -w filename | 该文件名是否存在且具有“可写”的权限 |
test -x filename | 该文件名是否存在且具有“可执行”的权限 |
test file1 -nt file2 | 判断file1是否比file2新 |
test file1 -ef file2 | 判断file1与file2是否为同一个文件,主要用于hard link |
test n1 -eq n2 | 两数值相等 |
test n1 -gt n2 | n1大于n2 |
test n1 -lt n2 | n1小于n2 |
test n1 -ge n2 | n1大于等于n2 |
test n1 -le n2 | n1小于等于n2 |
test -z string | 判断是否为空字符串,若是,返回true |
test str1=str2 | 判断str1是否等于str2 |
-a | 两个条件同时成立,如test -r file -a -x file,则file同时具有r与x权限时,才回传true |
-o | 任何一个条件成立,如test -r file -o -x file,则file具有r或x权限时,就可回传true |
! | 反向状态,如test ! -x file,当file不具有x时,才回传true |
3. 示例
题目:写一个script,让用户输入一个文件名,要求进行下面的判断:
这个文件是否存在,若不存在则给予一个“Filename does not exist”的信息,并中断程序;
若这个文件存在,则判断它是个目录或文件,结果输出“Filename is regular file”或“Filename is directory”;
判断一下,执行者的身份对这个文件或目录所拥有的权限,并输出权限数据。
#!bin/bash
# Program:
# A script which covers the using of test.
# History:
# -- liuyu First release echo "Please input a filename, I will check the filename's type and permission."
read -p "Input a filename: " filename
test -z $filename && echo "You must input a filename." && exit test ! -e $filename && echo "The filename '$filename' DO NOT exist" && exit test -f $filename && filetype="regular file"
test -d $filename && filetype="directory"
test -r $filename && perm="readable"
test -w $filename && perm="writable"
test -x $filename && perm="executable" echo "The filename: $filename is a $filetype"
echo "And the permissions are : $perm"
五、判断符号[]的使用
1. 功能
数据的判断、字符串判别
2. 用法
命令 | 意义 |
[ -z "$HOME" ] | 判断HOME变量是否为空 |
[ "$HOME" == "$var" ] | 判断两个变量是否相等 |
[ "$HOME" == "string" ] | 判断变量的内容是否为string |
3. 示例
题目:写一个script,要求如下:
当执行一个程序的时候,这个程序会让用户选择Y或N;
如果用户输入Y或y时,就显示“OK, continue”;
如果用户输入N或n时,就显示“Oh, interrupt!”;
如果不是Y/y/N/n之内的其他字符,就显示“I don't know what your choice is”。
#!/bin/bash
# Program:
# A practice with [].
# History:
# -- liuyu First release read -p "Please input (Y/N): " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit
echo "I don't know what your choice is." && exit
六、默认变量($0,$1...)
1. 概述
我们知道命令可以带有参数,而我们还需要知道的是shell script也可以在脚本文件名后面带有参数。(注意!一定是script文件喔!)
2. 示例
题目:重新启动系统注册表文件
命令:/etc/init.d/syslog restart
评讲:此命令可以重新启动/etc/init.d/syslog这个程序
题目:承上,关闭该服务
命令:/etc/init.d/syslog stop
3. 用法
文件名 opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
解释:执行的脚本文件名为$0这个变量,第一个接的参数就是$1。
补充:只要我们在script里面善用$1的话,就可以很简单地立即执行某些命令功能了!
补充:除了这些数字的变量之外,我们还有一些较为特殊的变量可以在script内使用来调用这些参数。
$#:代表后接的参数“个数”
$@:代表"$1"、"$2"、"$3"、"$4"之意,每个变量是独立的(用双引号括起来)
$*:代表“"$1c$2c$3c$4"”,其中c为分隔字符,默认为空格键,所以本例中代表“"$1 $2 $3 $4"”之意
4. 练习
题目:假设你要执行一个可以带参数的script,执行该脚本后屏幕会显示如下的数据。
程序的文件名
共有几个参数
若参数的个数小于2则告知用户参数数量太少
全部的参数内容
第一个参数
第二个参数
#!/bin/bash
# Program:
# Default variable of shell script.
# History:
# -- liuyu First release echo "The script name is ==> $0"
echo "Total parameter number is ==> $#"
[ "$#" -lt ] && echo "The number of parameter is less than 2. Stop here." && exit
echo "Your whole parameter is ==> '$@'"
echo "The 1st parameter ==> $1"
echo "The 2nd parameter ==> $2"
sh07.sh
注意:参数个数就是你执行该文件时在该文件后加的参数个数,即,若你执行“bash sh07.sh”,则参数个数为0。
5. shift:造成参数变量号码偏移
我们要知道的是——脚本后面所接的变量能够进行偏移。
我们通过一个例子来说明偏移是什么。
题目:我们将上个文件中的内容稍作变化,用来显示每次偏移后参数的变化情况。
#!/bin/bash
# Program:
# Default variable of shell script.
# History:
# -- liuyu First release echo "Total parameter number is ==> $#"
echo "Your whole parameter is ==> '$@'"
shift #进行第一次“一个变量的shift”
echo "Total parameter number is ==> $#"
echo "Your whole parameter is ==> '$@'"
shift #进行第二次“三个变量的shift”
echo "Total parameter number is ==> $#"
echo "Your whole parameter is ==> '$@'"
sh08.sh
如果我们执行“bash sh08.sh one two three four five six”,我们会看到如下显示:
分析:通过这个结果,我们知道shift会移动变量,而且shift后面可以接数字,代表拿掉最前面的几个参数的意思。
注:“七/八/九”都属于条件判断式下的子内容。
七、条件判断式——if...then
1. 单层判断
【命令】
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的命令工作内容;
fi
【条件判断式】
当有多个条件要判别时,除了可以将多个条件写入一个中括号内的情况之外,还可以使用多个中括号,而此时每个中括号内只写入一个判断式。
即[ "$yn" == "Y" -o "$yn" == "y" ] 可以被替换为 [ "$yn" == "Y" ] || [ "$yn" == "y" ]
【示例】
题目:将sh06.sh这个脚本修改成为if...then的样式
#!/bin/bash
# Program:
# A practice with [].
# History:
# -- liuyu First release read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
exit
fi
if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "oh, interrupt!"
exit
fi
echo "I don't know what your choice is." && exit
sh06-2.sh
2. 多层判断
【命令1】
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的命令工作内容;
else
当条件判断式不成立时,可以进行的命令工作内容;
fi
【命令2】
if [ 条件判断式一 ]; then
当条件判断式一成立时,可以进行的命令工作内容;
elif [ 条件判断式二 ]; then
当条件判断式二成立时,可以进行的命令工作内容;
else
当条件判断式一与二均不成立时,可以进行的命令工作内容;
fi
【示例】
题目:将sh06-2.sh这个脚本修改成为if...elif...else的样式
#!/bin/bash
# Program:
# A practice with [].
# History:
# -- liuyu First release read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "oh, interrupt!"
else
echo "I don't know what your choice is."
fi
【练习】
题目:一般来说,如果你不希望用户由键盘输入额外的数据时,可以使用上一节提到的参数功能($1)!让用户在执行命令时就将参数代进去。现在我们想让用户输入“hello”这个关键字时,利用参数的方法可以这样依序设计:①判断$1是否为hello,如果是的话,就显示“Hello,how are you?”;②如果没有加任何参数,就提示用户必须要使用的参数;③而如果加入的参数不是hello,就提醒用户仅能使用hello为参数。
#!/bin/bash
# Program:
# Check $ is equal to "hello"
# History:
# -- liuyu FIrst release if [ "$1" == "hello" ]; then
echo "Hello,how are you?"
elif [ "$1" == "" ]; then
echo "You must input parameters,ex> {$0 someword}"
else
echo "The only parameter is 'hello',ex> {$0 hello}"
fi
评讲:我们在$1的位置分别输入hello、没有输入、随意输入,就可以看到不同的输出。
八、利用case...esac判断
九、利用函数功能
十、循环(loop)
1. 条件循环
【while do done】
while [ condition ] //中括号内的状态就是判断式
do //循环的开始
程序段落
done //循环的结束
解释:当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止。
【until do done】
until [ condition ]
do
程序段落
done
解释:当 condition 条件成立时,就终止循环,否则就持续进行循环的程序段。
【练习】
题目1:假设我要让用户输入yes或者是YES才结束程序的执行,否则就一直进行告知用户输入字符串。
#!/bin/bash
# Program:
# while:Repeat question until user input correct answer.
# History:
# -- liuyu First release while [ "$yn" != "yes" -a "$yn" != "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."
sh13.sh(while)
#!/bin/bash
# Program:
# until:Repeat question until user input correct answer.
# History:
# -- liuyu First release until [ "$yn" == "yes" -o "$yn" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."
sh13-2.sh(until)
题目2:计算1+2+3+...+100这个数据。
#!/bin/bash
# Program:
# Use loop to calculate "1+2+3+...+100" result.
# History:
# -- liuyu First release ans= # 这是累加的数值变量
i= # 这是累计的数值,亦即是1,,...
while [ "$i" != "" ]
do
i=$(($i+))
ans=$(($ans+$i))
done
echo "The result of '1+2+3+...+100' is ==> $ans"
sh14.sh
题目3:让用户自行输入一个数字,让程序由1+2+...直到用户输入的数字为止。
#!/bin/bash
# Program:
# practice of until of page .
# History:
# -- liuyu First release ans=
i=
read -p "Please input a integer: " n
if [ "$n" -le "" ]; then
echo "Sorry, you input a integer which isn't bigger than one!"
exit ;
else
until [ "$i" -eq "$n" ]
do
i=$(($i+))
ans=$(($ans+$i))
done
echo "The result of '1+2+3+...+$n' is ==> $ans"
fi
2. 固定循环
【for do done】
for var in con1 con2 con3 ...
do
程序段
done
解释:变量var的内容在第 i 次循环时为coni
【练习】
题目1:假设我有三种动物,分别是 dog, cat, elephant 三种,我想每一行都输出这样:“There are dogs...”之类的字样。
#!/bin/bash
# Program:
# Using for...loop to print animals
# History:
# -- liuyu First release for animal in dog cat elephant
do
echo "There are ${animal}s..."
done
题目2:系统上的账号都是写在/etc/passwd内的第一个字段,你能不能通过管道命令 cut 找出单纯的账号名称后,以 id 及 finger 分别检查用户的标识符与特殊参数呢?
#!/bin/bash
# Program:
# Use id,finger command to check system account's information.
# History:
# -- liuyu First release users=$(cut -d ':' -f1 /etc/passwd)
for username in $users
do
id $username
finger $username
done
评讲:执行此脚本后,我们的系统账号就会被获取出来检查。
补充:这个操作还可以用在每个账号的删除/更改上面呢!
题目3:我现在需要一连串的数字来进行循环。如我想要利用 ping 这个可以判断网络状态的命令。来进行网络状态的实际检测时,我想要检测的域是本机所在的192.168.1.1~192.168.1.100,由于有100台主机,故我不想在 for 后面输入1到100。
#!/bin/bash
# Program:
# Use ping command to check the network's PC state.
# History:
# -- liuyu First release network="192.168.1" #先定义一个域的前面部分
for sitenu in $(seq ) #seq为sequence(连续)的缩写之意
do
#下面的语句取得ping的回传值是正确的还是失败的
ping -c l -w l ${network}.${sitenu} &> /dev/null && result= || result=
#开始回显结果是正确的启动(UP)还是错误的没有连通(DOWN)
if [ "$result" == ]; then
echo "Sever ${network}.${sitenu} is UP."
else
echo "Sever ${network}.${sitenu} is DOWN."
fi
done
评讲:上面这一串命令执行之后就可以显示出192.168.1.1~192.168.1.100共100部主机目前是否能与你的机器连通!如果你的域与我们所在的位置不同,则直接修改上面那个network的变量内容即可!
题目4:让用户输入某个目录文件名,然后找出某目录内的文件名的权限。
#!/bin/bash
# Program:
# User input dir name, I find the permission of files.
# History:
# -- liuyu First release # .先看看这个目录是否存在
read -p "Please input a directory: " dir
if [ "$dir" == "" -o ! -d "$dir" ]; then
echo "The $dir is NOT exist in your system."
exit ;
fi # . 开始测试文件
filelist=$(ls $dir) # 列出所有在该目录下的文件名
for filename in $filelist
do
perm=""
test -r "$dir/$filename" && perm="$perm readable"
test -w "$dir/$filename" && perm="$perm writable"
test -x "$dir/$filename" && perm="$perm executable"
echo "The file $dir/$filenam's permission is $perm."
done
评讲:此题涉及判断式与循环的综合。
【另一种使用方式——适合于数值运算中】
for ( ( 初始值; 限制值; 执行步长 ) )
do
程序段
done
解释:初始值为某个变量在循环当中的初始值(直接以类似i=1设置好);限制值为当变量的值在这个限制值的范围内,就继续进行循环,例如i<=100;执行步长为每做一次循环时变量的变化量,如i=i+1。
题目:进行1累加到用户输入的循环
#!/bin/bash
# Program:
# Use for to calculate "1+2+...+${your_input}".
# History:
# -- liuyu release read -p "Please input a number, I will count for 1+2+...+your_input: " n ans=
for ((i=; i<=$n; i=i+))
do
ans=$(($ans+$i))
done
echo "The result of '1+2+3+...+$n' is ==> $ans."
sh19.sh
十一、shell script的追踪与调试
1. 概述
script在执行之前,最怕出现语法错误的问题了!那么我们如何调试呢?
我们有没有办法不需要通过直接执行该 script 就可以来判断是否有问题呢?
有的,而且很简单,只要在执行该script的命令中加上相关参数即可。
2. 用法
bash [-nx] 文件名 //-n为不执行script,仅查询语法的问题;-x为将使用到的script内容显示到屏幕上(执行过程显示出来,很重要)