SHELL脚本编程进阶(二)

写在前面(最重要)

本文部分资料和示例援引自以下书籍。在此,感谢原作者的创作,以及所有译者的付出,向他们致敬。

  1. Advanced Bash-Scripting Guide
  2. 《高级Bash脚本编程指南》Revision 10中文版
  3. Linux脚本编程执导

其中 《高级Bash脚本编程指南》Revision 10中文版 是 《Advanced Bash-Scripting Guide》 的中文翻译版,文档翻译正在进行中,再次感谢译者付出。

前言

在之前的文章 Linux 基础命令(十)—— SHELL脚本编程进阶(一) 中,我们已经详细的介绍了SHELL脚本编程的循环与分支的相关内容,这些循环与分支在bash编程中有着至关重要的地位。今天我们将要介绍bash编程进阶的第二部分,也是实际使用中很重要的一部分,函数和数组。

函数和数组

第一部分 函数

1. 函数定义

2. 函数使用

3. 函数返回值

4. 函数参数

5. 函数变量

6. 函数的递归调用

第二部分 数组

1. 数组声明与赋值

2. 数组引用

3. 数组的数据处理

第一部分 函数

函数定义

函数,任何一门高级编程语言中都具备的一种代码结构。其实是借用了工程上模块化的思想。函数的作用能够将简化代码的编写,使得程序结构更加的清晰。同时函数能够复用重复的代码,实现代码重用和模块化。 bash中函数是由若干条shell命令组成的语句块,与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分。 函数由两部分组成:函数名和函数体 ,bash中函数的定义如下所示。

#语法一:function fun_name{
	...函数体...}#语法二:function fun_name (){
	...函数体...}#语法三:fun_name (){
	...函数体...}

实际使用过程中, 三种方式没有什么区别,根据自己的喜好去使用就可以。

函数使用

函数使用场景一般是在bash脚本中,定义函数,然后进行调用。

#!/bin/bash##函数定义hello (){
    echo "Hello World!"}#函数调用hello

同时也推荐,bash 脚本中需要使用的函数全部定义到一个单独的文件中,然后在bash脚本中进行引用。这样做的好处就是不光这一个bash脚本可以引用该文件里面的函数,其他的bash脚本也可以引用里面的某些函数。CentOS典型的例子有很多,例如/etc/init.d/functions文件。

#单独定义一个函数文件#执行vim /app/funcs#函数定义hello (){
    echo "Hello World!"}#函数调用hello

然后在bash脚本中进行调用

#!/bin/bash#引用函数文件. /app/funcs#调用函数名称hello

函数返回值

bash中有两种返回值

  • 函数执行结果返回值,使用echo命令返回,相当于Java 中的return关键字。
  • 函数的退出状态码,默认是函数体最后一条命令的退出状态码。当然也可以自定义,使用return关键字。自定义退出状态码,其格式为:
    • return 从函数中返回,用最后状态命令决定返回值。
    • return 0 无错误返回
    • return 1-255 有错误返回
#!/bin/bash##定义一个能够返回字符串的函数hello_echo (){
  echo "Hello"   }#定义一个返回命令执行结果的函数hello_return (){
  ls /etc/* &> /dev/null 
  return }#定义一个自定义状态结果的函数hello_return_error (){
  echo "test" &> /dev/null  
  return 1}#调用函数的返回结果echo "result 1 is $(hello_echo)"#调用函数的结果状态值hello_returnecho "result 2 is $?"#调用函数的结果状态值hello_return_errorecho "result 3 is $?"

函数参数

bash 中函数支持参数的传递。但是与其他高级编程语言不同的是,bash中并不会显示的指定参数的类型和个数,而是直接在函数的最后的传入参数。与bash脚本的参数传递是一致的。
在函数体中当中,可使用$1, $2, …调用这些参数;还可以使用[email protected], $*, $#等特殊变量。这一点与bash脚本几乎一致。

#!/bin/bash#hello (){
    echo "参数的个数是$#"
    echo "输出所有的参数[email protected]"
    echo "输出所有的参数$*"
    echo "第一个参数是$1"
    
    for arg in [email protected];do 
        echo "$arg"
    done}#调用函数,并在调用的时候传入参数。hello python java c#

因为bash的写法太过于灵活,以至于让人感觉bash并不是很严谨,在使用的时候可能要抛弃以往那些高级编程语言的思维。上面示例的结果如下图所示。

[[email protected] function]#./f4.sh  参数的个数是3
输出所有的参数python java c#
输出所有的参数python java c#
第一个参数是python
python
java
c#

函数变量

bash中的变量的作用域有三种类型:

  • 环境变量:在当前shell和子shell中有效。
  • 本地变量:只在当前shell进程中有效。
  • 局部变量:只在函数的生命周期中有效。函数运行结束,变量失效。

在函数中定义局部变量的的方式是 local VARIABLE_NAME=VALUE

#!/bin/bash#hello (){
	#定义一个函数体内的局部变量
    local name="HelloBash"
    echo $name}echo $name		#没有值  因为脚本中并没有定义这样一个本地变量hello			#会输出HelloBash

函数的递归调用

函数的递归调用指的是,函数间接地或者直接地调用自身。但是在递归地同时一定要注意什么时候结束递归,避免死循环。 这是编程地一种基本能力。

实验一 实现斐波那契数列

斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)利用函数,求n阶斐波那契数列

#!/bin/bash##实现了斐波那契数列read -p "请输入阶数" N

fibonacci (){
    if (( $1==0 ));then        echo 0 
    elif (( $1==1 ));then        echo 1    else
        #echo $(($[fibonacci $[$1-1]]+$[fibonacci $[$1-2]]))
       let num=$(fibonacci $[$1-1])+$(fibonacci $[$1-2])
       echo $num
    fi}if [ $N -ge 0 ] &> /dev/null ;then
    for I in `seq 0 $N`;do        fibonacci $I
    done
else    echo "$N 不是数字或者$N 小于0"
    exitfi

实验二 实现汉诺塔

汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤。

#!/bin/bashread -p "please input the number of plates:" NUM#执行的步骤数STEPS=0

move () {
    let STEPS++    echo "$STEPS:  move plate $1     $2---------->$3"}#传入的参数 NUM A B Chanoi () {
   
   if [ $1 -eq 1 ];then 
        move 1  $2 $4
   else        hanoi $[$1-1] $2 $4 $3
        move $1 $2 $4
        hanoi $[$1-1] $3 $2 $4
   fi}hanoi $NUM  A  B  C

实验三 fork炸弹

fork 炸弹是一种恶意地程序。实质上是一个简单地递归程序,由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源。

#在bash命令提示符后输入以下内容,系统很快就会崩溃。:(){ :|:& };:#下面这段代码是fork炸弹地形象展示。bomb() { bomb | bomb & }; bomb

顺便附上前几年比较火的让浏览器瞬间崩溃的12行代码,其实原理与fork炸弹是类似的。

<html><body><script>var total="";for (var i=0;i<1000000;i++){
	total= total+i.toString();
	history.pushState(0,0,total);}</script></body></html>

第二部分 数组

bash 中没有像其他高级编程语言那么多的基本数据类型(int double)和引用数据类型(List ArrayList)。bash是一种弱类型的编程语言。 数组是存储多个元素的连续的内存空间,相当于多个变量的集合。

数组声明与赋值

数组的声明有两种方式

  • 显示声明:declare -a ARRAY_NAME
  • 直接赋值:ARRAY_NAME[INDEX]=VALUE ,如果使用这种方式,Bash会自动创建数组。

数组的赋值有多种方式

  • 一次只赋值一个元素 ARRAY_NAME[INDEX]=VALUE
  • 一次赋值全部元素 ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
  • 只赋值特定元素 ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

在介绍了数组的引用之后,一并来举例说明数组的使用。

注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持,这就是关联数组,有点类似于高级编程语言中的字典

数组引用

数组的引用有下面几种方式

  • ${ARRAY_NAME[INDEX]}省略[INDEX]表示引用下标为0的元素
  • 引用所有的元素 ${ARRAY_NAME[*]},${ARRAY_NAME[@]}
  • 数组的长度 ${#ARRAY_NAME[*]} ,${#ARRAY_NAME[@]}
  • 删除数组中的某个元素:导致稀疏格式 ,unset ARRAY[INDEX]
  • 删除整个数组:unset ARRAY

数组的数据处理

数组数据的处理涉及到了数组数据的读取和追加。

  • 数组切片 ${ARRAY[@]:offset:number}

    • offset :要跳过的元素个数
    • number:要取出的元素个数
    • 区偏移量之后的所有元素 ${ARRAY[@]:offset}
  • 向数组中追加元素 ARRAY[${#ARRAY[*]}]=value

实验一 实现矩阵转置

# 编写脚本实现矩阵转置 matrix.sh
#	1 2 3 				1 4 7
#	4 5 6 		==>>	2 5 8
#	7 8 9 				3 6 9

#!/bin/bash
declare -A matrix
num_rows=3
num_columns=3

matrix=([1,1]="1" [1,2]="2"  [1,3]="3"  [2,1]="4"  [2,2]="5"  [2,3]="6"  [3,1]="7"  [3,2]="8"  [3,3]="9" ) 

f1="%9s"
for ((i=1;i<=num_rows;i++)) do
    for ((j=1;j<=num_columns;j++)) do
        printf "$f1" ${matrix[$i,$j]}
    done
echo
done

echo 
echo 

for ((j=1;j<=num_columns;j++)) do
    for ((i=1;i<=num_rows;i++)) do
        printf "$f1" ${matrix[$i,$j]}
    done
echo
done

这里我们需要注意的是,在bash中并没有二维数组的这个概念。所以我们只能够使用自定义下标的方式来模拟二维数组,原理上是相似的。 实现效果如下图所示。

实验二 编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;要统计其下标为偶数的文件中的行数之和

#!/bin/bash##bash中数组支持直接使用通配符来进行赋值declare -a files=(/var/log/*.log)declare -i lines=0for i in $(seq 0 $[${#files[*]}-1]);do
    #判断下标是不是偶数
    if [ $[$i%2] -eq 0 ];then        let lines+=$(wc -l ${files[$i]} | cut -d‘ ‘ -f1)
    fi
doneecho "Lines:$lines"

迄今为止,bash编程的大部分知识都已经介绍的差不多了,但是介绍的内容比较浅显,并没有深入的介绍。同时bash编程由于具有巨大的灵活性,导致使用方式多种多样,实际生产中应该根据自己的实际情况来灵活使用。

个人博客地址:http://www.pojun.tech/ 欢迎访问

时间: 2024-12-25 05:00:01

SHELL脚本编程进阶(二)的相关文章

shell脚本编程进阶练习题

这两天学习了shell脚本编程进阶,作为一枚文科生,小编觉得...恩..脚本很烧脑.....,不过小编还是做了些题,稍作总结后,呈给各位看官,内容如下: 一.条件选择if语句 选择执行: 注意:if语句可嵌套 单分支 if 判断条件;then 条件为真的分支代码 fi 双分支 if 判断条件; then 条件为真的分支代码 else 条件为假的分支代码 fi 多分支 if 判断条件1; then 条件为真的分支代码 elif 判断条件2; then 条件为真的分支代码 elif 判断条件3; t

SHELL脚本编程进阶(一)

写在前面(最重要) 本文部分资料和示例援引自以下书籍.在此,感谢原作者的创作,以及所有译者的付出,向他们致敬. Advanced Bash-Scripting Guide <高级Bash脚本编程指南>Revision 10中文版 Linux脚本编程执导 其中 <高级Bash脚本编程指南>Revision 10中文版 是 <Advanced Bash-Scripting Guide> 的中文翻译版,文档翻译正在进行中,再次感谢译者付出. 前言 在之前的文章中,我们已经详细

shell脚本编程进阶

前言:进入正题之前我们先来复习一下有关脚本编程的条件测试 一,流程控制 过程式编程语言 a.顺序执行 b.选择执行 c.循环执行 顺序执行 顾名思义,就是按照你脚本里所敲的内容顺序执行 选择执行 fi a.条件选择if语句,可以嵌套 b.格式 单支 if 判断条件;then 条件为真 fi 双分支 if 判断条件;then 条件为真 else 条件为假 fi 多分支 if 判断条件;then 条件为真 elif(相当于else if) 判断条件;then 条件为真 else 上述条件都为假 fi

第14章,Shell脚本编程进阶

更多内容请点击: Linux学习从入门到打死也不放弃,完全笔记整理(持续更新,求收藏,求点赞~~~~) http://blog.51cto.com/13683480/2095439 本章内容: 条件判断 循环 信号捕捉 函数 数组 高级字符串操作 高级变量 Expect 过程式编程语言执行方式: 顺序执行,选择执行,循环执行 条件选择----------------------------------------------------------------------- if语句: 结构: 

shell脚本编程——进阶篇(真刀实干)

条件测试 文件测试 整数测试 字符串与逻辑测试 if语句 if单分支语句 if双分支语句 if多分支语句 if嵌套语句 test命令测试特定的表达式 是否成立,当条件成立时,测试语句的返回值为0,否则为其他数值. 格式1:test 条件表达式 格式2:[ 条件表达式 ] (注意前后至少有1个空格,否则不予执行) 文件测试格式:[ 操作符 文件或目录 ]常用的测试操作符:1.-d:测试是否为目录(Directory)2.-e:测试目录或文件是否存在(Exist)3.-f:测试是否为文件(File)

Linux shell脚本编程入门(二) 循环语句

前面有了变量的概念和逻辑运算符,就可以写判断语句了,不过这里注意中括号的两边必须得留空格,不然报错. 运算符 赋值, 用 let 语句, 如 let "a=1" 算数运算, 支持 +  -  *  /  %(模运算)  **(幂运算) 位运算符, 支持 <<(左移)  >>(右移)  &(按位与)  |(按位或)  ~(按位取反)  ^(按位异或) if / then / else 语句 先举个栗子呗~ #!/bin/bash read var1 if

《Linux命令行与shell脚本编程大全》学习笔记(转)

第一部分:Linux命令行<Linux命令行与shell脚本编程大全> 第一章:初识Linux shell<Linux命令行与shell脚本编程大全> 第二章:走进shell<Linux命令行与shell脚本编程大全> 第三章:基本的bash shell命令<Linux命令行与shell脚本编程大全> 第四章:更多的bash shell命令<Linux命令行与shell脚本编程大全> 第五章:使用Linux环境变量<Linux命令行与she

shell脚本编程高级篇

SHELL脚本编程进阶循环执行:简单来说就是把一些指令重复循环. 循环代码具体的指令有三种: for , while , until其中for, while用的最多.for循环 for 变量名 in 列表;do 循环体 done 关键字的帮助都是用help来查询.for循环语法:在shell编程中 for,in,do,done.这些都是他的关键字,其中循环的指零就放在do和done之间.WORDS决定了循环次数.循环的次数由in 后面跟的WORDS(字符串)的数量决定.字符串的个数决定了do和d

《Linux命令行与Shell脚本编程大全第2版.布卢姆》pdf

下载地址:网盘下载 内容简介  · · · · · · 本书是一本关于Linux 命令行与shell 脚本编程的全面教程.全书分为四部分:第一部分介绍Linuxshell 命令行:第二部分介绍shell 脚本编程基础:第三部分深入探讨shell 脚本编程的高级内容:第四部分介绍如何在现实环境中使用shell 脚本.本书不仅涵盖了详尽的动手教程和现实世界中的实用信息,还提供了与所学内容相关的参考信息和背景资料. 本书内容全面,语言简练,示例丰富,适合于linux 系统管理员及Linux 爱好者阅读