Shell可以进行类似程序 (program) 的撰写,并且不需要经过编译 (compile) 就能够执行。
shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达式、管线命令与数据流重导向等功能,以达到我们所想要的处理目的
1 脚本程序的攥写
[mcb@localhost ~]$ mkdir bin
[mcb@localhost ~]$ cd ./bin
[mcb@localhost bin]$ vim hello.sh
#!/bin/bash #“ #! ”是用来宣告 shell 的
# Program:
# This program shows "Hello World!" in your screen.
# History:
# 2023/06/21 MCB First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World! \a \n"
exit 0
[mcb@localhost bin]$ sh hello.sh
Hello World!
第一行 #!/bin/bash 在宣告这个 script 使用的 shell 名称: 因为我们使用的是 bash ,所以,必须要以“ #!/bin/bash ”来宣告这个文件内的语法使用 bash 的语法!
1.1 脚本编写习惯
在脚本的文件头处记录好:
- script 的功能;
- script 的版本信息;
- script 的作者与联络方式;
- script 的版权声明方式;
- script 的 History (历史纪录);
- script 内较特殊的指令,使用“绝对路径”的方式来下达;
- script 运行时需要的环境变量预先宣告与设置。
2 shell脚本练习
2.1 交互式脚本
使用read命令编写一个脚本,它可以让用户输入:1.firstname与2.lastname,最后在屏幕上显示:【your full name is:】的内容
[mcb@localhost bin]$ vim showname.sh
#!/bin/bash
# Program:
# Wser inputs his first name and last name. Program shows his full name.
#history:
#2023/06/21 MCB First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input your first name: " firstname #提示使用者输入
read -p "Please input your last name: " lastname #提示使用者输入
echo -e "\nYou full name is: ${firstname} ${lastname}" #结果由屏幕输出
[mcb@localhost bin]$ sh showname.sh
Please input your first name: Changbao
Please input your last name: Ma
You full name is: Changbao Ma
利用shell脚本创建三个不同时间的文件
[mcb@localhost bin]$ vim create_3_filename.sh
#!/bin/bash
# Program:
# Program create three files.which named by user's input and date command.
# History:
# 2023/06/21 MCB First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1.让使用者输入文件名称,并取得filename这个变量
echo -e "I will use touch command to create three files." #纯粹显示信息
read -p "Please input your filename: " fileuser #提示使用者输入
#2.为了避免使用者随意按下enter,利用变量功能分析文件名是否有设置
filename=${fileuser:-"filename"} #开始判断是否有配置文件名
#3.开始利用date命令来取得所需要的文件名
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}
#4.建立文件名
touch "${file1}"
touch "${file2}"
touch "${file3}"
[mcb@localhost bin]$ sh create_3_filename.sh
I will use touch command to create three files.
Please input your filename: file
[mcb@localhost bin]$ ls
create_3_filename.sh file20230619 file20230620 file20230621 hello.sh showname.sh
:-是一起的;fileuser 如果有值的话,就用所拥有的值赋予给filename变量;无值的话,就把filenname赋予给fileuser,再赋予给filename变量
数值运算:简单的加减乘除
[mcb@localhost bin]$ vim multiplying.sh
#!/bin/bash
# Program:
# User input 2 integer numbers; program will cross these two numbers.
# History:
# 2023/06/21 MCB First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "you should input two numbers,and i will multiplying them!\n"
read -p "first number:" firstnum
read -p "second number:" secnum
declare -i num=${firstnum}*${secnum}
echo -e "\nthe result of ${firstnum}x${secnum} is ==>${num}"
[mcb@localhost bin]$ sh multiplying.sh
you should input two numbers,and i will multiplying them!
first number:10
second number:20
the result of 10x20 is ==>200
其中:
declare -i num=${firstnum}*${secnum} echo -e "\nthe result of ${firstnum}x${secnum} is ==>${num}"
可以替换成
total=$((${firstnum}*${secnum})) echo -e "\nthe result of ${firstnum}x${secnum} is ==>${total}"
推荐使用:var=$((运算内容))
bc: 含有小数的运算
利用 bc 命令(bc为计算器命令,可进入命令行也可利用管道,第四章有说明)可以实现含有小数的运算
[mcb@localhost bin]$ echo "3.14*5.26" | bc
16.51
2.2 脚本执行方式差异(source、sh script、./script)
直接执行脚本 sh filename.sh 其实是通过子进程执行,子进程产生的变量不会传递到父进程
利用 source filename.sh 会直接在父进程中执行,脚本结束后变量仍然存在
利用source来执行脚本是在父进程中执行,脚本结束后变量仍然存在,如下:
[mcb@localhost bin]$ source showname.sh
Please input your first name: Changbao
Please input your last name: Ma
You full name is: Changbao Ma
[mcb@localhost bin]$ echo ${firatname} ${lastname}
Changbao Ma
3. 判断式
3.1 test命令
用来检查某个文件是否存在,返回值需要通过echo $?来查看
[mcb@localhost bin]$ test -e /wm && echo "exist" || echo "not exist"
not exist
test所有参数详见p396;
3.2 判断符号 [ ]
[mcb@localhost bin]$ [ -z "${MCB}"]; echo $?
0
//用于判断变量是否为空,非空则返回1,上面由于变量MCB为空,所以返回了0
- 中括号内的每个组件都需要有空格来分隔
- 中括号内的变量,最好都以双引号括起来
- 中括号内的常数,最好都以单或双引号括起来
[mcb@localhost bin]$ name="maico"
[mcb@localhost bin]$ [ ${name} == "magic"]
$name不用双引号括起来,就会默认为 [ maico == "abc" ]
3.3 shell 脚本的默认变量($0、$1...)
重新启动系统的网络,可以这样做
[mcb@localhost ~]$ file /etc/init.d/network
/etc/init.d/network: Bourne-Again shell script, ASCII text executable
# 使用 file 来查询后,系统告知这个文件是个 bash 的可执行 script 喔!
[mcb@localhost ~]$ /etc/init.d/network restart
restart 是重新启动的意思,上面的指令可以『重新启动 /etc/init.d/network 这支程序』的意思
通过read 功能虽然可以给予一些变量去执行不同任务,但存在的问题是你得要手动由键盘输入一些判断式;而透过指令后面接参数, 那么一个指令就能够处理完毕而不需要手动再次输入一些变量行为:如下
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
执行的脚本档名为 $0
这个变量,第一个接的参数就是 $1
- $# ::代表后接的参数『 个数』,以上为例这里显示『 4 』
- $@ :代表『 "$1" 2" 3" 4" 』之意,每个变量是独立的 (用双引号括起来 );
- $* :代表『 "$1 c$2 c$3 c$4" $4" 』,其中 c 为分隔字符, 默认空格键为分隔字符, 所以本例中代表『 "$1 $2 $3 $4" 』 之意。
例题:执行一个可以携带参数的 script ,执行该脚本后屏幕会显示如下的数据:
程序的文件名是什么?
共有几个参数?
若参数的个小于 2 则告知使用者参数量太少
全部的参数内容是什么?
第一个参数是什么?
第二个参数是什么?
[mcb@localhost ~]$ vim how_paras.sh
#!/bin/bash
# Program:
# Program shows the script name, parameters...
# History:
# 2023/06/22 mcb first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "the script name is ==>${0}"
echo "total paramter number is==>$#"
[ "$#" -lt 2 ] && echo "The number of parameter is less than 2. stop here."&&exit 0
echo "your whole paraneter is ==> $@"
echo "The 1st parameter ==> ${1}"
echo "The 2nd parameter ==> ${2}"
执行结果:
[mcb@localhost ~]$ sh how_paras.sh mcb mn gm
the script name is ==>how_paras.sh
total paramter number is==>3
your whole paraneter is ==> mcb mn gm
The 1st parameter ==> mcb
The 2nd parameter ==> mn
4 条件判断式
4.1 if...then
4.1.1 单层、简单条件判断式
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
fi <==将 if 反过来写,就成为 fi ,结束 if 之意;
[ "${yn}" == "Y" -o "${yn}" == "y" ]
上式可替换为
[ "${yn}" == "Y" ] || [ "${yn}" == "y" ]
其中:-o在test命令中表示两个条件任意一个成立,就可以返回true
[mcb@localhost ~]$ vim ans_yn.sh
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
# 2023/06/22 mcb First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input (Y/N): " yn
if [ "${yn}" == "Y" ] ||[ "${yn}" == "y" ]; then
echo "OK, continue"
exit 0
fi
if [ "${yn}" == "N" ] || [ "${yn}" == "n" ]; then
echo "Oh, interrupt!"
exit 0
fi
echo "I don't know what your choice is" && exit 0
运行结果:
[mcb@localhost ~]$ sh ans_yn.sh
Please input (Y/N): y
OK, continue
4.1.2 多重、复杂条件判断式:if[ ];then...elif[ ];then...else..
# 一个条件判断,分成功进行与失败进行 (else)
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
else
当条件判断式不成立时,可以进行的指令工作内容;
fi
更复杂情况时:
# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况执行
if [ 条件判断式一 ]; then
当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
当条件判断式二成立时,可以进行的指令工作内容;
else
当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi
netstat: 用来查询目前主机有打开的网络服务端口
[mcb@localhost bin]$ netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
tcp6 0 0 :::111 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 ::1:631 :::* LISTEN
tcp6 0 0 ::1:25 :::* LISTEN
udp 0 0 0.0.0.0:111 0.0.0.0:*
udp 0 0 0.0.0.0:5353 0.0.0.0:*
udp 0 0 0.0.0.0:745 0.0.0.0:*
udp 0 0 0.0.0.0:67 0.0.0.0:*
udp 0 0 0.0.0.0:68 0.0.0.0:*
udp 0 0 0.0.0.0:53321 0.0.0.0:*
udp6 0 0 :::111 :::*
udp6 0 0 :::745 :::*
常见的 port 与相关网络服务的关系是:
- 80: WWW
- 22: ssh
- 21: ftp
- 25: mail
- 111: RPC(远端程序调用)
- 631: CUPS(打印服务功能)
如何通过 netstat 去侦测我的主机是否有打开21, 22, 25及 80这四个主要的网络服务端口?
[mcb@localhost bin]$ vim netstat.sh
#!/bin/bash
# Program:
# using nastat ang grep to detect WWW,SSH,FTP and MAIL services.
# History:
# 2023/06/22 mcb First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#1.先写一些告知操作
echo "Now I will detect your linux server's services!"
echo -e "The www, ftp, ssh, and mail(smtp) will be detect!\n"
#2.开始执行一些测试任务,并且输出一些信息
testfile=/dev/shm/netstat_checking.txt
netstat -tuln > ${testfile}
testing=$(grep ":80" ${testfile})
if [ "${testing}" != " "]; then
echo "WWW is running in your system."
fi
testing=$(grep ":22" ${testfile})
if [ "${testing}" != " "]; then
echo "SSH is running in your system."
fi
testing=$(grep ":21" ${testfile})
if [ "${testing}" != " "]; then
echo "FTP is running in your system."
fi
testing=$(grep ":25" ${testfile})
if [ "${testing}" != " "]; then
echo "MAIL is running in your system."
fi
echo "FTP is running in your system."
fi
testing=$(grep ":25" ${testfile})
if[ "${testing}" != ""];then
echo "MAIL is running in your system."
fi
注意:if与[ ]之间有空格,if [ ];
grep在多个文件中检索某个字符串
命令格式:
- grep "被查找的字符串t" filename1 filename2 filename3 ...
- grep "被查找的字符串" *.log
4.2 case ..... esac 判断
case $变量名称 in <==关键字为 case ,还有变量前有钱字号
"第一个变量内容") <==每个变量内容建议用双引号括起来,关键字则为小括号 )
程序段
;; <==每个类别结尾使用两个连续的分号来处理!
"第二个变量内容")
程序段
;;
*) <==最后一个变量内容都会用 * 来代表所有其他值
不包含第一个变量内容与第二个变量内容的其他程序执行段
exit 1
;;
esac <==最终的 case 结尾!“反过来写”思考一下!
[mcb@localhost bin]$ vim hello_1.sh
#!/bin/bash
# program
# this script only accept the following paraments: one two three!
# History
# 2023/06/22 MCB First release
path=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "This program will print your selection !"
read -p "Input your choice" choice
#case ${1} in 可以替换
#echo "This program will print your selection !"
#read -p "Input your choice" choice
case ${choice} in
"one")
echo "your choice is one"
;;
"two")
echo "your choice is two"
;;
"three")
echo "your choice is three"
;;
*)
echo "your input is error!"
;;
esac
运行结果:
[mcb@localhost bin]$ sh hello_1.sh
This program will print your selection !
Input your choice: one
your choice is one
4.3 function功能
function fname()
{
程序段
}
[mcb@localhost bin]$ vim show.sh
#!/bin/bash
# program
# this script only accept the following paraments: one two three!
# History
# 2023/06/22 MCB First release
path=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
function printit(){
echo -n "your choice is " #加上-n可以不换行在同一行显示
}
echo "This program will print your selection !"
read -p "Input your choice: " choice
#case ${1} in 可以替换
#echo "This program will print your selection !"
#read -p "Input your choice" choice
case ${choice} in
"one")
printit; echo ${choice} | tr 'a-z' 'A-Z'
;;
"two")
printit; echo ${choice} | tr 'a-z' 'A-Z'
;;
"three")
printit; echo ${choice} | tr 'a-z' 'A-Z'
;;
*)
echo "your input is error!"
;;
esac
运行结果:
[mcb@localhost bin]$ sh show.sh
This program will print your selection !
Input your choice: two
your choice is TWO
补充:tr命令详解
5 loop 循环
5.1 while do done, until do done (不定循环)
while [ condition ] <==中括号内的状态就是判断式
do <==do 是循环的开始!
程序段落
done <==done 是循环的结束
当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止的意思。还有另外一种不定循环的方式:
[mcb@localhost bin]$ vim yes_to_stop.sh
#!/bin/bash
# program
# repeat question until user input correct answer!
# History
# 2023/06/22 MCB First release
path=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "please input yes or YES to stop this program: " yn
while [ "${yn}" != "yes" -a "${yn}" != "YES" ]
do
read -p "Please input yes or YES to stop this program: " yn
done
echo "ok! you input the correct answer."
运行结果:
[mcb@localhost bin]$ sh yes_to_stop.sh
Please input yes or YES to stop this program: yes
ok! you input the correct answer.
until [ condition ]
do
程序段落
done
这种方式恰恰与 while 相反,它说的是“当 condition 条件成立时,就终止循环, 否则就持续进行循环的程序段。”
[mcb@localhost bin]$ vim yes_to_stop1.sh
#!/bin/bash
# program
# repeat question until user input correct answer!
# History
# 2023/06/22 MCB First release
path=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
until [ "${yn}" == "yes" ] || [ "${yn}" == "YES" ]
do
read -p "Please input yes or YES to stop this program: " yn
done
echo "ok! you input the correct answer."
运行结果:
[mcb@localhost bin]$ sh yes_to_stop1.sh
Please input yes or YES to stop this program: no
Please input yes or YES to stop this program: yes
ok! you input the correct answer.
求1-100的和:
[mcb@localhost bin]$ vim yes_to_stop.sh
#!/bin/bash
# program
# 对1-100进行求和!
# History
# 2023/06/22 MCB First release
path=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
i=0
s=0
while [ "${i}" != "100" ]
do
i=$((${i}+1))
s=$((${s}+${i}))
done
echo "ok! the result of 1+2+...100= $s"
运行结果:
[mcb@localhost bin]$ sh yes_to_stop.sh
ok! the result of 1+2+...100= 5050
5.2 for...do...done (固定循环)
相对于 while, until 的循环方式是必须要“符合某个条件”的状态, for 这种语法,则是“ 已经知道要进行几次循环”的状态。
for var in con1 con2 con3 ...
do
程序段
done
以上面的例子来说,这个 $var 的变量内容在循环工作时:
- 第一次循环时, $var 的内容为 con1 ;
- 第二次循环时, $var 的内容为 con2 ;
- 第三次循环时, $var 的内容为 con3 ;
- ....
由于系统上面的各种帐号都是写在 /etc/passwd 内的第一个字段,你能不能通过管线命令的 cut 捉出单纯的帐号名称后,以 id 分别检查使用者的识别码与特殊参数呢?由于不同的 Linux 系统上面的帐号都不一样!此时实际去捉 /etc/passwd 并使用循环处理,就是一个可行的方案了!程序可以如下:
[[email protected] bin]$ vim userid.sh
#!/bin/bash
# Program
# Use id, finger command to check system account's information.
# History
# 2015/07/17 VBird first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
users=$(cut -d ':' -f1 /etc/passwd) # 撷取帐号名称
for username in ${users} # 开始循环进行!
do
id ${username}
done
除了使用 $(seq 1 100) 之外,你也可以直接使用 bash 的内置机制来处理喔!可以使用 {1..100} 来取代 $(seq 1 100) ! 那个大括号内的前面/后面用两个字符,中间以两个小数点来代表连续出现的意思!例如要持续输出 a, b, c...g 的话, 就可以使用“ echo {a..g} ”这样的表示方式!
5.3 for...do...done 的数值处理
推荐使用以下方法:
for (( 初始值; 限制值; 执行步阶 )) do 程序段 done
这种语法适合于数值方式的运算当中,在 for 后面的括号内的三串内容意义为:
- 初始值:某个变量在循环当中的起始值,直接以类似 i=1 设置好;
- 限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100;
- 执行步阶:每作一次循环时,变量的变化量。例如 i=i+1。
[mcb@localhost bin]$ vim cal_1_100.sh
#!/bin/bash
# program
# 对1-100进行求和!
# History
# 2023/06/22 MCB First release
path=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "请输入一个数字,将计算从1到该数字的累加值: " num
s=0
for (( i=1; i<=$num; i=i+1 ))
do
s=$((${s}+${i}))
done
echo "the results is: ${s}"
运行结果:
[mcb@localhost bin]$ sh cal_1_100.sh
请输入一个数字,将计算从1到该数字的累加值: 20
the results is: 210
6 shell script 的追踪与 debug
直接以 bash 的相关参数来进行判断可能产生的语法错误
sh [-nvx] scripts.sh
选项与参数:
-n :不要执行 script,仅查询语法的问题;
-v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x :将使用到的 script 内容显示到屏幕上,这是很有用的参数!