一、定义函数
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)':没有那个文件或目录