Shell编程--函数、变量作用域、return、wait、进程数控制(文件描述符、管道命令)

本文详细介绍了Linux shell脚本中的函数定义与调用,包括不同定义方法及函数内部命令执行机制。还探讨了变量作用域,强调了`local`关键字在限制变量作用域中的作用。此外,讲解了`return`命令的使用以及如何通过`wait`来管理后台进程。最后,提到了文件描述符的概念,包括其读写原理、管道命令以及如何创建和管理自定义文件描述符。
摘要由CSDN通过智能技术生成

一、函数

定义方法:

方法一:
函数名(){
	代码序列
}
方法二:
function  函数名(){
	代码序列
}
方法三:
function 函数名 {
	代码序列
}

1)函数定义并不会导致函数内的任何命令被执行,仅当通过函数名称调用时,函数内的命令才会被触发执行。

[root@localhost ~]# mymkdir(){        #<==定义函数
> mkdir /tmp/test
> touch /tmp/test/hi.txt
> }
[root@localhost ~]# mymkdir           #<==调用函数
[root@localhost ~]# ls /tmp/test/hi.txt 
/tmp/test/hi.txt
[root@localhost ~]# 

2)使用unset可以取消函数的定义

[root@localhost ~]# unset mymkdir
[root@localhost ~]# mymkdir
-bash: mymkdir: 未找到命令

3)函数体内部可以通过$1、$2 读取位置参数,也可以读取全局变量。

[root@localhost jiaofan]# ./check_service.sh  sshd
[2022-02-14T20:17:+0800]:service sshd is active
[2022-02-14T20:17:+0800]:service vsftpd is not active
[2022-02-14T20:17:+0800]:service httpd is not active
[root@localhost jiaofan]# cat check_service.sh 
#!/bin/bash
#检查服务状态

date_time=$(date +'%Y-%m-%dT%H:%S:%z')

function check_services(){
	for i in "$@"
	do
		if systemctl --quiet is-active ${i}.service
		then
			echo -e "[$date_time]:\e[92mservice $i is active\e[0m"
		else
			echo "[$date_time]:service $i is not active" >&2
		fi
	done
}

check_services sshd vsftpd httpd
[root@localhost jiaofan]# 

案例:打印输出信息。

[root@localhost jiaofan]# ./usage.sh  -m
              total        used        free      shared  buff/cache   available
Mem:         995672      534476      176120        7848      285076      311380
Swap:       2097148           0     2097148
[root@localhost jiaofan]# cat usage.sh 
#!/bin/bash
#功能描述:使用函数输出帮助信息

function print_usage(){
	cat << EOF
Usage: --help | -h
	print help information for script
Usage: --memory | -m
	monitor memory informatiom
Usage: --network | -n
	monitor network interface information
EOF
}

case $1 in
--memory|-m)
	free;;
--network|-n)
	ip -s link;;
--help|-h)
	print_usage;;
*)
	print_usage;;
esac
[root@localhost jiaofan]# 

二、变量作用域

1)默认在函数外部或函数内部定义和使用变量的效果相同。函数外部的变量在函数内部可以直接调用,反之,函数内部的变量也可以在函数外部直接调用,可以看出函数和子进程有很大区别,父进程是不能调用子进程的环境信息的。
如下案例可以看出,函数可以调用外部变量,函数外部也可以调用函数内部。

[root@localhost jiaofan]# ./functiom-demo1.sh 
jiaofan
jiaofan:jerry:anqila
[root@localhost jiaofan]# cat functiom-demo1.sh 
#!/bin/bash

name_var1=jiaofan
name_var2=tom

demo(){
	name_var2=jerry
	name_var3=anqila
	echo $name_var1
}

demo
echo "$name_var1:$name_var2:$name_var3"
[root@localhost jiaofan]# 

2)如何阻止函数外部调用函数内部的变量:变量前加local

[root@localhost jiaofan]# ./functiom-demo1.sh 
jiaofan:jerry                   #<==函数改变变量的值,在函数内部成功
jiaofan:tom:anqila              #<==函数改变外部变量的值,在函数外部失败
[root@localhost jiaofan]# cat functiom-demo1.sh 
#!/bin/bash

name_var1=jiaofan
name_var2=tom

demo(){
	local name_var2=jerry          #<==函数改变外部变量的值
	name_var3=anqila
	echo $name_var1:$name_var2
}

demo
echo "$name_var1:$name_var2:$name_var3"
[root@localhost jiaofan]# 

3)关联数组是一种特殊情况。

  • 在函数外部定义的关联数组为全局变量
  • 在函数体内部定义的关联数组默认是函数体内部有效的局部变量。
[root@localhost jiaofan]# ./functiom-demo3.sh 
xx yy zz
88 99
xx yy zz
11 22
[root@localhost jiaofan]# cat functiom-demo3.sh 
#!/bin/bash
#功能描述:函数中变量的作用域

#在函数外部定义的数组和关联数组都是全部变量
a=(aa bb cc)
declare  -A b
b[a]=11
b[b]=22

#定义demo函数
#在函数体内部定义的普通数组为全局变量
#在函数体内部定义的关联数组为局部变量

demo(){
	a=(xx yy zz)
	declare -A b
	b[a]=88
	b[b]=99
	echo ${a[@]}
	echo ${b[@]}
}

demo      #<==改变了数组a的值,但是没有改变关联数组b的值。
echo ${a[@]}
echo ${b[@]}
[root@localhost jiaofan]# 

4)定义函数不会导致函数被执行。

[root@localhost jiaofan]# ./functiom-demo4.sh    
jiaofan   #<==只要不调用函数,函数体就不会执行。
[root@localhost jiaofan]# cat functiom-demo4.sh 
#!/bin/bash
#功能描述:函数中变量的作用域

name=jiaofan
dome(){
	name=tom
}
echo $name
[root@localhost jiaofan]# 

三、return 返回值

执行完函数时,默认整个函数的状态码为函数内部最后一个命令的返回值。但是在函数中如果使用了exit 命令就会导致整个脚本直接退出。可以使用ruture 命令立刻让函数中断并返回特定的状态码,并且不会影响脚本中后续的命令。

[root@localhost jiaofan]# ./functiom-demo4.sh 
3.10.0-1160.el7.x86_64
demo1 status : 0
start demo2
demo2 status : 100  #<==返回return 指定的数,可以用变量代替
hello hemo3         #<==执行函数demo3时遇到exit,导致脚本退出,没有执行返回demo3的状态码
[root@localhost jiaofan]# cat functiom-demo4.sh 
#!/bin/bash
#功能描述:自定义函数返回码

#默认以函数最后一条命令的状态作为返回码
demo1(){
	uname -r
}

#使用return 可以让函数立刻结束,并返回状态码,return的有效范围为0~255
demo2(){
	echo "start demo2"
	return 100
	echo "demo2 end"
}

#使用exit定义函数的返回码,则执行函数会导致脚本退出
demo3(){
	echo "hello hemo3"
	exit
}

demo1
echo "demo1 status : $?"
demo2
echo "demo2 status : $?"
demo3
echo "demo3 status : $?"
[root@localhost jiaofan]# 

四、wait

在脚本处理时可以会把大量的命令放入后台执行,比如ping 命令测试网段的网络连通性。这就会造成几个问题:

  1. 脚本执行完返回到命令行中,但是此时后天进程陆续执行完毕,返回结果都打印到命令行中。
  2. 可能造成屏幕宕机。

wait可以解决这个问题:

  1. wait如果输入进程号作为参数,可以等待某个进程或后台执行结束并返回该进程状态。
  2. 如果没有任何参数,则wait会等待当前shell激活的所有子进程结束,返回最后一个进程的退出状态。
[root@localhost jiaofan]# ./multi_ping.sh 
192.168.91.2 is up
192.168.91.1 is up
失败的有台
192.168.91.159 is up
[root@localhost jiaofan]# vi multi_ping.sh
[root@localhost jiaofan]# ./multi_ping.sh 
192.168.91.1 is up
192.168.91.159 is up
192.168.91.2 is up
[root@localhost jiaofan]# cat multi_ping.sh 
#!/bin/bash

net="192.168.91"
multi_ping(){
	ping -c2 -i0.2 -W1 $1 &>/dev/null
	if [ $? -eq 0 ];then
		echo "$1 is up"
	fi
}

#通过循环反复调用函数并将其放入后台并行执行
for i in {1..254}
do
	multi_ping $net.$i &
done
wait
[root@localhost jiaofan]# 

五、进程数控制

1、文件描述符

系统中内核默认会为每一个进程创建是哪个标椎的文件描述符,分别是0(标准输入)、1(标准输出)和2(标准错误输出)。通过查看/proc/PID号/fd/目录下的文件,就可以查看每个进程拥有的所有文件描述符。

[root@localhost jiaofan]# ls -l /proc/$$/fd       #<==查看当前shell的文件描述符
总用量 0
lrwx------. 1 root root 64 215 14:00 0 -> /dev/pts/2
lrwx------. 1 root root 64 215 14:00 1 -> /dev/pts/2
lrwx------. 1 root root 64 215 14:00 2 -> /dev/pts/2
lrwx------. 1 root root 64 215 14:24 255 -> /dev/pts/2
[root@localhost jiaofan]# ls -l /proc/1/fd | head -4      #<==查看systemd 的文件描述符
总用量 0
lrwx------. 1 root root 64 215 10:20 0 -> /dev/null
lrwx------. 1 root root 64 215 10:20 1 -> /dev/null
lr-x------. 1 root root 64 215 10:54 10 -> anon_inode:inotify
[root@localhost jiaofan]# 

1)打开文件时系统内核就会为特定的进程自动创建对应的文件描述符。

在终端一用vim打开一个文件

[root@localhost jiaofan]# vim /var/log/messages
Feb 13 10:17:11 localhost systemd: Starting Network Manager Script Dispatcher Service...
Feb 13 10:17:11 localhost dhclient[792]: bound to 192.168.91.159 -- renewal in 753 seconds.
... ...

在终端二上查看进程号,并查看文件描述符

[root@localhost jiaofan]# ps -aux | grep vim
root       3268  0.0  0.5 150080  5696 pts/2    S+   14:27   0:00 vim /var/log/messages
root       3270  0.0  0.0 112824   988 pts/1    R+   14:28   0:00 grep --color=auto vim
[root@localhost jiaofan]# ls -l /proc/3268/fd
总用量 0
lrwx------. 1 root root 64 215 14:28 0 -> /dev/pts/2
lrwx------. 1 root root 64 215 14:28 1 -> /dev/pts/2
lrwx------. 1 root root 64 215 14:28 2 -> /dev/pts/2
lrwx------. 1 root root 64 215 14:28 4 -> /var/log/.messages.swp   #<==文件默认会将数据写入这个文件内,当我们保存时才会写入/var/log/messages文件
[root@localhost jiaofan]# 

2)手动自定义文件描述符

a)创建文件描述符

exec  自定义文件描述符<>文件名  #<==写入时,从第一行开始替换
exec  自定义文件描述符>文件名   #<==等于>重定向,全部重新写入。
exec  自定义文件描述符>>文件名  #<==追加,可以在内容之后增加新的内容 

b)关闭文件描述符

exec  文件描述符<&-

或者

exec  文件描述符>&-

案例一:创建输出的文件描述符

[root@localhost jiaofan]# touch test.txt
[root@localhost jiaofan]# exec 12>test.txt       #<==创建仅可以输出的文件描述符
[root@localhost jiaofan]# ls -l /proc/$$/fd      #<==查看文件描述符
总用量 0
lrwx------. 1 root root 64 215 10:54 0 -> /dev/pts/1
lrwx------. 1 root root 64 215 10:54 1 -> /dev/pts/1
l-wx------. 1 root root 64 215 11:41 12 -> /root/jiaofan/test.txt
lrwx------. 1 root root 64 215 10:54 2 -> /dev/pts/1
lrwx------. 1 root root 64 215 11:05 255 -> /dev/pts/1
[root@localhost jiaofan]# echo "hello" >&12      #<==通过&12调用文件描述符
[root@localhost jiaofan]# echo "world" >&12
[root@localhost jiaofan]# cat test.txt           #<==查看是否重定向成功
hello
world
[root@localhost jiaofan]# cat <&12               #<==报错,不支持读
cat: -: 错误的文件描述符
[root@localhost jiaofan]# exec 12<&-             #<==关闭文件描述符
[root@localhost jiaofan]# echo "jiaofan" >&12    #<==关闭之后再无法使用该文件描述符
-bash: 12: 错误的文件描述符
[root@localhost jiaofan]# 

案例二:创建读文件描述符

[root@localhost jiaofan]# exec 12<test.txt     #<==创建读文件描述符
[root@localhost jiaofan]# cat <&12             #<==通过&12调用文件描述符
hello
world
[root@localhost jiaofan]# cat <&12             #注意,文件只能被读取一次
[root@localhost jiaofan]# echo "hello" >&12    #报错,不支持写入
-bash: echo: 写错误: 错误的文件描述符
[root@localhost jiaofan]# exec 12<&-           #关闭文件描述符
[root@localhost jiaofan]# 

案例三:创建既可以输入又可以输出的文件描述符

[root@localhost jiaofan]# exec 12<>test.txt   #<==创建既可以输入又可以输出的文件描述符
[root@localhost jiaofan]# cat <&12            #<==读出
hello
world
[root@localhost jiaofan]# echo "tom" >&12     #<==写入
[root@localhost jiaofan]# cat test.txt
hello
world
tom
[root@localhost jiaofan]# exec 12<&- 
[root@localhost jiaofan]# 

注:如果你先写入,在读出将会什么都读不到,因为写入会改变指针的位置,下面说一下原因。

3)文件描述符读写原理
在这里插入图片描述
里面有三行数据,读一行文件描述符就会往下走一行,文件描述符是不能往上移动的。读完就只能重新打开文件描述符了。如果是写入,则文件描述符直接移动到最后的位置,什么都读不出来了。

4)使用read -u可以一次只读取一行数据

[root@localhost jiaofan]# exec 12<>test.txt
[root@localhost jiaofan]# read -u12 content    #<==读取第一行赋值给content
[root@localhost jiaofan]# echo $content
hello
[root@localhost jiaofan]# read -u12 content
[root@localhost jiaofan]# echo $content
world
[root@localhost jiaofan]# read -u12 content
[root@localhost jiaofan]# echo $content
tom
[root@localhost jiaofan]# read -u12 content
[root@localhost jiaofan]# echo $content

[root@localhost jiaofan]# exec 12<&-

5)使用read -u -n 可以一次只读取任意字符

[root@localhost jiaofan]# exec 12<test.txt
[root@localhost jiaofan]# read -u12 -n1 content     #<==读取一个字符
[root@localhost jiaofan]# echo $content
h
[root@localhost jiaofan]# read -u12 -n1 content     #<==在读取一个字符
[root@localhost jiaofan]# echo $content
e
[root@localhost jiaofan]# read -u12 -n2 content     #<==在读取两个字符
[root@localhost jiaofan]# echo $content
ll
[root@localhost jiaofan]# exec 12<&-

6)创建文件描述符时,如果文件描述符对应的文件不存在,系统会自动创建新的空文件

7)> 、<> 、>>这三个的区别案例

>写入时会删除所有数据重新写入

[root@localhost jiaofan]# cat test.txt
hello
world
[root@localhost jiaofan]# exec 12>test.txt
[root@localhost jiaofan]# echo "aaa" >&12
[root@localhost jiaofan]# cat test.txt        #<==删除了hello world,加入了新的内容
aaa
[root@localhost jiaofan]# exec 12>&-

>>写入时追加,追加到内容之后

[root@localhost jiaofan]# cat test.txt
aaa
[root@localhost jiaofan]# exec 12>>test.txt
[root@localhost jiaofan]# echo "bbb" >&12
[root@localhost jiaofan]# cat test.txt       #<==bbb追加到了第二行
aaa
bbb
[root@localhost jiaofan]# exec 12>&-

<>写入时从第一行开始替换

[root@localhost jiaofan]# cat test.txt 
aaa
bbb
[root@localhost jiaofan]# exec 12<>test.txt
[root@localhost jiaofan]# echo "ccc" >&12
[root@localhost jiaofan]# cat test.txt         #<==第一行aaa,变成了ccc
ccc
bbb
[root@localhost jiaofan]# 

8)写入文件会使文件字符移动到写入的内容之后

[root@localhost jiaofan]# cat test.txt 
aaa
bbb
[root@localhost jiaofan]# exec 12<>test.txt
[root@localhost jiaofan]# echo "ccc" >&12
[root@localhost jiaofan]# cat test.txt 
ccc
bbb
[root@localhost jiaofan]# cat <&12    #<==第一行aaa,变成了ccc,文件描述符查看时,是bbb,这就是说插入时,文件描述符移动到了插入的内容之后
bbb
[root@localhost jiaofan]# 

2、管道命令

管道命令也是一种通信方式,我们之前接触过匿名管道,这里我们要创建管道,实现自己想要的进程之间的通信。管道文件也叫FIFO文件(first in first out:先进先出)

特点:

  • FIFO文件由命名创建( mknod 或 mkfifo 命令),可以在文件系统中直接看到
  • 写入管道的数据一旦被读取后,就不可以在重复读取
  • 进程往命名管道中写入数据时,如果管道中没有数据,则写进程会被堵塞。
  • 进程往命名管道中读取数据时,如果管道中没有数据,则读进程会被堵塞。
  • 命名管道中的数据常驻内存,并不实际写入磁盘,读写效率会更高。

1)写入数据时,写堵塞

[root@localhost jiaofan]# mkfifo pipe_file1                    #<==创建命名管道,不指定权限
[root@localhost jiaofan]# mkfifo -m 644 pipe_file2             #<==创建命名管道,指定权限
[root@localhost jiaofan]# ls -l                                #<==查看文件属性,第一列为p
prw-r--r--. 1 root root          0 215 16:05 pipe_file1
prw-r--r--. 1 root root          0 215 16:06 pipe_file2
[root@localhost jiaofan]# echo "hellp world" > pipe_file1      #<==写堵塞,当读出时,自动解除写堵塞
[root@localhost jiaofan]# 

2)读文件,自动解除写堵塞,读完以后在读就进入读堵塞

[root@localhost jiaofan]# cat pipe_file1     #<==写堵塞解除
hellp world
[root@localhost jiaofan]# cat pipe_file1     #<==读堵塞

3)cat 会一次性全部读完,而read 命令可以一次读取一行信息

[root@localhost jiaofan]# cat multi_procs.sh 
#!/bin/bash

#创建命名管道文件,并绑定固定的文件描述符
pipefile=/tmp/procs_$$.tmp
mkfifo  $pipefile
exec 12<>$pipefile

#通过文件描述符往命名管道中写入5行任意数据,用于控制进程数量
for i in {1..5}
do
	echo "" >&12  &
done

#通过read命令的-u 选项指定从特定的文件描述符中读取数据行
#每次读取一行数据,每读取一行数据就启动一个耗时的进程sleep,并放入后台执行
#因为命名管道中只有5行数据,读取5行后read会被堵塞,也就无法继续启动sleep进程
#每当任意一个sleep进程结束,就通过文件描述符在写入任意数据到命名管道
#当管道中有数据后,read则继续读取数据,继续开启新的进程

for  j in {1..20}
do
	read -u12
	{
		echo -e "\e[32mstart sleep no.$j\e[0m"
		sleep 5
                echo -e "\e[32mstop sleep no.$j\e[0m"
		echo "" >&12
	} &
done
wait
rm -rf $pipefile

案例1:一次开启10个ping,测试整个网段

[root@localhost jiaofan]# cat multi_ping.sh 
#!/bin/bash

num=10
net="192.168.91"
pipefile="/tmp/multiping_$$.tmp"

multi_ping(){
	ping -c2 -i0.2 -W1 $1 &>/dev/null
	if [ $? -eq 0 ];then
		echo "$1 is up"
	fi
}

mkfifo  $pipefile
exec 12<>$pipefile
for i in `seq $num`
do
	echo " " >&12 &
done

for j in {1..254}
do
	read -u12
	{
		multi_ping $net.$j
		echo "" >&12
	}&
done
wait
rm -rf $pipfile
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值