2、shell变量
变量基础
变量查看
语法解析
基本格式
$变量名
示例
查看默认的shell类型
[root@localhost ~]# echo $SHELL
/bin/bash
4种查看变量的写法:
变量定义
普通语法解析
基本格式
变量名=变量值
注意:
= 两侧不允许有空格
示例
查看一个空值变量名
[root@localhost ~]# echo $myname
定制变量实践
[root@localhost ~]# myname=shuji
[root@localhost ~]# echo $myname
shuji
错误的定制变量命令
[root@localhost ~]# echo $myage
[root@localhost ~]# myage = 18
bash: myage: 未找到命令
[root@localhost ~]# echo $myage
类型变量定义
命令语法
declare 参数 变量名=变量值
参数解析:
-i 将变量看成整数
-r 使变量只读 readonly,==**该变量的值无法改变,并且不能为unset**==
-x 标记变量为全局变量,类似于export
-a 指定为索引数组(普通数组);查看普通数组
-A 指定为关联数组;查看关联数组
注意:
在生产场景中,这种方法比较鸡肋,使用频率 0-20次/3年
设定制定类型的变量值
[root@localhost ~]# declare -i mynum='shuzi'
[root@localhost ~]# echo $mynum
0
[root@localhost ~]# declare -i mynum='123456'
[root@localhost ~]# echo $mynum
123456
设定只读类型变量
[root@localhost ~]# declare -r myread1="aaa"
[root@localhost ~]# myread2=myread
[root@localhost ~]# readonly myread2
查看只读变量
[root@localhost ~]# declare -r | grep myread
declare -r myread1="aaa"
declare -r myread2="myread"
[root@localhost ~]# readonly -p | grep myread
declare -r myread1="aaa"
declare -r myread2="myread"
无法使用unset删除只读变量
[root@localhost ~]# unset myread1 myread2
bash: unset: myread1: 无法反设定: 只读 variable
bash: unset: myread2: 无法反设定: 只读 variable
借助于exit方式删除只读变量
[root@localhost ~]# exit
...... 重新登录后再次查看
[root@localhost ~]# declare -r | grep myread
[root@localhost ~]#
查看只读变量示例:
也可以使用管道然后过滤
删除只读变量示例:
先退出终端
重新连接就没了
变量移除
语法解析
基本格式
unset 变量名
示例
查看刚才定制的变量名
[root@localhost ~]# echo $myname
shuji
移除变量名
[root@localhost ~]# unset myname
[root@localhost ~]# echo $myname
[root@localhost ~]#
本地变量
普通变量
变量分类
所谓的本地变量就是:在当前系统的某个环境下才能生效的变量,作用范围小。本地变量按照变量值的生成方式包含两种:
普通变量:
自定义变量名和变量值
命令变量:
自定义变量名,而变量值是通过一条命令获取的
基本格式
序号 | 样式 | 要点 |
---|---|---|
方式一 | 变量名=变量值 | 变量值必须是一个整体,中间没有特殊字符 "=" 前后不能有空格 |
方式二 | 变量名='变量值' | 原字符输出,我看到的内容,我就输出什么内容, |
方式三 | 变量名="变量值" | 如果变量值范围内,有可以解析的变量A,那么首先解析变量A, 将A的结果和其他内容组合成一个整体,重新赋值给变量 |
习惯:
数字不加引号,其他默认加双引号
因为bash属于弱类型语言,默认会将所有内容当成字符串
变量定义
查看默认的空值变量
[root@localhost ~]# echo $name
方法1设定变量
[root@localhost ~]# name=shuji
[root@localhost ~]# echo $name
shuji
方法2设定变量
[root@localhost ~]# name='shuji1'
[root@localhost ~]# echo $name
shuji1
方法3设定变量
[root@localhost ~]# name="shuji2"
[root@localhost ~]# echo $name
shuji2
清理变量
[root@localhost ~]# unset name
作用区别演示
查看默认的空值变量
[root@localhost ~]# echo $name2
方法1设定变量的要点,变量值必须是一个整体
[root@localhost ~]# name2=shuji haoshuai
bash: haoshuai: 未找到命令
[root@localhost ~]# echo $name2
原因解析:
空格是一个特殊符号,表示两条命令的隔开
它将shuji 和 haoshuai当成两条命令了,所以报错信息是命令找不到
方法2设定变量
[root@localhost ~]# name2='shuji haoshuai'
[root@localhost ~]# echo $name2
shuji haoshuai
方法3设定变量
[root@localhost ~]# name2="shuji haoweiwu"
[root@localhost ~]# echo $name2
haoweiwu
清理变量
[root@localhost ~]# unset name2
定制基础变量
[root@localhost ~]# name=shuji
[root@localhost ~]# echo $name
shuji
方法2设定变量
[root@localhost ~]# name2='dan-$name'
[root@localhost ~]# echo $name2
dan-$name
方法3设定变量
[root@localhost ~]# name2="shuang-$name"
[root@localhost ~]# echo $name2
shuang-shuji
命令变量
基本格式
定义方式一:
变量名=`命令`
注意:
` 是反引号
定义方式二:推荐使用
变量名=$(命令)
执行流程:
1、执行 ` 或者 $() 范围内的命令
2、将命令执行后的结果,赋值给新的变量名A
简单实践
命令变量实践
查看默认的空值变量
[root@localhost ~]# echo $myuser
方法1设定变量名
[root@localhost ~]# myuser=`whoami`
[root@localhost ~]# echo $myuser
root
查看默认的空值变量
[root@localhost ~]# echo $mydir
方法2设定变量名
[root@localhost ~]# mydir=$(pwd)
[root@localhost ~]# echo $mydir
/root
清理变量
[root@localhost ~]# unset mydir myuser
其他常见的实践
自动生成一系列数字
[root@localhost ~]# NUM=`seq 10`
[root@localhost ~]# echo $NUM
1 2 3 4 5 6 7 8 9 10
文件备份添加时间戳
[root@localhost ~]# touch file-a
[root@localhost ~]# cp file-a file-a-$(date +%F)
[root@localhost ~]# ls file-a*
file-a file-a-2022-06-08
简单小综合实践
[root@localhost ~]# cat get_netinfo_v2.sh
#!/bin/bash
# 功能:获取当前主机的网卡设备信息
# 作者:wangshuji
# 版本:V0.2
# 联系:www.superopsmsb.com
# 定制基础变量
RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"
# 获取ip地址信息
IPDDR=$(ifconfig eth0 | grep -w inet | awk '{print $2}')
# 获取掩码地址信息
NETMAST=$(ifconfig eth0 | grep -w inet | awk '{print $4}')
# 获取广播地址信息
BROADCAST=$(ifconfig eth0 | grep -w inet | awk '{print $6}')
# 获取MAC地址信息
MACADDR=$(ifconfig eth0 | grep ether | awk '{print $2}')
# 打印网络基本信息
$GREEN---------主机网卡基本信息---------$END
echo -e "HOSTNAME: $RED `hostname` $END"
echo -e "IP: $RED $IPDDR $END"
echo -e "NetMask: $RED $NETMAST $END"
echo -e "Broadcast: $RED $BROADCAST $END"
echo -e "MAC Address: $RED $MACADDR $END"
$GREEN---------主机网卡基本信息---------$END
全局变量
基本定义
全局变量是什么
全局变量就是:在当前系统的所有环境下都能生效的变量。
基本语法
查看全局环境变量
env 只显示全局变量,一般结合 grep 和管道符来使用
printenv 效果与env等同
export 查看所有的环境变量,包括声明的过程等信息,一般不用
declare -x 效果与export类似
定义全局变量方法一:
变量=值
export 变量
定义全局变量方法二:(最常用)
export 变量=值
简单实践
查看所有的全局变量
[root@localhost ~]# env
XDG_SESSION_ID=4
HOSTNAME=localhost
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
...
查看制定的全局变量,grep筛选
[root@localhost ~]# env | grep SHELL
SHELL=/bin/bash
定制本地变量
[root@localhost ~]# envtype=local
[root@localhost ~]# echo $envtype
local
从全局变量中查看
[root@localhost ~]# env | grep envtype
[root@localhost ~]#
结果显示:
无法从全局变量中查看本地变量的名称
方法1定制全局变量
[root@localhost ~]# echo $envtype
local
[root@localhost ~]# env | grep envtype
[root@localhost ~]# export envtype
[root@localhost ~]# env | grep envtype
envtype=local
方法2定制全局变量
[root@localhost ~]# export myuser=root
[root@localhost ~]# env | grep myuser
myuser=root
鸡肋方法定制全局变量,别用,不好
[root@localhost ~]# declare -x mydir=/root
[root@localhost ~]# env | grep mydir
mydir=/root
清理全局变量
[root@localhost ~]# unset envtype myuser mydir
文件体系
变量文件
变量文件
在linux环境中,有很多目录下的文件都可以定制一些作用范围更广的变量,这些文件或文件所在的目录有:
作用范围在制定的用户范围:
~/.bashrc
~/.bash_profile
作用的范围在系统范围:
/etc/profile
/etc/profile.d/env_file_name
简单实践
bashrc 或 bash_profile 实践
查看未知的变量名
[root@localhost ~]# echo $NAME
定制变量名到文件中
[root@localhost ~]# echo NAME=shuji >> ~/.bashrc
[root@localhost ~]# source ~/.bashrc
[root@localhost ~]# echo $NAME
shuji
新开一个终端查看效果
[root@localhost ~]# echo $NAME
shuji
新开一个普通用户的终端查看效果
[root@localhost ~]# su - python
[python@localhost ~]$ echo $NAME
[python@localhost ~]$
清理.bashrc 文件里的变量,然后清除当前环境下的变量名
unset NAME
新开一个普通用户的终端查看效果
profile实践
系统下的所有用户都能查到变量
查看未知的变量名
[root@localhost ~]# echo $PROFILE
定制变量名到文件中
[root@localhost ~]# echo PROFILE=shuji >> /etc/profile
[root@localhost ~]# source /etc/profile
[root@localhost ~]# echo $PROFILE
shuji
新开一个终端查看效果
[root@localhost ~]# echo $PROFILE
shuji
新开一个普通用户的终端查看效果
[root@localhost ~]# su - python
[python@localhost ~]$ echo $PROFILE
shuji
[python@localhost ~]$
嵌套shell
export原理
原理解析
用户登录时:
用户登录到Linux系统后,系统将启动一个用户shell。
在这个shell中,可以使用shell命令或声明变量,也可以创建并运行 shell脚本程序。
运行脚本时:
运行shell脚本程序时,系统将创建一个子shell。 此时,系统中将有两个shell
- 一个是登录时系统启动的shell,另一个是系统为运行脚本程序创建的shell。
当一个脚本程序运行完毕,它的脚本shell将终止,可以返回到执行该脚本之前的shell。
意义解读
从这种意义上来说,用户可以有许多 shell,每个shell都是由某个shell(称为父shell)派生的。
在子shell中定义的变量只在该子shell内有效。如果在一个shell脚本程序中定义了一个变量,当该脚本程序运行时,这个定义的变量只是该脚本程序内的一个局部变量,其他的shell不能引用它,要使某个变量的值可以在其他shell中被改变,可以使用export命令对已定义的变量进行输出。
export命令将使系统在创建每一个新的shell时定义这个变量的一个拷贝。这个过程称之为变量输出。
实践解读
当前父shell中定义变量中,分为局部变量和全局变量,不同点是:
- 局部变量只能作用于本父shell,子shell无法继续使用
- 如果使用了export将局部变量定义为全局变量,那么子shell创建的时候会继承父shell的全局变量
嵌套实践
简单实践
查看父shell的脚本
[root@localhost ~]# cat father.sh
#!/bin/bash
# 定制全局变量
export _xing='王'
_name="书记"
_age="42"
echo "父shell信息: $_xing$_name,$_age"
sleep 3
# 调用child.sh进行验证,最好放在同一目录下
/bin/bash child.sh
echo "父shell信息: $_xing$_name,$_age"
查看子shell的脚本
#!/bin/bash
# 显示父shell的全局变量
echo "子shell信息: $_xing$_name,$_age"
# 同名变量 子shell 的优先级高于父shell,但是不会传递给父shell
_xing="王胖胖"
echo "子shell修改后的信息: $_xing"
执行测试效果
[root@localhost ~]# /bin/bash father.sh
父shell信息: 王书记,42
子shell信息: 王,
子shell修改后的信息: 王胖胖
父shell信息: 王书记,42
执行脚本
验证子脚本的同名变量是否会覆盖父脚本
验证子脚本修改变量后,父脚本是否受影响
内置变量
脚本相关
基础知识
脚本相关的变量解析
序号 | 变量名 | 解析 |
---|---|---|
1 | $0 | 获取当前执行的shell脚本文件名 |
2 | $n | 获取当前执行的shell脚本的第n个参数值,n=1..9,当n为0时表示脚本的文件名,如果n大于9就要用大括号括起来\$ |
3 | $# | 获取当前shell命令行中参数的总个数 |
4 | $? | 获取执行上一个指令的返回值(0为成功,非0为失败) |
简单实践
实践1 - $0 获取脚本的名称
[root@localhost ~]# cat get_name.sh
#!/bin/bash
# 获取脚本的名称
echo "我脚本的名称是: file.sh"
echo "我脚本的名称是:$0"
实践2 - $n 获取当前脚本传入的第n个位置的参数
[root@localhost ~]# cat get_args.sh
#!/bin/bash
# 获取指定位置的参数
echo "第一个位置的参数是: $1"
echo "第二个位置的参数是: $2"
echo "第三个位置的参数是: $3"
echo "第四个位置的参数是: $4"
实践3 - $# 获取当前脚本传入参数的数量
[root@localhost ~]# cat get_number.sh
#!/bin/bash
# 获取当前脚本传入的参数数量
echo "当前脚本传入的参数数量是: $#"
实践4 - $? 获取文件执行或者命令执行的返回状态值
[root@localhost ~]# bash nihao
bash: nihao: No such file or directory
[root@localhost ~]# echo $?
127
[root@localhost ~]# ls
get_name.sh get_args.sh get_number.sh
[root@localhost ~]# echo $?
0
字符串相关
基础知识
字符串相关的变量解析
字符串计数
${#file} 获取字符串的长度
字符串截取
- 语法为${var:pos:length} 表示对变量var从pos开始截取length个字符,pos为空表示0
${file:0:5} 从0开始,截取5个字符
${file:5:5} 从5开始,截取5个字符
${file::5} 从0开始,截取5个字符
${file:0-6:3} 从倒数第6个字符开始,截取之后的3个字符
${file: -4} 返回字符串最后四个字节,-前面是"空格"
简单实践
字符串实践
定制字符串内容
[root@localhost ~]# string_context="dsjfdsafjkldjsklfajkdsa"
[root@localhost ~]# echo $string_context
dsjfdsafjkldjsklfajkdsa
获取字符串长度
[root@localhost ~]# echo ${#string_context}
23
从0开始,截取5个字符
[root@localhost ~]# echo ${string_context:0:5}
dsjfd
从5开始,截取5个字符
[root@localhost ~]# echo ${string_context:5:5}
safjk
从0开始,截取5个字符
[root@localhost ~]# echo ${string_context::5}
dsjfd
从倒数第6个字符开始,截取之后的3个字符
[root@localhost ~]# echo ${string_context:0-6:3}
ajk
返回字符串最后四个字节,-前面是"空格"
[root@localhost ~]# echo ${string_context: -4}
kdsa
默认值相关
基础知识
语法解读
格式一:${变量名:-默认值}
变量a如果有内容,那么就输出a的变量值
变量a如果没有内容,那么就输出默认的内容
格式二:${变量名+默认值}
无论变量a是否有内容,都输出默认值
实践1 - 有条件的默认值
购买手机的时候选择套餐:
如果我输入的参数为空,那么输出内容是 "您选择的套餐是: 套餐 1"
如果我输入的参数为n,那么输出内容是 "您选择的套餐是: 套餐 n"
[root@localhost ~]# select_default_value.sh
#!/bin/bash
# 套餐选择演示
a="$1"
echo "您选择的手机套餐是: 套餐 ${a:-1}"
实践2 - 强制默认值
国家法律强制规定:
不管我说国家法定结婚年龄是 多少岁,都输出 国家法定结婚年龄(男性)是 22 岁
[root@localhost ~]# froce_default_value.sh
#!/bin/bash
# 默认值演示示例二
a="$1"
echo "国家法定结婚年龄(男性)是 ${a+22} 岁"
其他相关
基础知识
脚本相关的变量解析
序号 | 变量名 | 解析 |
---|---|---|
1 | $_ | 在此之前执行的命令或脚本的第一个内容 |
2 | $@ | 传给脚本的所有参数 |
3 | $* | 是以一个单字符串显示里所有向脚本传递的参数,与位置参数不同,参数可超过9个 |
4 | $$ | 是脚本运行的当前进程的ID号,作用是方便以后管理它杀掉他 |
5 | $! | 前一条命令进程的ID号,作用是方便以后管理它杀掉他 |
简单实践
实践1 - 其他变量的作用
[root@localhost ~]# cat get_other.sh
#!/bin/sh
echo "脚本执行命令的第一个内容: $_"
echo "传递给当前脚本的所有参数是: $@"
echo "单字符串显示所有参数: $*"
echo "当前脚本执行时候的进程号是: $$"
sleep 5 &
echo "上一条命令执行时候的进程号是: $!"
实践2 - $$ 获取当前的进程号
查看当前的进程号
[root@localhost ~]# echo $$
4759
[root@localhost ~]# ps aux | grep 4759
root 4759 0.0 0.0 116712 3356 pts/1 Ss 00:11 0:00 -bash
root 5547 0.0 0.0 112828 984 pts/1 S+ 02:00 0:00 grep --color=auto 4759
杀死当前的进程,等于正在使用的bash被杀死了
[root@localhost ~]# kill -9 4759
───────────────────────────────────────────
Session stopped
- Press <return> to exit tab
- Press R to restart session
- Press S to save terminal output to file
实践3 - $@ 和 $* 的区别
定制father脚本
[root@localhost ~]# cat father.sh
#!/bin/bash
echo "$0: 所有的参数 $@"
echo "$0: 所有的参数 $*"
echo '将 $* 值传递给 child-1.sh 文件'
/bin/bash child-1.sh "$*"
echo '将 $@ 值传递给 child-2.sh 文件'
/bin/bash child-2.sh "$@"
定制两个child脚本
[root@localhost ~]# cat child-1.sh
#!/bin/bash
echo "$0: 获取所有的参数 $1"
[root@localhost ~]# cat child-2.sh
#!/bin/bash
echo "$0: 获取所有的参数 $1"
执行 father.sh 脚本
[root@localhost ~]# /bin/bash father.sh 1 2 3
father.sh: 所有的参数 1 2 3
father.sh: 所有的参数 1 2 3
将 $* 值传递给 child-1.sh 文件
child-1.sh: 获取所有的参数 1 2 3
将 $@ 值传递给 child-2.sh 文件
child-2.sh: 获取所有的参数 1
验证结果:
变量进阶
高级赋值
基础知识
简介
所谓的高级赋值,是另外的一种变量值获取方法,这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分的内容主要还是在字符串的基础上,如何更精细的获取特定的信息内容:主要涉及到的内容样式如下:
字符串截取按分隔符截取: # 右 % 左
${file#/} 删除匹配结果,保留第一个/右边的字符串
${file##/} 删除匹配结果,保留最后一个/右边的字符串
${file%/} 删除匹配结果,保留第一个/左边的字符串
${file%%/} 删除匹配结果,保留最后一个/左边的字符串
注意:
匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
${file/dir/path} 把第一个dir替换成path:/path1/dir2/dir3/n
${file//dir/path} 把所有dir替换成path:/path1/path2/path3/n
${file/#dir/path} 将从左侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
${file/%dir/path} 将从右侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
注意:
如果匹配内容使用的是正则符号,应该注意正则符号的写法
字符串转换
${file^^} 把file中的所有小写字母转换为大写
${file,,} 把file中的所有大写字母转换为小写
简单实践
实践1-字符串截取
字符串截取示例
[root@localhost ~]# string=abc12342341
[root@localhost ~]# echo ${string#a*3}
42341
[root@localhost ~]# echo ${string#c*3}
abc12342341
[root@localhost ~]# echo ${string#*c1*3}
42341
[root@localhost ~]# echo ${string##a*3}
41
[root@localhost ~]# echo ${string%3*1}
abc12342
[root@localhost ~]# echo ${string%%3*1}
abc12
字符串截取赋值
[root@localhost ~]# file=/var/log/nginx/access.log
[root@localhost ~]# filename=${file##*/}
[root@localhost ~]# echo $filename
access.log
[root@localhost ~]# filedir=${file%/*}
[root@localhost ~]# echo $filedir
/var/log/nginx
实践2-字符串替换
字符串替换示例
[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# echo ${str/apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str//apple/APPLE}
APPLE, tree, APPLE tree, APPLE
[root@localhost ~]# echo ${str/#apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str/%apple/APPLE}
apple, tree, apple tree, APPLE
使用正则的情况下,代表尽可能多的匹配
[root@localhost ~]# file=dir1@dir2@dir3@n.txt
[root@localhost ~]# echo ${file/#d*r/DIR}
DIR3@n.txt
[root@localhost ~]# echo ${file/%3*/DIR}
dir1@dir2@dirDIR
实践3-字符串转换
[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# upper_str=${str^^}
[root@localhost ~]# echo ${upper_str}
APPLE, TREE, APPLE TREE, APPLE
[root@localhost ~]# lower_str=${upper_str,,}
[root@localhost ~]# echo ${lower_str}
apple, tree, apple tree, apple
嵌套变量
基础知识
场景现象
场景1:我们知道,命令变量的的表现样式:
ver=$(命令)
-- 执行原理是,当`` 或者 $() 范围中存在能够正常解析的命令的话,会先执行命令,然后将命令执行的结果交个一个变量名。
场景2:它还有另外一种样式 -- 普通变量的第三种样式双引号
ming=shuji; name="wang-$ming"
-- 解析原理:双引号会首先查看变量值范围内是否有可以解析的变量名,如果有的话,将解析后的结果放到变量值范围内,组合成一个新的变量值,然后交给变量名。
上面的两种场景的特点就在于,一个命令行中,借助于$() 或者 "" 发起一次隐藏的命令执行,但是有些场景下,表面上的一个命令需要发起更深一层的命令执行才可以实现指定的功能。在这种场景下,无论是$() 还是 ""都无法满足要求了
场景示例
循环遍历演示
[root@localhost ~]# for i in {1..10}; do echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读:这里出现一层隐藏命令执行
1 {1..10} 会自动进行命令解析
[root@localhost ~]# echo {1..10}
1 2 3 4 5 6 7 8 9 10
然后执行for命令
for i in 1 2 3 4 5 6 7 8 9 10
双层隐藏命令解读
[root@localhost ~]# n=10
[root@localhost ~]# for i in {1..$n}; do echo "$i "; done
{1..10}
示例解读:
在for语句中,其实我们的目的与上面的演示一样,但是区别在于这里有两层隐藏的命令执行
1 $n 需要解析成 10
2 {1..10} 需要解析成 1 2 3 4 5 6 7 8 9 10
最后执行 for命令 for 1 2 3 4 5 6 7 8 9 10
问题:
linux命令行,默认情况下是无法执行两层隐藏命令的执行的,在有些场景中,我们可以通过$() 来实现多层命令的解读,示例如下:
[root@localhost ~]# cmd=who
[root@localhost ~]# echo $(${cmd}ami)
root
但是,这里我们无法实现,因为 {1..10} 不是命令。
[root@localhost ~]# for i in $({1..$n}); do echo "$i "; done
-bash: {1..10}: 未找到命令
解决方法
在shell中,它提供了一个专属的命令,可以实现多层隐藏命令的解析,不仅仅能够解析,还能够将相关环境的属性重现,从而实现多层隐藏命令的顺利执行。这个命令就是 eval。
eval原理
1 eval命令将会首先扫描命令行整体
2 发现解析则解析,发现执行则预先执行,实现所有隐藏命令的成功执行
3 将隐藏命令执行的最终结果进行置换
4 最后执行命令行表面的命令。
简单实践
实践1-eval简单实践
for循环演示
[root@localhost ~]# n=10
[root@localhost ~]# for i in $(eval echo {1..$n}); do echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读
1 命令改造$(eval echo {1..$n})
1-1 $n先解析为10,命令替换为 {1..10}
1-2 通过 eval 带入 echo 命令环境
1-3 $() 执行 echo {1..10} 输出为 1 2 3 4 5 6 7 8 9 10
2 整体置换命令结果
for i in 1 2 3 4 5 6 7 8 9 10
实践2-eval的命令扩展演示
查看文件内容
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world
脚本内容演示
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world
[root@localhost ~]# cmd="cat infile.txt"
[root@localhost ~]# echo $(${cmd})
hello-in-world
[root@localhost ~]# echo ${cmd}
cat infile.txt
不是我们想要的,我们可以借助于eval 和 $() 方式来实现隐藏命令的解读
[root@localhost ~]# eval ${cmd}
hello-in-world
[root@localhost ~]# echo $(${cmd})
hello-in-world
实践3-eval变量名的预制解析
定制嵌套的环境变量
[root@localhost ~]# str=a
[root@localhost ~]# num=1
[root@localhost ~]# $str$num=hello
-bash: a1=hello: 未找到命令
借助于eval命令来实现
[root@localhost ~]# eval $str$num=hello
[root@localhost ~]# echo $a1
hello
借助于eval实现变量名的嵌套
[root@localhost ~]# eval $str=$a1
[root@localhost ~]# echo $a
hello
解读:
$str 就是 a,$a1就是hello,
eval执行的命令就是 a=hello
综合案例
免密认证
案例需求
A 以主机免密码认证 连接到 远程主机B
我们要做主机间免密码认证需要做三个动作
1、本机生成密钥对
2、对端机器使用公钥文件认证
3、验证
手工演示
本地主机生成秘钥对
[root@localhost ~]# ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Ncra/fPpaVs+M18l9Kn7CQq33zmWQSoJ/ujuugCkNjM root@localhost
The key's randomart image is:
+---[RSA 2048]----+
| |
| |
| . o . |
| o . + . . o.|
| E . S . . +.o|
|. + . o o o ..o.|
| .. ..+..o =|
| . .oo+ =%+|
| o*+ ooBO*O|
+----[SHA256]-----+
将公钥信息传递给远程主机的指定用户
[root@localhost ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
本地主机测试验证效果
[root@localhost ~]# ssh root@10.0.0.12 "ifconfig eth0 | grep netmas"
inet 10.0.0.12 netmask 255.255.255.0 broadcast 10.0.0.255
简单实践
remotehost_sshkey_auth.sh
#!/bin/bash
# 定制普通变量
user_dir="/root"
login_uesr='root'
login_pass='123456'
# 定制数组变量
target_type=(部署 免密 退出)
# 定制安装软件的函数
expect_install(){
yum install expect -y >> /dev/null
echo "软件安装完毕"
}
# 定制ssh秘钥对的生成
sshkey_create(){
# 清理历史秘钥
[ -d ${user_dir}/.ssh ] && rm -rf ${user_dir}/.ssh
# 生成新的秘钥
ssh-keygen -t rsa -P "" -f ${user_dir}/.ssh/id_rsa >> /dev/null
echo "秘钥生成完毕"
}
# 定制expect的认证逻辑
expect_process(){
# 注意:这里不要乱用$1,可以参考函数和脚本间的数组传参
command="$@"
expect -c "
spawn ${command}
expect {
\"*yes/no*\" {send \"yes\r\"; exp_continue}
\"*password*\" {send \"${login_pass}\r\"; exp_continue}
\"*Password*\" {send \"${login_pass}\r\";}
}"
}
# 跨主机密码认证
sshkey_auth(){
local host_list="$1"
for i in ${host_list}
do
command="/usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub"
remote="${login_uesr}@$i"
expect_process ${command} ${remote}
done
}
# 定制服务的操作提示功能函数
menu(){
echo -e "\e[31m---------------管理平台操作界面---------------"
echo -e " 1: 秘钥准备 2: 免密认证 3: 退出操作"
echo -e "-------------------------------------------\033[0m"
}
# 定制脚本帮助信息
Usage(){
echo "请输入有效的操作标识!!!"
}
# 定制业务逻辑
while true
do
menu
read -p "> 请输入要操作的目标类型: " target_id
if [ ${target_type[$target_id-1]} == "部署" ];then
echo "开始部署秘钥环境..."
expect_install
sshkey_create
elif [ ${target_type[$target_id-1]} == "免密" ];then
read -p "> 请输入免密10.0.0网段主机的范围,示例{12..19}: " num_list
# eval的隐藏命令解析
ip_list=$(eval echo 10.0.0.${num_list})
sshkey_auth ${ip_list}
elif [ ${target_type[$target_id-1]} == "退出" ];then
echo "准备退出管理操作界面..."
exit
else
Usage
fi
done
脚本执行效果
[root@localhost ~]# /bin/bash remotehost_sshkey_auth.sh
---------------管理平台操作界面---------------
1: 秘钥准备 2: 免密认证 3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署秘钥环境...
软件安装完毕
秘钥生成完毕
---------------管理平台操作界面---------------
1: 秘钥准备 2: 免密认证 3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入免密10.0.0网段主机的范围,示例{12..19}: {12..13}
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
---------------管理平台操作界面---------------
1: 秘钥准备 2: 免密认证 3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 3
准备退出管理操作界面...
《三体》中有句话——弱小和无知不是生存的障碍,傲慢才是。
所以我们不要做一个小青蛙