SHELL脚本编程

shell脚本的用途:
将简单的命令组合完成复杂的工作,自动化执行命令,提高工作效率
减少手工命令的重复输入,一定程度上避免人为错误
将软件或应用的安装及配置实现标准化
用于实现日常性的,重复性的运维工作,如:文件打包压缩备份,监控系统运行状态并实现告警等
shell脚本基本结构:
shell脚本编程:是基于过程式、解释执行的语言
编程语言的基本结构:
各种系统命令的组合
数据存储:变量、数组
表达式:a + b
控制语句:if
shell脚本:包含一些命令或声明,并符合一定格式的文本文件
格式要求:
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

shell脚本创建过程:
第一步:使用文本编辑器来创建文本文件
第一行必须包括shell声明序列:#!
示例:
#!/bin/bash
添加注释,注释以#开头

第二步:加执行权限
给予执行权限,在命令行上指定脚本的绝对路径或相对路径

第三步:运行脚本
直接运行解释器,将脚本作为解释器程序的参数运行

shell脚本注释规范:
1.第一行一般为调用使用的语言
2.程序名,避免更改文件名以免无法找到正确的文件
3.版本号
4.更改后的时间
5.作者相关信息
6.该程序的作用,及注意事项
7.最后是各版本的更新简要说明

执行shell脚本的5种方法:

  1. bash hello.sh
  2. cat hello.sh | bash
  3. bash < hello.sh
  4. chmod +x hello.sh ; ./hello.sh
  5. yum -y install httpd
    systemctl start httpd
    systemctl enable --now httpd
    cp hello.sh /var/www/html
    curl -s http://10.0.0.8/hello.sh | bash
    wget -qO - http://10.0.0.8/hello.sh | bash

shell脚本调试:
只检测脚本中的语法错误,但无法检查出错误命令,但不真正执行脚本:
bash -n xxx.sh
调试并执行:
bash -x xxx.sh
总结:脚本错误常见的有三种
语法错误,会导致后续的命令不继续执行,可以用bash -n检查错误,提示的出错行数不一定是准确的
命令错误,默认后续的命令还会继续执行,用bash -n无法检查出来,可以使用bash -x进行观察
逻辑错误:只能使用bash -x进行观察

变量:
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据
变量类型:
内置变量,如:PS1,PATH,UID,HOSTNAME,$ , B A S H P I D , P P I D , ,BASHPID,PPID, BASHPIDPPID?,HISTSIZE
用户自定义变量
不同的变量存放的数据不同,决定了以下
数据存储方式
参与的运算
表示的数据范围
变量数据类型:
字符
数值:整型、浮点型,bash不支持浮点数

静态和动态语言
静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
动态编译语言:不用事先声明,可随时改变类型,如:bash,Python
强类型和弱类型语言
强类型语言:不同类型数据操作,必须经过强制转换成同一类型才能运算,如java , c# ,python
如:参考以下 python 代码
print(‘magedu’+ 10) 提示出错,不会自动转换类型
print(‘magedu’+str(10)) 结果为magedu10,需要显示转换类型
弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会
自动进行隐式类型转换;变量无须事先定义可直接调用
如:bash ,php,javascript

shell中变量命名法则:
不能是程序中的保留字和内置变量:如:if, for
只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”,和主机名相反
见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
统一命名规则:驼峰命名法, studentname,大驼峰StudentName 小驼峰studentName
变量名大写:STUDENT_NAME
局部变量小写
函数名小写

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

变量赋值:
name=‘value’
value可以是以下多种形式:
直接字串:name=‘root’
变量引用:name=" U S E R " 命 令 引 用 : n a m e = ‘ C O M M A N D ‘ 或 者 n a m e = USER" 命令引用:name=`COMMAND` 或者 name= USER"name=COMMANDname=(COMMAND)
注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量会随着脚本结束,也会自动删除
变量引用:
$name
n a m e 弱 引 用 和 强 引 用 : " {name} 弱引用和强引用: " name"name" 弱引用,其中的变量引用会被替换为变量值
‘$name’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

变量追加值:
[root@centos8 ~]#TITLE=CTO
[root@centos8 ~]#TITLE+=:wang
[root@centos8 ~]#echo $TITLE
CTO:wang

利用变量实现动态命令:
[root@centos8 ~]#CMD=hostname
[root@centos8 ~]#$CMD
centos8.wangxiaochun.com

显示已定义的所有变量:set
删除变量:unset

环境变量:
可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
一旦子进程修改从父进程继承的变量,会将新的值传递给孙子进程
一般只在系统配置文件中使用,在脚本中较少使用
变量声明和赋值:
声明并赋值:
export name=VALUE
declare -x name=VALUE
也可以分两步实现:
name=VALUE
export name
变量引用:
$name
${name}
显示所有环境变量:
env
printenv
export
declare -x
删除变量:unset name
bash内建的环境变量:
PATH
SHELL
USER
UID
HOME
PWD
SHLVL #shell的嵌套层数,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_ #下划线,表示前一命令的最后一个参数

只读变量:只能声明定义,但后续不能修改和删除,即常量
声明只读变量:
readonly name
declare -r name
查看只读变量:
readonly [-p]
declare -r

位置变量:在bash shell中内置的变量,在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, … 对应第1个、第2个等参数,shift [n]换位置
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲ 传递给脚本的参数的个数 注意…@ $* 只在被双引号包起来的时候才会有差异

清空所有位置变量:
set –

范例: ∗ 和 *和 @的区别
[root@centos8 scripts]#cat f1.sh
#!/bin/bash
echo “f1.sh:all args are $@”
echo “f1.sh:all args are ∗ " . / f i l e . s h " *" ./file.sh " "./file.sh"*”
[root@centos8 scripts]#cat f2.sh
#!/bin/bash
echo “f2.sh:all args are $@”
echo “f2.sh:all args are ∗ " . / f i l e . s h " *" ./file.sh " "./file.sh"@”
[root@centos8 scripts]#cat file.sh
#!/bin/bash
echo “file.sh:1st arg is $1”
[root@centos8 scripts]#./f1.sh a b c
f1.sh:all args are a b c
f1.sh:all args are a b c
file.sh:1st arg is a b c
[root@centos8 scripts]#./f2.sh a b c
f2.sh:all args are a b c
f2.sh:all args are a b c
file.sh:1st arg is a

退出状态码变量:
进程执行后,将使用 ? 保 存 状 态 码 的 相 关 数 字 , 不 同 的 值 反 应 成 功 或 失 败 , ?保存状态码的相关数字,不同的值反应成功或失败, ??的取值范围:0~255
$?的值为0 代表成功
$?的值为1~255 代表失败

用户可以在脚本中使用以下命令自定义退出状态码
exit [n]
注意:
脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

展开命令执行顺序:
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配*、?、[abc]等等
准备I/0重导向 <、>
运行命令

防止扩展:
反斜线(\)会使随后的字符按原意解释
加引号来防止扩展:
单引号(’’)防止所有扩展
双引号("")也可防止扩展,但"$"字符例外

变量扩展:
``:反引号,命令替换
\:反斜线,禁止单个字符扩展
!:历史命令替换

脚本安全和set:
set命令:可用来定制shell环境

$-变量:
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命令实现脚本安全:
-u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit
-o option 显示,打开或者关闭选项
显示选项:set -o
打开选项:set -o 选项
关闭选项:set +o 选项
-x 当执行命令时,打印命令及其参数,类似 bash -x

格式化输出printf:
格式:
printf “指定的格式” “文本1” ”文本2“……
常用格式替换符:
%s 字符串
%f 浮点格式
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%c ASCII字符,即显示对应参数的第一个字符
%d,%i 十进制整数
%o 八进制值
%u 不带正负号的十进制值
%x 十六进制值(a-f)
%X 十六进制值(A-F)
%% 表示%本身
说明:%s中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,-表示左对齐

常用转义符:
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ 表示\本身

算术运算:
注意:bash只支持整数,不支持小数
实现算术运算:
let var=算术表达式
((var=算术表达式))
var= [ 算 术 表 达 式 ] v a r = [算术表达式] var= []var=((算术表达式))
var=$(expr arg1 arg2 arg3 …)
declare -i var = 数值
echo ‘算术表达式’ | bc

内建的随机数生成器变量:
$RANDOM 取值范围:0~32767

增强型赋值:
+= i+=10 相当于 i=i+10
-= i-=j 相当于 i=i-j
*=
/=
%=
++ i++,++i 相当于 i=i+1
– i–,--i 相当于 i=i-1
格式:
let varOPERvalue

逻辑运算:
1为真
0为假
与(&):和0相与,结果为0,和1相与,结果保留原值
0 & 1 = 0
1 & 0 = 0
0 & 0 = 0
1 & 1 =1
或(|):和1相或,结果为1,和0相或,结果保留原值
0 | 1 = 1
1 & 0 = 1
0 & 0 = 0
1 & 1 =1
非(!)
异或(^):异或的两个值,相同为假,不同为真。两个数字X,Y异或得到结果Z,Z再和任意两者之一X异或,将得出另一个值Y
0 ^ 1 = 1
1 ^ 0 = 1
0 ^ 0 = 0
1 ^ 1 =0

短路运算:
短路与:
CMD1 短路与 CMD2
第一个CMD1结果为真(1),第二个CMD2必须要参与运算,才能得到最终的结果
第一个CMD1结果为假(0),总的结果必定为0,因此不需要执行CMD2
短路或:
CMD1 短路或 CMD2
第一个CMD1结果为真(1),总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为假(0),第二个CMD2必须要参与运算,才能得到最终的结果

条件测试命令:
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令来辅助完成测试过程,实现评估布尔声明,以便在条件性环境下进行执行
若真,则状态码变量 $? 返回0
若假,则状态码变量 $? 返回1
条件测试命令
test EXPRESSION
[ EXPRESSION ] #和test 等价,建议使用 [ ]
[[ EXPRESSION ]]
注意:EXPRESSION前后必须有空白字符

变量测试:
[ -v NAME ] 判断变量NAME是否定义
[ -R NAME ] 判断NAME变量是否定义且名称引用

数值测试:
-eq 是否等于
-ne 是否不等于
-lt 是否小于
-le 是否小于等于
-gt 是否大于
-ge 是否大于等于

算数表达式比较:
== 相等
!= 不相等
<=
>=
<
>

字符串测试:
test和[]用法:
-z STRING 字符串是否为空,没定义或空为真,不空为假
-n STRING 字符串是否不空,不空为真,空为假
STRING1 = STRING2 是否等于,注意 = 前后有空格
STRING1 != STRING2 是否不等于
> ascii码是否大于ascii码
< 是否小于
[[]]用法:
[[ expression ]]用法
== 左侧字符串是否和右侧的PATTERN相同
注意:此表达式用于[[ ]]中,PATTERN为通配符
=~ 左侧字符是否能被右侧的正则表达式的PATTERN所匹配
注意:此表达式用于[[ ]]中,扩展的正则表达式
建议:当使用正则表达式或通配符使用[[]],其他情况一般使用[]
在比较字符串时,建议变量放在""中

#通配符
[root@centos8 ~]#NAME=“linux1”
[root@centos8 ~]#[[ “$NAME” == linux* ]]
[root@centos8 ~]#echo KaTeX parse error: Expected 'EOF', got '#' at position 21: …root@centos8 ~]#̲[[ "NAME" == “linux*” ]]
[root@centos8 ~]#echo KaTeX parse error: Expected 'EOF', got '#' at position 21: …root@centos8 ~]#̲NAME="linux*" […NAME" == “linux*” ]]
[root@centos8 ~]#echo $?
0
#结论:[[ == ]] == 右侧的 * 做为通配符,不要加"",只想做为*, 需要加""或转义

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

文件权限测试:
-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限
注意:最终结果由用户对文件的实际权限决定,而非文件属性决定

文件属性测试:
-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

关于()和{}:
( CMD1;CMD2;… )和{ CMD1;CMD2;…; }都可以将多个命令组合在一起,批量执行
( list )会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境
{ list; }不会开启子shell,在当前shell中运行,会影响当前shell环境
范例:
umask
(umask 066;touch f1.txt)
umask
(cd /data/;echo $BASHPID)
{ cd /data/;echo $BASHPID; }

组合测试条件:
第一种方式[]:
[ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[ ! EXPRESSION ] 取反
说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持

第二种方式:
COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2
COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND #非,取反
结论:如果&&和||混合使用,&&要在前,||放在后面

使用read命令来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY
格式:
read [options] [name …]
常见选项:
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d ‘字符’ 输入结束符
-t N TIMEOUT为N秒

范例:实现运维工作菜单
#!/bin/bash
#***********************************************
#Author: xu
#Mail: 1653213432@qq.com
#Version: 1.0
#Date: 2020-08-06
#FileName: work_menu.sh
#Copyright©: 2020 All rights reserved
#***********************************************
. /etc/init.d/functions
echo -en “\e[1;$[RANDOM%7+31]m”
cat <<EOF
请选择:
1)备份数据库
2)清理日志
3)软件升级
4)软件回滚
5)删库跑路
EOF
echo -en “\e[0m”
read -p “请选择上面对应的数字1-5:” MENU
[ $MENU -eq 1 ] && bash backup.sh
[ $MENU -eq 2 ] && action “清理日志”
[ $MENU -eq 3 ] && action “软件升级”
[ $MENU -eq 4 ] && action “软件回滚”
[ $MENU -eq 5 ] && action “删库跑路”

bash shell的配置文件很多,可以分成下面类别:
按生效范围分两类:
全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置
~/.bash_profile
~/.bashrc

shell登录分两种方式:交互式登录和非交互式登录
交互式登录:
直接通过终端输入账号密码登录
使用su - USERNAME切换的用户
配置文件生效和执行顺序:
#放在每个文件最前
/etc/profile
/etc/profile.d/. sh
/etc/bashrc
~/.bash_ profile
~/.bashrc
/etc/bashrc
#放在每个文件最后
/etc/profile.d/
.sh
/etc/bashrc
/etc/profile
/etc/bashrc #此文件执行两次
~/.bashrc
~/.bash_profile
注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序

非交互式登录:
su UserName
图形界面下打开的终端
执行脚本
任何其它的bash实例
执行顺序:
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc

按功能划分分类:
profile类和bashrc类
profile类为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:
(1) 用于定义环境变量
(2) 运行命令或脚本

bashrc类:为非交互式和交互时登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
(1) 定义命令别名和函数
(2) 定义本地变量

编辑配置文件生效:
修改profile和bashrc文件后需生效的两种方法:
1、重新启动shell进程
2、source|. 配置文件
注意:source 会在当前shell中执行脚本,所有一般只用于执行配置文件,或在脚本中调用另一个脚本的场景

bash退出任务:
保存在~/.bash_logout文件中(用户),在退出登录shell时运行
功能:
创建自动备份
清除临时文件

选择执行if语句:
格式:
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]… [ else COMMANDS; ] fi
单分支:
if 判断条件;then
条件为真的分支代码
fi
双分支:
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支:
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码

else
以上条件都为假的分支代码
fi
说明:
多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
if 语句可嵌套

条件判断case语句:
格式:
case WORD in [PATTERN [| PATTERN]…) COMMANDS ;;]… esac
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;

*)
默认分支
;;
esac
case支持glob风格的通配符:
* 任意长度任意字符
? 任意单个字符
[] 指定范围内的任意单个字符
| 或者,如: a|b

循环:
将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件
重复运行次数
循环次数事先已知
循环次数事先未知
常见的循环的命令:for,while,until
格式1:
for NAME [in WORDS … ] ; do COMMANDS; done
方式1:
for 变量名 in 列表;do
循环体
done
方式2:
for 变量名 in 列表
do
循环体
done

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

for循环列表生成方式:
直接给出列表:
整数列表:
{start…end}
$(seq [start [step]] end)
返回列表的命令:
( C O M M A N D ) 使 用 g l o b , 如 : ∗ . s h 变 量 引 用 , 如 : (COMMAND) 使用glob,如:*.sh 变量引用,如: (COMMAND)使glob.sh@, ∗ , *, ,#

格式2:
双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作
I=10;((I++))

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done
说明:
控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

循环while:
格式:
while COMMANDS; do COMMANDS; done
while CONDITION; do
循环体
done
说明:
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为true
退出条件:CONDITION为false

无限循环:
while true; do
循环体
done

循环until:
格式:
until COMMANDS;do COMMANDS;done
until CONDITION;do
循环体
done
说明:
进入条件: CONDITION 为false
退出条件: CONDITION 为true
无限循环:
until false; do
循环体
Done

循环控制语句continue:
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
格式:
while CONDITION1; do
CMD1

if CONDITION2; then
continue
fi
CMDn

done

循环控制语句break
break [N]:提前结束第N层整个循环,最内层为第1层
格式:
while CONDITION1; do
CMD1

if CONDITION2; then
break
fi
CMDn

done

循环控制shift命令
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

while循环的特殊用法,遍历文件或文本的每一行
格式:
while read line;do
循环体
done < /PATH/FROM/SOMEFIlE
说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
范例:
[root@centos8 ~]#echo mage wang zhang | while read X Y Z; do echo $X $Y $Z;done
mage wang zhang

循环与菜单select:
格式:

select NAME [in WORDS ... ;] do COMMANDS; done
select variable in list ;do
    循环体命令
done

说明:

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示
    PS3 提示符,等待用户输入
  • 用户输入菜单列表中的某个数字,执行相应的命令
  • 用户输入被保存在内置变量 REPLY 中
  • select 是个无限循环,因此要用 break 命令退出循环,或用 exit 命令终止脚本。也可
    以按 ctrl+c退出循环
  • select 经常和 case 联合使用
  • 与 for 循环类似,可以省略 in list,此时使用位置参量
    范例:
#!/bin/bash
#
sum=0
PS3="请点菜(1-6): "
select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束;do
case $REPLY in
1)
 echo $MENU 价格是 100
 let sum+=100
 ;;
 2)
 echo $MENU 价格是 88
 let sum+=88
 ;;
3)
echo $MENU价格是 66
let sum+=66
;;
4)
echo $MENU 价格是 166
let sum+=166
;;
5)
echo $MENU 价格是 200
let sum+=200
;;
6)
echo "点菜结束,退出"
break
;;
*)
echo "点菜错误,重新选择"
;;
esac
done
echo "总价格是: $sum"

定义函数:

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

查看函数:

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

删除函数:
格式:

unset func_name

函数调用方式:

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中
    调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码
    函数的生命周期:被调用时创建,返回时终止

交互式环境下使用和调用函数:
范例:实现判断CentOS的主版本

[root@centos8 ~]#centos_version() {
> sed -rn 's#^.* +([0-9]+)\..*#\1#p' /etc/redhat-release
> }
[root@centos8 ~]#centos_version
8

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

范例:

#!/bin/bash
disable_selinux(){
		sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
		echo "SElinux已禁用,重新启动后才可生效"
}
disable_firewall(){
  	systemctl disable --now firewalld &> /dev/null
  	echo "防火墙已禁用"
}
set_ps1() {
  	echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
  	echo "提示符已修改成功,请重新登录生效"
}
set_eth(){
  	sed -i.bak  '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
  	grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
  	echo "网络名称已修改成功,请重新启动才能生效"
}
PS3="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关防火墙
修改提示符
修改网卡名
以上全实现
退出
'
select M in $MENU ;do
case $REPLY in
1)
  disable_selinux
  ;;
2)
  disable_firewall
   ;;
3)
  set_ps1
   ;;
4)
  set_eth
  ;;
5)
   disable_selinux
   disable_firewall
   set_ps1
   set_eth
    ;;
6)
  break
  ;;
*)
  echo "请输入正确的数字"
  esac
done

使用函数文件:
可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数文件名可任意选取,但最好与相关任务有某种联系,例如:functions
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用declare -f或set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
实现函数文件的过程:
1.创建函数文件,只存放函数的定义
2.在shell脚本或交互式shell中调用函数文件,格式如下:
sh . filename 或 source filename
范例:

#!/bin/bash
. /etc/init.d/functions
disable_selinux(){
  sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
  action "SElinux已禁用,重新启动后才可生效"
}
disable_firewall(){
  systemctl disable --now firewalld &> /dev/null
  action "防火墙已禁用"
}
set_ps1() {
  echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
  action "提示符已修改成功,请重新登录生效"
}
set_eth(){
  sed -i.bak  '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
  grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
  action "网络名称已修改成功,请重新启动才能生效"
}
PS3="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关防火墙
修改提示符
修改网卡名
以上全实现
退出
'
select M in $MENU ;do
case $REPLY in
1)
  disable_selinux
  ;;
2)
  disable_firewall
   ;;
3)
  set_ps1
  ;;
4)
  set_eth
  ;;
5)
  disable_selinux
  disable_firewall
  set_ps1
  set_eth
  ;;
6)
  break
  ;;
*)
  echo "请输入正确的数字"
  esac
done

函数返回值:
函数的执行结果返回值:

  • 使用echo等命令进行输出
  • 函数体中调用命令的输出结果
    函数的退出状态码:
  • 默认取决于函数中执行的最后一条命令的退出状态码
  • 自定义退出状态码,其格式为:
    return 从函数中返回,用最后状态命令决定返回值
    return 0 无错误返回
    return 1-255 有错误返回
    环境函数:
    类似于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
    定义环境函数:
export -f function_name
declare -xf function_name

查看环境函数:

export -f
declare -xf

函数参数:
函数可以接受参数:

  • 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1
    arg2 …
  • 在函数体中当中,可使用$1, 2 , . . . 调 用 这 些 参 数 ; 还 可 以 使 用 2, ...调用这些参数;还可以使用 2,...使@, $*, $#等特殊变量
    范例:实现进度条功能
#!/bin/bash
function print_chars()
{
  # 传入的第一个参数指定要打印的字符串
 local char="$1"
  # 传入的第二个参数指定要打印多少次指定的字符串
 local number="$2"
 local c
  for ((c = 0; c < number; ++c)); do
     printf "$char"
  done
}
COLOR=32
declare -i end=50
for ((i = 1; i <= end; ++i)); do
 printf "\e[1;${COLOR}m\e[80D["
 print_chars "#" $i
 print_chars " " $((end - i))
 printf "] %3d%%\e[0m" $((i * 2))
  sleep 0.1s
done
echo

函数变量:
变量作用域:

  • 普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地
    变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
  • 环境变量:当前shell和子shell有效
  • 本地变量:函数的生命周期;函数结束时变量被自动销毁
    注意:
  • 如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
  • 由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
    在函数中定义本地变量的方法:
local NAME=VALUE

函数递归:函数直接或者间接调用自身,注意递归层数,可能会陷入死循环
范例:测试递归的嵌套深度

test() {
	let i++
	echo i=$i
	test
}
test

信号捕捉trap:

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

#忽略信号的操作
trap '' 信号

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

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

#当脚本退出时,执行finish函数
trap finish EXIT 

范例:

#!/bin/bash
trap "echo 'Press ctrl+c or ctrl+\ '" int quit
trap -p
for((i=0;i<=10;i++))
do
      sleep 1
      echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i++))
do
      sleep 1
      echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i++))
do
      sleep 1
      echo $i
done

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

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

常见选项:

-d	临时创建目录
-p DIR或--tmpdir=DIR

范例:

[root@centos8 ~]#mktemp /tmp/testXXX
[root@centos8 ~]#tmpdir=`mktemp -d /tmp/testdirXXX`
[root@centos8 ~]#mktemp --tmpdir=/testdir testXXXXXX

安装复制文件install:
install 功能相当于cp,chmod,chown,chgrp 等相关工具的集合
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 目录

范例:

[root@centos8 ~]#install -m 700 -o wang -g admins srcfile desfile
[root@centos8 ~]#install -m 770 -d /testdir/installdir
[root@centos8 ~]#install -m 600 -o wang -g bin anaconda-ks.cfg /data/a.cfg
[root@centos8 ~]#ll /data/a.cfg
-rw------- 1 wang bin 1550 Jun 23 20:28 /data/a.cfg
[root@centos8 ~]#install -m 700 -o mage -g daemon -d /data/testdir
[root@centos8 ~]#ll -d /data/testdir
drwx------ 2 mage daemon 6 Apr 29 15:09 /data/testdir

交互式转化批处理工具expect:
范例:安装expect及mkpasswd工具

[root@centos8 ~]#yum -y install expect
[root@centos8 ~]#rpm -ql expect|head
/usr/bin/autoexpect
/usr/bin/dislocate
/usr/bin/expect
/usr/bin/ftp-rfc
/usr/bin/kibitz
/usr/bin/lpunlock
/usr/bin/mkpasswd
/usr/bin/passmass
/usr/bin/rftp
/usr/bin/rlogin-cwd
[root@centos8 ~]#mkpasswd -l 15 -d 3 -C 5
9T{htJmcA7pgCJ2

expect语法:

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 启动新的进程
  • expect 从进程接收字符串
  • send 用于向进程发送字符串
  • interact 允许用户交互
  • exp_continue 匹配多个字符串在执行动作后加此命令
    范例1:
#!/usr/bin/expect
spawn scp /etc/redhat-release 10.0.0.7:/data
expect {
      "yes/no" { send "yes\n";exp_continue }
      "password" { send  "magedu\n" }
}
expect eof

范例2:

#!/usr/bin/expect
spawn ssh 10.0.0.7
expect {
      "yes/no" { send "yes\n";exp_continue }
      "password" { send  "magedu\n" }
}
interact

范例3:expect变量

set ip 10.0.0.7
set user root
set password magedu
set timeout 10
spawn ssh $user@$ip
expect {
      "yes/no" { send "yes\n";exp_continue }
      "password" { send "$password\n" }
}
interact

范例4:expect 位置参数

#!/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

范例5:expect执行多个命令

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
      "yes/no" { send "yes\n";exp_continue }
      "password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo magedu |passwd --stdin haha\n" }
send "exit\n"
expect eof
#./ssh4.exp 10.0.0.7 root magedu

范例6:shell脚本调用expect

#!/bin/bash
ip=$1
user=$2
password=$3
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" }
expect "]#" { send "echo magedu |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
#./ssh5.sh 192.168.8.10 root magedu

范例7:shell脚本利用循环调用expect在centos和ubuntu上批量创建用户

#!/bin/bash
NET=10.0.0
user=root
password=magedu
IPLIST="
7
18
101
"
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 "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

范例8:

#!/bin/bash
NET=10.0.0
user=root
password=magedu
IPLIST="
7
18
"
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=enforcing/SELINUX=disabled/'
/etc/selinux/config\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

数组:存储多个元素的连续的内存空间,相当于多个变量的集合

声明数组:

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

注意:两者不可相互转换
数组赋值:
(1)一次只赋值一个元素

ARRAY_NAME[INDEX]=VALUE

范例:

weekdays[0]="Sunday"
weekdays[4]="Thursday"

(2)一次赋值全部元素

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

范例:
title=("ceo" "coo" "cto") num=({0..10}) alpha=({a..g}) file=( *.sh )
(3)只赋值特定元素

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

(4)交互式数组值对赋值
read -a ARRAY

显示所有数组:
declare -a

引用数组元素:

${ARRAY_NAME[INDEX]}
如果省略[INDEX]表示引用下标为0的元素

范例:

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

引用数组所有元素

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

范例:

[root@centos8 ~]#echo ${title[@]}
ceo coo cto
[root@centos8 ~]#echo ${title[*]}
ceo coo cto

数组的长度,即数组中元素的个数

${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

范例:

[root@centos8 ~]#echo ${#title[*]}
3

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

unset ARRAY[INDEX]

[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto

删除整个数组

 unset ARRAY
 
 [root@centos8 ~]#unset title
 [root@centos8 ~]#echo ${title[*]}
 
 [root@centos8 ~]#

数组切片:

${ARRAY[@]:offset:number}
offset #要跳过的元素个数
number #要取出的元素个数
#取偏移量之后的所有元素
{ARRAY[@]:offset}

范例:
[root@centos8 ~]#num=({0..10}) [root@centos8 ~]#echo ${num[*]:2:3} 2 3 4 [root@centos8 ~]#echo ${num[*]:6} 6 7 8 9 10
向数组中追加元素:

ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

范例:

[root@centos8 ~]#num[${#num[@]}]=11
[root@centos8 ~]#echo ${#num[@]}
12
[root@centos8 ~]#echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11

关联数组:

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

注意:关联数组必须先声明在调用
范例:

[root@centos8 ~]#declare -A student
[root@centos8 ~]#student[name1]=lijun
[root@centos8 ~]#student[name2]=ziqing
[root@centos8 ~]#student[age1]=18
[root@centos8 ~]#student[age2]=16
[root@centos8 ~]#student[gender1]=m
[root@centos8 ~]#student[city1]=nanjing
[root@centos8 ~]#student[gender2]=f
[root@centos8 ~]#student[city2]=anhui
[root@centos8 ~]#student[gender2]=m
[root@centos8 ~]#student[name50]=alice
[root@centos8 ~]#student[name3]=tom
[root@centos8 ~]#for i in {1..50};do echo student[name$i]=${student[name$i]};
done

字符串切片处理:
基于偏移量取字符串:

#返回字符串变量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前空格
${var: -length:-offset}
范例:
  ```
[root@centos8 script40]#str=abcdef我你他
[root@centos8 script40]#echo ${#str}
9
[root@centos8 script40]#echo ${str:2}
cdef我你他
[root@centos8 script40]#echo ${str:2:3}
cde
[root@centos8 script40]#echo ${str:-3}
abcdef我你他
[root@centos8 script40]#echo ${str: -3}
我你他
[root@centos8 script40]#echo ${str:2:-3}
cdef
[root@centos8 script40]#echo ${str: -2:-3}
-bash: -3: substring expression < 0
[root@centos8 script40]#echo ${str: -3:-2}
我
[root@centos8 script40]#echo ${str:-3:-2}
abcdef我你他
[root@centos8 script40]#echo ${str: -3:-2}
我
[root@centos8 script40]#echo ${str: -5:-2}
ef我

基于模式取子串:

#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次 
出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
${var#*word}:

#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间
的所有内容
${var##*word}:

范例:

[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file#*/}
log/messages
[root@centos8 ~]#echo ${file##*/} 
messages 
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中, 第一次出现的word,删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
${var%word*}

#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}

范例:

[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file%/*}
var/log
[root@centos8 ~]#echo ${file%%/*}
var
[root@centos8 ~]#url=http://www.magedu.com:8080
[root@centos8 ~]#echo ${url##*:}
8080
[root@centos8 ~]#echo ${url%%:*}
http

查找替换:

#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}

#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}

#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}

#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}

查找并删除

删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}

删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}

删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}

删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}	

字符大小写转换:

#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}

高级变量赋值:
在这里插入图片描述
范例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
[root@centos8 ~]#unset title
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
mage

范例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
mage
[root@centos8 ~]#unset title
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
mage

高级变量用法-有类型变量

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

变量间接引用:
eval命令:
范例:

[root@centos8 ~]# CMD=whoami
[root@centos8 ~]# echo $CMD
whoami
[root@centos8 ~]# eval $CMD
root
[root@centos8 ~]# n=10        
[root@centos8 ~]# echo {0..$n}    
{0..10}
[root@centos8 ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10
[root@centos8 ~]#for i in `eval echo {1..$n}` ;do echo i=$i ;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
[root@centos8 ~]#i=1
[root@centos8 ~]#j=a
[root@centos8 ~]#$j$i=hello
-bash: a1=hello: command not found
[root@centos8 ~]#eval $j$i=hello
[root@centos8 ~]#echo $j$i
a1	

#方法1
eval tempvar=$KaTeX parse error: Expected 'EOF', got '#' at position 11: variable1 #̲方法2 tempvar={!variable1}

范例:

[root@centos8 ~]#ceo=name
[root@centos8 ~]#name=mage
[root@centos8 ~]#echo $ceo
name
[root@centos8 ~]#echo $$ceo
33722ceo
[root@centos8 ~]#echo $$
33722
[root@centos8 ~]#echo \$$ceo
$name
[root@centos8 ~]#eval echo \$$ceo
mage
[root@centos8 ~]#eval tmp=\$$ceo
[root@centos8 ~]#echo $tmp
mage
[root@centos8 ~]#echo ${!ceo}
mage

[root@server ~]# N1=N2
[root@server ~]# N2=wangxiaochun
[root@server ~]# eval NAME=\$$N1
[root@server ~]# echo $NAME
wangxiaochun
[root@server ~]# NAME=${!N1}
[root@server ~]# echo $NAME
wangxiaochun
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值