13变量进阶

3 阅读7分钟

变量进阶

高级赋值

基础知识

所谓的高级赋值,是另外的一种变量值获取方法,这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分的内容主要还是在字符串的基础上,如何更精细的获取特定的信息内容,主要涉及到的内容样式如下:

字符串截取按分隔符截取: # 右(匹配左边)  % 左(匹配右边)
    ${file#/}	   	删除匹配结果,保留第一个/右边的字符串
    ${file##/}		删除匹配结果,保留最后一个(贪婪模式)/右边的字符串
    ${file%/}		删除匹配结果,保留第一个/左边的字符串
    ${file%%/}		删除匹配结果,保留最后一个(贪婪模式)/左边的字符串
    注意:
        匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
    ${file/dir/path}	把第一个dir替换成path
    ${file//dir/path}	把所有dir替换成path
    ${file/#dir/path} 	匹配左边dir替换成path
    ${file/%dir/path} 	匹配右边dir替换成path
    注意:
		如果匹配内容使用的是正则符号,应该注意正则符号的写法
字符串转换
    ${file^^}		把file中的所有小写字母转换为大写
    ${file,,}		把file中的所有大写字母转换为小写

简单实践

# 实践1-字符串截取

# 字符串截取示例
string=abc12342341
# 匹配左边"a*3",返回右边的内容
echo ${string#a*3}
42341
# 匹配左边"c*3",匹配不上返回字符串本身
echo ${string#c*3}
abc12342341
# 匹配左边"*c1*3",返回右边的内容
echo ${string#*c1*3}
42341
# 贪婪模式匹配左边"a*3",返回右边的内容
echo ${string##a*3}
41
# 匹配右边"3*1",返回左边的内容
echo ${string%3*1}
abc12342
# 贪婪模式匹配右边"3*1",返回左边的内容
echo ${string%%3*1}
abc12

# 字符串截取赋值
file=/var/log/nginx/access.log

filename=${file##*/};echo $filename
access.log

filedir=${file%/*};echo $filedir
/var/log/nginx

# 实践2-字符串替换
str="apple, tree, apple tree, apple"
# 将第一个tree替换成TREE
echo ${str/tree/TREE}
apple, TREE, apple tree, apple
# 将所有tree替换成TREE
echo ${str//tree/TREE}
apple, TREE, apple TREE, apple

# 从左边开始匹配
echo ${str/#tree/TREE}
apple, tree, apple tree, apple

echo ${str/#apple/APPLE}
APPLE, tree, apple tree, apple

# 从右边开始匹配
echo ${str/%tree/TREE}
apple, tree, apple tree, apple

echo ${str/%apple/APPLE}
apple, tree, apple tree, APPLE

# 使用正则的情况下,代表尽可能多的匹配
file=dir1@dir2@dir3@n.txt

echo ${file/#d*r/DIR}
DIR3@n.txt

echo ${file/%3*/DIR}
dir1@dir2@dirDIR

# 实践3-字符串转换
str="apple, tree, apple tree, apple"

upper_str=${str^^};echo $upper_str
APPLE, TREE, APPLE TREE, APPLE

lower_str=${upper_str,,};echo ${lower_str}
apple, tree, apple tree, apple

嵌套变量

基础知识

场景现象

场景1:我们知道,命令变量的的表现样式:
	ver=$(命令)
	-- 执行原理是,当`` 或者 $() 范围中存在能够正常解析的命令的话,会先执行命令,然后将命令执行的结果交个一个变量名。
场景2:它还有另外一种样式 -- 普通变量的第三种样式双引号 
	ming=shuji; name="wang-$ming"
	-- 解析原理:双引号会首先查看变量值范围内是否有可以解析的变量名,如果有的话,将解析后的结果放到变量值范围内,组合成一个新的变量值,然后交给变量名。
上面的两种场景的特点就在于,一个命令行中,借助于$() 或者 "" 发起一次隐藏的命令执行,但是有些场景下,表面上的一个命令需要发起更深一层的命令执行才可以实现指定的功能。在这种场景下,无论是$() 还是 ""都无法满足要求了

场景示例

# 循环遍历演示
for i in {1..10};do echo -n "$i ";done
1 2 3 4 5 6 7 8 9 10 
# 这里出现一层隐藏命令执行,{1..10} 会自动进行命令解析
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;do echo -n "$i ";done
1 2 3 4 5 6 7 8 9 10

# 双层隐藏命令解读
# for语句中,其实我们的目的与上面的演示一样,但是区别在于这里有两层隐藏的命令执行
# $n 需要解析成 10,{1..10} 需要解析成 1 2 3 4 5 6 7 8 9 10,最后执行 for命令 for 1 2 3 4 5 6 7 8 9 10。但是这里$n没有解析成功
n=10
for i in {1..$n};do echo -n "$i ";done
{1..10} 

# linux命令行,默认情况下是无法执行两层隐藏命令的执行的,在有些场景中,我们可以通过$()来实现多层命令的解读,示例如下:
cmd=who

echo $(${cmd}ami)
root

# 但是上面的例子无法实现,因为{1..10}不是命令
for i in $({1..$n});do echo -n "$i ";done
-bash: {1..10}: command not found

解决方法

在shell中,它提供了一个专属的命令,可以实现多层隐藏命令的解析,不仅仅能够解析,还能够将相关环境的属性重现,从而实现多层隐藏命令的顺利执行。这个命令就是 eval
eval原理
	1 eval命令将会首先扫描命令行整体
	2 发现解析则解析,发现执行则预先执行,实现所有隐藏命令的成功执行
	3 将隐藏命令执行的最终结果进行置换
	4 最后执行命令行表面的命令

简单实践

# 实践1-eval简单实践
for i in $(echo {1..$n});do echo -n "$i ";done
{1..10} 
# 示例解读:命令改造$(eval echo {1..$n})
# 1. $n先解析为10,命令替换为 {1..10}
# 2. 通过 eval 带入 echo 命令环境
# 3. $() 执行 echo {1..10} 输出为 1 2 3 4 5 6 7 8 9 10
# 4. 整体置换命令结果:for i in 1 2 3 4 5 6 7 8 9 10
for i in $(eval echo {1..$n});do echo -n "$i ";done
1 2 3 4 5 6 7 8 9 10 

# 实践2-eval的命令扩展演示
echo 'hello-in-world' > infile.txt
cmd="cat infile.txt"

echo ${cmd}
cat infile.txt

# 我们可以借助于 eval 和 $() 方式来实现隐藏命令的解读
eval ${cmd}
hello-in-world

echo $(${cmd})
hello-in-world

# 实践3-eval变量名的预制解析
# 定制嵌套的环境变量
str=a
num=1
# -bash: a1=hello: command not found
$str$num=hello
# 借助于eval命令来实现
eval $str$num=hello

echo $a1
hello

# 借助于eval实现变量名的嵌套
# $str就是a,$a1就是hello,eval执行的命令就是a=hello
eval $str=$a1

echo $a
hello

综合案例

免密认证

案例需求

A 以主机免密码认证 连接到 远程主机B
我们要做主机间免密码认证需要做三个动作
    1、本机生成密钥对
    2、对端机器使用公钥文件认证
    3、验证

手工演示

# 本地主机生成秘钥对
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:pjUBMWZCbtWkumwq/IYavWEkdswHsqQJvyzK0cqFu6U root@localhost.localdomain
The key's randomart image is:
+---[RSA 2048]----+
|   .o B+.        |
|   . = +.        |
|.o .o . .        |
|+o=...   .       |
|+oo+..  S        |
|.=+o.. + .       |
|+oB++ .          |
|=*B*             |
|+E*.             |
+----[SHA256]-----+

# 将公钥信息传递给远程主机的指定用户,按照提示输入yes和密码即可
ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.91.100

# 本地主机测试验证效果
ssh root@192.168.91.100 "ifconfig ens33 | grep netmask"
        inet 192.168.91.100  netmask 255.255.255.0  broadcast 192.168.91.255

简单实践

remotehost_sshkey_auth.sh

#!/bin/bash
# 功能:设置ssh跨主机免密码认证
# 版本:v0.1
# 作者:example
# 联系:www.example.com

# 定制普通变量
user_dir="/root"
login_uesr='root'
login_pass='zhangjiabao'

# 定制数组变量
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,可以参考函数和脚本间的数组传参
    local 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="$@"
    local i
    for i in ${host_list}
    do
        local command="/usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub"
        local 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 "> 请输入免密192.168.91网段主机的范围,示例{102..103}: " num_list
        # eval的隐藏命令解析
        ip_list=$(eval echo 192.168.91.${num_list})
        sshkey_auth ${ip_list}
    elif [ ${target_type[$target_id-1]} == "退出" ];then
        echo "准备退出管理操作界面..."
        exit
    else
        Usage
    fi
done
# 准备好192.168.91.102和192.168.91.103两个节点进行测试
/bin/bash remotehost_sshkey_auth.sh
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署秘钥环境...
软件安装完毕
秘钥生成完毕
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入免密192.168.91网段主机的范围,示例{102..103}: {102..103}
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.91.102
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '192.168.91.102 (192.168.91.102)' can't be established.
ECDSA key fingerprint is SHA256:fFK1C9qzpNWRtBAq1dZHIEGT1fMtDD3gAQ2gWAslYTw.
ECDSA key fingerprint is MD5:ed:76:a9:08:3f:0a:a5:9a:3a:ea:f3:81:26:25:95:9d.
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@192.168.91.102's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@192.168.91.102'"
and check to make sure that only the key(s) you wanted were added.

spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.91.103
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '192.168.91.103 (192.168.91.103)' can't be established.
ECDSA key fingerprint is SHA256:fFK1C9qzpNWRtBAq1dZHIEGT1fMtDD3gAQ2gWAslYTw.
ECDSA key fingerprint is MD5:ed:76:a9:08:3f:0a:a5:9a:3a:ea:f3:81:26:25:95:9d.
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@192.168.91.103's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@192.168.91.103'"
and check to make sure that only the key(s) you wanted were added.

---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 3
准备退出管理操作界面...

# 本地主机测试验证效果
ssh root@192.168.91.102 "ifconfig ens33 | grep netmask"
        inet 192.168.91.102  netmask 255.255.255.0  broadcast 192.168.91.255

ssh root@192.168.91.103 "ifconfig ens33 | grep netmask"
        inet 192.168.91.103  netmask 255.255.255.0  broadcast 192.168.91.255