(六)Shell编程之函数、脚本引用、符号展开、重定向

一、定义函数

shell中函数的定义格式如下:

[ function ] funname [()]
{
    action;
    [return int;]
}

说明:

  • 可以带function fun()定义,也可以直接fun()定义,如果带function函数名后的小括号可以省略。
  • 返回值语句[return int;]可以不写,如果不加,将以最后一条命令运行结果,作为返回值。注意return只能返回一个数值n(0-255)

下面我们直接举个栗子🌰~

#!/bin/bash

firstFun () {
    echo "这是我的第一个 shell 函数!"
}
function secondFun {
    echo "这是我的第二个 shell 函数!"
}
echo "-----函数开始执行-----"
firstFun
secondFun
echo "-----函数执行完毕-----"

 输出结果:

再来一个栗子🌰~

#!/bin/bash
funWithReturn () {
        echo "这个函数会对输入的2个数字进行相加运算"
        echo "输入第一个数字: "
        read aNum
        echo "输入第二个数字: "
        read anotherNum
        echo "两个数字分别为 $aNum 和 $anotherNum !"
        return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"

 输出结果:

注意: 函数的返回值在调用该函数后可以通过   $?  来获得

所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

二、函数参数

在shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...

下面我们直接举个栗子🌰~

#!/bin/bash
funWithParam () {
        echo "第一个参数为 $1 !"
        echo "第二个参数为 $2 !"
        echo "第十个参数为 $10 !"
        echo "第十个参数为 ${10} !"
        echo "第十一个参数为 ${11} !"
        echo "参数总数有 $# 个!"
        echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

输出结果:

 不难发现,echo "第十个参数为 $10 !"  第三行输出的值为10,为什么呢?不是应该为34吗,是因为$10被拆分为$1+0, 就是第一个参数1的值加上字符串0的拼接等于10,所以大家用的时候还是尽量注意加中括号给解释器区分一下区间。

参数区分给大家罗列一下~

参数处理说明
$#传递到脚本或函数的参数个数
$*以一个单字符串显示所有向脚本传递的参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数
$-显示Shell使用的当前选项,与set命令功能相同
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误

三、脚本引用

和其他语言一样,Shell也可以引用外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

Shell文件包含的语法格式如下:

. filename  # 注意点号(.)和文件名中间有一空格
或
source filename

下面我们直接举个栗子🌰~

先创建2个shell脚本文件。

function4.sh代码如下:

#!/bin/bash
work () {
       echo "第一个参数为 $1 !"
       echo "第二个参数为 $2 !"
}

function5.sh代码如下,还要赋予function5.sh这个脚本执行的权限chmod +x filename.sh:

#!/bin/bash
# 或者这么写 
# soure ./function4.sh
. ./function4.sh


work 11 22 33 44 55

执行function5.sh结果:

四、输出命令

(1)echo就不讲啦

(2)printf

printf命令模仿C程序库(library)里的printf()程序。

printf 由 POSIX标准所定义,因此使用printf的脚本比使用echo移植性好。

printf使用引用文本或空格分隔的参数,可以在printf中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认的printf不会像echo自动添加换行符,我们可以手动添加 \n

printf命令的语法:

printf format-string [arguments...]
  • format-string:为格式控制字符串
  • arguments:为参数列表
$echo "hello, shell"
hello, shell
$printf "hello,shell\n"
hello, shell

 

五、重定向

(1)标准输入输出 

一般情况下,每个Unix/Linux命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
  • 标准输出文件(stdout):stdout的文件描述符为1,Unix程序默认向stdout输出数据。
  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

我们直接来个栗子🌰更容易理解~

(2)输出重定向

 我输入ls的时候,显示很多文件,会把这个输出的内容重定向到默认的标准输出文件(stdout),

那同学们,我们可不可以把标准输出文件定义为我们自定义的一个文件呢?

当然可以,

ls 1>res.txt

那我们输入ls 1>res.txt的时候发现下面没有输出任何东西,为什么?

因为我们把输出内容从标准输出文件重定向到res.txt这个文件里面去了。

这个时候我们cat一下看看

还有一种情况,我们想ls aaa这个文件夹,但是没有这个aaa文件夹,怎么重定向?

 同样的道理~这个时候没有输出任何内容,因为被重定向在了response.txt文件里。

 这个时候我们cat一下这个response.txt试试看


 但是我们每次重定向一次,就会覆盖当前txt文件里面的内容,我们可以通过 << 往文件里追加内容

echo 666 >> response.txt 

(3)输入重定向

六、/dev/null文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null: 

command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容妈妈,那么什么也读取不到。但是 /dev/null文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。

如果希望屏蔽stdout 和stderr,可以这样写:

$ command > /dev/null 2>&1
$ command & /dev/null  # 简写

这里的2和>之间不可以有空格, 2>是一体的时候才表示错误输出。

七、shell展开

(1)简介

Shell接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell会展开词元里面的特殊字符,展开完成后才会调用相应的命令。

这种特殊字符的展开,称为shell展开(expansion)。其中有些用到通配符,又称为通配符展开(wildcard expansion)。Bash一共提供七种展开。

  • 大括号展开
  • 波浪线展开
  • 参数/变量展开
  • 算数展开
  • 子命令展开
  • 分词
  • 文件名展开

展开的顺序是:大括号展开、波浪展开、参数和变量展开、算数展开和子命令展开(以从左到右的方式完成),分词和文件名扩展。

Bash是先进行展开,再执行命令。因此,展开的结果是由Bash负责的,与所要执行的命令无关。命令本身并不存在参数扩展,收到什么参数就原样执行。

下面我们来举个栗子🌰~

 

 为什么我们输入 $name  他会输出liyifeng呢? 因为shell接收到用户输入的命令之后,会根据空格将用户的输入拆分,进行符号展开。

(2)大括号展开

大括号扩展 {...} 表示分别扩展成大括号里面的所有值,各个值之间使用逗号分隔。比如, {1,2,3}扩展成1 2 3

 注意1:有一个需要注意的地方是,大括号内部的逗号前后不能有空格。否则,大括号扩展会失效。 

注意2:逗号前面可以没有值, 表示扩展的第一项为空

 注意3:大括号还可以嵌套

 

注意4:大括号扩展有一个简写形式{start..end},表示扩展成一个连续序列。

还支持逆序

如果遇到无法理解的简写,大括号模式就会原样输出,不会扩展。 

还能补齐宽度,如果整数前面有前导0,将会强制每项具有相同的宽度,宽度不够会添加前导0。

  这种简写形式还可以使用第二个双点号( start..end..step ),用来指定扩展的步长。

 

(3) 波浪线展开

还可以这么写 cd ~/aa,就是进入主目录下面的aa文件夹

 

也可以这么写 cd ~zhanzhao,进入zhanzhao这个用户的家目录

 

还可以这么写 ~+,效果等同于 pwd 命令,显示当前所在的目录。

 每个变量的赋值都要检查紧跟在':'或第一个'='后面的未加引号的波浪前缀。在这些情况下,也会执行波浪展开。

 

 (4) 变量展开

4.1: 正常用法

Bash将美元符号 $ 开头的词元视为变量,将其扩展成变量值。

变量名除了放在美元符号后面,也可以放在 ${} 里面

name=wangyibo
echo $name
echo ${name}



# 输出结果
wangyibo
wangyibo

还可以通过 ${!变量名} 进行间接扩展,它首先在 !变量名 部分展开成变量对应的值,然后将这个值当做变量名,在进行变量展开。

name=dengchao
n=name
echo ${!n}


输出结果:
dengchao

 

4.2: 其他用法

1.如果parameter未设置或为空,则word的展开将被替换。否则,参数的值将被替换。

${parameter:-word}

栗子🌰(其实就是给个默认值的意思):

echo ${name:-word}
word

 2.如果parameter未设置或为空,则将word的展开部分赋给parameter。然后替换parameter的值。位置参数和特殊参数不能以这种方式赋值。

echo ${name:=word}

栗子🌰(其实就是给个默认赋值的意思):

echo ${name:=word}
word
echo $name
word

3.如果parameter未设置或为空,则word的展开(或word不存在时的展开消息)被写入标准错误,如果shell不是交互式的,则退出。

${parameter:?word}

栗子🌰:

echo ${aaa:?error}
-bash: aaa:error

echo ${aaa:?1223}
-bash: aaa:1223

 4.如果parameter未设置或为空,则不替换任何内容,否则替换word的展开。

${parameter:+word}

栗子🌰:

name=xinlan
echo ${name:+word}

word

5.子字符串扩展。类似于python的切片,它获取长度为length的参数值的字符,从offset位置开始。如果省略了length,则它从offset指定的字符开始,一直扩展到值的末尾。

${parameter:offset}

${parameter:offset:length}

 栗子🌰:

$ string=01234567890abcdefgh
$echo ${string:7}
7890abcdefgh

$ echo ${string:7:0}


$ echo ${string:7:2}
78

如果offset为负数,则该值将表示从结束开始的字符偏移量。也就是说 -1 表示倒数第一个,-2倒数第二个以此类推。请注意,负偏移量必须与冒号之间至少有一个空格隔开,以避免与":-"展开形式混淆。 

栗子🌰:

$ string=01234567890abcdefgh
$echo ${string: -7}
bcdefgh

$ echo ${string: -7:0}


$ echo ${string: -7:2}
bc

如果length的值为负数,则它被解释为从结束开始的字符偏移量, 展开结果为和offset之间的字符。

注意:如果是参数 @, 或者是下标为 @ 或 * 的索引数组时,length不能为负数, 否则会报错。

$ string=01234567890abcdefgh
$echo ${string:7:-2}
7890abcdef

$ echo ${string: -7:-2}
bcdef

6. 扩展为名称以前缀开头的变量名

${!prefix*}

${!prefix@}

 栗子🌰:

echo ${!na*}
na name

7.获取字符串参数的长度:

name="abcdef"
${#name}

6

还有很多就不一一赘述了。。 

(5)子命令展开

命令替换允许命令的输出替换命令本身。

当命令被如下所示包围时,就会发生命令替换:

$(command)

或者

`command`

 

 

 

(6)算数展开

算数展开允许对算数表达式求值并替换结果。算数展开的格式为:

$(( expression ))

 表达式会被当作双引号内的表达式来处理,但是括号内的双引号不会被特别处理。表达式中的所有标记都要进行参数和变量展开、命令替换和引号删除。结果被视为要求值的算数表达式。算数展开可以嵌套。

(7)文件名展开(重点)

文件名展开主要是通过三个字符 ? *[ ]

7.1 ?字符

? 字符代表文件路径里面的任意单个字符,不包括空字符。比如, Data???匹配所有 Data 后面跟着三个字符的文件名。

 如果文件不存在,展开就不会发生。

7.2 * 字符

* 字符代表文件路径里面的任意数量的任意字符,包括零个字符

注意1:* 不会匹配隐藏文件(以 . 开头的文件),即 ls * 不会输出隐藏文件。如果要匹配隐藏文件,需要写成 .*

注意2:* 只匹配当前目录,不会匹配子目录,可以通过ls */*匹配子目录

ls *

ls */*

 

7.3 [ ]字符 

# 显示所有隐藏文件
$ echo .*

# 如果要匹配隐藏文件,同时要排除 . 和 .. 这2个特殊的隐藏文件,可以与方括号扩展结合使用
$ echo .[!.]*

注意,如果文件不存在,展开不会发生,如果文件存在就会原样输出。

方括号扩展的形式是 [...],只有文件确实存在的前提下才会扩展。如果文件不存在,就会原样输出。括号之中的任意一个字符。比如, [aeiou]可以匹配五个元音子母中的任意一个。

 

 

 (8)量词语法

量词语法用来控制模式匹配的次数。它只有在 Bash 的 extglob 参数打开的情况下才能使用,不过一般是默认打开的。下面的命令可以查询。

$ shopt extglob
extglob      on

如果 extglob 参数是关闭的, 可以用下面的命令打开。

$ shopt -s extglob

量词语法有下面几个:

  • ?(pattern-list) : 模式匹配零次或一次
  • *(pattern-list) : 模式匹配零次或多次
  • +(pattern-list) : 模式匹配一次或多次
  • @(pattern-list) : 只匹配一次模式
  • !(pattern-list) : 匹配给定模式以外

举栗子🌰~ 

# ?(.)匹配零个或一个点
$ ls abc?(.)txt
abctxt  abc.txt
# ?(def)匹配零次或一个
$ ls abc?(def)
abc abcdef
# @(.txt|.php)匹配文件有且只有一个 .txt 或 .php后缀名
$ ls abc@(.txt|.php)
abc.php abc.txt
# +(.txt)匹配文件有一个或多个.txt后缀名
$ ls abc+(.txt)
abc.txt abc.txt.txt
# !(b)表示匹配单个字母b以外的任意内容,所以除了ab.txt以外,其他文件名都能匹配
$ ls a!(b).txt
a.txt abb.txt ac.txt

量词语法也属于文件名扩展,如果不存在可匹配的文件,就会原样输出

# 没有 abc 开头的文件
$ ls abc?(def)
ls:无法访问'abc?(def)':没有那个文件或目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值