美文网首页Linux初学者学习笔记
20170829 Shell编程进阶(二)

20170829 Shell编程进阶(二)

作者: 哈喽别样 | 来源:发表于2017-09-04 08:30 被阅读13次
  • 函数的基本含义
  • 函数的定义和使用
  • 数组
  • 字符串处理
  • 特殊的处理变量用法

一、函数基本含义:

  • 函数:多条Shell命令组成的语句块,实现代码重用、模块化编程

  • 函数与Shell程序的区别:

    • 函数不能独立运行,Shell程序可以独立运行
    • 函数只能在Shell程序中运行,Shell程序可以在其他Shell程序建立的子Shell中运行
    • 函数可以修改Shell中的变量,子Shell程序无法修改父Shell程序的变量
  • 使用函数前必须对其定义,定义中需要阐明函数名和函数体

  • 函数的定义:函数的名称建议有一定含义,增加可读性
    函数定义的语法格式共有3种,效果相同

    • 格式1:
      function f_name {
      函数体
      ......
      }
    • 格式2:
      function f_name () {
      函数体
      ......
      }
    • 格式3:
      f_name() {
      函数体
      ......
      }
  • 函数的使用:给出函数名,则函数名位置自动被替换为函数代码

  • 函数的定义和使用方式:

    • 可以在交互式环境中直接定义函数
    • 可以在脚本中定义和使用函数
    • 可以把函数的定义编写在一个脚本文件中
  • 函数的返回值:

    • 函数体内echo语句输出值
    • 函数体内调用命令的输出结果
  • 函数的退出状态码

    • 默认状态:退出状态码取决于函数体执行的最后一条命令的退出状态码
    • 自定义退出状态码 return命令:
      • return,默认状态的退出状态码:return前最后执行命令的退出状态码
      • return NUM:以指定的数字NUM(0-255)作为退出状态码,一般0指代成功,1-255可以自定义各类错误

二、函数的定义与使用详解:

(一)函数的定义:
  • 交互式环境定义
f_name () {          //第一行输至{回车
>函数体语句           //>是提示符,后面输入函数体语句,回车换行继续输入
>函数体语句
> ......
>}                   //输入}后回车定义本函数完毕
  • 在脚本中定义函数
    shell是解释性语言,运行时从上至下执行语句,故函数的定义语句必须在其被调用之前

  • 函数定义文件
    使用函数文件中定义的函数前,必须先执行函数定义文件,此时函数的定义将载入当前shell环境中,语法:. filename 或者 source filename

(二)函数的使用
  • 只需给出函数名即可调用函数

    • set:查询当前所有定义的函数,这些函数都已经载入所在shell
      语法:set f_name, f_name:函数名称
    • unset:撤销函数的定义,被撤销函数的定义将从shell中卸载
      语法:unset f_name,f_name:函数名称
  • 环境函数:在子进程中使用父进程定义的函数
    语法:
    声明环境函数:export -f f_name,f_name函数名称
    查看环境函数:export -f 或者 declare -xf

  • 函数的参数:函数可以接受参数,从而扩展函数的使用功能

    • 传递参数给函数:在调用函数时,在函数名后直接附加以空格分隔的给定参数
      语法:f_name arg1 arg2 arg3...,f_name:函数名称;arg1,arg2,arg3:参数
    • 函数体内调用参数,使用位置变量$1, $2, $3以及一些特殊变量:$*, $@, $#
  • 函数变量的作用域:

    • 环境变量:在父进程、子进程均有效的变量,使用export var_name声明
    • 本地变量:仅在本进程范围内有效的变量,函数内对本地变量的修改将影响到函数外
    • 局部变量:仅在函数的生命周期内有效,函数调用结束则自动消失,故函数内对局部变量的修改对函数外无影响,使用local var_name声明
    • 当本地与局部变量名称相同时,从函数的声明周期角度分析,函数体内部使用局部变量运算,函数体外使用本地变量运算,两个变量相互隔离
  • 函数的递归调用:函数调用自身

    • 函数的递回调用一般需要有结束条件,使子层函数的结果最终能够返回至上层函数中
    • 不同的语言的最大递归层数不同
    • fork炸弹:不断fork进程的无限循环,最终耗尽系统资源
      • 函数实现
        :(){ :|:& };:,:冒号是函数名称,本函数与下面的函数写法相同
        bomb() { bomb | bomb & }; bomb,函数名为bomb
      • 脚本实现
        #! /bin/bash
        ./$0|./$0&
        
  • 实验:
    汉诺塔问题:三根柱子,在一根柱子上从下往上按照大小顺序摞着N片盘子。要求把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤

    分析:
    想要把N个盘子从一个柱子移动到另一个柱子,可以分为三个步骤:(1)把N-1个盘子从柱子A移动到柱子B;(2)把第N个盘子从柱子A移动到柱子C;(3)把N-1个盘子从柱子B移动到柱子C。而步骤1和步骤3都能够再分解为如上的三个步骤,所以汉诺塔问题是一个递归问题。通过递归函数,可以实现输出汉诺塔的移动步骤。

    代码:

read -p "please type the layer of hannuota: " num 
from="A"
via="B"
to="C"
#第2步的函数实现
move () {
        echo "plate:$1 $2 --> $3"
}
#实现递归函数
hannuota () {
#       echo num:$1 from:$2 via:$3 to:$4
        if [ $1 -eq 1 ]; then
                move $1 $2 $4
        else
#递归过程具体实现
                hannuota $[ $1-1 ] $2 $4 $3
                move $1 $2 $4
                hannuota $[ $1-1 ] $3 $2 $4
        fi  
}
#调用函数
hannuota $num $from $via $to

执行结果如下:

三、数组:

(一)数组的定义:
  • 数组:存储多个元素的连续的内存空间,相当于多个变量的集合

  • 数组的索引:从0开始编号,数字索引方式

  • 数组的关联索引:自定义索引,使用字母、数字等符号作为索引,从bash 4.0开始支持

  • 稀疏数组:数组的索引编号不连续,可以创建时不连续,也可能因数组的增删操作而变得不连续

(二)数组的语法介绍:
  • 声明数组:普通数组和关联数组一经声明不可互相转换
    普通数组:declare -a ARRAY_NAME
    关联数组:declare -A ARRAY_NAME,关联数组必须先声明才能被调用

  • 数组元素的赋值:
    (1)给单个元素赋值:
    ARRAY_NAME[#]=VALUE
    (2)一次将数组元素赋值:
    ARRAY_NAME=("VAL1" "VAL2" "VAL3" "VAL4" ...)
    (3)给特定的数组元素赋值:
    ARRAY_NAME=([0]="VAL1" [2]="VAL2" ...)
    (4)交互式赋值:
    read -a ARRAY_NAME

  • 显示所有数组:
    declare -a

(三)数组数据的处理:
  • 引用数组元素:${ARRAY_NAME[#]}
    注明:${ARRAY_NAME}相当于${ARRAY_NAME[0]}

  • 引用数组所有元素:
    ${ARRAY_NAME[*]} 或者 ${ARRAY_NAME[@]}

  • 数组的长度(数组元素的个数)
    ${#ARRAY_NAME[*]} 或者 ${#ARRAY_NAME[@]}

  • 添加数组元素至数组尾部
    ARRAY_NAME[${#ARRAY_NAME[*]}]=VALUE

  • 删除数组元素
    unset ARRAY_NAME[#]

  • 删除数组
    unset ARRAY_NAME

  • 数组切片
    ${ARRAY_NAME[@]:offset:number}
    offset:跳过元素的个数
    number:取出元素的个数

  • 实验:如下图所示,实现转置矩阵matrix.sh

1 2 3              1 4 7
4 5 6    ===>      2 5 8
7 8 9              3 6 9

分析发现需要交换的数字其特点在于,两个数字所在位置的行号与列号正好相反。如数字2位于行1列2,而数字4位于行2列1。由此,可以分别采用两次循环操作行号和列号,并交换符合要求的数字。为了防止已经交换的数字再次交换,实际上需要虚幻的数字位于数字1至数字9所连接成的对角线上方数字即可。

代码如下:

#要求用户输入想要N*N的矩阵
read -p "please type the number of matrix: " matrix_num
#从0行0列开始数组编号,所以先对用户输入值自减1
let matrix_num--      
num=1
#定义数组
declare -a matrix   
#从1开始为矩阵赋值   
for i in `seq 0 $matrix_num`; do  
        for j in `seq 0 $matrix_num`; do
                matrix[$i$j]=$num
                let num++
        done
done
#定义函数,实现矩阵打印
func() {
        for p in `seq 0 $matrix_num`; do
                for q in `seq 0 $matrix_num`; do
                        echo -ne  "${matrix[$p$q]}\t"
                done
                echo
        done
}
#调用函数,打印矩阵
func
echo
# 将矩阵转置
for x in `seq 0 $matrix_num`; do
        for y in `seq $x $matrix_num`; do
                if ! [ $x -eq $y ]; then
                        temp=${matrix[$x$y]}
                        matrix[$x$y]=${matrix[$y$x]}
                        matrix[$y$x]=$temp
                fi
        done
done
#再次调用func函数,打印矩阵
func

执行结果如下:

四、字符串处理:

(一)字符串切片:
  • ${#var}:返回字符串变量的长度

  • ${var:offset}:返回字符串从第offset个(不含第offset个)到最后字符的部分

  • ${var:offset:num}:返回字符串从第offset个(不含第offset个)共num个字符长度的部分

  • ${var: -length}:返回字符串最右侧的length个字符,冒号后有一个空格

  • ${var:offset:-length}:返回字符串从第offset个(不含第offset个)到距最右侧共length个字符的部分

  • ${var: -length:-offset}:从最右侧向左取length个字符开始,至距最右侧offset个字符之间的字符,-length前有一个空格

(二)基于模式取子字符串:
  • ${var#*word}:删除从字符串开始至第一次出现word之间的所有字符

  • ${var##*word}:贪婪模式,删除从字符串开始至最后一次出现word之间的所有字符

  • ${var%word*}:删除从最右侧开始至第一次出现word之间的所有字符

  • ${var%%word*}:贪婪模式,删除从最右侧开始至最后一次出现word之间的所有字符

(三)查找替换

  • ${var/pattern/substr}:将查找到的第一次匹配pattern的字符串替换为substr

  • ${var//pattern/substr}:将查找到的所有匹配pattern的字符串替换为substr

  • ${var/#pattern/substr}:将行首匹配pattern的字符串替换为substr

  • ${var/%pattern/substr}:将行尾匹配pattern的字符串替换为substr

(四)查找删除

  • ${var/pattern}:将查找到的第一次匹配pattern的字符串删除

  • ${var//pattern}:将查找到的所有匹配pattern的字符串删除

  • ${var/#pattern}:将行首匹配pattern的字符串删除

  • ${var/%pattern}:将行尾匹配pattern的字符串删除

(五)大小写转换

  • ${var^^}:字符串全部转换为大写

  • ${var,,}:字符串全部转换为小写

五、特殊的处理变量用法:

(一)声明为有类型变量:declare 和 typeset,两者等价

语法:
declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示此脚本前定义过的所有函数名及其内容
-F 仅显示此脚本前定义过的所有函数名
-x 声明或显示环境变量和函数
-l 声明变量为小写字母
-u 声明变量为大写字母

(二)eval命令:
  • 功能:先扫描一次命令行执行全部替换,再执行命令

  • 适用于一次扫描不能实现功能的变量,eval命令可以实现两次扫描

(三)间接变量引用:
  • 如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用

  • 实现方法:
    eval tempvar=$$variable1
    tempvar=${!variable1}

(四)创建临时文件 mktemp命令:
  • 功能:创建并显示临时文件,可避免冲突

  • 语法:
    mktemp [OPTION]... [TEMPLATE]
    TEMPLATE: filename.XXXX,至少要出现三个
    OPTION:
    -d: 创建临时目录
    -p DIR或--tmpdir=DIR:指明临时文件所存放目录位置

(五)安装复制文件 install命令:
  • 可以实现文件复制,所有者、所属组修改,权限修改在一条命令中执行

  • 语法:
    install [OPTION]... [-T] SOURCE DEST 单文件
    install [OPTION]... SOURCE... DIRECTORY
    install [OPTION]... -t DIRECTORY SOURCE...
    install [OPTION]... -d DIRECTORY...创建空目录

  • 选项:
    -m MODE,默认755
    -o OWNER
    -g GROUP

(六)expect命令:
  • 功能:实现自动化交互式操作场景

  • expect 语法:
    expect [选项] [ -c cmds] [ [ -[f|b] ] cmdfile] [ args ]

  • 选项
    -c:从命令行执行expect脚本,expect默认交互式执行
    -d:可以输出调试信息

  • expect中相关命令
    spawn:启动新的进程
    send:用于向进程发送字符串
    expect:从进程接收字符串
    interact:允许用户交互
    exp_continue:匹配多个字符串在执行动作后加此命令

  • expect最常用的语法(tcl语言:模式-动作)

    • 单一分支模式语法:
      expect “hi” {send “You said hi\n"}
      匹配到hi后,会输出“you said hi”,并换行

    • 多分支模式语法:
      expect "hi" { send "You said hi\n" }
      "hehe" { send “Hehe yourself\n" }
      "bye" { send “Good bye\n" }
      匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
      expect {
      "hi" { send "You said hi\n"}
      "hehe" { send "Hehe yourself\n"}
      "bye" { send “Good bye\n"}
      }

相关文章

  • 20170829 Shell编程进阶(二)

    函数的基本含义函数的定义和使用数组字符串处理特殊的处理变量用法 一、函数基本含义: 函数:多条Shell命令组成的...

  • shell 案例

    Shell编程一 Shell防范ARP攻击 Shell编程二 Shell防范DDos攻击 Shell编程三 ...

  • shell编程进阶

    流程控制 if 单条件 双分支 多分支 case case支持glob风格的通配符: *: 任意长度任意字符?: ...

  • java web目录

    java web目录 web编程基础web编程进阶(一)web编程进阶(二)web编程原理

  • shell脚本编程进阶

    一、流程控制 过程式编程语言 二、条件选择:if语句 单分支 双分支 多分支 实例 三、条件判断:case语句 四...

  • shell脚本编程(进阶)

    以三台机器搭建的集群为例 1.查看三台机器的进程 2.拷贝文件脚本 3.启动集群shell脚本 3.1 编写启动脚...

  • Shell基础 -Linux从入门到精通第九天(非原创)

    文章大纲 一、关于shell二、shell进阶(重点)三、学习资料下载四、参考文章 一、关于shell 1. 什么...

  • BigData~01:Shell

    Shell编程基础 内容大纲 一、Shell编程二、高级文本处理命令:sed、awk、cut三、crontab定时...

  • shell 第一天

    shell编程初识 1.1 shell编程初识 shell的定义 Shell 是命令解释器 Shell 也是...

  • 20170824 Shell编程进阶(一)

    选择执行:if语句条件判断:case语句循环控制:for语句循环控制:while语句和until语句循环控制:co...

网友评论

    本文标题:20170829 Shell编程进阶(二)

    本文链接:https://www.haomeiwen.com/subject/fsuadxtx.html