shell函数+数组+运算+符号+交互
函数是什么:
一个代码段,起一个名字,通过函数名可以重复调用这个代码。将一组命令集合或语句形成一段可用代码,这些代码块称为shell函数。
函数的优点:
- 代码模块化,调用方便,节省内存
- 代码模块化,代码量少,排错简单
- 代码模块化,可以改变代码的执行顺序
可以使用$$9作为位置参数:
return 0 :表示执行成功,1-127表示失败;
函数的语法:
语法一:
函数名() {代码块return N
}语法二:
function 函数名() {代码块return N
}函数中return说明:
1.return可以结束一个函数,类似于前面讲的循环控制语句break(结束当前循环,执行循环体后面的代码)
2.return默认返回函数中最后一个命令的退出状态,也可以给定参数值,该参数值的范围是0-256之间。
3.如果没有return命令,函数将返回最后一个Shell的退出值。
函数调用:
例:
[root@zuolaoshi shell04]# cat 1.sh
#!/bin/bash
hello(){
echo "hello zuolaoshi $1"
hostname
}
menu(){
cat << EOF
1. mysql
2. web
3. app
4. exit
EOF
}
1.当前命令行调用:
调用
source 1.sh #加载到环境中
. 1.sh #执行脚本
第一个函数调用hello gky #传入参数
结果
hello zuolaoshi gky
gky-virtual-machine
第二个函数调用 menu
结果
1. mysql
2. web
3. app
4. exit
2.定义到用户的环境变量中
写到用户的配置文件中/etc/profile /etc/bashrc
~/.bash_profile ~/.bashrc
这几个都是可能的配置文件名,前两个是系统的,后两个是用户的在配置文件里写入
if [ -f /etc/bashrc ]; then. /etc/bashrc
fiaaaa(){
echo "hello zuolaoshi $1"
hostname
}
bbbb(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}
写完以后重新加载一下环境
source /home/gky/.bashrc就可以在全局中调用了
3.脚本中调用
aa(){
echo "hello zuolaoshi $1"
hostname
}aa gky #直接用函数,并且传入参数
shell数组
数组的作用:
数组可以让用户一次赋予多个值,需要读取数据时只需通过索引调用就可以方便读出了。
数组分类:
普通数组:只能使用整数作为数组索引(元素的索引)
关联数组:可以使用字符串作为数组索引(元素的索引)
数组的语法:
数组名称=(元素1 元素2 元素3 ...)
赋值方法:
一次赋一个值:变量名=变量值
array[0]=v1
array[1]=v2
array[3]=v3一次赋多个值:
array=(var1 var2 var3 var4)
array1=(`cat /etc/passwd`) //将文件中每一行赋值给array1数组
array2=(`ls /root`)
array3=(harry amy jack "Miss zhang")
array4=(1 2 3 4 "hello world" [10]=linux)
取值方法:
${数组名称[索引]}
数组定义:array=(var1 var2 var3 var4)
元素 var1 var2 var3 var4
索引 0 1 2 3我想要var2的数据${array[1]}
echo ${array[0]} 获取第一个元素
echo ${array[*]} 获取数组里的所有元素
echo ${#array[*]} 获取数组里所有元素个数
echo ${!array[@]} 获取数组元素的索引索引
echo ${array[@]:1:2} 从索引为1的元素开始获取;获取后面2个元素
创建数组:arr=(1 2 3 4 5)
通过下标修改值:arr [0]="A"
遍历所有数组的值:
::注意::
小括号里执行的命令会自动生成一个数组。
例如:
关联数组:(用的少,类似于python里的字典)
首先需要申明该数组为关联数组。
申明方式: declare -A 数组名称
赋值:
一次赋一个值:数组名[索引]=变量值
array2[1]=linux
array2[2]=php
array2[3]=shell一次赋多个值:
array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss zhang")查看关联数组:declare -A
数组取值:(和上面普通数组一样)
shell的数据处理:
数据检索命令行检索:grep egrep字符串检索:cut tr数据处理命令 数据排序:sort数据去重: uniq文本数据合并: paste数据输出: tee数据处理: xargs
[root@manage01 ~]# sort -n 2.txt |uniq
1
2
3
5
6
10
99
9999如何将上一个命令的输出,作为下一个命令的参数呢?xargs 上一个命令的输出作为下一个命令的命令行参数回顾:
linux 命令格式
命令 命令选项 参数
ls -l /
========================
xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。xargs 一般是和管道一起使用。命令格式:
''[somecommand]|[filename]'' |xargs -item commandOPTIONS:
-a file 从文件中读入作为sdtin
-E flag flag必须是一个以空格分隔的标志,当xargs分析到含有flag这个标志的时候就停止。
-p 当每次执行一个argument的时候询问一次用户。
-n num 后面加次数,表示命令在执行的时候一次用的argument的个数,默认是用所有的。
-t 表示先打印命令,然后再执行。
-i 或者是-I,这得看linux支持了,将xargs的每项名称,一般是一行一行赋值给 {},可以用 {} 代替。
-r no-run-if-empty 当xargs的输入为空的时候则停止xargs,不用再去执行了。
-d delim 分隔符,默认的xargs分隔符是回车,argument的分隔符是空格,这里修改的是xargs的分隔符。注意:linux命令格式一般为
命令 命令选项 参数
上一个命令的输出就是下一个命令的参数 这句话结合命令语法 应该知道输出的内容在下一个命令的位置了吧。案例
[root@zuolaoshi ~]# find / -name zuolaoshi |xargs gzip
[root@zuolaoshi ~]# cat 1
1
2
3
4
5
6
7
8
9
10
[root@zuolaoshi ~]# xargs -a 1
1 2 3 4 5 6 7 8 9 10[root@zuolaoshi ~]# xargs -a 1 -E 5
1 2 3 4这样就明白使用xargs -a 为什么读取文件的时候会把文件中的所有内容都输出了吧
[root@zuolaoshi ~]# xargs -a 1 -p
echo 1 2 3 4 5 6 7 8 9 10 ?...y
1 2 3 4 5 6 7 8 9 10
[root@zuolaoshi ~]# xargs -a 1 -p
echo 1 2 3 4 5 6 7 8 9 10 ?...n同理为什么把文件中的所有行按一行输出呢,原因就是默认输出所有
[root@zuolaoshi ~]# xargs -a 1 -n3
1 2 3
4 5 6
7 8 9
10
[root@zuolaoshi ~]# xargs -a 1 -n3 -p
echo 1 2 3 ?...y
echo 4 5 6 ?...1 2 3
y
echo 7 8 9 ?...4 5 6
y
echo 10 ?...7 8 9
y
10和-p命令选项一样,显示他是怎么执行的,只不过这个不需要确认。
[root@zuolaoshi ~]# cat 1 |xargs -t
echo 1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10为何读入文件会把所有行都放在一行呢?这个和xargs的列分割符有关系
默认是回车
我们可以使用-d 改掉默认列与列的默认分割符为其他,自然就会换行了
[root@zuolaoshi ~]# xargs -a 1 -d "@"
1
2
3
4
5
6
7
8
9
10
9、shell字符
有基础的同学不要和正则表达式中的符号含义搞混淆了。 !: 执行历史命令 !! 执行上一条命令$: 变量中取内容符+ - * / %: 对应数学运算 加 减 乘 除 取余数 &: 后台执行;: 分号可以在shell中一行执行多个命令,命令之间用分号分割 \: 转义字符``: 反引号 命令中执行命令 echo "today is `date +%F`"' ': 单引号,脚本中字符串要用单引号引起来,但是不同于双引号的是,单引号不解释变量" ": 双引号,脚本中出现的字符串可以用双引号引起来通配符 ~: 家目录 # cd ~ 代表进入用户家目录*: 星号是shell中的通配符 匹配所有?: 问号是shell中的通配符 匹配除回车以外的一个字符[list]: 匹配[list]中的任意单个字符
[!list]: 匹配除list中的任意单个字符
{string1,string2,...}: 匹配string1,string2或更多字符串重定向
> 覆盖输入
>> 追加输入
< 输出
<< 追加输出管道命令
|: 管道符 上一个命令的输出作为下一个命令的输入 cat filename |
运算:
整数运算:
支持数字
1.expr:只能做整数运算,格式比较古板,注意空格
[root@zuolaoshi ~]# expr 1 + 1
2
[root@zuolaoshi ~]# expr 5 - 2
3
[root@zuolaoshi ~]# expr 5 \* 2 #注意*出现应该转义,否则认为是通配符
10
[root@zuolaoshi ~]# expr 5 / 2
2
[root@zuolaoshi ~]# expr 5 % 2
1
[root@zuolaoshi ~]# num=`expr 5 % 2` 会四舍五入,小数点后直接省略
[root@zuolaoshi ~]# echo $num
1
支持变量
2.let命令:只能做整数运算,且运算元素必须是变量,无法直接对整数做运算
[root@zuolaoshi ~]# let a=100+3;echo $a
103
root@zuolaoshi ~]# let a=100-3;echo $a
97
[root@zuolaoshi ~]# let a=100/3;echo $a
33
[root@zuolaoshi ~]# let a=100*3;echo $a
300
[root@zuolaoshi ~]# let a=100%3;echo $a
1
[root@zuolaoshi ~]# let a=100**3;echo $a **是次幂
1000000
[root@zuolaoshi ~]# a=100
[root@zuolaoshi ~]# let a++;echo $a
101
[root@zuolaoshi ~]# let a--;echo $a
100
[root@zuolaoshi ~]# let a-=3;echo $a
97
[root@zuolaoshi ~]# let a+=5;echo $a
102
支持数字
3.双小圆括号运算,在shell中(( ))也可以用来做数学运算
[root@zuolaoshi ~]# echo $(( 100+3 ))
103
[root@zuolaoshi ~]# echo $(( 100-3 ))
97
[root@zuolaoshi ~]# echo $(( 100%3 ))
1
[root@zuolaoshi ~]# echo $(( 100*3 ))
300
[root@zuolaoshi ~]# echo $(( 100/3 ))
33
[root@zuolaoshi ~]# echo $(( 100**3 )) #开方运算
1000000
4.bc
直接输入命令bc进入一个计算机界面;
或者
echo "3 + 2"|bc
2.3 浮点运算
1.bc (宣告小数位,保留几位小数)
浮点运算是采用的命令组合的方式来实现的 echo “scale=N;表达式”|bc
[root@zuolaoshi ~]# echo "scale=2;3+100"|bc
103
[root@zuolaoshi ~]# echo "scale=2;100-3"|bc
97
[root@zuolaoshi ~]# echo "scale=2;100/3"|bc
33.33
[root@zuolaoshi ~]# echo "scale=2;100*3"|bc
300echo "$1 $2 $3"|bc
2.python
echo "print(3/2)" |python
获取随机整数
echo "import random;print(random.randint(1,100))"|python
获取随机小数
echo "import random;print(random.random())"|python
整形比较运算
运算符解释:精确比较-eq 等于 equal-gt 大于-lt 小于模糊比较-ge 大于或等于-le 小于或等于-ne 不等于
3.3、字符串比较运算
3.3.1 字符串比较运算符
运算符解释,注意字符串一定别忘了使用引号引起来== 等于 != 不等于-n 检查字符串的长度是否大于0 -z 检查字符串的长度是否为0
3.3.2 比较两个字符串关系
[root@zuolaoshi ~]# test 'root' == 'root';echo $?
0
[root@zuolaoshi ~]# test 'root' != 'root1';echo $?
0
[root@zuolaoshi ~]# name=
[root@zuolaoshi ~]# test -n "$name";echo $?
1
[root@zuolaoshi ~]# test -z "$name";echo $?
0
4.1、逻辑运算符
- 逻辑与运算 &&
- 逻辑或运算 ||
- 逻辑非运算 !
5.1、test判断命令
命令用法
test [命令选项] 表达式
命令选项
-d 检查文件是否存在且为目录
-e 检查文件是否存在
-f 检查文件是否存在且为文件
-r 检查文件是否存在且可读
-s 检查文件是否存在且不为空
-w 检查文件是否存在且可写
-x 检查文件是否存在且可执行
-O 检查文件是否存在并且被当前用户拥有
-G 检查文件是否存在并且默认组为当前用户组
-nt file1 -nt file2 检查file1是否比file2新
-ot file1 -ot file2 检查file1是否比file2旧
-ef file1 -ef file2 检查file1是否与file2是同一个文件,判定依据的是i节点以上只列出部分命令选项,详细的可以通过:man test获得。
一、与文件存在与否的判断
-e 是否存在 不管是文件还是目录,只要存在,条件就成立
-f 是否为普通文件
-d 是否为目录
-S socket
-p pipe
-c character
-b block
-L 软link
- 文件权限相关的判断
-r 当前用户对其是否可读
-w 当前用户对其是否可写
-x 当前用户对其是否可执行
-u 是否有suid
-g 是否sgid
-k 是否有t位
- 两个文件的比较判断
file1 -nt file2 比较file1是否比file2新
file1 -ot file2 比较file1是否比file2旧
file1 -ef file2 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode
- 整数之间的判断
-eq 相等
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于浮点比较运算
比较两个浮点数字的大小
给脚本两个浮点数字 返回他两的关系#思路
1、获得两个浮点数字
2、处理为整形
3、比较
4、输出
- 字符串之间的判断
-z 是否为空字符串 字符串长度为0,就成立
-n 是否为非空字符串 只要字符串非空,就是成立
string1 == string2 是否相等
string1 != string2 不等
- 多重条件判断
逻辑判断符号:
&& (and 逻辑与) 两个条件同时满足,整个大条件为真
|| (or 逻辑或) 两个条件满足任意一个,整个大条件为真
! 非运算
shell的人机交互:
read命令
功能:默认接受键盘的输入,回车符代表输入结束 应用场景:人机交互 命令选项
-p打印信息
-t限定时间
-s不回显
-n输入字符个数
expect
expect 是一个非常强大的自动化工具,尤其是当你需要与交互式程序进行交互(例如登录到远程服务器、输入密码、确认提示等)时。expect 可以模拟用户的键盘输入,从而实现自动化。
基本概念
- spawn:启动一个新的进程或命令。
- expect:等待特定的输出或提示。
- send:向进程发送字符串。
- interact:让用户接管交互。
- expect eof:等待子进程结束。
#!/usr/bin/expectspawn <command> # 启动命令
expect "<pattern>" { send "<response>\r" } # 等待模式并发送响应
expect eof # 等待命令结束
常用命令
- send_user:向用户发送消息(通常用于调试)。
- set timeout:设置 expect 的超时时间。
- exp_internal 1:启用调试模式,显示详细的交互过程。
- interact:让用户接管交互,通常用于需要在某个点手动操作的场景。
练习1:
案例需求 写一个平滑关闭服务脚本。
案例思路
- 判断服务进程文件是否存在,存在读取PID并判断是否存在进程
- 进程存在就使用Kill命令结束服务
- 不存在就报“服务已经结束“
案例步骤 1、检查服务PID文件 2、检查进程是否存在 3、杀死进程
案例代码
#!/bin/bash
#
#Author: www.zuolaoshi.cn
#
#Release:
#Description:找到服务的PID号,如果服务开启则杀死,否则提示服务已经关闭或不存在#1、判断PID
#注意PID的路径,如果服务的PID不在这里可以做个软连接
if [ -f /var/run/$1.pid ];then#2、如果存在PID=`cat /var/run/$1.pid`#3、统计进程数process_num=`ps aux|grep $PID|wc -l`#5、判断进程数大于2则杀死if [ $process_num -ge 2 ];thenkill -s QUIT $PID else#5、判断小于2则提示进程不存在,同时删除服务PID文件echo "service $1 is close"rm -f /var/run/$1.pidfi
else#2、不存在echo "service $1 is close"
fi
写一个KFC餐厅点餐程序
案例需求 写一个程序,模拟KFC点餐系统,要求有以下功能:
- 1、点餐功能
- 2、结算功能
- 3、打印流水单
案例步骤
- 1、交互点餐
- 2、结账收银
- 3、打印流水单给客户
案例代码
#!/bin/bash
#
#Author:www.zuolaoshi.cn
#
#Release:
#Description: #1)录入单价
HBB=19.8
JC=12.3
KL=9.9#2)定义输出
cat <<EOFwelcome to KFC
今天KFC提供菜品如下:1)汉堡2)鸡翅3)可乐
EOFecho -e "\n请您输入希望购买菜品的数量,不需要输入0\n"
###1.用户交互
#定义变量类型为整形
declare -i NUM_HBB
declare -i NUM_JC
declare -i NUM_KLread -p "汉堡: " NUM_HBB
read -p "鸡翅: " NUM_JC
read -p "可乐: " NUM_KL###2.计算输出
HBB_price=`echo "scale=2;$HBB*$NUM_HBB"|bc`
JC_price=`echo "scale=2;$JC*$NUM_JC"|bc`
KL_price=`echo "scale=2;$KL*$NUM_KL"|bc`
total_price=`echo "scale=2;$HBB_price+$JC_price+$KL_price"|bc`###3.付款
echo -n "合计: $total_price "
read -p "请付款: " USER_price###4.打印小票
clear
echo -e "\t\tKFC结算单"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo -e "商品\t单价\t数量\t合计"
echo -e "汉堡包\t$HBB\t$NUM_HBB\t$HBB_price"
echo -e "鸡翅\t$JC\t$NUM_JC\t$JC_price"
echo -e "可乐\t$KL\t$NUM_KL\t$KL_price"
echo -e "\n\n"
echo "总计: $total_price"
echo -e "支付:$USER_price"
echo -e "找零: `echo "scale=2;$USER_price-$total_price"|bc`"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo -e "地址:北京东大桥路33号KFC店\n联系电话:400-123-456\nwww.kfc.com"
给虚拟机添加一块磁盘(以sdb为例),要求使用脚本对该磁盘分三个区:
1)主分区 /dev/sdb3 543M 文件系统 ext4 要求开机自动挂载到/data/data1目录
- 逻辑分区 /dev/sdb5 2G
- 逻辑分区 /dev/sdb6 3G
使用/dev/sdb5 /dev/sdb6 新建卷组vg100,并创建一个PE为16M,容量为2.5G的逻辑卷lv100,
格式化为xfs,默认开机自动挂载到/data/data2目录
#!/bin/bash
#
#Author: Bai Shuming
#Created Time: 2019/11/1 21:05
#Release:
#Description:
#
#给虚拟机添加一块磁盘(以sdb为例),要求使用脚本对该磁盘分三个区:
# 1)主分区 /dev/sdb3 543M 文件系统 ext4 要求开机自动挂载到/data/data1目录
# 2) 逻辑分区 /dev/sdb5 2G
# 3) 逻辑分区 /dev/sdb6 3G
#使用/dev/sdb5 /dev/sdb6 新建卷组vg100,并创建一个PE为16M,容量为2.5G的逻辑卷lv100,
#格式化为xfs,默认开机自动挂载到/data/data2目录
#1、分区
fdisk /dev/sdb <<EOF
n
p
3+543M
n
e
4n+2G
n+3G
w
EOF#2、创建逻辑卷
#2.1 创建物理卷
pvcreate /dev/sdb5 /dev/sdb6
#2.2 创建卷组
vgcreate -s 16M vg100 /dev/sdb{5..6}
#2.3 创建逻辑卷
lvcreate -L 2.5G -n lv100 vg100
#3、格式化
mkfs.ext4 /dev/sdb3
mkfs.xfs /dev/vg100/lv100#4、修改/etc/fstab,实现自动挂载
echo "/dev/sdb3 /data/data1 ext4 defaults 0 0" >> /etc/fstab
echo "/dev/vg100/lv100 /data/data2 xfs defaults 0 0" >> /etc/fstab#5、挂载分区
mkdir -p /data/data{1..2}
mount -a#6、验证并输出挂载结果
mount |grep "/dev/sdb3"
test $? -eq 0 && echo "/dev/sdb3 挂载成功" || echo "/dev/sdb3挂载失败"##注意检索的时候,mount输出中LV的表示方式,或者直接检索挂载点/data/data2也可以。
mount |grep "vg100-lv100"
test $? -eq 0 && echo "/dev/vg100/lv100 挂载成功" || echo "/dev/vg100/lv100挂载失败"
自动登录远程服务器
#!/usr/bin/expectspawn ssh root@192.168.8.10
expect "password: " { send "root123\r" }
expect "# " { send "ls -l\r" }
expect "# " { send "cat /etc/os-release\r" }
expect "# " { send "exit\r" }
expect eof
示例 2:自动安装软件包
#!/usr/bin/expectspawn sudo apt-get install package_name
expect "\[Y/n\]" { send "Y\r" }
expect "Do you want to continue?" { send "Y\r" }
expect eof
示例 3:自动 FTP 上传文件
#!/usr/bin/expectset timeout 20
spawn ftp ftp.example.com
expect "Name .*:" { send "username\r" }
expect "Password:" { send "your_password\r" }
expect "ftp> " { send "put /local/path/file.txt /remote/path/file.txt\r" }
expect "ftp> " { send "quit\r" }
expect eof
高级用法
变量和循环
#!/usr/bin/expectset file_list [list "file1.txt" "file2.txt" "file3.txt"]
foreach file $file_list {spawn scp $file user@example.com:/remote/pathexpect "password: " { send "your_password\r" }expect "$ "
}
expect eof
处理多个 expect 模式
#!/usr/bin/expectspawn command
expect {"pattern1" { send "response1\r" }"pattern2" { send "response2\r" }timeout { send_user "Timed out waiting for pattern\n"; exit 1 }
}
expect eof
linux信号类型:
信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行相应的处理函数。
进程可以通过三种方式来响应一个信号:
1)忽略信号,即对信号不做任何处理,其中有两个信号不能忽略:SIGKILL及SIGSTOP。
2)捕捉信号。
3)执行缺省操作,Linux对每种信号都规定了默认操作。
Linux究竟采用上述三种方式的哪一个来响应信号呢?取决于传递给响应的API函数。
Linux支持的信号有:
编号 | 信号名称 | 缺省动作 | 描述 |
---|---|---|---|
1 | SIGHUP | 终止 | 终止进程,挂起 |
2 | SIGINT | 终止 | 键盘输入中断命令,一般是CTRL+C |
3 | SIGQUIT | CoreDump | 键盘输入退出命令,一般是CTRL+\ |
4 | SIGILL | CoreDump | 非法指令 |
5 | SIGTRAP | CoreDump | trap指令发出,一般调试用 |
6 | SIGABRT | CoreDump | abort(3)发出的终止信号 |
7 | SIGBUS | CoreDump | 非法地址 |
8 | SIGFPE | CoreDump | 浮点数异常 |
9 | SIGKILL | 终止 | 立即停止进程,不能捕获,不能忽略 |
10 | SIGUSR1 | 终止 | 用户自定义信号1,像Nginx就支持USR1信号,用于重载配置,重新打开日志 |
11 | SIGSEGV | CoreDump | 无效内存引用 |
12 | SIGUSR2 | 终止 | 用户自定义信号2 |
13 | SIGPIPE | 终止 | 管道不能访问 |
14 | SIGALRM | 终止 | 时钟信号,alrm(2)发出的终止信号 |
15 | SIGTERM | 终止 | 终止信号,进程会先关闭正在运行的任务或打开的文件再终止,有时间进程在有运行的任务而忽略此信号。不能捕捉 |
16 | SIGSTKFLT | 终止 | 处理器栈错误 |
17 | SIGCHLD | 可忽略 | 子进程结束时,父进程收到的信号 |
18 | SIGCONT | 可忽略 | 让终止的进程继续执行 |
19 | SIGSTOP | 停止 | 停止进程,不能忽略,不能捕获 |
20 | SIGSTP | 停止 | 停止进程,一般是CTRL+Z |
21 | SIGTTIN | 停止 | 后台进程从终端读数据 |
22 | SIGTTOU | 停止 | 后台进程从终端写数据 |
23 | SIGURG | 可忽略 | 紧急数组是否到达socket |
24 | SIGXCPU | CoreDump | 超出CPU占用资源限制 |
25 | SIGXFSZ | CoreDump | 超出文件大小资源限制 |
26 | SIGVTALRM | 终止 | 虚拟时钟信号,类似于SIGALRM,但计算的是进程占用的时间 |
27 | SIGPROF | 终止 | 类似与SIGALRM,但计算的是进程占用CPU的时间 |
28 | SIGWINCH | 可忽略 | 窗口大小改变发出的信号 |
29 | SIGIO | 终止 | 文件描述符准备就绪,可以输入/输出操作了 |
30 | SIGPWR | 终止 | 电源失败 |
31 | SIGSYS | CoreDump | 非法系统调用 |
CoreDump(核心转储):当程序运行过程中异常退出时,内核把当前程序在内存状况存储在一个core文件中,以便调试。
Linux支持两种信号:
一种是标准信号,编号1-31,称为非可靠信号(非实时),不支持队列,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,如果第一个信号没有处理完,第二个信号将会丢弃。
另一种是扩展信号,编号32-64,称为可靠信号(实时),支持队列,发多少次进程就可以收到多少次。
信号类型比较多,我们只要了解下,记住几个常用信号就行了,红色标记的我觉得需要记下。
发送信号一般有两种情况:
一种是内核检测到系统事件,比如键盘输入CTRL+C会发送SIGINT信号。
另一种是通过系统调用kill命令来向一个进程发送信号。
trap命令
trap命令定义shell脚本在运行时根据接收的信号做相应的处理。
我们可以用trap命令捕捉信号(trap命令并不能捕获所有信号,但是常用信号HUP、INT、QUIT、TERM都是可以捕获的),执行我们规定的操作
命令格式:trap [-lp] [[arg] signal_spec ...]
-l #打印编号1-64编号信号名称
arg # 捕获信号后执行的命令或者函数
signal_spec # 信号名或编号
一般捕捉信号后,做以下几个动作:
1)清除临时文件
2)忽略该信号
3)询问用户是否终止脚本执行
示例1:按CTRL+C不退出循环
#!/bin/bash
trap "" 2 # 不指定arg就不做任何操作,后面也可以写多个信号,以空格分隔
for i in {1..10}; doecho $isleep 1
done# bash a.sh
123
^C
456
^C
78910
示例2:循环打印数字,按CTRL+C退出,并打印退出提示
#!/bin/bash
trap "echo 'exit...';exit" 2
for i in {1..10}; doecho $isleep 1
done
# bash test.sh
123
^Cexit
...
示例3:让用户选择是否终止循环
#!/bin/bash
trap "func" 2func() {read -p "Terminate theprocess? (Y/N): " inputif [ $input == "Y"] ;thenexitfi
} for i in {1..10}; doecho $i sleep 1
done
# bash a.sh
123
^CTerminate the process? (Y/N): Y# bash a.sh
123
^CTerminate the process? (Y/N): N
456...
其他案例
# 操作1:捕捉信号、执行引号内的操作
trap "echo 已经识别中断信号:ctrl+c" INT# 示例2:捕捉信号、不执行任何操作
trap "" INT # 示例3:也可以同时捕获多个信号
trap "" HUP INT QUIT TSTP
例1:
[root@egon test]# cat m.sh
#!/bin/bashtrap "echo 已经识别到中断信号:ctrl+c" INT
trap 'echo 已经识别到中断信号:ctrl+\\' QUIT
trap 'echo 已经识别到中断信号:ctrl+z' TSTPread -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]#
例2:
#!/bin/bash
trap "" HUP INT QUIT TSTP # kill -HUP 当前进程的pid,也无法终止其运行clear
i=0
while true
do[ $i == 0 ] && i=1 || i=0if [ $i == 0 ];thenecho -e "\033[31m 红灯亮 \033[0m"elseecho -e "\033[32m 绿灯亮 \033[0m"fisleep 1clear
done
可以使用kill -9终止以上进程
4、 关于HUP信号
要了解Linux的HUP信号,需要从hangup说起
在 Unix 的早期版本中,每个终端都会通过 modem 和系统通讯。
当用户 logout 时,modem 就会挂断(hang up)电话。
同理,当 modem 断开连接时,就会给终端发送 hangup 信号来通知其关闭所有子进程。
综上,我们知道,当用户注销(logout)或者网络断开或者终端关闭时,终端都会收到Linux HUP信号(hangup)信号,然后终端在结束前会关闭其所有子进程。
如果我们想让我们的进程在后台一直运行,不要因为用户注销(logout)或者网络断开或者终端关闭而一起被干掉,那么我们有两种解决方案
- 方案1:让进程忽略Linux HUP信号
- 方案2:让进程运行在新的会话里,从而成为不属于此终端的子进程,就不会在当前终端挂掉的情况下一起被带走。
4.1 nohup命令
针对方案1,我们可以使用nohup命令,nohup 的用途就是让提交的命令忽略 hangup 信号,该命令通常与&符号一起使用
nohup 的使用是十分方便的,只需在要处理的命令前加上 nohup 即可,但是 nohup 命令会从终端解除进程的关联,进程会丢掉STDOUT,STDERR的链接。标准输出和标准错误缺省会被重定向到 nohup.out 文件中。一般我们可在结尾加上"&"来将命令同时放入后台运行,也可用">filename 2>&1"来更改缺省的重定向文件名。
nohup 是一个 Unix 和类 Unix 系统(如 Linux)中的命令,用于运行另一个命令,并且忽略挂起(hangup)信号。
这通常在你想要运行一个长时间的任务,并且不希望它在你关闭终端或断开 SSH 连接时被终止时非常有用。
这里是 nohup 命令的基本用法和一些选项的详细解释:
nohup COMMAND [ARG]...
这里,COMMAND 是你想要运行的命令,而 [ARG]... 是该命令可能需要的任何参数。
选项:
- --help:显示帮助信息并退出。
- --version:输出版本信息并退出。
标准输入、输出和错误:
- 如果标准输入(stdin)是一个终端,nohup 会将其重定向到一个不可读的文件。
- 如果标准输出(stdout)是一个终端,nohup 会尝试将其追加到 'nohup.out' 文件中(如果可能)。如果当前目录没有写入权限,它会尝试写入到 $HOME/nohup.out。
- 如果标准错误(stderr)是一个终端,nohup 会将其重定向到标准输出。
保存输出到文件:
- 如果你想要将输出保存到特定的文件中,而不是默认的 'nohup.out' 或 $HOME/nohup.out,你可以使用 shell 的重定向功能。例如:nohup COMMAND > FILE 会将输出保存到 FILE 文件中。
注意:正如帮助信息中提到的,你的 shell 可能有自己的 nohup 版本或与之相关的功能。因此,在使用时,最好查看你当前 shell 的文档,以了解它所支持的具体选项和行为。
最后,当你使用 nohup 运行一个命令后,它会返回一个进程 ID(PID),你可以使用 ps 命令或其他进程管理工具来查看和管理这个进程。
后台运行脚本并将输出重定向到文件
nohup ./my_script.sh > output.txt &
- nohup:启动命令,忽略挂起信号。
- ./my_script.sh:要运行的脚本。
- > output.txt:将脚本的标准输出重定向到output.txt文件。
- &:将命令放到后台执行。
后台运行命令并将输出重定向到/dev/null
nohup command > /dev/null &
- /dev/null:是一个特殊的设备文件,它会丢弃所有写入它的数据。这个选项用于当你不需要保存命令的输出时。
后台运行命令并将输出和错误信息都重定向到文件
nohup command > output.log 2>&1 &
- > output.log:将命令的标准输出重定向到output.log文件。
- 2>&1:将标准错误(文件描述符2)重定向到标准输出(文件描述符1)的当前位置,即output.log文件。
使用nohup时忽略SIGHUP信号
nohup -i command > output.log &
- -i:在某些系统中,这个选项用于忽略SIGHUP信号,但请注意,并非所有版本的nohup都支持这个选项。
查看后台运行的nohup命令
使用jobs命令(仅在当前终端中有效):
jobs
使用ps命令(跨终端查看):
ps -ef | grep nohup
- jobs命令可以列出当前终端中后台运行的任务。
- ps -ef | grep nohup命令可以查找所有与nohup相关的进程,并列出它们的信息。
nohup命令在Unix和类Unix系统中非常有用,特别是在需要长时间运行任务或确保任务在终端关闭或断开连接后仍能继续运行的情况下。
通过结合重定向和后台执行,nohup可以确保任务的输出被妥善保存,并且任务能够持续运行直到完成。
4.2 setsid命令
针对方案1,我们还可以用setsid命令实现,原理与4.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
4.3 在子shell中提交任务
原理同4.2
# 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。
shell符号详解
1. 基本符号
- # 表示注释,后面的内容不会被执行。
- ; 命令分隔符,用于在同一行中执行多个命令。
- && 逻辑与,前面的命令成功后才会执行后面的命令。
- || 逻辑或,前面的命令失败后才会执行后面的命令。
- & 在后台运行命令。
2. 重定向符号
- > 将输出重定向到文件,覆盖文件内容。
- >> 将输出重定向到文件,追加到文件内容末尾。
- < 从文件中读取输入。
- 2> 将标准错误输出重定向到文件。
- 2>>将标准错误输出追加到文件。
3. 管道符
- |将一个命令的输出作为另一个命令的输入。
4. 文件和目录符号
- . 表示当前目录。
- ..表示上级目录。
- ~ 表示当前用户的主目录。
- * 通配符,表示任意字符(零个或多个)。
- ? 通配符,表示单个字符。
5. 变量和参数
- $ 引用变量的值,例如 $VAR 表示变量 VAR 的值。
- $$ 表示当前 Shell 进程的 PID。
- $? 表示上一个命令的退出状态。
- $# 表示脚本参数的个数。
- $@ 表示所有传递给脚本的参数(以独立参数的形式)。
- $* 表示所有传递给脚本的参数(作为一个单独的字符串)。
6. 命令替换
- `command` 或 $(command):将命令执行的结果替换到当前命令中。
7. 条件表达式
- [ 和 ] 用于条件判断,类似于 test 命令。
- [[ 和 ]] 扩展版本的测试工具,支持更多的功能(如模式匹配)。
8. 其他符号
- {} 用于命令组、参数扩展等场景。
- () 用于子 Shell 执行,或者用于命令分组。
- ! 用于逻辑非,表示取反。
9. 拓展与替换
参数扩展:
- ${VAR} 扩展变量 VAR,用于避免变量名歧义。
- ${VAR:-default} 如果 VAR 未设置或为空,返回 default。
- ${VAR:+value} 如果 VAR 设置且不为空,返回 value;否则返回空。
- ${#VAR} 返回变量 VAR 的长度。
数组:
- array=(value1 value2 value3) 定义一个数组。
- ${array[index]} 访问数组元素。
- ${array[@]} 表示数组的所有元素。
10. Job Control(作业控制)
- jobs 列出当前后台作业。
- fg %job_id 将后台作业带回前台。
- bg %job_id 将作业放到后台运行。
- disown 移除作业的 shell 进程,允许作业独立于 shell 运行。
11. 其他常用命令和特性
命令历史:
- !! 执行最后一条命令。
- !n 执行历史第 n 条命令。
- !-n 执行倒数第 n 条命令。
快捷键:
- Ctrl + C 终止当前命令。
- Ctrl + Z 将当前前台命令挂起。
- Ctrl + D 表示 EOF,通常用于结束输入或退出 shell。
12. 执行权限命令
- chmod 用于更改文件或目录的权限。
chmod 755 filename
- chown 用于更改文件或目录的所属用户和组。
chown user:group filename
13. 相关环境符号
- $PATH 环境变量,定义可执行文件的搜索路径。
- $HOME 当前用户的主目录。
- $USER 当前用户的用户名。
- $SHELL 当前使用的 Shell 程序。
14. 输入输出参数
标准输入和输出:
- 标准输出(stdout)是文件描述符 1。
- 标准错误输出(stderr)是文件描述符 2。
- 可以通过重定向相关的描述符进行输入输出管理。
15. 特殊变量
- $! 上一个后台命令的 PID(进程ID)。
- $_ 最后执行的命令的最后一个参数。
16. 特殊符号的高级用法
16.1 << 和 <<<(Here Document 和 Here String)
- Here Document:<< 用于将多行文本作为输入传递给命令。
command << DELIMITER
text
to be
passed
DELIMITER
例如:
cat << EOF
Hello, World!
This is a here document example.
EOF
- Here String:<<< 用于将单个字符串传递给命令。
command <<< "string"
例如:
cat <<< "Hello, World!"
16.2 :(空命令/占位符)
- : 是一个 Shell 内建命令,它什么都不做,但常用于条件判断、循环、或作为占位符。
: # 这是一个占位符,通常用于不需要执行任何操作的地方
16.3 \(换行符)
- \ 用于在 Shell 脚本或命令行中将一行的命令延续到下一行。
command \--option \argument
16.4 () 和 {}(子 Shell 和命令组)
- 子 Shell:() 用于创建子 Shell,子 Shell 中的变量和环境不会影响父 Shell。
(cd /tmp; touch newfile.txt)
- 命令组:{} 用于将多个命令作为一个整体执行,通常用于输入输出重定向。
{ echo "Output 1"; echo "Output 2"; } > output.txt
17. 数学计算和表达式
- 整数计算:(()) 用于整数计算。
result=$(( 5 + 3 ))
echo $result # 输出 8
- 浮点计算:bc 命令用于浮点计算。
result=$(echo "scale=2; 5 / 3" | bc)
echo $result # 输出 1.66
18. 高级字符串操作
- 字符串替换:
VAR="hello world"
echo ${VAR/world/everyone} # 输出 "hello everyone"
- 字符串截取:
VAR="hello world"
echo ${VAR:0:5} # 输出 "hello"
- 字符串长度:
VAR="hello world"
echo ${#VAR} # 输出 11
19. 特殊文件描述符
标准输入/输出/错误:
- 标准输入(stdin):文件描述符为 0。
- 标准输出(stdout):文件描述符为 1。
- 标准错误(stderr):文件描述符为 2。
重定向示例:
command > output.txt
20. 进程和信号控制
20.1 &(后台运行)
- & 用于将命令在后台运行,这样你可以继续使用终端而不被该命令阻塞。
command &
- 获取后台进程的 PID:
command & echo $!
# $! 表示最后一个后台进程的 PID
20.2 wait(等待后台进程)
- wait 命令用于等待后台进程结束。
command1 &
command2 &
wait # 等待所有后台进程结束
20.3 Ctrl + Z 和 fg / bg(挂起和前后台切换)
- Ctrl + Z 可以将前台运行的进程挂起,并将其放到后台。
- fg 将后台挂起的进程带回前台运行。
fg %job_id # job_id 是通过 jobs 命令获取的
- bg 将挂起的进程放到后台继续运行。
bg %job_id
20.4 kill 和 killall(终止进程)
- kill 命令用于发送信号给进程,默认发送 SIGTERM 信号以终止进程。
kill pid
- 常见信号:
- SIGHUP (1):挂起信号,通常用于让进程重新读取配置文件。
- SIGINT (2):中断信号,通常通过 Ctrl + C 发送。
- SIGKILL (9):强制终止进程。
- SIGTERM (15):请求终止进程,可以被捕获和处理。
例如,强制终止进程:
kill -9 pid
- killall 可以终止所有匹配名称的进程:
killall process_name
21. 文件锁定和原子操作
21.1 flock(文件锁定)
- flock 用于对文件进行加锁,以确保在并发环境中文件操作的原子性。
flock /path/to/lockfile -c "command"
21.2 mktemp(创建临时文件或目录)
- mktemp 用于创建临时文件或目录,通常用于需要临时存储的场景。
temp_file=$(mktemp)
echo "Temporary data" > $temp_file
- 创建临时目录:
temp_dir=$(mktemp -d)