Shell编程基础(六)Shell函数基础 shell编程基本步骤
liebian365 2024-10-26 13:02 18 浏览 0 评论
基本概念:
将一段代码组合封装在一起实现某个特定的功能或返回某个特定的值,然后给这段代码取个名字,也就是函数名,在需要实现某个特定功能的时候直接调用函数名即可。
函数名的定义和定义变量的规则基本一致,但是函数名允许以数字开头。
定义函数
定义函数并不会导致函数内的任何命令被执行,仅当函数被调用时,函数内的命令才会被触发执行。
语法格式:
方式1:
函数名() {
命令序列
}
方式2:
function 函数名() {
命令序列
}
方式3:
function 函数名 {
命令序列
}
函数的基本使用
定义一个简单的函数:
[root@bogon func]# firstFun() {
> echo "This is my first function."
> }
调用函数:
[root@bogon func]# firstFun
This is my first function.
函数的参数
在函数内部可以使用位置变量($1、$2、...)来读取参数的值,在调用函数时添加相应的参数即可,或者读取其他全局变量都可以实现传递参数的功能。
如下脚本案例:实现监控指定服务是否启动的功能。
[root@bogon func]# vim check_service.sh
#!/bin/bash
# 功能描述:检测指定的服务是否开启
date_formate=$(date +"%Y-%m-%d %H:%M:%S")
function check_service() {
for i in "$@"
do
if systemctl --quiet is-active ${i}.service; then
echo -e "[$date_formate]: \033[92mservice $i is active\033[0m"
else
echo -e "[$date_formate]: \033[91mservice $i is not active\033[0m"
fi
done
}
check_service httpd sshd vsftd
[root@bogon func]# chmod +x check_service.sh
[root@bogon func]# ./check_service.sh
[2021-09-06 11:02:56]: service httpd is not active
[2021-09-06 11:02:56]: service sshd is active
[2021-09-06 11:02:56]: service vsftd is not active
变量的作用域和return返回值
变量的作用域
Shell脚本中执行函数时并不会开启子进程,默认在函数外部或函数内部定义和使用变量的效果相同。
来看下面的脚本示例:
[root@bogon func]# vim function_var1.sh
#!/bin/bash
var1="函数外部定义的变量var1"
echo -e "\033[31m$var1\033[0m"
echo "--------------------------------"
function demo() {
var2="函数内定义的变量var2"
echo $var2
# 在函数内部调用外部定义的变量var1
echo "函数内部调用外部定义的变量var1:$var1"
# 在函数内部修改外部定义的变量var1
var1="var1的值在函数内部被修改了"
echo "函数内部调用修改后的变量var1:$var1"
}
# 调用函数
demo
echo '------------------------------------------'
# 执行函数之后,再次输出变量var1和var2
echo "var1:$var1"
echo "var2:$var2"
[root@bogon func]# bash function_var1.sh
函数外部定义的变量var1
--------------------------------
函数内定义的变量var2
函数内部调用外部定义的变量var1:函数外部定义的变量var1
函数内部调用修改后的变量var1:var1的值在函数内部被修改了
------------------------------------------
var1:var1的值在函数内部被修改了
var2:函数内定义的变量var2
通过上例可以看出:函数外部的变量在函数内部可以直接调用,反之函数内部的变量也可以在函数外部直接调用。但是这样会导致变量混淆、数据可能被错误地修改等等问题,那么如何解决这些问题呢?
系统为我们提供了一个local语句,该语句可以使在函数内部定义的变量仅在函数内部有效。
[root@bogon func]# vim function_var2.sh
#!/bin/bash
var1="var1"
echo "var1:$var1"
echo "---------------------------"
demo() {
# 使用local定义局部变量
local var1="aaaaaaaa"
echo -e "\033[31m函数内部var1:$var1\033[0m"
}
demo
echo "--------------------------"
echo "var1:$var1"
[root@bogon func]# bash function_var2.sh
var1:var1
---------------------------
函数内部var1:aaaaaaaa
--------------------------
var1:var1
一般情况下,定义的普通变量和数组都是在当前Shell中有效的全局变量,但是使用declare定义的关联数组则是一种特殊情况。
如下示例:
[root@bogon func]# vim function_var3.sh
#!/bin/bash
# 函数外定义的索引数组和关联数组都是全局变量
arr1=(aa bb cc)
declare -A arr2
arr2[name]="Tome"
arr2[gender]="male"
arr2[age]=25
# 定义demo函数
# 在函数内部定义的索引数组为全局变量
# 在函数内部定义的关联数组为局部变量
demo() {
# 调用函数外的全局变量
echo ${arr1[@]}
echo ${arr2[@]}
# 给数组变量重新赋值
arr1=(11 22 33)
declare -A arr2
arr2[name]="Lucy"
arr2[gender]="female"
# 输出重新赋值后的变量值
echo ${arr1[@]}
echo ${arr2[@]}
}
demo
# 调用函数后,在函数外部重新输出变量的值
echo ${arr1[@]}
echo ${arr2[@]}
[root@bogon func]# bash function_var3.sh
aa bb cc
male Tome 25
11 22 33
female Lucy
11 22 33
male Tome 25
最后还要注意一下,定义函数不会导致函数被执行,因此函数在调用之前,无论是全局变量还是局部变量,都不可以在外部和内部之间相互调用。
return返回值
函数执行完成后,默认整个函数的状态码为函数内部最后一个命令的返回值,之前提到的exit命令可以自定义返回码,但是在函数中如果使用exit命令就会导致整个脚本直接退出。
在函数中我们可以使用return命令立刻让函数终端并返回特定的状态码,并且不会影响脚本中后续命令的执行。
注意:return只能返回特定的状态码(数字),不能是变量或其他字符。
[root@bogon func]# vim function_var4.sh
#/bin/bash
# 函数的返回状态码默认为最后一条命令的状态码
demo1() {
uname -r
}
# 使用return自定义返回状态码
function demo2() {
echo "demo2 function start"
return 100
echo "demo2 function end."
}
# 使用exit自定义返回码
function demo3 {
echo "demo3 function start"
exit 200
echo "demo3 function end."
}
demo1
echo "demo1 status:$?"
echo
demo2
echo "demo2 status:$?"
echo
demo3
echo "demo3 status:$?"
# demo3执行后直接退出脚本,后续命令不在执行
echo "这条命令不会执行了"
[root@bogon func]# bash function_var4.sh
3.10.0-862.el7.x86_64
demo1 status:0
demo2 function start
demo2 status:100
demo3 function start
多进程执行脚本
先来看一个使用多进程ping IP地址的示例:
[root@bogon func]# vim multi_ping.sh
#!/bin/bash
# 定义函数ping特定的IP
multi_ping() {
ping -c2 -i0.2 -W1 $1 &> /dev/null
if [ $? -eq 0 ];then
echo "$1 is up"
else
echo "$1 is down"
fi
}
net="192.168.4"
for i in {1..254}
do
# 调用函数并放入后台执行
multi_ping $net.$i &
done
# 所有后台子进程都结束后在退出脚本
Wait
[root@bogon func]# bash multi_ping.sh
192.168.4.8 is down
192.168.4.5 is down
192.168.4.1 is down
192.168.4.3 is down
192.168.4.2 is down
192.168.4.7 is down
192.168.4.6 is down
192.168.4.4 is down
192.168.4.9 is down
...
执行该脚本时立刻新打开一个终端,使用ps命令查看进程列表,会发现同时启动了几百个进程,对于ping这样的小程序还好,如果是一个非常消耗CPU、内存、磁盘I/O资源的程序,启动几百个这样的程序,系统将瞬间崩溃。
那么我们如何限制一次启动进程的数量呢?比如一次仅启动10个进程,等待10个进程都结束再启动10个,以此类推。这里就需要引入另外两个概念:文件描述符和命名管道。
文件描述符
文件描述符是一个非负整数,而内核需要通过这个文件描述符才能访问文件。当我们在系统中打开已有的文件或新建文件时,内核每次都会给特定的进程返回一个文件描述符,当进程需要对文件进行读或写操作时,都要依赖这个文件描述符进行。文件描述符就像是一本书的目录页(也叫索引),通过这个索引可以找到需要的内容。
在Linux或类Unix系统中内核默认会为每个进程创建三个标准的文件描述符,0(标准输入)、1(标准输出)、2(错误输出)。
查看每个每个进程拥有的所有文件描述符可以通过/proc/PID号/fd/目录下的文件来查看。
# 查看当前Shell的文件描述符
[root@bogon ~]# ll /proc/$/fd/
total 0
lrwx------ 1 root root 64 Sep 6 10:54 0 -> /dev/pts/1
lrwx------ 1 root root 64 Sep 6 10:54 1 -> /dev/pts/1
lrwx------ 1 root root 64 Sep 6 10:54 2 -> /dev/pts/1
lrwx------ 1 root root 64 Sep 6 17:11 255 -> /dev/pts/1
[root@bogon ~]# ls -l /proc/1/fd/
total 0
lrwx------ 1 root root 64 Sep 6 2021 0 -> /dev/null
lrwx------ 1 root root 64 Sep 6 2021 1 -> /dev/null
lr-x------ 1 root root 64 Sep 6 17:11 10 -> anon_inode:inotify
lr-x------ 1 root root 64 Sep 6 17:11 11 -> /proc/swaps
lrwx------ 1 root root 64 Sep 6 17:11 12 -> socket:[12962]
...
当打开文件时系统就会为特定的进程自动创建对应的文件描述符。
[root@bogon ~]# vim /var/log/messages
同时打开新的终端,通过ps命令查看vim的进程号,并观察该文件的文件描述符:
[root@bogon func]# ps -ao user,pid,comm | grep vim
root 3392 vim
[root@bogon func]# ll /proc/3392/fd
total 0
lrwx------ 1 root root 64 Sep 6 17:17 0 -> /dev/pts/1
lrwx------ 1 root root 64 Sep 6 17:17 1 -> /dev/pts/1
lrwx------ 1 root root 64 Sep 6 17:17 2 -> /dev/pts/1
lrwx------ 1 root root 64 Sep 6 17:17 4 -> /var/log/.messages.swp
通过上面的输出可以看出,vim进程除了拥有三个标准文件描述符外,还有一个编号为4的文件描述符,该文件描述符索引 /var/log/.messages.swp文件。Vim默认会将所有的修改都写入一个以.原文件名.swp的文件中,只有在vim中执行保存命令才会将数据同步到原文件中。
除了系统自动创建文件描述符,还可以通过命令手动自定义文件描述符。
创建文件描述符的语法格式:
exec 文件描述符< 文件名 # 创建仅可输入的文件描述符
exec 文件描述符> 文件名 # 创建仅可输出的文件描述符
exec 文件描述符<> 文件名 # 创建可输入输出的文件描述符
当关闭一个使用>创建的文件描述符之后,再重新创建该文件的文件描述符,使用该文件描述符向文件中添加内容时,将覆盖文件中原来的内容,此时可是使用>>创建追加的文件描述符。
exec 文件描述符>> 文件名
调用文件描述符:
&文件描述符
关闭文件描述符:
exec 文件描述符<&-
或
exec 文件描述符>&-
创建文件描述符时,如果文件描述符对应的文件不存在,系统会自动创建一个新的空文件。
# 创建仅可输出的文件描述符
[root@bogon func]# exec 12> test.txt
# 使用文件描述符添加内容
[root@bogon func]# echo hello >&12
[root@bogon func]# echo world >&12
# 关闭文件描述符
[root@bogon func]# exec 12>&-
[root@bogon func]# cat test.txt
hello
world
# 重新创建文件描述符
[root@bogon func]# exec 12> test.txt
# 向文件中添加内容,此时将覆盖文件中的原数据
[root@bogon func]# echo AAAAAAAA >&12
[root@bogon func]# exec 12>&-
[root@bogon func]# cat test.txt
AAAAAAAA
使用>>创建文件描述符追加内容
[root@bogon func]# exec 12>> test.txt
[root@bogon func]# echo BBBBBB >&12
[root@bogon func]# echo CCCCCCC >&12
[root@bogon func]# exec 12>&-
[root@bogon func]# cat test.txt
AAAAAAAA
BBBBBB
CCCCCCC
使用<创建仅可重定向输入的文件描述符
[root@bogon func]# exec 13<test.txt
[root@bogon func]# cat test.txt
hello world
# 重定向输出失败
[root@bogon func]# echo "AAAAAAAA" >&13
-bash: echo: write error: Bad file descriptor
[root@bogon func]# cat test.txt
hello world
[root@bogon func]# exec 13<&-
使用<>创建即可输入也可输出的文件描述符
# 创建可读写的文件描述符
[root@bogon func]# exec 14<>test.txt
# 使用文件描述符查看文件内容
[root@bogon func]# cat <&14
AAAAAAAA
BBBBBBB
CCCCCC
# 重定向写入
[root@bogon func]# echo "Hello World" >&14
[root@bogon func]# cat test.txt
AAAAAAAA
BBBBBBB
CCCCCC
Hello World
# 关闭文件描述符
[root@bogon func]# exec 14<&-
# 确认文件描述符已被关闭
[root@bogon func]# cat <&14
-bash: 14: Bad file descriptor
[root@bogon func]# echo "DDDDD" >&14
-bash: 14: Bad file descriptor
[root@bogon func]#
除了使用cat命令可以通过文件描述符读取文件的全部内容外,还可以使用read命令后跟-u选项也可以通过文件描述符读取文件内容。不同的是,read命令每次仅读取一行数据。
# 新建一个有三行数据的文件
[root@bogon func]# echo "line1
> line2
> line3" > new.txt
[root@bogon func]# cat new.txt
line1
line2
line3
# 创建文件描述符
[root@bogon func]# exec 12<new.txt
# 使用read命令读取文件内容,每次读取一行,将读取的内容赋值给变量content
[root@bogon func]# read -u12 content
# 输出读取到的内容
[root@bogon func]# echo $content
line1
[root@bogon func]# read -u12 content
[root@bogon func]# echo $content
line2
[root@bogon func]# read -u12 content
[root@bogon func]# echo $content
line3
# 读取到最后一行后没有数据,输出空行
[root@bogon func]# read -u12 content
[root@bogon func]# echo $content
# 此时使用cat也无法读取任何内容
[root@bogon func]# cat <&12
# 关闭文件描述符
[root@bogon func]# exec 12<&-
从上述示例中可以看出,文件描述符并不是简单的对应一个文件的。文件描述符中还包含有很多如权限、文件偏移量等相关的信息。文件偏移量更像一个指针,它指向文件的某个位置,默认情况下该指针指向的是文件的起始位置,当使用read命令读取一行数据后,该指针会指向下一行数据,以此类推,直到文件结束。
因为cat命令会读取文件的全部内容,所以当我们使用cat命令读取文件描述符时,文件描述符的指针会一次性跳到文件的末尾,一旦到了文件末尾,再通过文件描述符读取文件内容就为空。
[root@bogon func]# exec 12<new.txt
[root@bogon func]# cat <&12
line1
line2
line3
# 再次使用文件描述符读取文件内容为空
[root@bogon func]# cat <&12
[root@bogon func]# exec 12<&-
另外,read命令也可以通过-n选项指定读取任意字符的数据:
[root@bogon func]# exec 12<new.txt
[root@bogon func]# read -u12 -n2 str
[root@bogon func]# echo $str
li
[root@bogon func]# read -u12 -n3 str
[root@bogon func]# echo $str
ne1
[root@bogon func]# exec 12<&-
[root@bogon func]#
不仅查看内容会导致指针移动,写入数据也会导致指针移动。通过文件描述符追加写入数据后,就不能再使用文件描述符查看了,因为指针已经移动到文件末尾了。
[root@bogon func]# exec 12<>file.txt
[root@bogon func]# echo "Shell" >&12
[root@bogon func]# echo "Python" >&12
[root@bogon func]# cat <&12
[root@bogon func]# cat file.txt
Shell
Python
[root@bogon func]# exec 12<&-
命名管道
管道是进程间通信的一种方式,一个程序往管道中写数据,另一个程序就可以从管道中读取数据。之前使用“|”符号创建的是一个匿名管道,匿名管道仅可以实现父进程与子进程之间的数据交换,若要实现任意两个无关进程之间的通信,需要使用命名管道,也叫FIFO文件。
命名管道的特征:
- FIFO文件由命令(mknod或mkfifo)创建,可以在文件系统中直接看到;
- 写入管道的数据一旦被读取后,就不可以再重复读取;
- 进程往命名管道中写数据时,如果没有其他进程读取数据,则写进程会被阻塞;
- 进程尝试从命名管道中读取数据时,如果管道中没有数据,则读进程会被阻塞;
- 命名管道中的数据常驻内存,并不实际写入磁盘,读写效率会更高。
下面通过简单的示例演示一下读写数据被阻塞的情况:
# 创建命名管道2
[root@bogon func]# mkfifo pipe_file1
# 创建命名管道符并设置权限
[root@bogon func]# mkfifo -m 664 pipe_file2
[root@bogon func]# ls -l pipe_file*
prw-r--r-- 1 root root 0 Sep 7 12:02 pipe_file1
prw-rw-r-- 1 root root 0 Sep 7 12:03 pipe_file2
# 没有其他进程读数据,写数据进程被阻塞
[root@bogon func]# echo "hello world" > pipe_file1
新开终端,读取数据:
写阻塞自动解除:
再来看下度阻塞:
命名管道没有数据,读进程被阻塞
另一个终端中写数据
读阻塞自动解除:
了解了文件描述符和命名管道之后,我们就可以通过命名管道的阻塞功能有效地阻止开启过多的进程。但是只有命名管道还不够,正常情况下cat命名读取命名管道数据会一次性全部读完,我们需要每次读取一行数据,可以使用read命令通过文件描述符进行读取数据。
下面重写之前多进程ping IP的脚本:
[root@bogon func]# vim multi_ping1.sh
#!/bin/bash
# 定义函数,根据传入的IP地址进行ping操作
multi_ping() {
ping -c2 -i0.2 -W1 $1 &> /dev/null
if [ $? -eq 0 ];then
echo "$1 is up."
else
echo "$1 is down"
fi
}
# 定义一次开启的进程数量
num=10
# 创建命名管道文件
pipefile="/tmp/multiping_$.tmp"
mkfifo $pipefile
# 创建命名管道文件的文件描述符
exec 12<>$pipefile
# 向命名管道文件中写入任意字符,并放入后台执行
for i in $(seq $num)
do
echo "" >&12 &
done
# 定义IP主机位
net="192.168.4"
# 同过循环反复调用函数并将其放入后台并行执行
for j in {1..254}
do
# 使用read命名逐行读取数据,所有内容读完后read被阻塞,无法再启动新进程
read -u12
{
multi_ping $net.$j
# 向命名管道文件中重新写入数据
echo "" >&12
} &
done
wait
# 程序执行结束,删除命名管道文件
rm -rf $pipefile
[root@bogon func]# bash multi_ping1.sh
192.168.4.2 is down
192.168.4.1 is down
192.168.4.3 is down
192.168.4.4 is down
192.168.4.5 is down
192.168.4.7 is down
192.168.4.6 is down
192.168.4.8 is down
192.168.4.9 is down
192.168.4.10 is down
在另一终端中使用ps命令查看进程:
[root@bogon func]# ps aux | grep "ping -c2 "
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)