Shell脚本入门到入土,两个数字X,Y异或得


一、Shell脚本的基本用法

1 shell脚本自动生成注释

[root@rocky01 ~]# cat .vimrc
set ts=4
set et

autocmd BufNewFile *.sh,*.script exec ":call SetTitle()"
function SetTitle()
        if expand("%:e") == "sh"
        call setline(1,"#!/bin/bash")
        call setline(2,"  ")
        call setline(3,"#******************************************************")
        call setline(4,"# Author:          会不会有那么一天                 ")
        call setline(5,"# QQ:              791270697                        ")
        call setline(6,"# Date:            ".strftime("%Y-%m-%d                  ")."     ")
        call setline(7,"# FileName:        ".expand("%")."                         ")
        call setline(8,"# Version:         1.0.0                            ")
        call setline(9,"# Description:     The test script                  ")
        call setline(10,"# BLOG:            https://www.cnblogs.com/Willoneday   ")
        call setline(11,"#******************************************************")
        call setline(12,"")
        call setline(13,"")
        endif
endfunc
autocmd BufNewFile * normal G

 


2 执行远程主机的shell脚本

[root@rocky01 ~]# mv hello.sh /data/httpd-2.4.54/htdocs/
[root@rocky01 ~]# hostname -I
10.0.0.128
[root@rocky01 ~]# curl 10.0.0.128/hello.sh
#!/bin/bash
echo 'Hello, world!'

#方法一
[root@rocky01 ~]# curl 10.0.0.128/hello.sh 2> /dev/null |bash
Hello, world!

#方法二
[root@rocky01 ~]# curl -s 10.0.0.128/hello.sh  |bash
Hello, world!

 


3 在远程主机运行本地shell脚本

[root@ubuntu01 ~]# hostname -I
10.0.0.129
[root@ubuntu01 ~]# cat work2.sh
echo "我在`hostname -I`上运行"
[root@ubuntu01 ~]#
[root@ubuntu01 ~]# ssh 10.0.0.128 /bin/bash  < work2.sh
root@10.0.0.128's password:
我在10.0.0.128 上运行

 


4 shell脚本调试

bash -n ScriptName.sh 只检测脚本中的语法错误,但无法检查出命令错误,但不真正执行脚本

bash -x ScriptName.sh 调试并执行

总结:脚本错误常见的有三种

  • 语法错误:会导致后续的命令不继续执行,可以用bash -n 检查错误,提示的出错行数不一定是准确的
  • 命令错误:默认后续的命令还会继续执行,用bash -n 无法检查出来 ,可以使用 bash -x 进行观察
  • 逻辑错误:只能使用 bash -x 进行观察

 


5 变量

5.1 变量类型

  • 内置变量,如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE
  • 用户自定义变量

 

5.2 变量命名法则

  • 区分大小写
  • 不能使程序中的保留字和内置变量:如:if, for
  • 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”,和主机名相反

 

5.3 变量定义和引用

变量的生效范围等标准划分变量类型

  • 普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
  • 环境变量:生效范围为当前shell进程及其子进程
  • 本地变量:生效范围为当前shell进程中某代码片断,通常指函数

5.3.1 变量赋值

  • 直接字串:name='root'
  • 变量引用:name="$USER"
  • 命令引用:name=`COMMAND` 或者 name=$(COMMAND)

5.3.2 变量引用

  • $name
  • ${name}

弱引用和强引用

  • "$name" 弱引用,其中的变量引用会被替换为变量值
  • '$name'  强引用,其中的变量引用不会被替换为变量值,而保持原字符串
范例:变量的引用
[root@rocky01 ~]# NUM=`seq 3`
[root@rocky01 ~]# echo $NUM
1 2 3
[root@rocky01 ~]# echo "$NUM"
1
2
3
[root@rocky01 ~]# echo '$NUM'
$NUM
范例:变量追加
[root@rocky01 ~]# TITLE=Willoneday
[root@rocky01 ~]# TITLE+=:Yes
[root@rocky01 ~]# echo $TITLE
Willoneday:Yes
范例:删除变量

unset <name>

范例:显示系统信息
[root@rocky01 ~]# cat system_info.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-07
# FileName:        system_info.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"
. /etc/os-release

$GREEN----------------------Host systeminfo--------------------$END
echo -e  "HOSTNAME:     $RED`hostname`$END"
echo -e  "IPADDR:       $RED` hostname -I`$END"
echo -e  "OSVERSION:    $RED$PRETTY_NAME$END"
echo -e  "KERNEL:       $RED`uname -r`$END"
echo -e  "CPU:         $RED`lscpu|grep '^Model name'|tr -s ' '|cut -d : -f2` $END"
echo -e  "MEMORY:       $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e  "DISK:         $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
$GREEN---------------------------------------------------------$END

 

5.4 环境变量

  •  可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
  • 一般只在系统配置文件中使用,在脚本中较少使用

显示所有环境变量:

  • env
  • printenv
  • export
  • declare -x

bash内的环境变量

  • PATH
  • SHELL
  • USER
  • UID
  • HOME
  • PWD
  • SHLVL   #shell的嵌套层数,即深度
  • LANG
  • MAIL
  • HOSTNAME
  • HISTSIZE
  • _            #下划线,表示前一命令的最后一个参数

 

5.5 位置变量

在bash shell中内置的变量, 在脚本代码中调用通过命令行传递给脚本的参数

  • $1, $2, ... 对应第1个、第2个等参数,shift [n]换位置
  • $0 shell脚本本身的名字
  • $* 传递给脚本的所有参数,全部参数合为一个字符串
  • $@ 传递给脚本的所有参数,每个参数为独立字符串
  • $# 传递给脚本的参数的个数

注意:$@ $* 只在被双引号包起来的时候才会有差异

 

5.6 退出状态码变量

$?的值为0        #代表成功
$?的值是1到255   #代表失败

用户可以在脚本中使用以下命令自定义退出状态码 exit n

注意:

  • 脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
  • 如果exit后面无数字,终止退出状态取决于exit命令前面命令执行结果
  • 如果没有exit命令, 即未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

 

5.7 随机数生成器变量

$RANDOM  取值范围:0-32767

范例:生成 0 - 49 之间随机数

[root@rocky01 ~]# echo $[$RANDOM%50]

范例:随机字体颜色

[root@rocky01 ~]# echo -e "\033[1;$[RANDOM%7+31]mWilloneday\033[0m"

 


6 算数运算

注意:bash 只支持整数,不支持小数

实现算术运算:

  • (1) let var=算术表达式
  • (2) ((var=算术表达式)) 和(1)等价
  • (3) var=$[算术表达式]
  • (4) var=$((算术表达式))
  • (5) var=$(expr arg1 arg2 arg3 ...)
  • (6) declare -i var = 数值
  • (7) echo '算术表达式' | bc

范例:自增自减

let i+=1
let i++
let i-=1
let i--

[root@rocky01 ~]# i=1; let j=i++; echo "i=$i,j=$j"
i=2,j=1
[root@rocky01 ~]# i=1; let j=++i; echo "i=$i,j=$j"
i=2,j=2

 


7 逻辑运算

7.1 与或非

  • 与:&  一假则假,全真才真
  • 或:   一真则真,全假才假
  • 非:
  • 异或:^ 异或的两个值,相同为假,不同为真。两个数字X,Y异或得到结果Z,Z再和任意两者之一X异或,将得出另一个值Y

范例:变量互换

[root@rocky01 ~]# x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo x=$x,y=$y
x=20,y=10

 

7.2 短路运算

  • 短路与 &&

CMD1 && CMD2

第一个CMD1结果为真(1),第二个CMD2必须要参与运算,才能得到最终的结果

第一个CMD1结果为假(0),总的结果必定为0,因此不需要执行CMD2

  • 短路或 ||

CMD1 || CMD2

第一个CMD1结果为真(1),总的结果必定为1,因此不需要执行CMD2

第一个CMD1结果为假(0),第二个CMD2 必须要参与运算,才能得到最终的结果

  • 短路与和或组合

CMD1 && CMD2 || CMD3

当CMD1执行成功时,会执行CMD2

当CMD1执行失败时,会执行CMD3

 

 


8 条件测试命令

条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便用在条件性环境下进行执行

  • 若真,则状态码变量 $? 返回0
  • 若假,则状态码变量 $? 返回

条件测试命令

  • test EXPRESSION
  • [ EXPRESSION ] #和test 等价,建议使用 [ ]
  • [[ EXPRESSION ]] 相关于增强版的 [ ], 支持[]的用法,且支持扩展正则表达式和通配符

          注意:EXPRESSION前后必须有空白字符

8.1 变量测试

#判断 NAME 变量是否定义
[ -v NAME ]

 

8.2 数值测试

  • -eq 等于
  • -ne 不等于
  • -gt 大于
  • -ge 大于等于
  • -lt 小于
  • -le 小于等于

 

8.3 字符串测试

[ ] 

  • [ -z STRING ] 字符串没定义或空为真,不空为假,
  • [ -n STRING ] 字符串不空为真,空为假
  • [ STRING ]     字符串不空为真,空为假
  • [ STRING1 = STRING2 ] 是否等于,注意 = 前后有空格
  • [ STRING1 != STRING2 ] 是否不等于

注意:在比较字符串时,建议变量放在“ ”中

[[  ]]

[[ == ]] 左侧字符串是否和右侧的PATTERN相同

注意:此表达式用于 == 右侧为通配符

 

[[ =~ ]] 左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配

注意: 此表达式用于 =~ 右侧为扩展的正则表达式

 

注意:[[ == ]] == 右侧的 * 做为通配符,不要加“”,只想做为*符号使用时, 需要加 “” 或转义

建议:当使用正则表达式或通配符使用[[  ]],其它情况一般使用 [  ]

 

范例:使用 [ ]
[root@rocky01 ~]# echo $NAME

[root@rocky01 ~]# [ -n "$NAME" ]
[root@rocky01 ~]# echo $?
1

[root@rocky01 ~]# NAME=Willoneday
[root@rocky01 ~]# [ -n "$NAME" ]
[root@rocky01 ~]# echo $?
0
[root@rocky01 ~]# [ "$NAME" ]
[root@rocky01 ~]# echo $?
0
范例:使用 [[  ]] 和 通配符
#通配符*
[root@rocky01 ~]# FILE="Will*"
[root@rocky01 ~]# [[ $FILE == W* ]]
[root@rocky01 ~]# echo $?
0

#通配符?
[root@rocky01 ~]# FILE="Will"
[root@rocky01 ~]# [[ $FILE == ???? ]]
[root@rocky01 ~]# echo $?
0
[root@rocky01 ~]# [[ $FILE == ??? ]]
[root@rocky01 ~]# echo $?
1

#引号
#结论:[[ == ]] == 右侧的 * 做为通配符,不要加 " " ,只想做为*符号使用时, 需要加 " " 或转义
[root@rocky01 ~]# NAME="linux1"
[root@rocky01 ~]# [[ "$NAME" == linux* ]]
[root@rocky01 ~]# echo $?
0
[root@rocky01 ~]# [[ "$NAME" == "linux*" ]]
[root@rocky01 ~]# echo $?
1
[root@rocky01 ~]# NAME="linux*"
[root@rocky01 ~]# [[ "$NAME" == "linux*" ]]
[root@rocky01 ~]# echo $?
0
范例:判断合法的考试成绩(满分为100)
[root@rocky01 ~]# [[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
范例:判断合法的正负数
[root@rocky01 ~]# [[ "$N" =~ ^[0-9]+$ ]]
范例:判断合法IP
[root@rocky01 ~]# [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$  ]]

 

8.4 文件测试

8.4.1 文件存在性测试

  • -a FILE:同 -e,建议使用-e
  • -e FILE: 文件存在性测试,存在为真,否则为假
  • -b FILE:是否存在且为块设备文件
  • -c FILE:是否存在且为字符设备文件
  • -d FILE:是否存在且为目录文件
  • -f FILE:是否存在且为普通文件
  • -h FILE:或 -L FILE:存在且为符号链接文件
  • -p FILE:是否存在且为命名管道文件
  • -S FILE:是否存在且为套接字文件

8.4.2 文件权限测试

  • -r FILE:是否存在且可读
  • -w FILE: 是否存在且可写
  • -x FILE: 是否存在且可执行
  • -u FILE:是否存在且拥有suid权限
  • -g FILE:是否存在且拥有sgid权限
  • -k FILE:是否存在且拥有sticky权限

注意:最终结果由用户对文件的实际权限决定,而非文件属性决定

 


9 ( ) 和 { }

(CMD1;CMD2;...)和 { CMD1;CMD2;...; } 都可以将多个命令组合在一起,批量执行

( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境

{ list } 不会启子shell,在当前shell中运行,会影响当前shell环境

范例

[root@rocky01 ~]# name=Willoneday;( echo $name;name=Dream;echo $name );echo $name
Willoneday
Dream
Willoneday

[root@rocky01 ~]# name=Willoneday;{ echo $name;name=Dream;echo $name; };echo $name
Willoneday
Dream
Dream

 


10 组合测试条件

10.1 第一种方式

[ EXPRESSION1 -a EXPRESSION2 ]    #并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ]   #或者, EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[ ! EXPRESSION ]                                 #取反

说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持

 

10.2 第二种方式

 COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN

如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2

 

COMMAND1 || COMMAND2   #或者,短路或,代表条件性的OR ELSE

如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2

 

! COMMAND   #非,取反

范例:用户不存在则创建

[root@rocky01 ~]# getent passwd Willoneday
[root@rocky01 ~]# id Willoneday &> /dev/null || useradd Willoneday
[root@rocky01 ~]# getent passwd Willoneday
Willoneday:x:1001:1001::/home/Willoneday:/bin/bash

范例:网络状态判断

[root@rocky01 ~]# cat ip.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-07
# FileName:        ip.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

ip=`hostname -I`
ping $ip -c 1 &> /dev/null && echo  "$ip在线" || echo "$ip不在线"
ip2=1.1.1.1
ping $ip2 -c 1 &> /dev/null && echo  "$ip2在线" || echo "$ip2不在线"


[root@rocky01 ~]# bash ip.sh
10.0.0.128 在线
1.1.1.1不在线

范例:操作系统判断

[root@rocky01 ~]# . /etc/os-release; [[ $ID == "rocky" ]] && [[ $VERSION_ID == 8* ]] && echo Rocky8 || echo NO Rocky8
Rocky8

范例:磁盘空间判断

[root@rocky01 ~]# cat disk_check.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-07
# FileName:        disk_check.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

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

 


11 read命令接受输入

 使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY

常见选项

  • -p   指定要显示的提示
  • -s   静默输入,一般用于密码
  • -n N 指定输入的字符长度N
  • -d '字符'   输入结束符
  • -t N TIMEOUT为N秒

范例:基本用法

[root@rocky01 ~]# read
Willoneday
[root@rocky01 ~]# echo $REPLY
Willoneday

[root@rocky01 ~]# read NAME AGE
Willoneday 20
[root@rocky01 ~]# echo $NAME
Willoneday
[root@rocky01 ~]# echo $AGE
20

范例:read和 重定向

[root@rocky01 ~]# cat test.txt
1 2
[root@rocky01 ~]# read i j < test.txt ; echo i=$i j=$j
i=1 j=2

[root@rocky01 ~]# echo 1 2 | read x y ; echo x=$x y=$y
x= y=
[root@rocky01 ~]# echo 1 2 | ( read x y ; echo x=$x y=$y )
x=1 y=2
[root@rocky01 ~]# echo 1 2 | { read x y ; echo x=$x y=$y; }
x=1 y=2

 范例:判断用户输入的是否为 YES

[root@rocky01 ~]# read -p "Are you rich?yes or no: " ANSWER
Are you rich?yes or no: yes
[root@rocky01 ~]# [[ $ANSWER =~ ^([Yy]|[Yy][Ee][Ss])$ ]] && echo "You are rich" || echo "No"
You are rich

 


二 、shell 的配置文件

1 按生效范围分类

1.1 全局配置

针对所有用户皆有效

  • /etc/profile
  • /etc/profile.d/*.sh
  • /etc/bashrc

1.2个人配置

只针对特定用户有效

~/.bashrc

~/.bash_profile

 

 


2 按登录方式分类

2.1 交互式登录

  • 直接通过终端输入账号密码登录
  • su - UserName

2.2 非交互式登录

  • su UserName
  • 图形界面下打开的终端
  • 执行脚本
  • 任何其它的bash实例

 


3 按功能划分分类

3.1 profile类

profile类为交互式登录的shell提供配置

全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile

功用:

(1) 用于定义环境变量

(2) 运行命令或脚本

3.2 bashrc类

为非交互式和交互式登录的shell提供配置

全局:/etc/bashrc
个人:~/.bashrc

功用:

(1) 定义命令别名和函数

(2) 定义本地变量

 


三、流程控制

1 if 选择执行语句

1.1 格式

if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi

1.2 单分支

if 判断条件;then
    条件为真的分支代码
fi

1.3 双分支

if 判断条件; then
    条件为真的分支代码
else
    条件为假的分支代码
fi

1.4 多分支

if 判断条件1; then
    条件1为真的分支代码
elif 判断条件2; then
    条件2为真的分支代码
elif 判断条件3; then
    条件3为真的分支代码
...
else
    以上条件都为假的分支代码
fi

范例:身体质量指数 (BMI)

[root@rocky01 ~]# cat bmi.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-08
# FileName:        bmi.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

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 2;
fi

BMI=`echo $WEIGHT/$HIGH^2|bc`

if [ $BMI -le 18 ];then
    echo "太瘦了,多吃点!"
elif [ $BMI -lt 24 ];then
    echo "身材很棒!"
else
    echo "太胖了,注意节食,加强运动!"
fi

 


2 case 条件判断语句

2.1 格式

 case: case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac

case 变量引用 in
PAT1)
    分支1
    ;;
PAT2)
    分支2
    ;;
...
*)
    默认分支
    ;;
esac

case支持glob风格的通配符:

  • *   任意长度任意字符
  • ?   任意单个字符
  • [ ] 指定范围内的任意单个字符
  • |   或者,如: a|b

范例:Yes or No

[root@rocky01 ~]# cat yes-no.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-08
# FileName:        yes-no.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

read -p "Do you agree(yes/no)? " INPUT

case $INPUT in
[yY]|[Yy][Ee][Ss])
    echo "You input is YES"
    ;;
[Nn]|[Nn][Oo])
    echo "You input is NO"
    ;;
*)
    echo "Input fales,please input yes or no!"
    ;;
esac

 


3 for 循环

3.1 格式1

for NAME [in WORDS ... ] ; do COMMANDS; done

for 变量名 in 列表
do
    循环体
done

执行机制

  • 依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
  • 如果省略 [in WORDS ... ] ,此时使用位置参数变量 in "$@

3.2 格式2

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done

for((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
    循环体
done

 

范例:100以内的奇数和

[root@rocky01 ~]# sum=0;for i in {1..100..2};do let sum+=i;done;echo sum=$sum
sum=2500

范例:使用位置参数变量

[root@rocky01 ~]# cat for-sum.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-08
# FileName:        for-sum.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

sum=0
for i in $@;
do
    let sum+=i
done
echo sum=$sum
[root@rocky01 ~]#
[root@rocky01 ~]# bash for-sum.sh 1 2 3 10
sum=16

范例:将目录YYYY-MM-DD/中所有文件,移动到YYYY-MM/DD/下

#1.先创建好环境
[root@rocky01 opt]# cat create_dir.sh
#!/bin/bash

PDIR=/data/test

for i in {1..365};
do
    #创建过去365天的目录
    DIR=`date -d "-$i day" +%F`
    mkdir -p $PDIR/$DIR

    #随机创建10个文件
    for j in {1..10};
    do
        touch $PDIR/$DIR/$RANDOM.log
    done
done
#2.开始移动
[root@rocky01 opt]# cat mv_file.sh
#!/bin/bash

PDIR=/data/test
cd $PDIR || {  echo 无法进入 $PDIR;exit 1; }

for DIR in *;
do
    YYYY_MM=`echo $DIR |cut -d "-" -f 1,2`
    DD=`echo $DIR |cut -d "-" -f 3`
    [ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
    mv $DIR/* $YYYY_MM/$DD
done
rm -rf $PDIR/*-*-*

范例:生成进度

[root@rocky01 ~]# for((i = 0; i <= 100; ++i)); do printf "\e[4D%3d%%" $i;sleep 0.01s; done
100%

 


4 while 循环

4.1 格式

while COMMANDS; do COMMANDS; done

while CONDITION;
do
    循环体
done

说明

  • 进入条件:CONDITION为 true
  • 退出条件:CONDITION为 false
while true;
do
    循环体
done
while [ true ];
do
    循环体
done

 


4.2 while read (while的特殊用法)

while 循环的特殊用法,遍历文件或文本的每一行

4.2.1 格式

while read line; do
    循环体
done < /PATH/FILE

#依次读取/PATH/FILE文件中的每一行,且将行赋值给变量line

 

范例:查看可登录用户的用户名和UID

[root@rocky01 ~]# cat while_read_passwd.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-08
# FileName:        while_read_passwd.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

while read line;
do
    if [[ ! "$line" =~ /sbin/nologin$ ]];then
    echo $line | cut -d: -f1,3
    fi
done < /etc/passwd

[root@rocky01 ~]# bash while_read_passwd.sh
root:0
sync:5
shutdown:6
halt:7
rye:1000
Willoneday:1001

 


5 until 循环 

5.1 格式

until COMMANDS; do COMMANDS; done

until CONDITION;
do
    循环体
done

说明:

  • 进入条件: CONDITION 为false
  • 退出条件: CONDITION 为tru

执行条件和while相反,所以一般用while

 


6 continue 循环控制语句

continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层

6.1 格式

while CONDITION1;
do
    CMD1
    ...
    if CONDITION2;then
        continue
    fi
    CMDn
    ...
done

范例:有无continue效果展示

#有continue的效果
[root@rocky01 ~]# cat continue_yes.sh
#!/bin/bash

for((j=1;j<=3;j++))
do
    for((i=1;i<=3;i++));
    do
        [ $i -eq 2 ] && continue 2
        echo j=$j,i=$i
    done
done

[root@rocky01 ~]# bash continue_yes.sh
j=1,i=1
j=2,i=1
j=3,i=1
#无continue的效果
[root@rocky01 ~]# cat continue_no.sh
#!/bin/bash

for((j=1;j<=3;j++))
do
    for((i=1;i<=3;i++));
    do
        echo j=$j,i=$i
    done
done

[root@rocky01 ~]# bash continue_no.sh
j=1,i=1
j=1,i=2
j=1,i=3
j=2,i=1
j=2,i=2
j=2,i=3
j=3,i=1
j=3,i=2
j=3,i=3

 


7 break 循环控制语句 

break [N]:提前结束第N层整个循环,最内层为第1层

7.1 格式

while CONDITION1;
do
    CMD1
    ...
    if CONDITION2;then
    break
    fi
    CMDn
    ...
done

范例:N层break效果展示

#break第一层
[root@rocky01 ~]# cat break_yes1.sh
#!/bin/bash

for((j=1;j<=3;j++))
do
    for((i=1;i<=3;i++));
    do
        [ $i -eq 2 ] && break 1
        echo j=$j,i=$i
    done
done

[root@rocky01 ~]# bash break_yes1.sh
j=1,i=1
j=2,i=1
j=3,i=1
#break第二层
[root@rocky01 ~]# cat break_yes2.sh
#!/bin/bash

for((j=1;j<=3;j++))
do
    for((i=1;i<=3;i++));
    do
        [ $i -eq 2 ] && break 2
        echo j=$j,i=$i
    done
done

[root@rocky01 ~]# bash break_yes2.sh
j=1,i=1

 


8 shift 循环控制命令

说明

shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。

参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

范例:检测用户是否存在

[root@rocky01 ~]# cat shift_user.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-08
# FileName:        shift_user.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

#检测是否有参数
if [ $# -eq 0 ];then
    echo "Usage: $0 user1 user2 ..."
    exit
fi

#检测用户是否存在
while [ "$1" ];do
    if id $1 &> /dev/null;then
        echo "$1 is exist"
    else
        echo "$1 is no exist"
    fi
    shift
done

[root@rocky01 ~]# bash shift_user.sh
Usage: shift_user.sh user1 user2 ...
[root@rocky01 ~]# bash shift_user.sh root Willoneday qqq
root is exist
Willoneday is exist
qqq is no exist

 


9 select 循环与菜单

9.1 格式

select NAME [in WORDS ... ;] do COMMANDS; done

select NAME in list;
do 
    循环体命令
done

说明:

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示 PS3 提示符,等待用户输入
  • 用户输入菜单列表中的某个数字,执行相应的命令
  • 用户输入菜单列表中的某个数字,会将对应的WORD值赋值给NAME变量
  • 用户输入被保存在内置变量 REPLY 中
  • select 是个无限循环,因此要用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c退出循环
  • select 经常和 case 联合使用
  • 与 for 循环类似,可以省略 in list,此时使用位置参量

范例:菜单

[root@rocky01 ~]# cat select.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-08
# FileName:        select.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

sum=0
PS3="请点菜(1-3): "
select MENU in 北京烤鸭 小龙虾 点菜结束;
do
    case $REPLY in
    1)
        echo $MENU 价格是 100
        let sum+=100
        ;;
    2)
        echo $MENU 价格是 88
        let sum+=88
        ;;
    3)
        echo "点菜结束,退出"
        break
        ;;
    *)
        echo "点菜错误,重新选择"
        ;;
    esac
done
echo "总价格是: $sum"

[root@rocky01 ~]# bash select.sh
1) 北京烤鸭
2) 小龙虾
3) 点菜结束
请点菜(1-3): 1
北京烤鸭 价格是 100
请点菜(1-3): 2
小龙虾 价格是 88
请点菜(1-3): 1
北京烤鸭 价格是 100
请点菜(1-3): 3
点菜结束,退出
总价格是: 288

 


四、function 函数 

1 函数介绍

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程

它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分

 

函数和shell程序区别

  • Shell程序在子Shell中运行
  • 函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改

 


2 管理函数

函数由两部分组成:函数名和函数体

2.1 定义函数

#语法一:
func_name () {
 ...函数体...
}

#语法二:
function func_name {
 ...函数体...
} 

#语法三:
function func_name () {
 ...函数体...
}

2.2 查看函数

#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#查看指定当前已定义的函数名
declare -f func_name 
#查看当前已定义的函数名定义
declare -F func_name

2.3 删除函数

unset func_name

 


3 函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止

3.1 交互式环境调用函数

[root@rocky01 ~]# NAME () {
> echo "Willoneday"
> }
[root@rocky01 ~]# NAME
Willoneday

3.2 脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用其函数名即可

[root@rocky01 ~]# cat NAME.sh
#!/bin/bash

NAME () {
echo "Willoneday"
}

NAME

[root@rocky01 ~]# bash NAME.sh
Willoneday

3.3 使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数

函数文件名可任意选取,但最好与相关任务有某种联系,例如:functions

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用delcare -f 或set 命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数

若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

实现函数文件的过程:

  1. 创建函数文件,只存放函数的定义
  2. 在shell脚本或交互式shell中调用函数文件,格式如下:

.  filename 
source  filename

[root@rocky01 ~]# NAME
-bash: NAME: command not found
[root@rocky01 ~]# . NAME.sh
Willoneday
[root@rocky01 ~]# NAME
Willoneday

 


4 函数参数

函数可以接受参数:

  • 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
  • 在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量
[root@rocky01 ~]# cat NAME.sh
#!/bin/bash

NAME () {
echo "your name:$@"
}

[ $# -eq 0 ] && echo "Usage: $0 name1 name2..." && exit

NAME $@

[root@rocky01 ~]# bash NAME.sh
Usage: NAME.sh name1 name2...
[root@rocky01 ~]# bash NAME.sh Willoneday Rye
your name:Willoneday Rye

 


5 函数变量

变量作用域:

  • 普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
  • 环境变量:当前shell和子shell有效
  • 本地变量:函数的生命周期;函数结束时变量被自动销毁

注意:

  • 如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
  • 由于普通变量和局部变量会冲突,建议在函数中只使用本地变量

函数中定义本地变量:

local  NAME=VALUE

 


6 函数递归

函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环

递归特点:

  • 函数内部自已调用自已
  • 必须有结束函数的出口语句,防止死循环

范例:fork炸弹

[root@rocky01 ~]# :(){ :|:& };:

#该函数":"会在后台创建大量的子进程,将资源耗尽

 


五、脚本相关工具 

1 trap 信号捕捉

trap 命令可以捕捉信号,修改信号原来的功能,实现自定义功能

#进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '触发指令' 信号

#忽略信号的操作
trap '' 信号
 
#恢复原信号的操作
trap '-' 信号

#列出自定义信号操作
trap -p

#当脚本退出时,执行finish函数
trap finish EXIT
[root@rocky01 ~]# trap -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

范例:自定义trap信号

[root@rocky01 ~]# cat trap_test.sh
#!/bin/bash

#信号可以是名字或编号(trap -l)
#trap "echo 脚本执行中无法结束" 2
trap "echo 脚本执行中无法结束" INT

for i in {1..5}
do
    echo "${i}"
    sleep 2
done

[root@rocky01 ~]# bash trap_test.sh
1
^C脚本执行中无法结束
2
3
4
5

范例:finish函数

[root@rocky01 ~]# cat trap_exit.sh
#!/bin/bash

finish () {
    echo finish-`date +%F`| tee -a /root/finish.log
}

trap finish exit
while true;
do
    echo running
    sleep 2
done

[root@rocky01 ~]# bash trap_exit.sh
running
running
running
^Cfinish-2022-08-09

[root@rocky01 ~]# cat finish.log
finish-2022-08-09

 


2 mktemp 创建临时文件

mktemp 命令用于创建并显示临时文件,可避免冲突

2.1 格式

mktemp [OPTION]... [TEMPLATE]
说明:TEMPLATE: filenameXXX,X至少要出现三个

 常见选项:

  • -d #创建临时目录
  • -p DIR或--tmpdir=DIR   #指明临时文件所存放目录位置

范例:文件垃圾箱

#方法1:脚本实现
[root@rocky01 ~]# cat /data/scripts/rm.sh 
#!/bin/bash
DIR=`mktemp -d /tmp/trash-$(date +%F_%H-%M-%S)XXXXXX`
mv $* $DIR
echo $* is move to $DIR

[root@rocky01 ~]# alias rm=/data/scripts/rm.sh

#方法2:函数实现
[root@rocky01 ~]# function rm () { local trash=`mktemp -d /tmp/trashXXXX`;mv $* $trash; }

 


3 install 安装复制文件

install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合

install 命令格式

install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY... #创建空目录

选项

-m MODE,默认755
-o OWNER
-g GROUP
-d DIRNAME 目录

 


4 expect 交互式转化批处理工具 

expect 是由Don Libes基于 Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景

4.1 语法

expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]

常见选项:

  • -c:从命令行执行expect脚本,默认expect是交互地执行的
  • -d:可以调试信息

4.2 expect中相关命令

  • spawn 启动新的进程
  • expect 从进程接收字符串
  • send 用于向进程发送字符串
  • interact 允许用户交互
  • exp_continue 匹配多个字符串在执行动作后加此命令

4.3 单一分支模式语法

[root@rocky01 ~]# expect
expect1.1> expect "hi" {send "You said hi\n"}
hi
You said hi

4.4 多分支模式语法

#写法一
[root@rocky01 ~]# expect
expect1.1> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send "Good bye\n" }
hi
You said hi
expect1.2> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send "Good bye\n" }
hehe
Hehe yourself
expect1.3> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send "Good bye\n" }
bye
Good bye

#写法二
expect {
    "hi" { send "You said hi\n"}
    "hehe" { send "Hehe yourself\n"}
    "bye" { send  " Good bye\n"} }

范例:expect 变量

[root@rocky01 ~]# cat expect1
#!/usr/bin/expect
set ip 10.0.0.129
set user root
set password 000000
#10秒超时时间
set timeout 10

spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$password\n" }
}
interact

范例:expect 位置参数

[root@rocky01 ~]# cat expect2
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]

spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$password\n" }
}
interact

[root@rocky01 ~]# expect expect2 10.0.0.129 root 000000
spawn ssh root@10.0.0.129
root@10.0.0.129's password:
Last login: Tue Aug  9 21:22:01 2022 from 10.0.0.128

范例:shell脚本调用 expect

[root@rocky01 ~]# cat expect3.sh
#!/bin/bash

#******************************************************
# Author:          会不会有那么一天
# QQ:              791270697
# Date:            2022-08-09
# FileName:        expect3.sh
# Version:         1.0.0
# Description:     The test script
# BLOG:            https://www.cnblogs.com/Willoneday
#******************************************************

NET=10.0.0
user=root
password=000000
IPLIST="
4
13
17
"

for ID in $IPLIST;
do
    ip=$NET.$ID
    expect <<EOF
    set timeout 20

    spawn ssh $user@$ip
    expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "$password\n" }
    }

    expect "#" { send "sed -i 's/SELINUX=.*/SELINUX=disabled/g' /etc/selinux/config\n" }
    expect "#" { send "setenforce 0\n" }
    expect "#" { send "exit\n" }
    expect eof
EOF
done

 


六、array 数组

1 数组介绍

数组名和索引

  • 索引的编号从0开始,属于数值索引
  • 索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash 4.0版本之后开始支持
  • bash的数组支持稀疏格式(索引不连续)

 


2 声明数组

  • #普通数组可以不事先声明,直接使用
  • declare -a ARRAY_NAME
  •  
  • #关联数组必须先声明,再使用
  • declare -A ARRAY_NAME

  注意:两者不可相互转换

 


3 数组赋值

3.1  一次只赋值一个元素

ARRAY_NAME[INDEX]=VALUE

#范例
weekdays[0]="Sunday"
weekdays[4]="Thursday"

3.2 一次赋值全部元素

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

#范例
title=("ceo" "coo" "cto")
num=({0..10})

3.3 只赋值特定元素

ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

 


4 显示数组

declare -a

 


5 引用数组

5.1 引用特定的数组元素

${ARRAY_NAME[INDEX]}

#如果省略[INDEX]表示引用下标为0的元素

#范例
[root@rocky01 ~]# declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@rocky01 ~]# echo ${title}
ceo
[root@rocky01 ~]# echo ${title[0]}
ceo
[root@rocky01 ~]# echo ${title[1]}
coo
[root@rocky01 ~]# echo ${title[2]}
cto

5.2 引用数组所有元素

  • ${ARRAY_NAME[*]}
  • ${ARRAY_NAME[@]}
#范例
[root@rocky01 ~]# echo ${title[@]}
ceo coo cto
[root@rocky01 ~]# echo ${title[*]}
ceo coo cto

5.3 数组的长度(元素个数)

  • ${#ARRAY_NAME[*]}
  • ${#ARRAY_NAME[@]}
#范例
[root@rocky01 ~]# echo ${#title[*]}
3

5.4 数组的所有下标

  • ${!ARRAY_NAME[*]}
  • ${!ARRAY_NAME[@]}
#范例
[root@rocky01 ~]# echo ${!title[*]}
0 1 2

 


6 删除数组

6.1 删除数组中的某元素

注:删除数组中的某元素,会导致稀疏格式

 unset  ARRAY[INDEX]

6.2 删除整个数组

 unset  ARRAY

 


7 数组数据处理

7.1 数组切片

  • ${ARRAY[@]:offset:number}
  • ${ARRAY[*]:offset:number}
  • offset    #要跳过的元素个数
  • number #要取出的元素个数

取偏移量之后的所有元素:

  • {ARRAY[@]:offset}
  • {ARRAY[*]:offset}
#范例
[root@rocky01 ~]# num=({0..10})
[root@rocky01 ~]# echo ${num[*]:2:3}
2 3 4
[root@rocky01 ~]# echo ${num[*]:6}
6 7 8 9 10

7.2 向数组中追加元素

  • ARRAY[${#ARRAY[*]}]=value
  • ARRAY[${#ARRAY[@]}]=value
[root@rocky01 ~]# num[${#num[@]}]=11

 


8 关联数组

declare -A ARRAY_NAME

ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)

注意:关联数组必须先声明再调用

#范例
[root@rocky01 ~]# declare -A name
[root@rocky01 ~]# name[ceo]=xiaoming
[root@rocky01 ~]# name[cto]=xiaohong

[root@rocky01 ~]# echo ${name[ceo]}
xiaoming
[root@rocky01 ~]# echo ${name[cto]}
xiaohong

 


七、字符串处理

1 字符串切片

1.1 基于偏移量取字符串

  • #返回字符串变量var的字符的长度,一个汉字算一个字符
    ${#var} 
  • #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,
    offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
    ${var:offset} 
  • #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
    ${var:offset:number}
  • #取字符串的最右侧几个字符,取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
    ${var: -length}
  • #从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
    ${var:offset:-length}
  • #先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:-length前空格,并且length必须大于offset
    ${var: -length:-offset}
#范例
[root@rocky01 ~]# str=love中国
[root@rocky01 ~]# echo ${#str}
6
[root@rocky01 ~]# echo ${str:4}
中国
[root@rocky01 ~]# echo ${str:2:2}
ve

1.2 基于模式取子串

1.2.1 自左而右

  • #其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
    ${var#*word}
  • #从var变量的值中删除以word开头的部分
    ${var#word}
  • #同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左留右
    ${var##*word}
    ${var##word}
#范例
[root@rocky01 ~]# url=https://www.cnblogs.com/Willoneday
[root@rocky01 ~]# echo ${url#*/}
/www.cnblogs.com/Willoneday
[root@rocky01 ~]# echo ${url##*/}
Willoneday

1.2.2 自右而左

  • #其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第一个word为界删右留左
    ${var%word*}
    ${var%word}
  • #同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向左的最后一个word为界删右留左
    ${var%%word*}
    ${var%%word}
#范例
[root@rocky01 ~]# url=https://www.cnblogs.com/Willoneday
[root@rocky01 ~]# echo ${url%/*}
https://www.cnblogs.com

 


2 查找替换

  • #查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
    ${var/pattern/substr}
  • #查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
    ${var//pattern/substr}
  • #查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
    ${var/#pattern/substr}
  • #查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
    ${var/%pattern/substr}
#范例
[root@rocky01 ~]# name=Willoneday123123
[root@rocky01 ~]# echo ${name/123/456}
Willoneday456123
[root@rocky01 ~]# echo ${name//123/456}
Willoneday456456
#范例:查找并删除
[root@rocky01 ~]# name=Willoneday123
[root@rocky01 ~]# echo ${name/123}
Willoneday123
[root@rocky01 ~]# echo ${name//123}
Willoneday

 


3 字符大小写转换

  • #把var中的所有小写字母转换为大写
    ${var^^}
  • #把var中的所有大写字母转换为小写
    ${var,,}
#范例
[root@rocky01 ~]# url=https://www.cnblogs.com/Willoneday
[root@rocky01 ~]# echo ${url^^}
HTTPS://WWW.CNBLOGS.COM/WILLONEDAY

 


八、高级变量

1 高级变量赋值

#范例
[root@rocky01 ~]# title=ceo
[root@rocky01 ~]# name=${title-Willoneday}
[root@rocky01 ~]# echo $name
ceo
[root@rocky01 ~]#
[root@rocky01 ~]# title=""
[root@rocky01 ~]# name=${title-Willoneday}
[root@rocky01 ~]# echo $name

[root@rocky01 ~]# 
[root@rocky01 ~]# unset title
[root@rocky01 ~]# name=${title-Willoneday}
[root@rocky01 ~]# echo $name
Willoneday

 


2 有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的

declare [选项] 变量名
选项:

  • -r 声明或显示只读变量
  • -i 将变量定义为整型数
  • -a 将变量定义为数组
  • -A 将变量定义为关联数组
  • -f 显示已定义的所有函数名及其内容
  • -F 仅显示已定义的所有函数名
  • -x 声明或显示环境变量和函数,相当于export 
  • -l 声明变量为小写字母 declare -l var=UPPER
  • -u 声明变量为大写字母 declare -u var=lower

 


3 变量间接引用

3.1 eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描

#范例
[root@rocky01 ~]# x=y
[root@rocky01 ~]# y=z
[root@rocky01 ~]# echo $x
y
[root@rocky01 ~]# echo \$$x
$y
[root@rocky01 ~]# eval echo \$$x
z

3.2 间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为

bash Shell提供了两种格式实现间接变量引用

  • 方法1 #变量赋值
    eval tempvar=\$$variable1
    #显示值
    eval echo \$$variable1
    eval echo '$'$variable1
  • 方法2 #变量赋值
    tempvar=${!variable1}
    #显示值
    echo ${!variable1}

范例:${!var} 

[root@rocky01 ~]# cat test.sh
#/bin/bash
var() {
    local var="$1"
    echo "${!var}"
}

var 1 a
var 2 a b
var 3 a b c

[root@rocky01 ~]# bash test.sh
1
a
b

 

相关内容