一、函数
定义方法:
方法一:
函数名(){
代码序列
}
方法二:
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 命令测试网段的网络连通性。这就会造成几个问题:
- 脚本执行完返回到命令行中,但是此时后天进程陆续执行完毕,返回结果都打印到命令行中。
- 可能造成屏幕宕机。
wait可以解决这个问题:
- wait如果输入进程号作为参数,可以等待某个进程或后台执行结束并返回该进程状态。
- 如果没有任何参数,则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 2月 15 14:00 0 -> /dev/pts/2
lrwx------. 1 root root 64 2月 15 14:00 1 -> /dev/pts/2
lrwx------. 1 root root 64 2月 15 14:00 2 -> /dev/pts/2
lrwx------. 1 root root 64 2月 15 14:24 255 -> /dev/pts/2
[root@localhost jiaofan]# ls -l /proc/1/fd | head -4 #<==查看systemd 的文件描述符
总用量 0
lrwx------. 1 root root 64 2月 15 10:20 0 -> /dev/null
lrwx------. 1 root root 64 2月 15 10:20 1 -> /dev/null
lr-x------. 1 root root 64 2月 15 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 2月 15 14:28 0 -> /dev/pts/2
lrwx------. 1 root root 64 2月 15 14:28 1 -> /dev/pts/2
lrwx------. 1 root root 64 2月 15 14:28 2 -> /dev/pts/2
lrwx------. 1 root root 64 2月 15 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 2月 15 10:54 0 -> /dev/pts/1
lrwx------. 1 root root 64 2月 15 10:54 1 -> /dev/pts/1
l-wx------. 1 root root 64 2月 15 11:41 12 -> /root/jiaofan/test.txt
lrwx------. 1 root root 64 2月 15 10:54 2 -> /dev/pts/1
lrwx------. 1 root root 64 2月 15 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 2月 15 16:05 pipe_file1
prw-r--r--. 1 root root 0 2月 15 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
本文详细介绍了Linux shell脚本中的函数定义与调用,包括不同定义方法及函数内部命令执行机制。还探讨了变量作用域,强调了`local`关键字在限制变量作用域中的作用。此外,讲解了`return`命令的使用以及如何通过`wait`来管理后台进程。最后,提到了文件描述符的概念,包括其读写原理、管道命令以及如何创建和管理自定义文件描述符。
&spm=1001.2101.3001.5002&articleId=122949284&d=1&t=3&u=a2d762635b12454ba3c1e1ef00a6771c)
1万+

被折叠的 条评论
为什么被折叠?



