20170829 Shell编程进阶(二)

  • 函数的基本含义
  • 函数的定义和使用
  • 数组
  • 字符串处理
  • 特殊的处理变量用法

一、函数基本含义:

  • 函数:多条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"}
      }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容