第八章 数组的使用和信号控制

一、数组介绍

1.什么是数组?
	 数组就是一系列元素的集合,一个数组内可以存放多个元素
	 
2.为何要用数组?
	我们可以用数组将多个元素汇总到一起,避免单独定义的麻烦

二、数组的使用

1)数组的定义

# 方式一:array=(元素1 元素2 元素3)
array=(egon 18 male)

# 方式二:array=([key1]=value1 [key2]=value2 [key3]=value3)
array=([0]=111 [1]="two" [2]=333)

# 方式三:依次赋值
array_new[0]=111
array_new[1]=222
array_new[2]="third"

# 方式四:利用执行命令的结果设置数组元素:array=($(命令)) 或者 array=(`命令`)
该方式会将命令的结果以空格为分隔符切成多个元素然后赋值给数组
[root@aliyun ~]# ls /test
a.txt b.txt
[root@aliyun ~]# array3=(`ls /test`)
[root@aliyun ~]# declare -a |grep array3
declare -a array3='([0]="a.txt" [1]="b.txt")'

# ps:查看声明过的数组
declare -a

2)访问数组内元素

[root@aliyun ~]# array=(egon 18 male)

#1、按照索引访问数组内指定位置的元素
[root@aliyun ~]# echo ${array[0]
egon
[root@aliyun ~]# echo ${array[1]}
18
[root@aliyun ~]# echo ${array[2]}
male
[root@aliyun ~]# echo ${array[-1]} # 支持负向索引
male

# 2、访问数组内全部元素信息
[root@aliyun ~]# echo ${array[*]}
egon 18 male
[root@aliyun ~]# echo ${array[@]}
egon 18 male
# 3、获取数组元素的长度
[root@aliyun ~]# echo ${#array[*]}
3
[root@aliyun ~]# echo ${#array[@]}
3

3)修改/添加数组元素

[root@aliyun ~]# array=(egon 18 male)
[root@aliyun ~]# array[0]="EGON" # 修改
[root@aliyun ~]# array[3]="IT" # 添加
[root@aliyun ~]# declare -a |grep array
declare -a array='([0]="EGON" [1]="18" [2]="male" [3]="IT")'

4)删除数组元素

[root@aliyun ~]# unset array[0]
[root@aliyun ~]# echo ${array[*]}
18 male IT
[root@aliyun ~]# declare -a |grep array
declare -a array='([1]="18" [2]="male" [3]="IT")'
[root@aliyun ~]#
[root@aliyun ~]# unset array[2]
[root@aliyun ~]# declare -a |grep array
declare -a array='([1]="18" [3]="IT")'
[root@aliyun ~]# unset array # 删除整个数组
[root@aliyun ~]# echo ${array[*]}
[root@aliyun ~]#

5)数组内元素的截取

[root@aliyun ~]# array=(egon 18 male IT 1.80)
[root@aliyun ~]# echo ${array[*]:1}  # 从索引1开始,一直到最后
18 male IT 1.80
[root@aliyun ~]# echo ${array[*]:1:3} # 从索引1开始,访问3个元素
18 male IT
[root@aliyun ~]# array=(one two three four five fire)
[root@aliyun ~]# echo ${array[*]#one}
two three four five fire
[root@aliyun ~]# echo ${array[*]#f*e}
one two three four

6)数组内容的替换

[root@aliyun ~]# array=(one two three four five fire)
[root@aliyun ~]# echo ${array[*]/five/abc}
one two three four abc fire
[root@aliyun ~]# echo ${array[*]/f*e/abc}
one two three four abc abc

三、关联数组

1)数组的种类

1.数组分为两种:
普通数组:只能使用整数作为数组索引,我们前面介绍的都是普通数组
关联数组:可以使用字符串作为数组索引,需要用declare -A声明

2.声明声明数组
[root@aliyun ~]# declare -A info
[root@aliyun ~]# info["name"]="egon"
[root@aliyun ~]# info["age"]=18
[root@aliyun ~]# info["gender"]="male"
[root@aliyun ~]#
[root@aliyun ~]# declare -A |grep info
declare -A info='([gender]="male" [name]="egon" [age]="18" )'
[root@aliyun ~]#
[root@aliyun ~]# echo ${info[*]}
male egon 18
[root@aliyun ~]#
[root@aliyun ~]# echo ${info["name"]}
egon

四、遍历数组

方法一: 利用获取所有信息进行遍历 (适用于普通数组与关联数组)

# 例1
declare -A array
array=(["name"]="egon" ["age"]=18 ["gender"]="male")
for item in ${array[*]}
do
  echo $item
done

# 例2
array=("egon" 18 "male")
for item in ${array[*]}
do
  echo $item
done

方法二: 通过数组元数的索引进行遍历(适用于普通数组与关联数组)

# 例1
declare -A array
array=(["name"]="egon" ["age"]=18 ["gender"]="male")
for i in ${!array[*]}  # echo ${!array[*]} # 获取的是key信息:name age gender
do
  echo "$i:${array[$i]}"
done

# 例2
array=("egon" 18 "male")
for i in ${!array[*]}  # echo ${!array[*]} 直接获取所有元素的索引信息
do
  echo $i
  echo ${array[i]}
done

方法三:根据数组长度信息进行遍历,(适用于普通数组)

array=("egon" 18 "male")
for ((i=0;i<${#array[*]};i++))
do
  echo "$i:${array[$i]}"
done

五、练习

1)练习一

1.对指定的IP地址进行网络测试
#!/bin/bash
array=(
  10.0.0.7
  10.0.0.8
  10.0.0.9
  10.0.0.41
)
for info in ${array[*]}
do
ping -c 2 -W 1  $info
done

2)练习二

2.统计登录shell的种类及对应的个数(关联数组)
#!/bin/bash
declare -A  array_for_shell
while read line  # done后面接<将文件重定向给while;while后再接read将文件流赋值给变量
do
  login_shell=`echo $line | cut -d: -f7`
  let array_for_shell["$login_shell"]++  # 当使用let时,变量前面不必加上$                       
done < /etc/passwd
for k in ${!array_for_shell[*]}
do
  echo $k:${array_for_shell[$k]}
done

3)练习三

[root@egon test]# cat a.sh
#!/usr/bin/env bash
declare -A array
while read line
do
  let array[`echo $line | cut -d: -f7`]++
done < /etc/passwd
for k in ${!array[*]}
do
  echo $k:${array[$k]}
done
[root@egon test]#
[root@egon test]# ./a.sh
/sbin/nologin:41
/bin/sync:1
/bin/bash:2
/sbin/shutdown:1
/sbin/halt:1

六、信号说明

在脚本执行过程中, 可能会被一些键盘操作快捷方式所打断, 影响脚本运行

# HUP(1): 1、挂起信号 2、往往可以让进程重新加载配置
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内
的各个作业, 这时它们与控制终端不再关联。
登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和
后台进程组,一般都 属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程
将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中
止。不过,可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget
也 能继续下载。
此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
# INT(2): 中断, 通常因为按下ctrl+c而产生的信号,用于通知前台进程组终止进程。
# QUIT(3): 退出,和SIGINT类似, 但由QUIT字符(通常是Ctrl-\)来控制. 进程在因收到SIGQUIT退出
时会产生core文件, 在这个意义上类似于一个程序错误信号。
# TSTP(20): 停止进行运行,通常因为按下ctrl+z而产生的信号
# KILL (9)
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送
这个信号。
# TERM(15):
终止,是不带参数时kill默认发送的信号,默认是杀死进程,与SIGKILL不同的是该信号可以被阻塞和处理。
通常用TERM信号来要求程序自己正常退出,如果进程终止不了,我们才会尝试SIGKILL。
# ===============了解===============
# ABRT(6): 中止, 通常因某些严重错误产生的引号 
# SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有
表项,这时的子进程称为僵尸 进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉
它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。
# 更多详见:man 7 signal

七、捕捉信号

我们可以用trap命令捕捉信号(trap命令并不能捕获所有信号,但是常用信号HUP、INT、QUIT、
TERM都是可以捕获的),执行我们规定的操作

# 操作1:捕捉信号、执行引号内的操作
trap "echo 已经识别中断信号:ctrl+c" INT
# 示例2:捕捉信号、不执行任何操作
trap "" INT 
# 示例3:也可以同时捕获多个信号
trap "" HUP INT QUIT TSTP
#例一
[root@egon test]# cat m.sh
#!/bin/bash
trap "echo 已经识别到中断信号:ctrl+c" INT
trap 'echo 已经识别到中断信号:ctrl+\\' QUIT
trap 'echo 已经识别到中断信号:ctrl+z' TSTP
read -p "请输入你的名字: " name
echo "你的名字是:$name"
[root@egon test]# chmod +x m.sh
[root@egon test]# ./m.sh
请输入你的名字: ^C已经识别到中断信号:ctrl+c
^C已经识别到中断信号:ctrl+c
^C已经识别到中断信号:ctrl+c
^\已经识别到中断信号:ctrl+\
^\已经识别到中断信号:ctrl+\
^Z已经识别到中断信号:ctrl+z
^Z已经识别到中断信号:ctrl+z
egon
你的名字是:egon
[root@egon test]#

#例二
#!/bin/bash
trap "" HUP INT QUIT TSTP  # kill -HUP 当前进程的pid,也无法终止其运行
clear
i=0
while true
do
 [ $i == 0 ] && i=1 || i=0
if [ $i == 0 ];then
    echo -e "\033[31m 红灯亮 \033[0m"
  else
    echo -e "\033[32m 绿灯亮 \033[0m"
  fi
  sleep 1
  clear
done

可以使用kill -9终止以上进程

八、关于hup信号

要了解Linux的HUP信号,需要从hangup说起

在 Unix 的早期版本中,每个终端都会通过 modem 和系统通讯。
当用户 logout 时,modem 就会挂断(hang up)电话。
同理,当 modem 断开连接时,就会给终端发送 hangup 信号来通知其关闭所有子进程。

综上,我们知道,当用户注销(logout)或者网络断开或者终端关闭(注意注意注意,一定是终端整
体关闭,不是单纯的exit)时,终端都会收到Linux HUP信号(hangup)信号,然后终端在结束前会关
闭其所有子进程。
如果我们想让我们的进程在后台一直运行,不要因为用户注销(logout)或者网络断开或者终端关闭
而一起被干掉,那么我们有两种解决方案
方案1:让进程忽略Linux HUP信号
方案2:让进程运行在新的会话里,从而成为不属于此终端的子进程,就不会在当前终端挂掉的情
况下一起被带走。

1)nohup命令

针对方案1,我们可以使用nohup命令,nohup 的用途就是让提交的命令忽略 hangup 信号,该命令
通常与&符号一起使用

nohup 的使用是十分方便的,只需在要处理的命令前加上 nohup 即可,但是 nohup 命令会从终端解除进程的关联,进程会丢掉STDOUT,STDERR的链接。标准输出和标准错误缺省会被重定向到 nohup.out 文件中。一般我们可在结尾加上"&"来将命令同时放入后台运行,也可用">filename 2>&1"来更改缺省的重定向文件名。

2)setsid命令

针对方案1,我们还可以用setsid命令实现,原理与3.1是一样的,setid是直接将进程的父pid设置成
1,即让运行的进程归属于init的子进程,那么除非init结束,该子进程才会结束,当前进程所在的终端结束后并不会影响进程的运行

# 1、在终端2中执行命令
[root@egon ~]# setsid ping www.baidu.com # 也可以在后面加&符号
# 2、关闭终端2
# 3、在终端1中查看
[root@egon ~]# ps -ef |grep [p]ing
root   102335    1  0 17:53 ?     00:00:00 ping www.baidu.com

3)在子shell中提交任务

# 1、在终端2中执行命令
[root@egon ~]# (ping www.baidu.com &) # 提交的作业并不在作业列表中
# 2、关闭终端2
# 3、在终端1中查看
[root@egon ~]# ps -ef |grep [p]ing
root   102361    1  0 17:55 ?     00:00:00 ping www.baidu.com
     
可以看到新提交的进程的父 ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。因此并不
属于当前终端的子进程,从而也就不会受到当前终端的Linux HUP信号的影响了。
posted @ 2020-12-07 10:11  年少纵马且长歌  阅读(173)  评论(0编辑  收藏  举报