程序:算法+数据结构,数据是程序的核心javascript
算法:处理数据的方式php
数据结构:数据在计算机中的类型和组织方式html
按程序编程风格:java
过程式:以指令为中心,数据服务于指令node
对象式:以数据为中心,指令服务于数据python
按程序运行方式:mysql
编译运行:高级语言-->编译器-->机器代码-->执行linux
源代码须要编译器转换为程序文件,运行程序文件时不须要编译器的参与,所以程序执行效率高ios
好比:C,C++nginx
解释运行:高级语言-->执行-->解释器-->机器代码
源代码不须要事先编译,运行时启动解释器然后由解释器边解释边运行,所以效率比较低
好比:shell,python,php,JavaScript,perl
按编程实现是调用库仍是调用外部的程序文件:
非完整编程语言:利用系统上的命令及编程组件进行编程,shell脚本
完整的编程语言:利用库和编程组件进行编程,非shell脚本
按编程模型:
面向过程编程语言
以指令为中心来组织代码,以过程或函数为基础,数据服务于代码,围绕指令来组织数据;这种语言对底层硬件,内存等操做比较方便,可是写代码和调试维护等会很麻烦。 他们按照顺序执行,选择执行,循环执行 好比:C bash C++ python
面向对象的编程语言
以数据为中心来组织代码,以对象做为基本程序结构单位的程序设计语言,指令服务于数据,围绕数据来组织指令;指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。语言中提供了类、继承等成分。 对象:特定的数据类型 类class:实例化成为对象 好比:Java C++ python
综上所述可知:
shell脚本编程属于解释运行的过程式编程语言且依赖于外部程序文件来运行
编程基本结构
各类系统命令的组合
数据存储:变量、数组
表达式: a+b
语句: if
一种为shell编写的脚本程序;
是Linux命令的堆砌;
但因为Linux中不少命令不具备幂等性,须要设定程序逻辑来判断运行条件是否知足,以免其运行中发生错误
幂等性
即一个操做,不论执行多少次,产生的效果和返回的结果都是同样的!
减小重复性的工做
自动化经常使用命令
执行系统管理和故障排除
建立简单的应用程序
处理文本或文件
自动化安装操做系统
kickstart 底层shell脚本
cobbler 底层shell脚本
初始化操做系统 SSH优化 关闭SElinux 防火墙放行须要的端口(80 443 22修改 10050) YUM源 时间同步 系统最大描述符 内核参数优化 字符集优化 禁止开机自动启动 修改主机名称 (修改公司网卡名称)... 手动操做要注意 命令行安全 bash的命令历史 写入shell脚本(经常使用)
安装服务 Nginx PHP MySQL Rsync等等... 针对不一样的版本写入shell脚本自动安装
配置服务
启动服务 全部的服务底层的启动方式都是使用的shell脚本 公司本身研发的程序 nohup python3.5 test.py --redis --port --mysql --port -a xxxx & 复制一下 写入脚本 sh start_test_py.sh 如何中止py程序 ps axu|grep test.py |grep -v grep|awk '{print $2}'|xargs kill -9 复制一下 写入脚本 sh stop_test_py.sh 把py的进程的端口和PID取出来 来判断是否运行
日志统计 查看程序运行的状况 统计咱们须要的数据 日志切割 定时任务+脚本 统计数据 定时任务+脚本 ---> 经过邮件发送给管理员 ELK 日志统计界面 py开发日志界面 py界面----> 数据库 <----数据 日志展现
监控 监控服务 服务端口是否存在 服务是否存在 服务器的硬件资源使用状况 状态 日志 网络 Zabbix 经过脚本统计---> 测试---> 添加到zabbix服务 (cacti监控流量 Nagios宽带运营商 IT公司)
脚本存放在固定的目录/server/scripts
统一管理
脚本使用.sh
结尾,让咱们能识别是shell脚本
脚本命名,见名知其意
脚本内的注释最好不用中文(能够用)
脚本内的成对的符号一次性写完再写内容
默认解析器: #!/usr/bin/env bash
会本身判断使用的shell是什么,并加载相应的环境变量
程序名,避免更改文件名为没法找到正确的文件
版本号
修改时间
做者相关信息
该程序的做用,及注意事项
最后是各版本的更新简要说明
# 主函数 []<-() <-------函数注释这样写
function main(){
local var="Hello World!!!"
echo ${var}
}
# info级别的日志 []<-(msg:String) <-------带入参的函数注释
log_info(){
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2
}
# error级别的日志 []<-(msg:String) <-------带入参的函数注释
log_error(){
# todo [error]用红色显示 <------函数内注释
local msg=$1 # 将要输出的日志内容 <------变量的注释紧跟在变量的后面
if [[ x"${msg}" != x"" ]];then
# 注释 <-------函数内注释 `#` 与缩进格式对整齐
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]:[error] $*" >&2
fi
}
使用两个空格进行缩进,不使用tab缩进
不在一行的时候使用 \
进行换行,使用 \
换行的原则是整齐美观
使用解释器(sh或者bash)运行脚本,开启一个子shell运行脚本内容
执行脚本绝对路径或相对路径,须要脚本有执行权限
使用. 或者source运行脚本,在当前父shell中运行里面的内容
传递给|bash
执行,不经常使用
能够给脚本加上执行权限
chmod +x /server/scripts/one.sh
,并将脚本的绝对路径添加到path变量中echo PATH=/server/scripts:$PATH > /etc/profile.d/shell.sh
,使变量当即生效. /etc/profile.d/shell.sh
,就能够像运行普通命令同样直接执行脚本了!
[root@oldboyedu-lnb ~]# name=f;(echo $name;name=z;echo $name);echo $name # 小括号会开启子进程,赋予的变量,只在小括号内有效,执行完命令后,就会退出子进程。
f
z
f
[root@oldboyedu-lnb ~]# name=f;{ echo $name;name=z;echo $name; };echo $name # 大括号不会开启子进程,在当前进程有效,执行完命令后,留在当前进程。
f
z
z
# 语法检测
bash -n /path/to/script
# 调试执行
bash -x /path/to/script
echo -e "\033[31malong\033[0m" 显示红色along
echo -e "\033[1;31malong\033[0m" 高亮显示红色along
echo -e "\033[41malong\033[0m" 显示背景色为红色的along
echo -e "\033[31;5malong\033[0m" 显示闪烁的红色along
color=$[$[RANDOM%7]+31]
echo -ne "\033[1;${color};5m*\033[0m" 显示闪烁的随机色along
不能使程序中的保留字:例如if,for
只能使用数字、字母及下划线,且不能以数字开头
见名知义
统一命名规则:驼峰命名法
建议:
全局变量大写
局部变量小写
函数名小写
变量赋值使用 =
等号,左右不能留有空格
使用变量时推荐使用 "${}"
双引号和大括号包裹
var1="Hello World" # 正确,推荐使用双引号
var2=6.70 # 小数
var3="${var1}" # 推荐 双引号和大括号 包裹
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,单引号字串中不能出现单引号(对单引号使用转义符后也不行)。 双引号中的普通字符都会原样输出,可使用$引用变量,双引号中能够出现单引号。
常量必定要定义成readonly
函数中的变量要用local修饰,定义成局部变量,这样在外部遇到重名的变量也不会影响
web="www.chen-shang.github.io"
function main(){
local name="chenshang" # 这里使用local定义一个局部变量
local web="${web}" # 这里${}内的web是全局变量,以后在函数中在使用web变量都是使用的局部变量
local web2="${web}" # 对于全局变量,虽然在使用的时候直接使用便可,但仍是推荐使用一个局部变量进行接收,而后使用局部变量,以防止在多线程操做的时候出现异常(至关于java中的静态变量在多线程中的时候须要注意线程安全同样,但常量除外)
}
变量一经定义,不容许删除(也就是禁用unset命令)
强类型:
变量不通过强制转换,它永远是这个数据类型,不容许隐式的类型转换。通常定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误 如:java , c# ,python
弱类型:
语言的运行时会隐式作数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用 如:bash 不支持浮点数,php,javascript
shell中变量的基本类型就是String、数值(能够本身看作Int、Double之类的)、Boolean。
Boolean 实际上是Int类型的变种, 在shell中0表明真、非0表明假,因此每每在shell脚本中用 readonly TURN=0 && readonly FALSE=1
。
根据变量的生效范围等标准划分下面变量类型:
局部变量:生效范围为当前shell进程;对当前shell以外的其它shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
本地变量:生效范围为当前shell进程中某代码片段,一般指函数
位置变量:$1, $2, ...
来表示,用于让脚本在脚本代码中调用经过命令行传递给它的参数
特殊变量:$?, $0, $*, $@, $#,$$
变量赋值:name=‘value’
变量引用:${name}
或者 $name
(1) 能够是直接字串:name=“root" (2) 变量引用:name="$USER" (3) 命令引用:name=`COMMAND` name=$(COMMAND)
" "
弱引用,其中的变量引用会被替换为变量值
' '
强引用,其中的变量引用不会被替换为变量值,而保持原字符串
显示已定义的全部变量:
删除变量:
变量声明、赋值: export name=VALUE
declare -x name=VALUE
变量引用: $name, ${name}
显示全部环境变量: env
print env
export
declare -x
删除变量:unset name
bash内建的环境变量 PATH
SHELL
USER
UID
HOME
PWD
SHLVL
LANG
MAIL
HOSTNAME
HISTSIZE
_ 下划线
只能声明,但不能修改和删除
声明只读变量: readonly name
declare -r name
查看只读变量: readonly -p
在脚本代码中调用经过命令行传递给脚本的参数
$1, $2, ... 对应第一、第2等参数,shift [n]换位置,从$9之后须要加{}表示总体 set -- 清空全部位置变量
$0 脚本文件名称,若是全路径执行则带全路径,可使用basename只获取名字 $# 传递给脚本的参数的个数 $* 传递给脚本的全部参数 $@ 传递给脚本的全部参数 "$*" 所有参数合为一个字符串,可在循环中验证 "$@" 每一个参数为独立字符串,可在循环中验证 $$ 运行脚本的PID $! 上一个运行脚本的PID $_ 当前命令行的最后一个参数, 相似于ESC .
$$
和$BASHPID
区别:二者都是当前进程的编号,可是$BASHPID
更精确
只输出路径的基名
[root@oldboyedu-lnb ~]# basename /etc/passwd passwd
0 表明成功 1-255 表明失败 $? 保存上一条命令的退出状态
例如:
ping -c1 -W1 hostdown &> /dev/null echo $? exit [n] 自定义退出状态码
注意:
脚本中一旦遇到exit命令,脚本会当即终止;终止退出状态取决于exit命令后面的数字
若是未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
[root@shell ~]# test='I am oldboy' [root@shell ~]# url='www.baidu.com'
${var:n:x}
切片
[root@shell ~]# echo ${test:2:2} # (2,2+2]从第二个字符开始向后两位为止 am [root@shell ~]# echo $test|awk '{print $2}' am [root@shell ~]# echo $test|cut -c3-4 am
${#var}
字符长度
[root@shell ~]# echo ${#test} 11 [root@shell ~]# echo $test|wc -L 11 [root@shell ~]# expr length "$test" 11 [root@shell ~]# echo $test|awk '{print length}' 11 统计出字符串小于3的单词 笔试题 I am lzhenya teacher I am 18 [root@shell ~]# cat for.sh for i in I am lzhenya teacher I am 18 do [ ${#i} -lt 3 ] && echo $i done [root@shell ~]# sh for.sh I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|xargs -n1|awk '{if(length<3)print}' I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|awk '{for(i=1;i<=NF;i++)if(length($i)<3)print $i}' I am I am 18
删除匹配内容
支持通配符*
${var#}
从前日后匹配,${var##}
贪婪匹配
${var%}
从后往前匹配,${var%%}
贪婪匹配
若是要匹配#
或%
,需使用\
转义
[root@shell ~]# echo ${url#www.} baidu.com [root@shell ~]# echo ${url#*.} baidu.com [root@shell ~]# echo ${url#*.*.} com [root@shell ~]# echo ${url##*.} com [root@shell ~]# echo ${url%.com} www.baidu [root@shell ~]# echo ${url%.*} www.baidu [root@shell ~]# echo ${url%.*.*} www [root@shell ~]# echo ${url%%.*} www
${var/a/b}
替换匹配内容
${var//a/b}
贪婪匹配
[root@shell ~]# echo ${url/w/W} Www.baidu.com [root@shell ~]# echo ${url//w/W} WWW.baidu.com [root@shell ~]# echo ${url/baidu/sina} www.sina.com [root@shell ~]# echo $url|sed 's#www#WWW#g' WWW.baidu.com
+, -, *, /, %取模(取余), **(乘方)
,乘法符号有些场景中须要转义
整数计算使用 expr
或者 $[]
或者$(())
(运算最快)或者 let
小数计算使用 bc
计算器
实现算术运算:
(1) var=$(expr arg1 arg2 arg3 ...) (2) var=$[算术表达式] (3) var=$((算术表达式)) (4) let var=算术表达式 (5) declare –i var = 数值 (6) echo ‘算术表达式’ | bc
随机数
bash有内建的随机数生成器变量:$RANDOM(0-32767) 生成随机数 echo $RANDOM 生成指定范围随机数 示例: # 生成随机7个数(0-6) echo $[RANDOM%7] # 生成随机7个数(31-37) echo $[$[RANDOM%7]+31]
生成随机字符:cat /dev/urandom # 生成8个随机大小写字母或数字 cat /dev/urandom |tr -dc [:alnum:] |head -c 8 tr -dc ‘a-zA-Z0-9’</dev/urandom|head -c8
加强型赋值:
+=, -=, *=, /=, %=
let var OPER value 例如:let count+=3 自加3后自赋值
自增,自减: let var+=1 let var++ let var-=1 let var--
let var=i++ 是赋值后加 let var=++i 是先加后赋值
# 取1-63的余,其中 RANDOM%63 的值是0-62,加1就是1-63 echo $[RANDOM%63+1] # 生成随机颜色 echo -e "\033[1;$[RANDOM%7+31]m 字符串\033[0m"
逻辑运算
true, false
1, 0
与 &
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或 |
1 或 1 = 1
1 或 0 = 1
0 或 1 = 1
0 或 0 = 0
非 !
! 1 = 0 ! true
! 0 = 1 ! false
短路与 &&
第一个为0,结果一定为0
第一个为1,第二个必需要参与运算
短路或 ||
第一个为1,结果一定为1
第一个为0,第二个必需要参与运算
异或:^ 异或的两个值,相同为假,不一样为真
短路与和短路或
[ $RANDOM%6 –eq 0 ] && rm –rf /* || echo “click”
# 数字互换
A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B
A=01010=10
B=10100=20
A=$[A^B]=11110=30
A=11110=30
B=10100=20=10
B=$[A^B]=01010
A=11110=30
B=01010=10
A=$[A^B]=10100=20
[root@oldboyedu-lnb ~]# A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B
A=20 B=10
非特别说明,则全部文件类操做都会追踪到软连接的源文件
test EXPRESSION
[ EXPRESSION ]
(( EXPRESSION )) 算术表达式
[[ EXPRESSION ]] 不会发生文件名扩展或者单词分割,会发生参数扩展和命令替换
注意:EXPRESSION 先后必须有空白字符
test -d "$HOME" ;echo $? [ "abc" != "def" ];echo $? test EXPRESSION && echo "exist" || echo "not exist" 更人性化地显示结果 test EXPRESSION && echo true || echo false 更人性化地显示结果 test -e file && echo "exist" || echo "not exist" test 3 -gt 4 && echo true || echo false
bash的数值测试
-v VAR 变量VAR是否设置 数值测试: -gt 是否大于 -ge 是否大于等于 -eq 是否等于 -ne 是否不等于 -lt 是否小于 -le 是否小于等于
bash的字符串测试
= 是否等于 > ascii码是否大于ascii码 < 是否小于 != 是否不等于 =~ 左侧字符串是否可以被右侧的 正则表达式 所匹配 注意: 此表达式通常用于[[ ]]中,[[ ]]中匹配正则表达式或通配符,不须要引号 [[ hello == hell? ]] && echo true || echo false [[ 2\<3 ]] && echo true || echo false [[ 0 < 1 ]] && echo true || echo false [[ 2 -lt 3 ]] && echo true || echo false [ 2 \< 3 ] && echo true || echo false [ 1 = 1 ] && echo true || echo false -z "STRING“ 字符串是否为空,空为真,不空为假 -n "STRING“ 字符串是否不空,不空为真,空为假 注意:用于字符串比较时的用到的操做数都应该使用引号
bash的文件测试
存在性测试 -a FILE:同 -e -e FILE: 文件存在性测试,存在为真,不然为假 存在性及类别测试 -b FILE:是否存在且为块设备文件 -c FILE:是否存在且为字符设备文件 -d FILE:是否存在且为目录文件 -f FILE:是否存在且为普通文件 -h FILE 或 -L FILE:存在且为符号连接文件 -p FILE:是否存在且为命名管道文件 -S FILE:是否存在且为套接字文件
bash的文件权限测试
文件权限测试: -r FILE:是否存在且可读 -w FILE: 是否存在且可写 -x FILE: 是否存在且可执行 文件特殊权限测试: -u FILE:是否存在且拥有suid权限 -g FILE:是否存在且拥有sgid权限 -k FILE:是否存在且拥有sticky权限
bash的文件属性测试
文件大小测试: -s FILE: 是否存在且非空 文件是否打开: -t fd: fd 文件描述符是否在某终端已经打开 -N FILE:文件自从上一次被读取以后是否被修改过 -O FILE:当前有效用户是否为文件属主 -G FILE:当前有效用户是否为文件属组 双目测试: FILE1 -ef FILE2: FILE1是不是FILE2的硬连接 FILE1 -nt FILE2: FILE1是否新于FILE2(mtime) FILE1 -ot FILE2: FILE1是否旧于FILE2
bash的组合测试条件
第一种方式: EXPRESSION1 -a EXPRESSION2 而且,只能在test或[]中使用 EXPRESSION1 -o EXPRESSION2 或者,只能在test或[]中使用 ! EXPRESSION 非 第二种方式: COMMAND1 && COMMAND2 而且,短路与,表明条件性的AND THEN,只能在[[]]中使用 COMMAND1 || COMMAND2 或者,短路或,表明条件性的OR ELSE,只能在[[]]中使用 ! COMMAND 非 如:[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]]
条件性的执行操做符
示例:
grep -q no_such_user /etc/passwd || echo 'No such user' No such user ping -c1 -W2 station1 &> /dev/null \ > && echo "station1 is up" \ > || (echo 'station1 is unreachable'; exit 1) station1 is up test "$A" = "$B" && echo "Strings are equal" test “$A”-eq “$B” && echo "Integers are equal“ [ "$A" = "$B" ] && echo "Strings are equal" [ "$A" -eq "$B" ] && echo "Integers are equal“ [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab # 判断字符串为空或者localhost.localdomain,则临时修改主机名为 www.magedu.com [ -z “$HOSTNAME” -o $HOSTNAME "=="localhost.localdomain" ] && hostname www.magedu.com
read 把输入值分配给一个或多个shell变量 -a 后跟一个变量,该变量会被认为是个数组,而后给其赋值,默认是以空格为分割符 -e 在输入的时候可使用命令补全功能 -r 屏蔽\的转义功能 -u 后面跟fd,从文件描述符中读入,该文件描述符能够是exec新开启的 -p 指定输入前打印提示信息 -s 静默输入,输入的字符不在屏幕上显示,通常用于密码 -n N 指定输入的字符长度最大为N -d ‘字符’ 以输入的指定‘字符’做为结束符 -t N TIMEOUT为N秒,超时退出 read 从标准输入中读取值,给每一个单词分配一个变量,全部剩余单词都被分配给最后一个变量 read -p “Enter a filename: “ FILE 示例: 修改主机名称为shell,而且修改eth0网卡IP地址为88 [root@shell ~]# cat hostname.sh #!/bin/bash eth0_cfg='/etc/sysconfig/network-scripts/ifcfg-eth0' old_ip=`ifconfig eth0|awk 'NR==2{print $2}'|awk -F. '{print $NF}'` read -p "please input hostname: " name read -p "please input New IP: " IP hostnamectl set-hostname $name sed -i "s#$old_ip#$IP#g" $eth0_cfg grep $IP $eth0_cfg [root@shell ~]# cat ping.sh read -p "Please Input URL: " url ping -c2 -W1 $url &>/dev/null [ $? -ne 0 ] && echo "ping不通" || echo "通了"
cat <<EOF
、cat <<-EOF
的区别
用于执行脚本的时候,须要往一个文件里自动输入N行内容。
cat用于显示文本文件内容,所有输出。
EOF是END Of File的缩写,表示自定义终止符,Ctrl-D就表明EOF。
man说明:
If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter.
翻译:
若是重定向的操做符是<<-,那么分界符(EOF)所在行的开头部分的制表符(Tab)都将被去除。
也就是说:
cat <<EOF
中EOF必须顶行写,前面不能用制表符或者空格。若是结束分解符EOF前有制表符或者空格,则EOF不会被当作结束分界符,只会继续被当作stdin来输入。
cat <<-EOF
中就算最后的EOF前面有多个制表符和空格,但仍然会被当作结束分界符,表示stdin的结束。
trap命令用于指定在接收到信号后将要采起的动做,常见的用途是在脚本程序被中断时完成清理工做。当shell接收到sigspec指定的信号时,arg参数(命令)将会被读取,并被执行,而不会执行原操做。
trap [-lp] [[arg] sigspec ...] -l 让shell打印一个命令名称和其相对应的编号的列表 -p 若是有-p选项而没有提供arg参数,则会打印全部与sigspec指定信号相关联的的trap命令; 若是没有提供任何参数或者仅有-p选项,trap命令将会打印与每个信号有关联的命令的列表; [arg]参数缺省或者为“-”,每一个接收到的sigspec信号都将会被重置为它们进入shell时的值 [arg]参数是空字符串每个由sigspec指定的信号都会被shell和它所调用的命令忽略;
trap commands signals # commands 能够是任何有效的Linux命令,或一个用户定义的函数, # signals 能够是任意数量的信号,或你想来捕获的列表。 # 信号有3种表达方法:信号的数字二、全名SIGINT、缩写INT
参考实例:
trap "rm -f $WORKDIR/work1$ $WORKDIR/dataout$; exit" 1 2 收到指定信号后清理临时文件 trap '' 1 2 20 收到指定信号后忽略信号 trap '-' 1 2 20 trap 1 2 20 恢复信号的默认操做,重设陷阱
每一个sigspec信号都是是以名字或者编号的形式定义在signal.h头文件中,信号的名字是不区分大小写的,其前缀SIG是可选的,有如下状况:
若是sigspec是EXIT(0),那么arg指定的命令将会在shell上执行退出命令时执行
若是sigspec是DEBUG,那么arg指定的命令将会在如下每一个命令执行以前执行:
简单命令,for语句,case语句,select命令,算法命令,在函数内的第一条命令。
若是sigspec是ERR,那么arg指定的命令将会在任何简单命名执行完后返回值为非零值时执行,可是也有如下例外状况,arg命令不会执行,这些规则一样适用于errexit选项:
若是执行失败的命令是紧跟在while或者until关键字以后的一组命令中的一部分时
若是执行失败的命令是if测试语句的一部分时,是 && 和 ||链接的列表中的一部分时
若是执行失败的命令的返回值是被取反过的(经过!操做符)
若是sigspec是RETURN,那么arg指定的命令在每次shell函数或者脚本用"."或者内置的命令执行完成后执行
注意:
在shell入口处被忽略的命令是无法被trap和reset的。
被trap的信号,在建立的子进程中使用时会在子进程被建立时被重置为原始的值。
若是trap使用的sigspec信号是无效的信号,则trap命令返回false(失败),不然返回true(成功)。
① 打印0-9,ctrl+c不能终止
执行脚本后,打印0-9,每秒一个数字,ctrl+c转换为echo press ctrl+c
#!/bin/bash trap 'echo press ctrl+c' 2 for ((i=0;i<10;i++));do sleep 1 echo $i done
② 打印0-3,ctrl+c不能终止,3以后恢复,能终止
执行脚本后,打印0-3,每秒一个数字,ctrl+c不能终止,打印3以后解除捕获2信号,能终止
#!/bin/bash trap '' 2 trap -p for ((i=0;i<3;i++));do sleep 1 echo $i done trap '-' SIGINT for ((i=3;i<10;i++));do sleep 1 echo $i done
信号
信号是一种进程间通讯机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其余程序活终端发送的命令(即信号)。
应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。
进程收到一个信号后,会检查对该信号的处理机制:
若是是SIG_IGN,就忽略该信号;
若是是SIG_DFT,则会采用系统默认的处理动做,一般是终止进程或忽略该信号;
若是给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。
在有些状况下,咱们不但愿本身的shell脚本在运行时刻被中断,好比说咱们写得shell脚本设为某一用户的默认shell,使这一用户进入系统后只能做某一项工做,如数据库备份,咱们不但愿用户使用Ctrl+c之类可以进入到shell状态,作咱们不但愿作的事情。这便用到了信号处理。
常见信号:
1) SIGHUP: 无须关闭进程而让其重读配置文件
2) SIGINT: 停止正在运行的进程;至关于Ctrl+c
3) SIGQUIT: 至关于ctrl+\
9) SIGKILL: 强制杀死正在运行的进程;本信号不能被阻塞,处理和忽略。
15) SIGTERM :终止正在运行的进程(默认为15)
18) SIGCONT :继续运行
19) SIGSTOP :后台休眠
信号名称 | 信号数 | 描述 |
---|---|---|
SIGHUP | 1 | 本信号在用户终端链接(正常或非正常)结束时发出, 一般是在终端的控制进程结束时, 通知同一session内的各个做业, 这时它们与控制终端再也不关联。登陆Linux时,系统会分配给登陆用户一个终端(Session)。在这个终端运行的全部程序,包括前台进程组和后台进程组,通常都属于这个Session。当用户退出Linux登陆时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操做为终止进程,所以前台进程组和后台有终端输出的进程就会停止。对于与终端脱离关系的守护进程,这个信号用于通知它从新读取配置文件。 |
SIGINT | 2 | 程序终止(interrupt)信号, 在用户键入INTR字符(一般是Ctrl+C)时发出。 |
SIGQUIT | 3 | 和SIGINT相似, 但由QUIT字符(一般是Ctrl+/)来控制。进程在因收到SIGQUIT退出时会产生core文件,在这个意义上相似于一个程序错误信号。 |
SIGFPE | 8 | 在发生致命的算术运算错误时发出。不只包括浮点运算错误,还包括溢出及除数为0等其它全部的算术的错误。 |
SIGKILL | 9 | 用来当即结束程序的运行。本信号不能被阻塞,处理和忽略。 |
SIGALRM | 14 | 时钟定时信号,计算的是实际的时间或时钟时间。 alarm函数使用该信号。 |
SIGTERM | 15 | 程序结束(terminate)信号,与SIGKILL不一样的是该信号能够被阻塞和处理,一般用来要求程序本身正常退出。shell命令kill缺省产生这个信号。 |
SIGHUP 1 /* Hangup (POSIX). */ 终止进程 终端线路挂断 SIGINT 2 /* Interrupt (ANSI). */ 终止进程 中断进程 Ctrl+C SIGQUIT 3 /* Quit (POSIX). */ 创建CORE文件终止进程,而且生成core文件 Ctrl+ SIGILL 4 /* Illegal instruction (ANSI). */ 创建CORE文件,非法指令 SIGTRAP 5 /* Trace trap (POSIX). */ 创建CORE文件,跟踪自陷 SIGABRT 6 /* Abort (ANSI). */ SIGIOT 6 /* IOT trap (4.2 BSD). */ 创建CORE文件,执行I/O自陷 SIGBUS 7 /* BUS error (4.2 BSD). */ 创建CORE文件,总线错误 SIGFPE 8 /* Floating-point exception (ANSI). */ 创建CORE文件,浮点异常 SIGKILL 9 /* Kill, unblockable (POSIX). */ 终止进程 杀死进程 SIGUSR1 10 /* User-defined signal 1 (POSIX). */ 终止进程 用户定义信号1 SIGSEGV 11 /* Segmentation violation (ANSI). */ 创建CORE文件,段非法错误 SIGUSR2 12 /* User-defined signal 2 (POSIX). */ 终止进程 用户定义信号2 SIGPIPE 13 /* Broken pipe (POSIX). */ 终止进程 向一个没有读进程的管道写数据 SIGALARM 14 /* Alarm clock (POSIX). */ 终止进程 计时器到时 SIGTERM 15 /* Termination (ANSI). */ 终止进程 软件终止信号 SIGSTKFLT 16 /* Stack fault. */ SIGCHLD 17 /* Child status has changed (POSIX). */ 忽略信号 当子进程中止或退出时通知父进程 SIGCONT 18 /* Continue (POSIX). */ 忽略信号 继续执行一个中止的进程 SIGSTOP 19 /* Stop, unblockable (POSIX). */ 中止进程 非终端来的中止信号 SIGTSTP 20 /* Keyboard stop (POSIX). */ 中止进程 终端来的中止信号 Ctrl+Z SIGTTIN 21 /* Background read from tty (POSIX). */ 中止进程 后台进程读终端 SIGTTOU 22 /* Background write to tty (POSIX). */ 中止进程 后台进程写终端 SIGURG 23 /* Urgent condition on socket (4.2 BSD).*/ 忽略信号 I/O紧急信号 SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */ 终止进程 CPU时限超时 SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */ 终止进程 文件长度过长 SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */ 终止进程 虚拟计时器到时 SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */ 终止进程 统计分布图用计时器到时 SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ 忽略信号 窗口大小发生变化 SIGIO 29 /* I/O now possible (4.2 BSD). */ 忽略信号 描述符上能够进行I/O SIGPWR 30 /* Power failure restart (System V). */ SIGSYS 31 /* Bad system call. */
安装
yum install expect -y
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ] -c:从命令行执行expect脚本,默认expect是交互地执行的 -d:能够输出输出调试信息 示例: expect -c 'expect "\n" {send "pressed enter\n"} expect -d ssh.exp
expect中相关命令 :
spawn:启动新的进程 send:用于向进程发送字符串 expect:从进程接收字符串 interact:容许用户交互,并停留在远程链接的主机上 exp_continue:匹配多个字符串在执行动做后加此命令
expect最经常使用的语法(tcl语言:模式-动做)
单一分支模式语法:
匹配到hi后,会输出“you said hi”,并换行
expect “hi” {send “You said hi\n"}
多分支模式语法:
匹配hi,hello,bye任意字符串时,执行相应输出。
expect "hi" { send "You said hi\n" } \ "hehe" { send "Hehe yourself\n" } \ "bye" { send "Good bye\n" }
等同以下:
expect { "hi" { send "You said hi\n"} "hehe" { send "Hehe yourself\n"} "bye" { send " Good bye\n"} }
① 用户名密码自动登陆系统
#!/usr/bin/expect set ip 192.168.7.100 set user root set password centos set timeout 10 # 登陆 调用user和ip两个变量的值 spawn ssh $user@$ip expect { # 有发现yes/no 输入 yes\n "yes/no" { send "yes\n";exp_continue } # 有发现password输入$password的值 "password" { send "$password\n" } } # 容许用户交互 interact
② shell调用expect脚本
#!/bin/bash ip=$1 user=$2 password=$3 expect <<EOF # 开启expect命令多行重定向 set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } expect "]#" { send "echo centos |passwd --stdin hehe\n" } expect "]#" { send "exit\n" } expect eof # 结束语 EOF
③ 多主机批量操纵:根据相同用户名和密码,批量建立用户
一、建立IP地址清单
[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF 192.168.7.101 192.168.7.102 192.168.7.103 EOF
二、经过while实现批量读取文件内容
#!/bin/bash while read ip;do user=root password=centos expect <<EOF set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } # 远程ssh登陆后建立用户名 expect "]#" { send "echo centos |passwd --stdin hehe\n" } # 设置密码 expect "]#" { send "exit\n" } expect eof EOF done < iplist.txt
④ 多主机批量操纵:根据不一样用户名和密码传递公钥,实现免密钥登陆
一、建立IP地址,密码清单
[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF 192.168.7.101 wangwang 192.168.7.102 centos 192.168.7.103 hahahaha EOF
二、经过while实现批量读取文件内容
#!/bin/bash ssh-keygen -t rsa -P "" -f /root/.ssh/id_rsa while read ip password;do user=root set timeout 10 expect << EOF spawn ssh-copy-id -i /root/.ssh/id_rsa.pub $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect eof EOF done <iplist.txt
顺序执行
选择执行
循环执行
选择执行:可嵌套
单分支
if 判断条件;then
条件为真的分支代码
fi
双分支
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
else
以上条件都为假的分支代码
fi
逐条件进行判断,第一次遇为“真”条件时,执行其分支,然后结束整个if语句
Example:
根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then
echo 'Station1 is UP'
elif grep "station1" ~/maintenance.txt &> /dev/null; then
echo 'Station1 is undergoing maintenance'
else
echo 'Station1 is unexpectedly DOWN!'
exit 1
fi
① 判断年纪
请输入年纪,先判断输入的是否含有除数字之外的字符,有,输出"please input a int";没有,继续判断是否小于150,是否大于18。
#!/bin/bash read -p "Please input your age: " age if [[ $age =~ [^0-9] ]] ;then echo "please input a int" exit 10 elif [ $age -ge 150 ];then echo "your age is wrong" exit 20 elif [ $age -gt 18 ];then echo "good good work,day day up" else echo "good good study,day day up" fi
② 判断分数
请输入成绩,先判断输入的是否含有除数字之外的字符,有,输出"please input a int";没有,继续判断是否大于100,是否大于85,是否大于60。
#!/bin/bash read -p "Please input your score: " score if [[ $score =~ [^0-9] ]] ;then echo "please input a int" exit 10 elif [ $score -gt 100 ];then echo "Your score is wrong" exit 20 elif [ $score -ge 85 ];then echo "Your score is very good" elif [ $score -ge 60 ];then echo "Your score is soso" else echo "You are loser" fi
case 变量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; *) 默认分支 ;; esac case支持glob风格的通配符: *: 任意长度任意字符 ?: 任意单个字符 []: 指定范围内的任意单个字符 a|b: a或b
① 判断yes or no
请输入yes or no,回答Y/y、yes各类大小写组合为yes;回答N/n、No各类大小写组合为no。
#!/bin/bash read -p "Please input yes or no: " anw case $anw in [yY][eE][sS]|[yY]) echo yes ;; [nN][oO]|[nN]) echo no ;; *) echo false ;; esac
for (( i = 0; i < 10; i++ )); do 循环体 done for item in 列表; do 循环体 done
执行机制:依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
列表:
支持glob通配符,如:{1..10}
、*.sh
;
也能够引用变量 ${array}
,如:seq 1 $1
;
① 求(1+2+...+n)的总和
sum初始值为0,请输入一个数,判断输入的值是否以1-9开头,后面跟任意个0-9的数字,不是,就报错;是,进入for循环,i的范围为1~输入的数,每次的循环为sum=sum+i,循环结束,最后输出sum的值。
#!/bin/bash sum=0 read -p "Please input a positive integer: " num if [[ ! $num =~ ^[1-9][0-9]* ]] ;then echo "input error" else for i in `seq 1 $num` ;do sum=$[$sum+$i] done echo $sum fi
while [[ 循环控制条件 ]]; do 循环体 done while read -r item ;do 循环体 done < 'file_name' cat 'file_name' | while read line; do 循环体 done
循环控制条件;进入循环以前,先作一次判断;每一次循环以后会再次作判断;条件为“true” ,则执行一次循环;直到条件测试状态为“false” 终止循环
遍历文件的每一行:依次读取file_name文件中的每一行,且将行赋值给变量line
① 100之内全部正奇数之和
sum初始值为0,i的初始值为1;当i<=100时,进入循环,判断 i÷2取余,不为0时为奇数,sum=sum+i,i+1;为0时,i+1;循环结束,最后输出sum的值。
#!/bin/bash sum=0 i=1 while [ $i -le 100 ] ;do if [ $[$i%2] -ne 0 ];then let sum+=i let i++ else let i++ fi done echo "sum is $sum"
② 菜单
#!/bin/bash cat << EOF 1)gongbaojiding 2)kaoya 3)fotiaoqiang 4)haishen 5)baoyu 6)quit EOF while read -p "please choose the number: " num;do case $num in 1) echo "gongbaojiding price is 30" ;; 2) echo "kaoya price price is 80" ;; 3) echo "fotiaoqiang price is 200" ;; 4) echo "haishen price is \$20" ;; 5) echo "baoyu price is \$10" ;; 6) break ;; *) echo "please input again" esac done
③ 统计日志访问IP状况
#!/bin/bash # 其中access_log为访问日志,统计访问IP和次数,导出到文件iplist.txt中 sed -rn 's/^([^[:space:]]+).*/\1/p' access_log |sort |uniq -c > iplist.txt # while read 逐行处理 while read count ip;do if [ $count -gt 100 ];then # 将统计后的日志导出到文件crack.log中 echo from $ip access $count >> crack.log fi # while read 支持重定向,能够将要统计的文件导入到循环中 done < iplist.txt
④ 统计磁盘使用率大于指定值的信息
#!/bin/bash # 定义报警的磁盘使用率 WARNING=10 df | awk '{if($5>$WARNING)print $0}'
#!/bin/bash # 定义报警的磁盘使用率 WARNING=10 df |sed -rn '/^\/dev\/sd/s#^([^[:space:]]+).* ([[:digit:]]+)%.*$#\1 \2#p' | while read part use; do if [ $use -gt $WARNING ]; then echo $part will be full,use:$use fi done
#!/bin/bash # 定义报警的磁盘使用率 WARNING=10 df |awk -F"[[:space:]]+|%" '/dev\/sd/{print $1,$(NF-2)}' > disk.txt while read part use; do if [ $use -gt $WARNING ]; then echo $part will be full,use:$use fi done < disk.txt
until [[ 循环控制条件 ]]; do 循环体 done
进入条件:循环条件为false ;
退出条件:循环条件为true;
恰好和while相反,因此不经常使用,用while就行。
① 监控test用户,登陆就杀死
#!/bin/bash # 发现test用户登陆,条件为true,退出循环 until pgrep -u test &> /dev/null ;do # 每隔0.5秒扫描 sleep 0.5 done # 杀死test用户相关进程 pkill -9 -u test
select variable in list do 循环体 done
① select 循环主要用于建立菜单,按数字顺序排列的示菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入
② 用户输入菜单列表中的某个数字,执行相应的命令
③ 用户输入被保存在内置变量 REPLY 中
④ select 是个无限循环,所以要记住用 break 命令退出循环,或用 exit 命令终止脚本。也能够按 ctrl+c退出循环
⑤ select 常常和 case 联合使用
⑥ 与for循环相似,能够省略 in list, 此时使用位置参量
示例: 生成菜单,并显示选中的价钱
#!/bin/bash PS3="Please choose the menu: " select menu in mifan huimian jiaozi babaozhou quit do case $REPLY in 1|4) echo "the price is 15" ;; 2|3) echo "the price is 20" ;; 5) break ;; *) echo "no the option" esac done
注意:PS3是select的提示符,自动生成菜单,选择5退出循环。
continue [N]:提早结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
break [N]:提早结束第N层循环,最内侧为第1层
例:
while CONDTITON1; do CMD1 if CONDITION2; then continue / break fi CMD2 done
(2)案例:
① 求(1+3+...+49+53+...+100)的和
#!/bin/bash sum=0 for i in {1..100} ;do [ $i -eq 51 ] && continue [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; } done echo sum=$sum
分析:作1+3+...+99的循环,当i=51时,跳过此次循环,可是继续整个循环,结果为:sum=2449
② 求(1+3+...+49)的和
#!/bin/bash sum=0 for i in {1..100} ;do [ $i -eq 51 ] && break [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; } done echo sum=$sum
分析:作1+3+...+99的循环,当i=51时,跳出整个循环,结果为:sum=625
shift n 用于将参数列表list左移指定次数,最左端的那个参数就从列表中删除,其后边的参数继续进入循环,n是整数 1.依次读取输入的参数并打印参数个数: $ cat run.sh #!/bin/bash while [ $# != 0 ];do echo "第一个参数为:$1,参数个数为:$#" shift done $ sh run.sh a b c d e f 第一个参数为:a,参数个数为:6 第一个参数为:b,参数个数为:5 第一个参数为:c,参数个数为:4 第一个参数为:d,参数个数为:3 第一个参数为:e,参数个数为:2 第一个参数为:f,参数个数为:1 2.把参数进行左移3个: $ cat t.sh #!/bin/bash echo -e "./t.sh arg1 arg2 arg3 arg4 arg5 arg6" str1="${1},${2},${3}" echo "str1=$str1" shift 3 str2=$@ echo "str2=$str2" $ sh t.sh 1 2 3 4 5 6 7 str1=1,2,3 3.将参数从左到右逐个移动: $ cat shift.sh #!/bin/bash while [ $# -ne 0 ] do echo "第一个参数为: $1 参数个数为: $#" shift done $ sh shift.sh Lily Lucy Jake Mike 第一个参数为: Lily 参数个数为: 4 第一个参数为: Lucy 参数个数为: 3 第一个参数为: Jake 参数个数为: 2 第一个参数为: Mike 参数个数为: 1
① 建立指定的多个用户
#!/bin/bash if [ $# -eq 0 ] ;then echo "Please input a arg(eg:`basename $0` arg1)" exit 1 else while [ -n "$1" ];do useradd $1 &> /dev/null echo "User:$1 is created" shift done fi
分析:若是没有输入参数(参数的总数为0),提示错误并退出;反之,进入循环;若第一个参数不为空字符,则建立以第一个参数为名的用户,并移除第一个参数,将紧跟的参数左移做为第一个参数,直到没有第一个参数,退出。
② 打印直角三角形的字符
[root@oldboyedu-lnb ~]# sh trian.sh 1 2 3
1 2 3
2 3
3
true
永远成功
false
永远错误
无限循环
while true ;do 循环体 done # 或者 until false ;do 循环体 done
每次循环将循环体放入后台执行. 继续下一次循环. 最后等待全部线程执行完毕再退出脚本
for name in 列表 ;do { 循环体 }& done wait
① 搜寻指定ip(子网掩码为24的)的网段中,UP的ip地址
read -p "Please input network (eg:192.168.0.0): " net echo $net |egrep -o "\<(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>" [ $? -eq 0 ] || ( echo "input error";exit 10 ) IP=`echo $net |egrep -o "^([0-9]{1,3}\.){3}"` for i in {1..254};do { ping -c 1 -w 1 $IP$i &> /dev/null && \ echo "$IP$i is up" }& done wait
分析:请输入一个IP地址,例如192.168.37.234,若是格式不是0.0.0.0 则报错退出;正确则进入循环,IP变量的值为192.168.37. i的范围为1-254,并行ping 192.168.37.1-154,ping通就输出此IP为UP。
函数递归: 函数直接或间接调用自身 注意递归层数 递归实例: 阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是全部小于及等于该数的正整数的积,而且有0的阶乘为1,天然数n的阶乘写做n!
n!=1×2×3×...×n 阶乘亦能够递归方式定义:0!=1,n!=(n-1)!×n n!=n(n-1)(n-2)...1 n(n-1)! = n(n-1)(n-2)! 函数递归示例 示例:fact.sh #!/bin/bash fact() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(fact $[$1-1])] fi } fact $1
把命令行分红单个命令词
展开别名
展开大括号的声明({})
展开波浪符声明(~)
命令替换$()和``
再次把命令行分红命令词
展开文件通配(*、?、[abc]等等)
准备I/0重导向(<、>)
运行命令
反斜线\
会使随后的一个字符按原意解释
单引号'
防止全部扩展
双引号"
除了如下状况,防止全部扩展:
$ (美圆符号) 变量扩展(注意:"$" 输出 $,仍有特殊含义) ` (反引号) 命令替换 \ (反斜线) 禁止单个字符扩展 ! (叹号) 历史命令替换
HEAD_KEYWORD parameters; BODY_BEGIN BODY_COMMANDS BODY_END
将HEAD_KEYWORD和初始化命令或者参数放在第一行;
将BODY_BEGIN一样放在第一行;
复合命令中的BODY_COMMANDS部分以2个空格缩进;
BODY_END部分独立一行放在最后;
if
if [[ condition ]]; then # statements fi if [[ condition ]]; then # statements else # statements fi if [[ condition ]]; then # statements elif [[ condition ]]; then # statements else # statements fi
if 后面的判断 使用 双中括号[[]]
if [[ condition ]]; then
写在一行
while
while [[ condition ]]; do # statements done while read -r item ;do # statements done < 'file_name'
until
until [[ condition ]]; do # statements done
for
for (( i = 0; i < 10; i++ )); do # statements done for item in ${array}; do # statements done
case
case $var in pattern ) #statements ;; *) #statements ;; esac
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程。
它与shell程序形式上是类似的,不一样的是它不是一个单独的进程,不能独立运 行,而是shell程序的一部分,定义函数只对当前的会话窗口有效,若是再打开一个窗口再定义另一个函数,就对另外一个窗口有效,二者互不影响。
函数和shell程序比较类似,区别在于如下两种:
(1)Shell程序在子Shell中运行。
(2)而Shell函数在当前Shell中运行。所以在当前Shell中,函数能够对shell中变量进行修改。
function main(){ #函数执行的操做 #函数的返回结果 }
或
main(){ #函数执行的操做 #函数的返回结果 }
或
function main { #函数执行的操做 #函数的返回结果 }
使用关键字 function
显示定义的函数为 public 的函数,能够供外部脚本以 sh 脚本 函数 函数入参
的形式调用
未使用关键字 function
显示定义的函数为 privat 的函数, 仅供本脚本内部调用,注意这种privat是人为规定的,并非shell的语法,不推荐以 sh 脚本 函数 函数入参
的形式调用,注意是不推荐而不是不能。
本shell规约这样作的目的就在于使脚本具备必定的封装性,看到
function
修饰的就知道这个函数能被外部调用, 没有被修饰的函数就仅供内部调用。你就知道若是你修改了改函数的影响范围. 若是是被function修饰的函数, 修改后可能影响到外部调用他的脚本, 而修改未被function修饰的函数的时候,仅仅影响本文件中其余函数。
如 core.sh 脚本内容以下是
# 从新设置DNS地址 []<-() function set_name_server(){ > /etc/resolv.conf echo nameserver 114.114.114.114 >> /etc/resolv.conf echo nameserver 8.8.8.8 >> /etc/resolv.conf cat /etc/resolv.conf } # info级别的日志 []<-(msg:String) log_info(){ echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[32m [info] \033[0m $*" >&2 } # error级别的日志 []<-(msg:String) log_error(){ # todo [error]用红色显示 echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[31m [error] \033[0m $*" >&2 }
则我可使用 sh core.sh set_name_server
的形式调用 set_name_server
函数,但就不推荐使用 sh core.sh log_info "Hello World"
的形式使用 log_info
和 log_error
函数,注意是不推荐不是不能。
(1)可在交互式环境下定义函数
(2)可将函数放在脚本文件中做为它的一部分
#!/bin/bash # 定义function func_os_version,在大括号里边定义命令,取出操做系统的版本号,相似于定义别名同样 func_os_version () { sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release } # 直接写上函数名,或者用echo加反引号输出结果 echo OS version is `func_os_version`
若是命令过多,不太方便
(3)可放在只包含函数的单独文件中
而后将函数文件载入shell
文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
一旦函数文件载入shell,就能够在命令行或脚本中调用函数。可使用set
查看全部定义的函数,其输出列表包括已经载入shell的全部函数
若要改动函数,首先用unset function_name
从shell中删除函数。改动完毕后,再从新载入此文件
# 将定义的函数放到functions文件中
[root@centos-7 ~]# cat functions
func_os_version () {
sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release
}
[root@centos-7 ~]# cat osversion.sh
可使用declare -F
查看全部定义的函数
使子进程也可以使用
(1)声明:export -f function_name
(2)查看:export -f
或 declare -xf
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时建立,返回时终止
使用脚本单独调用函数中的某个函数
#!/usr/bin/env bash # shellcheck disable=SC1091,SC2155 readonly local TRUE=0 && readonly local FALSE=1 # 脚本使用帮助文档 manual(){ cat "$0"|grep -v "less \"\$0\"" \ |grep -B1 "function " \ |grep -v "\\--" \ |sed "s/function //g" \ |sed "s/(){//g" \ |sed "s/#//g" \ |sed 'N;s/\n/ /' \ |column -t \ |awk '{print $1,$3,$2}' \ |column -t } ###################################################################### # 主函数 main(){ (manual) } ###################################################################### # 执行函数 [Any]<-(function_name:String,function_parameters:List<Any>) execute(){ function_name=$1 shift # 参数列表以空格为分割左移一位,至关于丢弃掉第一个参数 function_parameters=$* (${function_name} "${function_parameters}") } case $1 in "-h" | "--help" | "?") (manual);; "") (main) ;; *) (execute "$@") ;; esac
使用如上的框架,只须要在 两个 ######################################################################
之间写函数,就可使用 sh 脚本名称 脚本中的某个函数 脚本中的某个函数的入参
的形式调用函数了。 使用 sh 脚本名称 ?
或者 sh 脚本名称 -h/--help
就能够查看这个脚本中的函数说明了。
在函数内部首先使用有意义的变量名接受参数,而后在使用这些变量进行操做,禁止直接操做$1
,$2
等,除非这些变量只用一次
函数的注释 函数类型的概念是从函数编程语言中的概念偷过来的,shell函数的函数类型指的是函数的输入到函数的输入的映射关系
# 主函数 []<-() <-------函数注释这样写 function main(){ local var="Hello World!!!" echo ${var} } # info级别的日志 []<-(msg:String) <-------带入参的函数注释 log_info(){ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2 }
main函数的函数类型是 []<-() , <- 左侧表的是函数的返回值类型用[]包裹, 右侧是函数的参数类型用()包裹,多个参数用 ',' 分隔, 参数的描述是从 Scala 语言中偷过来, 先是参数名称, 而后是参数类型, 中间用:分隔
对于main函数的注释来讲, #
顶格写,后面紧跟一个空格,其实这样写是遵循的markdown的语法, 后面再跟一个空格,而后是 []<-(),表明这个函数没有入参也没有返回值,这个函数的目的就是执行这个这个函数中的命令,但我不关心这个函数的返回值。也就是利用函数的反作用来完成咱们想要的操做。
对于log_info也是同样不过最后的函数类型是 []<-(msg:String) 表明入参是一个string类型的信息,而后也没有返回值。 关于函数的返回值,我理解的函数的返回值有两种形式,一种是显示的return一种是隐式的echo
如下是几种常见的写法
[]<-() [String]<-(var1:String,var2:String) [Boolean]<-(var1:String,var2:Int) []<-(var1:String)
一、函数的执行结果返回值:
(1) 使用echo等命令进行输出
(2) 函数体中调用命令的输出结果
二、函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值:
(1)return 0 无错误返回。
(2)return 1-255 有错误返回
执行一条命令的时候, 好比 pwd 正常状况下它输出的结果是 当前所处的目录
$ pwd /Users/chenshang
shell中必然有一种状态来标识一条命令是否执行成功,也就是命令执行结果的状态。
0表明真、成功的含义。
非零表明假、失败的含义。
因此 pwd 这条命令若是执行成功的话,命令的执行结果状态必定是0,而后返回值才是当前目录。若是这条命令执行失败的话,命令的执行结果状态必定不是0,有多是1 表明命令不存在,而后输出 not found,也有可能执行结果状态是2表明超时,而后什么也不输出。那怎么获取这个命令的执行结果和执行结果的状态呢?
function main(){ pwd }
执行main函数就会在控制台输出当前目录 若是想要将pwd的内容获取到变量中以供后续使用呢
function main(){ local dir=$(pwd) echo "dir is ${dir}" }
若是想要获取pwd的执行结果的状态呢
function main(){ local dir=$(pwd) local status=$? echo "pwd run status is ${status}" #这个stauts必定有值,且是int类型,取值范围在0-255之间 echo "dir is ${dir}" }
return 用来显示的返回函数的返回结果,例如
# 检查当前系统版本 [Integer]<-() function check_version(){ (log_info "check_version ...") # log_info是我写的工具类中的一个函数 local version # 这里是先定义变量,在对变量进行赋值,咱们每每是直接初始化,而不是像这样先定义在赋值,这里只是告诉你们能够这么用 version=$(sed -r 's/.* ([0-9]+)\..*/\1/' /etc/redhat-release) (log_info "centos version is ${version}") return "${version}" }
这样这个函数的返回值是一个数值类型,我在脚本的任何地方调用check_version这个函数后,使用 $? 获取返回值
check_version local version=$? echo "${version}"
注意这里不用 local version=$(check_version) 这种形式获取结果,这样也是获取不到结果的,由于显示的return结果,返回值只能是[0-255]的数值,这对于咱们通常的函数来讲就足够了,由于咱们使用显示return的时候每每是知道返回结果必定是数字且在[0-255]之间的,经常用在状态判断的时候。
本shell规约规定:
明确返回结果是在[0-255]之间的数值类型的时候使用显示 reuturn 返回结果
返回结果类型是Boolean类型,也就是说函数的功能是起判断做用,返回结果是真或者假的时候使用显示 return 返回结果
# 检查网络 [Boolean]<-() function check_network(){ (log_info "check_network ...") for((i=1;i<=3;i++));do http_code=$(curl -I -m 10 -o /dev/null -s -w %\{http_code\} www.baidu.com) if [[ ${http_code} -eq 200 ]];then (log_info "network is ok") return ${TRUE} fi done (log_error "network is not ok") return ${FALSE} } # 获取数组中指定元素的下标 [int]<-(TABLE:Array,target:String) function get_index_of(){ readonly local array=($1) local target=$2 local index=-1 # -1实际上是255 local size=${#array[@]} for ((i=0;i<${size};i++));do if [[ ${array[i]} == ${target} ]];then return ${i} fi done return ${index} }
return 用来显示的返回函数的返回结果,例如
# 将json字符串格式化树形结构 [String]<-(json_string:String) function json_format(){ local json_string=$1 echo "${json_string}"|jq . #jq是shell中处理json的一个工具 }
函数中全部的echo照理都应该输出到控制台上 例如
json_format "{\"1\":\"one\"}"
你会在控制台上看到以下输出
{ "1": "one" }
可是一旦你用变量接收函数的返回值,这些本该输出到控制台的结果就都会存储到你定义的变量中 例如
json=$(json_format "{\"1\":\"one\"}") echo "${json}" # 若是没有这句,上面的语句执行完成后,不会在控制台有任何的输出
咱们把 json_format 改造一下
# 将json字符串格式化树形结构 [String]<-(json_string:String) function json_format(){ local json_string=$1 echo "为格式化以前:${json_string}" # 其实新添加的只一句只是用来记录一行日志的 echo "${json_string}"|jq . # jq是shell中处理json的一个工具 }
echo "为格式化以前:${json_string}" 其实新添加的只一句只是用来记录一行日志的,可是json=$(json_format "{"1":"one"}") json 也会将这句话做为返回结果进行接收,但这是我不想要看到的。
子shell能够捕获父shell的变量,但不能改变父shell的变量,使用()将代码块包裹,包裹的代码块将在子shell中运行,子shell至关于独立的一个环境,不会影响到父shell的结果
因此若是我不想让 echo "为格式化以前:${json_string}" 这句话也做为结果的话,我就只须要用()将代码块包裹便可
# 将json字符串格式化树形结构 [String]<-(json_string:String) function json_format(){ local json_string=$1 (echo "为格式化以前:${json_string}") # 其实新添加的只一句只是用来记录一行日志的 echo "${json_string}"|jq . # jq是shell中处理json的一个工具 }
① 对不一样的成绩分段进行判断
[root@oldboyedu-lnb ~]# cat functions func_is_digit(){ # 判断参数$1不是空,就为真,取反,空为真 if [ ! "$1" ];then # 请输入数字 echo "Usage:func_is_digit number" return 10 # 若是输入是数字,返回0 elif [[ $1 =~ ^[[:digit:]]+$ ]];then return 0 else # 不然提醒不是数字 echo "Not a digit" return 1 fi }
[root@oldboyedu-lnb ~]# cat score.sh
② function配合case:代码发布与回滚与检验