shell程序设计

实验三 shell程序设计

预习报告

一、实验目的

  1. 在shell程序中熟练使用Linux命令、命令重定向和管道命令;
  2. 熟练运用扩展、条件判断、循环语句等shell语法结构;
  3. 能够针对实际的Linux管理任务编写shell程序。

二、实验要求

  1. 实验包括预习报告和实验报告;

  2. 实验预习报告应根据课程内容,查阅相关资料,列出与实验相关的背景知识;

  3. 实验报告应包括设计方案、详细步骤、结果分析等,关键过程和运行结果可配以截图说明。

三、实验原理

shell是一个接收由键盘输入的命令并将其传递,给操作系统来执行的程序,shell脚本是一个包含一系列命令的文件 ,shell既是命令行接口,也是脚本语言解释器,shell脚本文件的使用惯例后缀名通常使用.sh,权限通常为755和700(rw),执行脚本需显式地指定脚本的路径名,脚本中的命令尽量使用长选项,使用制表符缩进。

四、实验预习内容

变量:扩展变量在必要时使用{ }来界定,约定使用大写字母表示常量;一个未明确赋值的变量仅含一个空字符串 赋值时,变量名、等号和值之间不能含有空格,当赋值包含空格、制表符、换行符时,需用引号括起来。

数组:数组由元素组成,元素使用索引(下标)来访问。使用数组名[下标]=值赋值,使用${数组名[下标]}进行访问,缺省下标时表示下标为0元素,*下标和@下标表示数组中所有非空元素。declare-a数组名用来显式声明一个数组,unset删除数组。

函数:先定义,后使用,退出值$?变量上一命令的退出值,为0执行成功。在return时设置函数的退出值;使用local定义局部变量;

function name {
	commands 
	return 
}
name () { 
	commands 
	return 
}

位置参数:使用$n获取n位上的实参;$#表示除脚本名的其他参数;使用shift移动位置参数,每执行一次$#的值减一,后面的值向前移动一位(shift n表示移动n个位置参数);set为位置参数赋值或者重新赋值;$*$@表示从1开始的所有位置参数。

读取键盘输入:read交互式地为变量赋值,可以从键盘输入也可以使用重定向从文件读入,-p string:显示提示符,默认读到$REPLY变量;-s不显示输入地值。

变量扩展:基本扩展a="foo";echo"${a}_file";

${parameter:-word} 若parameter为空,则扩展为word的值,否则扩展为parameter的值

${parameter:=word} 若parameter为空,则扩展为word的值,并将word赋给parameter,否则扩展为parameter的值

${parameter:?word} 若parameter为空,则报错并退出,将word的内容输出到标准错误;否则扩展为parameter的值

${parameter:+word} 若parameter为空,则扩展为空,否则扩展为word的值

变量扩展:${#parameter}扩展为parameter字符串的长度(${#@}与${#*}$#相同,返回实参的个数(不包含 0 ) ) ; 0)); 0){parameter:offset}${parameter:offset:length} 提取一部分包含在parameter中的字符串(允许offset为负值,表示从字符串末尾开始,负值前必须有一个空格,避免与:-扩展混淆);${#name[*]}与${#name[@]} 数组中非空元素的个数;${#name[n]} 数组中下标为n元素的字符串长度。

evalexec对命令进行两次扫描;

整数运算符:let(())用来执行整数算术运算(let后跟的算数表达式不能包含空格,但是(())允许使用空格,若let命令的算法表达式包含shell的特殊字符,必须用双引号括起来,而(())内使用也可不使用双引号 只能用来处理整数,通过名字识别变量);使用$(())进行算数扩展;

shell程序设计_第1张图片

if语句

if commands; then 
	commands 
[elif commands; then
	commands] 
[else 
	commands] 
fi

case语句

case string in 
	pattern1) commands;; 
	pattern2) commands;; 
	…
	*) commands;; 
esac

while与until语句

# while语句
while condition; do
	commands 
done
# until语句
until condition; do
	commands 
done
# break n:从循环体中退出,跳出n重循环
# continue n:跳出到n层循环体并进行下一次循环
# exit n:退出脚本,设置退出值为n

for语句

# 循环变量从值表中取值,每取一个值,进入一次循环体
for variable in words; do
	commands 
done
animals=(”a dog” ”a cat” ”a fish”) 
for i in ${animals[*]}; do echo $i; done 
for i in ${animals[@]}; do echo $i; done 
for i in${animals[*]}; do echo $i; done 
for i in${animals[@]}; do echo $i; done
for ((e1; e2; e3)); do 
	commands 
done
# 类似于c语言for循环结构
((e1)) 
while ((e2)); do 
	commands 
	((e3)) 
done

基本的错误类型

语法错误:违反编程语言的规则而造成的,当发生语法错误时,脚本运行失败并提示出错的位置。
逻辑错误:由于程序的逻辑关系存在的问题。

  	1. 防御性编程
  	2. 输入验证
  	3. Design is a Function of Time 

测试

Release Early, Release Often,测试与开发同样重要 。
使用桩(stub)测试程序流程。
提高测试的覆盖率。

调试

经常使用echo或print能够快速地找到问题域,发现错误,追踪脚本的运行

bash的x标志

  1. 在shebang中加入-x,启用bash的追踪模式,用来监 视整个脚本的运行情况
  2. 使用set -x和set +x可对部分代码进行追踪
  3. 在输出中,+起始的行表示追踪信息
实验报告

一、 实验目的

  1. 在shell程序中熟练使用Linux命令、命令重定向和管道命令;
  2. 熟练运用扩展、条件判断、循环语句等shell语法结构;
  3. 能够针对实际的Linux管理任务编写shell程序。

二、 实验要求

  1. 实验包括预习报告和实验报告;

  2. 实验预习报告应根据课程内容,查阅相关资料,列出与实验相关的背景知识;

  3. 实验报告应包括设计方案、详细步骤、结果分析等,关键过程和运行结果可配以截图说明。

三、 实验原理

shell是一个接收由键盘输入的命令并将其传递,给操作系统来执行的程序,shell脚本是一个包含一系列命令的文件 ,shell既是命令行接口,也是脚本语言解释器,shell脚本文件的使用惯例后缀名通常使用.sh,权限通常为755和700(rw),执行脚本需显式地指定脚本的路径名,脚本中的命令尽量使用长选项,使用制表符缩进。

四、 实验内容

  • 读取一个整数n,输出斐波那契数列的前n项及它们的和。
      1 #!/bin/bash
      2 
      3 num=$1
      4 a1=1
      5 a2=1
      6 a3=0
      7 sum=2
      8 if [ $num -eq 1 ]; then
      9         echo -ne "fib:" $a1 "\n"
     10         echo -ne "sum:" 1 "\n"
     11 elif [ $num -eq 2 ]; then
     12         echo -ne "fib:" $a1 " " $a2 "\n"
     13         echo -ne "sum:" 2 "\n"
     14 else
     15         echo -ne "fib:" $a1 " " $a2 " "
     16         for ((i=3;i<=$num;i++)) do
     17                 let a3=$a2+$a1
     18                 echo -n $a3 " "
     19                 let sum=$sum+$a3
     20                 let a1=$a2
     21                 let a2=$a3
     22         done
     23         echo -ne "\nsum:" $sum "\n"
     24 fi

输出结果

[root@iZuf6cfng5wmyxerumg92jZ ~]# ./fibo.sh 10
fib: 1   1  2  3  5  8  13  21  34  55  
sum: 143 
  • 读取若干输入,把第二个位置参数及其以后的各个参数指定的普通文件复制到第一个位置参数指定的目录中,其中
    1. 若第一个参数指定的目录不存在,则创建它;若存在且为普通文件,则删除该文件,并创建为目录;若存在且为目录,则清空这个目录
    2. 第二个参数及其以后的参数,若为普通文件则复制到第一个参数指定的目录,否则忽略
   1 #!/bin/bash
   2 
   3 parameter_number=$#
   4
   5 file_path=$1
   6
   7 # 没有任何参数
   8 if [ $# -eq 0 ]; then
   9 	exit 1 # 退出
  10 fi
  11
  12 # 如果第一个参数指定的目录不存在
  13 if [ ! -d $file_path -a ! -f $file_path ]; then
  14	mkdir $file_path # 创建目录
  15 # 如果为普通文件 
  16 elif [ -f $file_path ]; then
  17	echo 123
  18 	rm -f $file_path # 删除它
  19 	mkdir $file_path # 创建目录
  20 # 如果目录存在
  21 elif [ -d $file_path ]; then
  22 	rm -f $file_path/* # 清空
  23 fi
  24 if [ $# -gt 1 ]; then
  25 # 循环复制文件到目录下面
  26 	for (( i=2; i <= $parameter_number; i++)); do
  27 			eval mv "\$$i" "$file_path/"
  28 	done
  29 fi

输出结果

wy@ZeroJean:~$ touch {1..4}.txt
wy@ZeroJean:~$ ls
1.txt  2.txt  3.txt  4.txt  e1.sh  e2.sh  e3.sh  e4.sh
wy@ZeroJean:~$ ./e2.sh 4.txt 1.txt 2.txt 3.txt 4.txt
123
mv: cannot move '4.txt' to a subdirectory of itself, '4.txt/4.txt'
wy@ZeroJean:~$ ls
4.txt  e1.sh  e2.sh  e3.sh  e4.sh
wy@ZeroJean:~$ cd 4.txt
wy@ZeroJean:~/4.txt$ ls
1.txt  2.txt  3.txt
  • 读取/etc目录下的所有普通文件,寻找其中最长的一行,输出其内容和长度(忽略 /etc 的所有子目录)
      1 #!/bin/bash|/bin/sh
      2 
      3 max_len=0
      4 max_str=""
      5 
      6 ls -l /etc |grep ^- | while read line ; do
      7         len=${#line}
      8         if ((len>max_len)); then
      9                 max_len=$len
     10                 max_str=$line
     11                 echo $max_str >tmp
     12                 echo -n "len:" >>tmp
     13                 echo $max_len>>tmp
     14         fi
     15 done
     16 
     17 echo /etc下最长文件为:
     18 cat < tmp
     19 rm tmp

输出结果

[root@iZuf6cfng5wmyxerumg92jZ ~]# sh null.sh 
/etc下最长文件为:
-rw-r--r--. 1 root root 5122 Aug 8 2019 makedumpfile.conf.sample
len:71
  • 依次读取年、月、日三个参数(如1991 2 14),计算它距离今天的天数。其中,

    1. 能被4整除但不能被100整除,或能被400整除的年为闰年

    2. 使用 date +"%-Y"date +"%-m"date +"%-d"可获得当前的年月日

      注:请编写函数并使用算术方法计算,下面三行可用来验证结果

#!/bin/bash

# 如果输入的参数不是3个
if [ ! $# -eq 3 ]; then
	# 直接退出
	exit 1
fi

# 记录差
dayGap=0
monthGap=0
yearGap=0

# 获取当前日期
currentTime=`date +%Y-%m-%d`

# 提取年月日 2021-12-19 
year=${currentTime:0:4}
month=${currentTime:5:2}
day=${currentTime:8:2}

# echo $year $month $day

# 判断一年是否是闰年
function isLeapYear () {
	t=$1
	let prv=$t%4
	let nxt=$t%100
	let aft=$t%400
	if [ $prv -eq 0 -a ! $nxt -eq 0 -o $aft -eq 0 ]; then
		echo 1
	else
		echo 0
	fi
}

function Month () {
	pMonth=$1
	pYear=$2
	pDay=0
	provisional=`isLeapYear $pYear`
#	echo "provisional=$provisional   $pYear" >> tp
	if [ $provisional -eq 1 ]; then
		case $pMonth in
			1) pDay=31;;
			2) pDay=29;;
			3) pDay=31;;
			5) pDay=31;;
			7) pDay=31;;
			8) pDay=31;;
			10) pDay=31;;
			12) pDay=31;;
			*) pDay=30;;
		esac
	else
		case $pMonth in
			1) pDay=31;;
			2) pDay=28;;
			3) pDay=31;;
			5) pDay=31;;
			7) pDay=31;;
			8) pDay=31;;
			10) pDay=31;;
			12) pDay=31;;
			*) pDay=30;;
		esac
	fi
	echo $pDay
}

function yearDay () {
	dd=0
	provision=$1
	for ((i=1; i<=12; i++)); do
		let c=`Month $i $provision`
#		echo $c >> temp
		let dd=$dd+$c
#		echo "dd=$dd" >> temp
	done
	echo $dd
}

# 获取输入日期
inputYear=$1
inputMonth=$2
inputDay=$3

# echo $inputYear $inputMonth $inputDay

# 两个日期在同一年
if [ $year -eq $inputYear ]; then
	# 同一个月
	if [ $inputMonth -eq $month ]; then
		let dayGap=$day-$inputDay
	# 不同月
	else
		provisionalYear=$inputYear
		let monthGap=$month-$inputMonth
		let $monthGap=$monthGap-1
		for (( i=$monthGap; i>0; i--)); do
			let temp=$inputMonth+$i
			let dayGap=$dayGap+`Month $temp $provisionalYear`
		done
		a=`Month $inputMonth $provisionalYear`
		let a=$a-$inputDay
		let dayGap=$dayGap+$a
		let dayGap=$dayGap+$day
	fi
# 不同年
else 
	# 计算输入年份和现在年份之间空白年的总天数
	let yearGap=$year-$inputYear
#	echo "yearGap=$yearGap" >> temp
	for ((i=$yearGap-1; i>0; i--)); do
		let provisionalYear=$inputYear+$i
		# echo "provisionalYear=$provisionalYear" >> temp
		for ((j=1; j<=12; j++)); do
			a=`Month $j $p provisionalYear`
			let dayGap=$dayGap+$a
		done
	done
	# 计算现在年份还有多少天
	provisionalYear=$year
	for ((i=1; i<$month; i++)); do
		let a=`Month $i $provisionalYear`
		let dayGap=$dayGap+$a
		echo "dayGap=$dayGap" >> temp
	done
	let dayGap=$dayGap+$day
	# 计算输入年剩余天数
	provisionalYear=$inputYear
	a=0
	for ((i=1; i<$inputMonth; i++)); do
		let b=`Month $i $provisionalYear`
		let a=$a+$b
#		echo "dd1=$a" >> temp
	done
	let a=$a+$inputDay
	let b=`yearDay $inputYear`
	# echo $b
	let b=$b-$a
	let dayGap=$dayGap+$b
fi

echo "相距$dayGap天"

输出结果

aistudio@jupyter-301017-3228551:~$ ./time.sh 2019 02 03
相距 1050天

检验程序

#!/bin/bash
timestamp1=`date +%s -d $1-$2-$3` 
timestamp2=`date +%s` 
echo $((($timestamp2 - $timestamp1)/86400))

检验程序输出结果

aistudio@jupyter-301017-3228551:~$ ./timeCheck.sh 2019 02 03
1050

五、 实验结论

  通过本次实验熟悉并练习了Linux命令、命令重定向和管道命令使用,并运用shell的扩展、条件判断以及循环语句的语法且根据实验要求所设计的实际的linux管理任务编写了shell程序。在实验中对学习中产生的相关困惑和疑问得到了很好的处理,收获颇多。

你可能感兴趣的:(笔记,学习笔记,linux)