7.SHELL脚本编程
1. shell脚本的基本结构
1.1 shell脚本的格式
-
指明解释器类型
通常被称为shebang,常见的有sh、bash、zsh,如
#!/bin/bash
指明此脚本使用/bin/bash
来解释执行格式:#!加解释器的路径
#!/bin/bash #!/usr/bin/python #!/usr/bin/perl
-
注释
单行注释以#开头,多行注释以
:<<!
开头以!
结尾在用户家目录新建.vimrc文件,自定义shell脚本模板
vim ~/.vimrc
#设置tab键长度等于4个空格 set expandtab #将tab键替换为4个空格 set tabstop=4 set et set ts=4 autocmd BufNewFile *.sh exec ":call SetTitle()" func SetTitle() if expand("%:e") == 'sh' call setline(1,"#!/bin/bash") call setline(2,"") call setline(3,"#********************************************************************") call setline(4,"#FileName: ".expand("%")) call setline(5,"#Author: John") call setline(6,"#Date: ".strftime("%F %T")) call setline(7,"#Description: The test script") call setline(8,"#********************************************************************") endif endfunc
1.2 shell脚本的调试
-
语法错误调试
使用
bash -n
调试,该命令不会真正执行shell脚本,只是输出语法错误 -
逻辑错误调试
使用
basn -x
调试,会逐条执行shell脚本,并输出,该命令会真正执行脚本 -
命令拼写错误
脚本中的命令拼写有误,也可以使用
bash -x
调试
2. 变量
2.1 变量介绍
-
变量分类
内置变量:
用户自定义变量:
-
命名规则
变量命名:字符数字下划线(数字不开头),常用大写字母表示,如VAR_NAME
函数命名:函数名一般用小写,如check_network()
2.2 变量赋值
- 格式:
NAME=VALUE
等号左右不能有空格
-
直接字符串:VALUE是字符串,单引号、双引号、不加引号都可以,没有区别
NAME=root
NAME='root'
NAME="root"
-
变量引用:VALUE是引用其他变量的值,双引号、不加引号都可以
NAME=$USER
NAME="$USER"
-
命令引用:VALUE是其他命令的输出结果,反引号、$()都可以
``NAME=
COMMAND```
NAME=$(COMMAND)`
-
范例:使用变量引用赋值时,改变其中一个变量的值后,另一个保持不变
[root@centos ~]# NAME=John [root@centos ~]# VAR_NAME=$NAME [root@centos ~]# echo $NAME $VAR_NAME John John [root@centos ~]# NAME=Dana [root@centos ~]# echo $NAME $VAR_NAME Dana John
[root@centos ~]# NAME=John [root@centos ~]# VAR_NAME=$NAME [root@centos ~]# echo $NAME $VAR_NAME John John [root@centos ~]# VAR_NAME=Allen [root@centos ~]# echo $NAME $VAR_NAME John Allen
-
范例:不加引号单行输出,双引号多行输出
[root@centos ~]# seq 3 1 2 3 [root@centos ~]# NUM=`seq 3` [root@centos ~]# echo $NUM 1 2 3 [root@centos ~]# echo "$NUM" 1 2 3
2.3 变量查找与删除
-
查找:
set
显示所有变量[root@centos ~]# set |grep NAME HOSTNAME=centos LOGNAME=root NAME=John VAR_NAME=Allen
-
删除:
unset NAME1 NAME2 NAME3
删除指定变量[root@centos ~]# unset VAR_NAME
2.4 普通变量
普通变量仅在当前进程中有效,即定义在命令行只在当前终端有效,定义在脚本中只在当前脚本有效
2.5 环境变量
定义在父进程中的环境变量,子进程可以使用
-
环境变量声明和赋值
# 方法一:常用 export name=VALUE # 方法二:不常用 declare -x name=VALUE
-
环境变量的引用
$NAME
不支持拼接下划线,${NAME}
支持拼接下划线#!/bin/bash NAME1=value NAME2=new #不拼接下划线,输出valuenew echo $NAME1$NAME2 #拼接下划线,输出value_new echo ${NAME1}_$NAME2 #错误拼接,只输出new,因为找不到叫NAME1_的变量名 #echo $NAME1_$NAME2
-
显示所有环境变量
env
、printenv
、export
、declare -x
这四个命令都可以[root@centos ~]# env HOSTNAME=centos SHELL=/bin/bash SSH_CLIENT=172.32.115.1 4946 22 USER=root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin ..
-
删除变量
[root@centos ~]# unset VAR_NAME
-
范例:子进程使用父进程中定义的环境变量
vim main_proc.sh
父进程中定义环境变量,并调用子进程脚本#!/bin/bash export MAIN_VAR="John" echo "main_proc pid is $BASHPID , MAIN_VAR is $MAIN_VAR" ./sub_proc.sh
vim sub_proc.sh
子进程中使用父进程中定义的环境变量#!/bin/bash SUB_VAR=$MAIN_VAR echo "sub_proc pid is $BASHPID , SUB_VAR is '$SUB_VAR' and ppid is $PPID"
脚本执行结果
[root@centos ~]# ./main_proc.sh main_proc pid is 1186 , MAIN_VAR is John sub_proc pid is 1187 , SUB_VAR is 'John' and ppid is 1186
-
范例:显示系统信息脚本
vim system_info.sh
#!/bin/bash GREEN="\033[1;32m" BLUE="\033[1;33m" RED="\033[1;31m" END="\033[0m" echo -e "$GREEN-----------------Host systeminfo---------------$END" echo -e "${BLUE}HOSTNAME: $RED`hostname`$END" echo -e "${BLUE}IPADDR: $RED` ifconfig enp0s8|grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' |head -n1`$END" echo -e "${BLUE}OSVERSION: $RED`cat /etc/redhat-release`$END" echo -e "${BLUE}KERNEL: $RED`uname -r`$END" echo -e "${BLUE}CPU: $RED`lscpu|grep 'Model name'|tr -s ' '|cut -d ' ' -f 3-8`$END" echo -e "${BLUE}MEMORY: $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END" echo -e "${BLUE}DISK: $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END" echo -e "$GREEN-----------------------------------------------$END"
-
范例:计算文本中所有人员的年龄总和
vim file.txt
num1=20 num2=18 num3=33
vim add_age.sh
#!/bin/bash VAR=`cut -d "=" -f 2 file.txt |tr '\n' '+'|grep -Eo ".*[0-9]+"` RES=$[VAR] echo $RES #练习的脚本 #a=`cat file.txt |cut -d "=" -f 2 |head -1` #b=`cat file.txt |cut -d "=" -f 2 |head -2|tail -1` #c=`cat file.txt |cut -d "=" -f 2 |tail -1` #echo $a $b $c #res=$[a+b+c] #echo $res;
2.6 只读变量
只能声明定义,但后续不能修改和删除,当前进程有效(命令行定义退出终端失效、脚本中定义的脚本执行结束后失效)
-
声明只读变量
#方式一 readonly NAME #方式二 declare -r NAME
-
查看只读变量
#方式一 readonly [-p] #方式二 declare -r
2.7 位置变量
在bash shell中内置的变量,执行脚本时传入的参数sh test.sh arg1 arg2
,可以理解为调用有入参的方法
-
参数含义
$1, $2, ... 对应第1个、第2个等参数,shift [n]换位置 $0 当前执行脚本的名称,包括路径 $* 传递给脚本的所有参数,全部参数合为一个字符串 $@ 传递给脚本的所有参数,每个参数为独立字符串 $# 传递给脚本的参数的个数 注意:$@ $* 只在被双引号包起来的时候才会有差异
-
清空所有位置变量
set --
-
范例:各个参数演示案例
./position.sh a b c
#!/bin/bash echo "\$1=$1,\$2=$2,\$3='$3'" #引用变量时$11表示第一个入参拼接字符1,${11}表示第11个入参 echo "\$11='$11',\$11='${11}'" #统计入参的个数$# echo "参数个数$#"
-
范例:移动文件
vim mv.sh
sh mv.sh a.txt b.txt
#!/bin/bash mv $* /tmp/
-
范例:
"$*"
作为一个字符串、$*``$@
作为单个字符串#!/bin/bash # vim num.sh echo "arg num is $#" # vim test.sh # "$*"作为整体,入参个数为1 ./num.sh "$*" # $*作为个体,入参个数为3 ./num.sh $* # $@作为个体,入参个数为3 ./num.sh "$@" #脚本执行命令 sh test.sh a b c
2.8 退出状态码变量
状态码:保存在$?
,上一条命令的执行结果,
-
0 代表成功
-
1到255 代表失败
-
范例:
[root@centos ~]# curl www.baidu.com &> /dev/null [root@centos ~]# echo $?
也可以指定脚本中的退出状态码
#指定退出状态码为37,使用echo $?的输出结果为37 exit 37
2.9 脚本安全和set
-
变量$-:当前shell的默认配置,通过查看
[root@centos ~]# echo $- himBH h:hashall,打开选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭 i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的 shell。所谓的交互式shell,在脚本中,i选项是关闭的 m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等 B:braceexpand,大括号扩展 H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的一个历史命令,“!n”返回第 n 个历史命令
-
set命令:脚本相关配置
该配置只在当前进程有效,命令行是当前终端,子进程无效
#查看配置 set -o [root@centos ~]# set -o allexport off braceexpand on emacs on errexit off errtrace off
配置在脚本中只对当前脚本有效,脚本执行结束后失效
#!/bin/bash #脚本中遇到一条错误,立即结束脚本,后续命令不再执行 set -e #禁止使用未定义的变量,不设置时可以使用未定义遍历,未定义变量的值为空(如 $UNVAR/* 被视为/*) set -o # -o option 显示,打开或者关闭选项(打开选项:set -o 选项;关闭选项:set +o 选项) # -x 当执行命令时,打印命令及其参数,类似 bash -x
2.10 格式化输出
-
格式:
printf "格式" "文本1" "文本2"
-
常见格式
%s 字符串 %f 浮点格式 %b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会转义 %c ASCII字符,即显示对应参数的第一个字符 %d %i 十进制整数 %o 八进制值 %u 不带正负号的十进制值 %x 十六进制值(a-f) %X 十六进制值(A-F) %% 表示%本身
-
常见转移字符
\a 警告字符,通常为ASCII的BEL字符 \b 后退 \f 换页 \n 换行 \r 回车 \t 水平制表符 \v 垂直制表符 \ 表示\本身
-
范例
[root@centos ~]# printf "%s\n" 1 2 3 4 1 2 3 4 [root@centos ~]# printf "%f\n" 1 2 3 4 1.000000 2.000000 3.000000 4.000000 #.2f 表示保留两位小数 [root@centos ~]# printf "%.2f\n" 1 2 3 4 1.00 2.00 3.00 4.00 [root@centos ~]# printf "(%s)" 1 2 3 4;echo "" (1)(2)(3)(4) [root@centos ~]# printf " (%s) " 1 2 3 4;echo "" (1) (2) (3) (4) [root@centos ~]# printf "%s %s\n" 1 2 3 4 1 2 3 4 [root@centos ~]# printf "%s %s %s\n" 1 2 3 4 1 2 3 4 #%-10s 表示宽度10个字符,左对齐 [root@centos ~]# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50 #将十进制的17转换成16进制数 [root@centos ~]# printf "%X" 17 11[root@centos ~]# #将十六进制C转换成十进制 [root@centos ~]# printf "%d\n" 0xC 12 #加颜色输出 [root@centos ~]# VAR="welcome to world";printf "\033[31m%s\033[0m\n" $VAR welcome to world [root@centos ~]# VAR="welcome to world";printf "\033[31m%s\033[0m\n" "$VAR" welcome to world
3. 运算符与条件判断
3.1 算术运算符
-
bash中算术运算需要特定的格式,否则就当成了字符串进行运算
#常用的3种格式 var=$[算术表达式] let var=算术表达式 var=$((算术表达式)) #不常用,计算乘法需要转义 var=$(expr arg1 arg2 arg3 ...) [root@centos ~]# var=$[2 * 3];echo $var 6 [root@centos ~]# var=$[2*3];echo $var 6 [root@centos ~]# var=$(expr 2 \* 3);echo $var 6
-
bash中的算术运算只支持整数运算
[root@centos ~]# echo $[5/2] 2
-
内置随机变量
$RANDOM
取值范围:0-32767#$RANDOM%n 生成0到n-1的随机数是### #生成0-4的随机数 [root@centos ~]# echo $[$RANDOM%5] #生成7到22的随机数 [root@centos ~]# echo $[$RANDOM%16+7]
3.2 逻辑运算符
-
真假:
true``false
1``0
-
与:&,意思
true&false
为false(假) -
或:|,或者
1&0
为1(真) -
非:!,不是
!false
为真 -
异或:,将值用二进制表示,逐位对比,不同为真(01为真),相同为假(1^1为假)
[root@centos ~]# echo $[12^8] 4 # 1100 12的二进制 # 1000 8的二进制 # ------------- # 0100 4的二进制
-
短路与、短路或:根据第一个条件的结果与或判断是否要执行第二个条件,判断是否短路
3.3 条件判断
判断条件表达式的返回结果是真是假,需要特定的格式
-
常见格式
#常用格式,[ E ]与表达式之间必须有空格 [ EXPRESSION ] #格式,效果等同于[ ] test EXPRESSION #不常用,当表达式需要使用通配符或扩展正则时才使用 [[ "$VAR" == 通配符 ]] [[ "$VAR" =~ 扩展正则表达式 ]] [[ EXPRESSION ]]
-
变量表达式判断
[ -v VARNAME ]
:判断变量名为VARNAME的变量是否声明(即set命令查看是否有该变量)#未声明,结果为假 [root@centos ~]# [ -v VARNAME ] [root@centos ~]# echo $? 1 #已声明,结果为真 [root@centos ~]# VARNAME= [root@centos ~]# [ -v VARNAME ] [root@centos ~]# echo $? 0
-
数值表达式判断
格式
[ $arg1 OP $arg2 ]
,OP是操作符([ $arg1 -ne $arg2 ]
),常见如下-eq 等于 equal -ne 不等于 not-equal -gt 大于 greater-than -ge 大于等于 greater-than-or-equal -lt 小于 less-than -le 小于等于 less-than-or-equal
注意:[ ]前后有空格,切arg前面的$必须有
[root@centos ~]# a=10;b=35 [root@centos ~]# [ $a -gt $b ];echo $? 1 [root@centos ~]# [ $a -lt $b ];echo $? 0 #变量名前面必须有$,否则报错 [root@centos ~]# [ a -ne b ] -bash: [: a: integer expression expected
-
字符串表达式判断
格式
[ OP $STR ]
或[ $STR1 OP $STR2 ]
OP是操作符(如[ -n $STR ]
、[ $STR1 = $STR2 ]
),常见如下-z "STRING"
:是否为空 (未声明、声明未赋值、声明赋空值、声明赋空格)均为空-n "STRING"
:是否不空[root@centos ~]# [ -z $STR1 ];echo $? 0 [root@centos ~]# STR1= [root@centos ~]# [ -z $STR1 ];echo $? 0 [root@centos ~]# STR1='' [root@centos ~]# [ -z $STR1 ];echo $? 0 [root@centos ~]# STR1=' ' [root@centos ~]# [ -z $STR1 ];echo $? 0 [root@centos ~]# STR1='abc' [root@centos ~]# [ -z $STR1 ];echo $? 1
=
、!=
、>
、<
: 注意,比较的是ascii码,切操作符两边要有空格(没空格是赋值的格式)[root@centos ~]# STR1="abc";STR2="ahc" [root@centos ~]# [ $STR1 = $STR2 ];echo $? 1 [root@centos ~]# STR1="abc";STR2="abc" [root@centos ~]# [ $STR1 = $STR2 ];echo $? 0 #不规范格式 [root@centos ~]# [ $STR1=$STR2 ] #变量最好使用双引号,不然识别不了变量中的空格 [root@centos ~]# STR="THIS IS STRING" [root@centos ~]# [ "$STR" ] [root@centos ~]# [ $STR ] -bash: [: IS: binary operator expected # 就相当于 [root@centos ~]# [ "THIS IS STRING" ] [root@centos ~]# [ THIS IS STRING ] -bash: [: IS: binary operator expected
-
通配符与扩展正则表达式判断
[[ EXPR ]]
[[ "$VAR" == 通配符表达式 ]]
、[[ "$VAR" != 通配符表达式 ]]
[root@centos ~]# FILE=test.log [root@centos ~]# [[ "$FILE" == *.log ]];echo $? 0 [root@centos ~]# [[ "$FILE" != *.log ]];echo $? 1
[[ "$VAR" =~ 扩展正则表达式 ]]
[root@centos ~]# FILE=test.log [root@centos ~]# [[ "$FILE" =~ \.log$ ]];echo $? 0 [root@centos ~]# N=100 [root@centos ~]# [[ "$N" =~ ^[0-9]+$ ]];echo $? 0 [root@centos ~]# N=AA100 [root@centos ~]# [[ "$N" =~ ^[0-9]+$ ]];echo $? 1 #IP地址正则表达式:^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ [root@centos ~]# IP=192.68.7.2 [root@centos ~]# [[ "$IP" =~ IP正则表达式 ]];echo $? 0 [root@centos ~]# IP=192.68.7.255 [root@centos ~]# [[ "$IP" =~ IP正则表达式 ]];echo $? 0 [root@centos ~]# IP=192.68.7.256 [root@centos ~]# [[ "$IP" =~ IP正则表达式 ]];echo $? 1
-
文件判断
help test
:查看所有判断参数含义# 文件存在判断 -a FILE True if file exists. -e FILE True if file exists. -s FILE True if file exists and is not empty. #文件类型判断 -f FILE True if file exists and is a regular file. -d FILE True if file is a directory. -b FILE True if file is block special. -c FILE True if file is character special. -h FILE True if file is a symbolic link. -L FILE True if file is a symbolic link. -p FILE True if file is a named pipe. -S FILE True if file is a socket. -t FD True if FD is opened on a terminal. # 文件权限判断 -r FILE True if file is readable by you. -w FILE True if the file is writable by you. -x FILE True if the file is executable by you. -u FILE True if the file is set-user-id. -g FILE True if file is set-group-id. -k FILE True if file has its `sticky' bit set. -O FILE True if the file is effectively owned by you. -G FILE True if the file is effectively owned by your group. -N FILE True if the file has been modified since it was last read. # 字符串判断、变量声明判断 -z "$STR" 判断是否为空 -n "$STR" 非空判断 -v VARNAME 判断变量是否声明(不加$)
-
组合使用参数
[ EXPR1 -a EXPR2 ]
:-a
并且 (and),EXPR1 EXPR2均成立为真,如[ -n "$pid" -a -z "$force" ]
[ EXPR1 -o EXPR2 ]
:-o
或者 (or),EXPR1 EXPR2有一个真即为真,如[ -n "$pid" -o -z "$force" ]
[ ! EXPR ]
:!
非,如[ ! -x /bin/cgexec ]
COMMAND1 && COMMAND2
:&&
短路与,并且,如[ -z "$gotbase" ] && base=${1##*/}
COMMAND1 || COMMAND2
:||
短路或,或者! COMMAND
:!
非,取反 -
范例:ping主机连通性测试
[root@centos ~]#cat ping.sh #!/bin/bash IP=172.32.115.1 ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || { echo "$IP is unreachable"; exit; } echo "Script is finished" [root@centos ~]#sh ping.sh 172.32.115.1 is up Script is finished
-
范例:磁盘空间利用率监控
[root@centos8 ~]#cat disk_check.sh #!/bin/bash WARNING=80 SPACE_USED=`df|grep '^/dev/sd'|tr -s ' ' %|cut -d% -f5|sort -nr|head -1` [ "$SPACE_USED" -ge $WARNING ] && echo "disk used is $SPACE_USED,will be full" | mail -s diskwaring root
-
范例:摘自系统脚本
cat /etc/init.d/functions
,查看各个参数的使用# 片段一 if [ $PPID -ne 1 -a -z "$SYSTEMCTL_SKIP_REDIRECT" ] && \ [ -d /run/systemd/system ] ; then case "$0" in /etc/init.d/*|/etc/rc.d/init.d/*) _use_systemctl=1 ;; esac fi # 片段二 # Save basename. [ -z "$gotbase" ] && base=${1##*/} # See if it's already running. Look *only* at the pid file. __pids_var_run "$base" "$pid_file" [ -n "$pid" -a -z "$force" ] && return # make sure it doesn't core dump anywhere unless requested corelimit="ulimit -S -c ${DAEMON_COREFILE_LIMIT:-0}" # if they set NICELEVEL in /etc/sysconfig/foo, honor it [ -n "${NICELEVEL:-}" ] && nice="nice -n $NICELEVEL" # if they set CGROUP_DAEMON in /etc/sysconfig/foo, honor it if [ -n "${CGROUP_DAEMON}" ]; then if [ ! -x /bin/cgexec ]; then echo -n "Cgroups not installed"; warning echo else cgroup="/bin/cgexec"; for i in $CGROUP_DAEMON; do cgroup="$cgroup -g $i"; done fi fi
3.4 小括号和大括号
-
查看帮助:
man bash
搜索\(list\)
、\{list;\}
(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list. { list; } list is simply executed in the current shell environment. list must be terminated with a new‐ line or semicolon. This is known as a group command. The return status is the exit status of list. Note that unlike the metacharacters ( and ), { and } are reserved words and must occur where a reserved word is permitted to be recognized. Since they do not cause a word break, they must be separated from list by whitespace or another shell metacharacter.
-
小括号:
(CMD;CMD;CDM)
会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境[root@centos init.d]# echo $BASHPID 944 [root@centos init.d]# (echo $BASHPID) 1125 [root@centos init.d]# NAME=John;(echo $NAME;NAME=Sara;echo $NAME);echo $NAME John Sara John # umask临时生效,可以使用括号 [root@centos init.d]# umask;(umask 555;umask);umask 0022 0555 0022
-
大括号:
{ CMD;CMD;CMD; }
不会启子shell, 在当前shell中运行,会影响当前shell环境[root@centos init.d]# echo $BASHPID 944 [root@centos init.d]# { echo $BASHPID; } 944 [root@centos init.d]# NAME=John;{ echo $NAME;NAME=Sara;echo $NAME; };echo $NAME John Sara Sara
3.5 键盘输入read
-
格式:
read [options] [name ...]
,常见options-p "hell,world" 指定提示语言 -s 静默输入,一般用于密码 -n num 指定输入的字符长度N -d ' ' 指定任意结束字符,遇到该字符即停止输入 -t num TIMEOUT为N秒
-
范例:
#不指定变量接收read输入,默认保存在REPLY变量中 [root@centos ~]# read -p "what's your name:" what's your name:John [root@centos ~]# echo $REPLY John #可以一次指定多个变量接收多个read输入,但是不建议使用多个 [root@centos ~]# read -p "what's your name:" NAME1 NAME2 what's your name:Sara Jane [root@centos ~]# echo $NAME1 $NAME2 Sara Jane
-
范例:功能菜单
vim readtest.sh
#!/bin/bash cat <<EOF ###################################################### # 各函数功能说明如下: # # 1.check_network: 检查发布主机到其他主机的网络 # # 2.init_selinux: 关闭selinux # # 3.init_firewalld: 关闭防火墙 # # 4.init_yum: 配置yum # # 5.init_hosts: 配置主机名与IP映射关系 # ###################################################### EOF read -p "please choose num:" MENU [ "$MENU" -eq 1 ] && echo "num is $MENU:check_network" [ "$MENU" -eq 2 ] && echo "num is $MENU:init_selinux" [ "$MENU" -eq 3 ] && echo "num is $MENU:init_firewalld" [ "$MENU" -eq 4 ] && echo "num is $MENU:init_yum" [ "$MENU" -eq 5 ] && echo "num is $MENU:init_hosts"
-
范例:面试题考点,管道中的命令是运行在子进程中
[root@centos ~]# echo "John" "Sara" | read NAME1 NAME2;echo "$NAME1 is a boy,$NAME2 is a girl." is a boy, is a girl. [root@centos ~]# echo "John" "Sara" | (read NAME1 NAME2;echo "$NAME1 is a boy,$NAME2 is a girl.") John is a boy,Sara is a girl.
3.6 bash的配置文件
-
按作用范围分类
全局配置:对所有登录用户生效
/etc/profile
、/etc/profile.d/*.sh
、/etc/bashrc
个人配置:对当前登录用户生效
~/.bash_profile
、~/.bashrc
-
按登录方式分类
交互式登录,即直接登录,配置文件执行顺序
#当存在冲突的配置时,理论上靠后的文件会覆盖前面的 /etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc -->/etc/bashrc
非交互式登录,包括
su命令
、执行脚本
等任何可以开启子进程shell的命令,配置文件执行顺序#当存在冲突的配置时,理论上靠后的文件会覆盖前面的 /etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc
-
按功能分来
profile类:定义环境变量、运行命令或脚本
全局:/etc/profile, /etc/profile.d/*.sh 个人:~/.bash_profile
bashrc类:定义本地变量、函数、别名
全局:/etc/bashrc 个人:~/.bashrc
-
配置生效命令
#两种命令都可以,原理是source和.执行时都不会创建子进程,直接在当前shell中执行,所以会根据配置文件中的配置去更新shell环境,从而配置生效 source /etc/bashrc . /etc/profile #也可以退出重新登录shell,因为登录时会加载配置文件,从而配置生效
-
退出时配置文件
#用户退出时,会执行该配置文件中的命令,默认为空,可以自定义一下操作 ~/.bash_logout
4. 流程控制
4.1 条件判断if
-
格式:包括单分支、双分支、多分支
if COMMANDS; then COMMANDS #elif COMMANDS; then # COMMANDS #... else COMMANDS fi
-
范例:摘自系统脚本
if [ -x /usr/bin/id ]; then if [ -z "$EUID" ]; then # ksh workaround EUID=`/usr/bin/id -u` UID=`/usr/bin/id -ru` fi USER="`/usr/bin/id -un`" LOGNAME=$USER MAIL="/var/spool/mail/$USER" fi # Path manipulation if [ "$EUID" = "0" ]; then pathmunge /usr/sbin pathmunge /usr/local/sbin else pathmunge /usr/local/sbin after pathmunge /usr/sbin after fi
-
范例:
vim if_bmi.sh
#!/bin/bash read -p "请输入身高(m为单位): " HIGH if [[ ! "$HIGH" =~ ^[0-2].?[0-9]{,2}$ ]];then echo "输入错误的身高" exit 1 fi read -p "请输入体重(kg为单位): " WEIGHT if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]];then echo "输入错误的体重" exit 1 fi BMI=`echo $WEIGHT/$HIGH^2|bc` if [ $BMI -le 18 ] ;then echo "你太瘦了,多吃点" elif [ $BMI -lt 24 ] ;then echo "身材很棒!" else echo "你太胖了,注意节食,加强运动" fi
4.2 条件判断case
-
格式
# PATTERN通配符,$VARNAME支持拼接其他字符串 如 ":${PATH}:" case "$VARNAME" in PATTERN) COMMANDS ;; PATTERN) COMMANDS ;; *) COMMANDS ;; esac
-
支持统配符匹配PATTERN
* 任意长度任意字符 ? 任意单个字符 [] 指定范围内的任意单个字符 | 或,如 a或b
-
范例
#摘自系统脚本profile pathmunge () { case ":${PATH}:" in *:"$1":*) ;; *) if [ "$2" = "after" ] ; then PATH=$PATH:$1 else PATH=$1:$PATH fi esac } #摘自系统脚本function # Evaluate shvar-style booleans is_true() { case "$1" in [tT] | [yY] | [yY][eE][sS] | [tT][rR][uU][eE] | 1) return 0 ;; esac return 1 } # A function to start a program. daemon() { # Test syntax. local gotbase= force= nicelevel corelimit local pid base= user= nice= bg= pid_file= local cgroup= nicelevel=0 while [ "$1" != "${1##[-+]}" ]; do case $1 in '') echo $"$0: Usage: daemon [+/-nicelevel] {program}" "[arg1]..." return 1 ;; --check) base=$2 gotbase="yes" shift 2 ;; --pidfile=?*) pid_file=${1#--pidfile=} shift ;; [-+][0-9]*) nice="nice -n $1" shift ;; *) echo $"$0: Usage: daemon [+/-nicelevel] {program}" "[arg1]..." return 1 ;; esac done }
-
范例:功能菜单
vim readtest.sh
#!/bin/bash cat <<EOF ###################################################### # 各函数功能说明如下: # # 1.check_network: 检查发布主机到其他主机的网络 # # 2.init_selinux: 关闭selinux # # 3.init_firewalld: 关闭防火墙 # # 4.init_yum: 配置yum # # 5.init_hosts: 配置主机名与IP映射关系 # ###################################################### EOF read -p "please choose num:" MENU case "$MENU" in 1) echo "num is $MENU:check_network" ;; 2) echo "num is $MENU:init_selinux" ;; 3) echo "num is $MENU:init_firewalld" ;; 4) echo "num is $MENU:init_yum" ;; 5) echo "num is $MENU:init_hosts" ;; *) echo "input is error" ;; esac