02shell变量

0 阅读5分钟

shell变量

变量基础

变量场景

数据存储

所谓的数据存储,我们从三方面来理解这句话:

  1. 数据保存到哪里:各种媒介,CPU、内存、磁盘、磁带、网盘...
  2. 数据保存的效果:完整、安全、有效
  3. 数据保存的单元:存储空间

数据的存储空间默认不是一个整体,而是由一个个的存储单元组成,每一个存储单元都有一个唯一的整数编号,我们称这个编号为:地址

  • 存储单元的作用:存储数据+读写数据
  • 存储空间大小:1字节(B) = 8bit == 00000000
  • 地址特点:十六进制,例如:0x20000000

image.png

对于数据的存储来说,主要有两种样式:物理地址和逻辑地址

  • 物理地址:内存或硬盘中真正存储数据的位置,也就是说通过磁盘设备查找的位置
  • 逻辑地址:用于查找物理地址的存储块地址叫逻辑地址。程序中用的地址一般都是逻辑地址
  • 逻辑地址包括两部分:起始值(十六进制)+偏移量(十六进制)
  • 数据表的描述主要是逻辑地址,因为程序一般存储的是逻辑地址

数据一旦存储下来就不再发生变化了,而程序中可能会在很多场景中用到同一个数据,就会出现两个问题:

  1. 物理地址人听得懂,机器看不懂 ,所以用逻辑地址找物理地址
  2. 软件可以通过逻辑地址找到数据地址,但是软件不懂场景,所以人用逻辑地址的别名来代指向同一个xx地址

应用程序中为了多场景应用这个逻辑地址的别名,本质上就是“变量”

变量场景

变量的本质其实就是通过一个名称帮助程序快速找到内存中具体数据的地址,变量说白了就是指向xx值

编程语言在数据调用层面分类的话,可以分为两类:

  • 静态编译语言,使用变量前,先声明变量类型,之后类型不能改变,在编译时检查。如:java,c
  • 动态编译语言,不用事先声明,可随时改变类型。如:bash,Python

根据编程语言在使用变量的程度上,可以划分为强类型、弱类型语言:

  • 强类型语言,不同类型数据操作,必须经过强制转换成同一类型才能运算。如java , c# ,python。示例:print('test' + 10) 提示出错,不会自动转换类型;print('test' + str(10)) 结果为test10,需要显示转换类型
  • 弱类型语言,语言的运行时会隐式做数据类型转换,无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换,变量无须事先定义可直接调用。如:bash ,php,javascript。示例:echo 'aaa'+222

变量定义

变量定义

变量包括三部分:变量名 - 不变的,变量值 - 变化的,赋值动作 - 变量名指向变量值。表现样式:变量名=变量值

变量的全称应该称为变量赋值,简称变量。在工作中,我们一般指xx是变量,其实是将这两者作为一个整体来描述了。准确来说,我们一般所说的变量其实指的是:变量名

命名规范

  1. 名称有意义
  2. 名称细节
    • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
    • 中间不能有空格,可以使用下划线_
    • 不能使用标点符号
    • 不能使用bash里的关键字(可用help命令查看保留关键字)
  3. 命名样式
    • 大驼峰HelloWorld,每个单词的首字母是大写
    • 小驼峰helloWorld,第一个单词的首字母小写,后续每个单词的首字母是大写
    • 下划线: Hello_World
    • 大小写字母: helloworld, HELLOWORLD

注意:对于开发人员来说,他们对于变量名的规范比较多,什么类、函数、对象、属性、命名空间等都有要求。对于运维人员来说,记住一个词 -- 有意义

变量分类

shell中的变量分为三大类:

  • 本地变量,变量名仅仅在当前终端有效
  • 全局变量,变量名在当前操作系统的所有终端都有效
  • shell内置变量,shell解析器内部的一些功能参数变量

注意:这里的变量分类的特点仅仅是从字面上来理解的,因为在实际操作的时候,还会涉及到环境优先级的问题。所以生产中对于这三者的划分没有特别大的强制

基本操作

变量查看

# 语法解析:基本格式,$变量名
# 查看默认的shell类型
echo $SHELL
/bin/bash

变量定义

普通语法解析:基本格式,变量名=变量值;注意:= 两侧不允许有空格

# 查看一个空值变量名
echo $myname

# 定制变量实践
myname=zhang

echo $myname
zhang

# 错误的定制变量命令
echo $myage

myage = 18
-bash: myage: command not found

echo $myage

类型变量定义:命令语法,declare 参数 变量名=变量值

declare [-aAfFgilrtux] [-p] [name[=value] ...] typeset [-aAfFgilrtux] [-p] [name[=value] ...]声明变量或赋予它们属性,如果没有指定名称,则显示变量的值。-p选项将显示每个名称的属性和值。当-p与名称参数一起使用时,将忽略其他选项。当提供-p而不提供名称参数时,它将显示具有附加选项指定的属性的所有变量的属性和值。如果-p没有提供其他选项,那么declare将显示所有shell变量的属性和值。-f选项将限制显示shell函数。-F选项禁止显示函数定义;仅打印函数名称和属性。如果使用shopt启用了extdebug shell选项,那么还会显示定义该函数的源文件名和行号。-F选项表示-f。-g选项强制在全局范围内创建或修改变量,即使在shell函数中执行declare也是如此。在所有其他情况下都会忽略它。以下选项可用于将输出限制为具有指定属性的变量或为变量赋予属性:

选项说明
-a每个名称都是一个索引数组变量
-A每个名称都是一个关联数组变量
-f仅使用函数名
-i该变量被视为一个整数;在为变量赋值时执行算术计算
-l当为变量赋值时,所有大写字符都转换为小写字符。禁用大写属性
-r将名称设置为只读。这些名称不能被随后的赋值语句赋值或取消设置。(变量的值无法改变且不能unset)
-t为每个名称指定跟踪属性。跟踪函数从调用shell继承DEBUG和RETURN陷阱。trace属性对变量没有特殊的含义
-u当为变量赋值时,所有小写字符都转换为大写字符。禁用小写属性
-x标记名称,以便通过环境导出到后续命令(标记变量为全局变量,类似于export)

注意:在生产场景中,这种方式比较鸡肋,使用频率很低

# 设定制定类型的变量值
declare -i mynum='test'

echo $mynum
0

declare -i mynum='123456'

echo $mynum
123456

# 设定只读类型变量
declare -r myread1="aaa"
myread2=myread
readonly myread2

# 查看只读变量
declare -r | grep myread
declare -r myread1="aaa"
declare -r myread2="myread"

readonly -p | grep myread
declare -r myread1="aaa"
declare -r myread2="myread"

# 无法使用unset删除只读变量
unset myread1 myread2
-bash: unset: myread1: cannot unset: readonly variable
-bash: unset: myread2: cannot unset: readonly variable

# 借助于exit方式删除只读变量
exit

# 重新登录后再次查看
declare -r | grep myread

变量移除

# 语法解析:基本格式,unset 变量名
myname=zhang

echo $myname
zhang

# 移除变量名
unset myname
echo $myname

本地变量

本地变量分类

所谓的本地变量就是:在当前系统的某个环境下才能生效的变量,作用范围小

本地变量按照变量值的生成方式包含两种:

  • 普通变量,自定义变量名和变量值
  • 命令变量,自定义变量名,而变量值是通过一条命令获取的

普通变量

基本格式

序号样式要点
方式一变量名=变量值变量值必须是一个整体,中间没有特殊字符,"=" 前后不能有空格
方式二变量名='变量值'原字符输出,我看到的内容,我就输出什么内容
方式三变量名="变量值"如果变量值范围内,有可以解析的变量A,那么首先解析变量A,将A的结果和其他内容组合成一个整体,重新赋值给变量B

习惯:数字不加引号,其他默认加双引号。因为bash属于弱类型语言,默认会将所有内容当成字符串

# 查看默认的空值变量
echo $name

# 方法1设定变量
name=zhang

echo $name
zhang

# 方法2设定变量
name='zhang1'

echo $name
zhang1

# 方法3设定变量
name="zhang2"

echo $name
zhang2

# 清理变量
unset name

# 方法1设定变量的要点,变量值必须是一个整体
# -bash: world: command not found,空格是一个特殊符号,表示两条命令的隔开,它将hello和world当成两条命令了,所以报错信息是命令找不到
name=hello world

# 方法2设定变量
name='hello world'

echo $name
hello world

# 方法3设定变量
name="hello world2"

echo $name
hello world2

# 清理变量
unset name

# 定制基础变量
name=hello

echo $name
hello

# 方法2设定变量
name2='$name-world'

echo $name2
$name-world

# 方法3设定变量
name2="$name-world"

echo $name2
hello-world

命令变量

基本格式

  • 定义方式一,变量名=命令,这里是反引号
  • 定义方式二,变量名=$(命令)

执行流程:执行 ` 或者 $() 范围内的命令,将命令执行后的结果,赋值给新的变量名

# 查看默认的空值变量
echo $myuser

# 方法1设定变量名
myuser=`whoami`

echo $myuser
root

# 查看默认的空值变量
echo $mydir

# 方法2设定变量名
mydir=$(pwd)

echo $mydir
/root

# 清理变量
unset mydir myuser

# 自动生成一系列数字
NUM=`seq 10`

echo $NUM
1 2 3 4 5 6 7 8 9 10

# 文件备份添加时间戳
touch file-a
cp file-a file-a-$(date +%F)

ls file-a*
file-a  file-a-2023-12-05

rm -f file-a*

简单小综合实践

cat > get_netinfo_v2.sh << "EOF"
#!/bin/bash
# 功能:获取当前主机的网卡设备信息
# 作者:test
# 版本:V0.2
# 联系:www.test.com

# 定制基础变量
RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"

# 获取ip地址信息
IPDDR=$(ifconfig ens33 | grep -w inet | awk '{print $2}')
# 获取掩码地址信息
NETMAST=$(ifconfig ens33 | grep -w inet | awk '{print $4}')
# 获取广播地址信息
BROADCAST=$(ifconfig ens33 | grep -w inet | awk '{print $6}')
# 获取MAC地址信息
MACADDR=$(ifconfig ens33 | 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
EOF

/bin/bash get_netinfo_v2.sh
---------主机网卡基本信息---------
 HOSTNAME:      test
 IP:            192.168.91.101
 NetMask:       255.255.255.0
 Broadcast:     192.168.91.255
 MAC Address:   00:0c:29:2c:2e:d5
---------主机网卡基本信息---------

rm -f get_netinfo_v2.sh

全局变量

基本操作

全局变量就是:在当前系统的所有环境下都能生效的变量

定义全局变量

  • 方法一,变量=值,export 变量
  • 方法二(最常用),export 变量=值

查看全局环境变量

  • env,只显示全局变量,一般结合grep和管道符来使用
  • printenv,效果与env等同
  • export,查看所有的环境变量,包括声明的过程等信息,一般不用
  • declare -x,效果与export类似
# 查看指定的全局变量
env | grep SHELL
SHELL=/bin/bash

# 定制本地变量
envtype=local

echo $envtype
local

# 从全局变量中查看
# 结果显示:无法从全局变量中查看本地变量的名称
env | grep envtype

# 方法1定制全局变量
export envtype

env | grep envtype
envtype=local

# 方法2定制全局变量
export myuser=root

env | grep myuser
myuser=root

# 鸡肋方法定制全局变量
declare -x mydir=/root

env | grep mydir
mydir=/root

# 清理全局变量
unset envtype myuser mydir

文件体系

变量文件

在linux环境中,有很多目录下的文件都可以定制一些作用范围更广的变量,这些文件或文件所在的目录有:

  • 作用范围在指定的用户范围,~/.bashrc, ~/.bash_profile
  • 作用的范围在系统范围,/etc/profile, /etc/profile.d/env_file_name

.bashrc实践

# 查看未知的变量名
echo $NAME
# 定制变量名到文件中
echo NAME=zhang >> ~/.bashrc
source ~/.bashrc

echo $NAME
zhang

# 新开一个终端查看效果
echo $NAME
zhang

# 切换到其他用户,在终端查看效果
su zhang
# NAME没有值
echo $NAME
exit

# 清理.bashrc文件里的变量,然后清除当前环境下的变量名
# 删除最后一行
sed -i '$ d' ~/.bashrc
unset NAME

profile实践

# 查看未知的变量名
echo $PROFILE
# 定制变量名到文件中
echo PROFILE=test >> /etc/profile
source /etc/profile

echo $PROFILE
test

# 新开一个终端查看效果
echo $PROFILE
test

# 切换到其他用户,在终端查看效果
su - zhang

echo $PROFILE
test

exit

# 清理/etc/profile文件里的变量,然后清除当前环境下的变量名
# 删除最后一行
sed -i '$ d' /etc/profile
unset PROFILE

嵌套shell

export原理

  • 用户登录时,用户登录到Linux系统后,系统将启动一个用户shell。在这个shell中,可以使用shell命令或声明变量,也可以创建并运行shell脚本程序
  • 运行脚本时,运行shell脚本程序时,系统将创建一个子shell。此时,系统中将有两个shell。一个是登录时系统启动的shell,另一个是系统为运行脚本程序创建的shell。当一个脚本程序运行完毕,它的脚本shell将终止,可以返回到执行该脚本之前的shell

image.png

从这种意义上来说,用户可以有许多shell,每个shell都是由某个shell(称为父shell)派生的。在子shell中定义的变量只在该子shell内有效。如果在一个shell脚本程序中定义了一个变量,当该脚本程序运行时,这个定义的变量只是该脚本程序内的一个局部变量,其他的shell不能引用它,要使某个变量的值可以在其他shell中被改变,可以使用export命令对已定义的变量进行输出

export命令将使系统在创建每一个新的shell时定义这个变量的一个拷贝,这个过程称之为变量输出

当前父shell中定义变量中,分为局部变量和全局变量,不同点是:

  • 局部变量只能作用于本父shell,子shell无法继续使用
  • 如果使用了export将局部变量定义为全局变量,那么子shell创建的时候会继承父shell的全局变量
# 查看父shell的脚本
cat > father.sh << "EOF"
#!/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"
EOF

# 查看子shell的脚本
cat > child.sh << "EOF"
#!/bin/bash
# 显示父shell的全局变量
echo "子shell信息: $_xing$_name,$_age"

# 同名变量子shell的优先级高于父shell,但是不会传递给父shell
_xing="王胖胖"
echo "子shell修改后的信息: $_xing"
EOF

# 执行测试效果
/bin/bash father.sh
父shell信息: 王书记,42
子shell信息: 王,
子shell修改后的信息: 王胖胖
父shell信息: 王书记,42

rm -f child.sh father.sh

内置变量

脚本相关

脚本相关的变量解析

序号变量名解析
1$0获取当前执行的shell脚本文件名
2$n获取当前执行的shell脚本的第n个参数值,n=1..9, 当n为0时表示脚本的文件名,如果n大于9就要用大括号括起来${10}
3$#获取当前shell命令行中参数的总个数
4$?获取执行上一个指令的返回值(0为成功,非0为失败)
# $0 获取脚本的名称
cat > get_name.sh << "EOF"
#!/bin/bash
# 获取脚本的名称
echo "我脚本的名称是: file.sh"
echo "我脚本的名称是:$0"
EOF

/bin/bash get_name.sh
我脚本的名称是: file.sh
我脚本的名称是:get_name.sh

# $n 获取当前脚本传入的第n个位置的参数
cat > get_args.sh << "EOF"
#!/bin/bash
# 获取指定位置的参数
echo "第一个位置的参数是: $1"
echo "第二个位置的参数是: $2"
echo "第三个位置的参数是: $3"
echo "第四个位置的参数是: $4"
EOF

/bin/bash get_args.sh 1001 1002 1003 1004
第一个位置的参数是: 1001
第二个位置的参数是: 1002
第三个位置的参数是: 1003
第四个位置的参数是: 1004

# $# 获取当前脚本传入参数的数量
cat > get_number.sh << "EOF"
#!/bin/bash
# 获取当前脚本传入的参数数量
echo "当前脚本传入的参数数量是: $#"
EOF

/bin/bash get_number.sh 1001 1002 1003 1004
当前脚本传入的参数数量是: 4

# $? 获取文件执行或者命令执行的返回状态值
bash nihao
bash: nihao: No such file or directory

echo $?
127

ls *.sh
get_args.sh  get_name.sh  get_number.sh

echo $?
0

ls *.sh | xargs rm -f

字符串相关

字符串相关的变量解析

${#var}获取字符串的长度

${var:pos:length}表示对变量var从pos开始截取length个字符,pos为空表示0

  • ${var:0:5} 从0开始,截取5个字符
  • ${var:5:5} 从5开始,截取5个字符
  • ${var::5} 从0开始,截取5个字符
  • ${var:0-6:3} 从倒数第6个字符开始,截取之后的3个字符
  • ${var: -4} 返回字符串最后四个字节,-前面是"空格"
# 定制字符串内容
string_context="dsjfdsafjkldjsklfajkdsa"

# 获取字符串长度
echo ${#string_context}
23

# 从0开始,截取5个字符
echo ${string_context:0:5}
dsjfd

# 从5开始,截取5个字符
echo ${string_context:5:5}
safjk

# 从0开始,截取5个字符
echo ${string_context::5}
dsjfd

# 从倒数第6个字符开始,截取之后的3个字符
echo ${string_context:0-6:3}
ajk

# 返回字符串最后四个字节,-前面是"空格"
echo ${string_context: -4}
kdsa

默认值相关

语法解读

  • 格式一,${变量名:-默认值};变量a如果有内容,那么就输出a的变量值;变量a如果没有内容,那么就输出默认的内容
  • 格式二,${变量名+默认值};无论变量a是否有内容,都输出默认值
# 有条件的默认值
# 购买手机的时候选择套餐,如果我输入的参数为空,那么输出内容是 "您选择的套餐是: 套餐 1";如果我输入的参数为n,那么输出内容是 "您选择的套餐是: 套餐 n"
cat > select_default_value.sh << "EOF"
#!/bin/bash
# 套餐选择演示
a="$1"
echo "您选择的手机套餐是: 套餐 ${a:-1}"
EOF

/bin/bash select_default_value.sh
您选择的手机套餐是: 套餐 1

/bin/bash select_default_value.sh 3
您选择的手机套餐是: 套餐 3

# 强制默认值
# 国家法律强制规定:不管我说国家法定结婚年龄是多少岁,都输出 国家法定结婚年龄(男性)是22岁
cat > froce_default_value.sh << "EOF"
#!/bin/bash
# 默认值演示示例二
a="$1"
echo "国家法定结婚年龄(男性)是 ${a+22} 岁"
EOF

/bin/bash froce_default_value.sh
国家法定结婚年龄(男性)是 22 岁

/bin/bash froce_default_value.sh 18
国家法定结婚年龄(男性)是 22 岁

rm -f select_default_value.sh froce_default_value.sh

其他相关

脚本相关的变量解析

序号变量名解析
1$_在此之前执行的命令或脚本的第一个内容
2$@传给脚本的所有参数
3$*是以一个单字符串显示所有向脚本传递的参数,与位置参数不同,参数可超过9个
4$$是脚本运行的当前进程的ID号,作用是方便以后管理它杀掉他
5$!前一条命令进程的ID号,作用是方便以后管理它杀掉他
# 其他变量的作用
cat > get_other.sh << "EOF"
#!/bin/sh
echo "脚本执行命令的第一个内容: $_"
echo "传递给当前脚本的所有参数是: $@"
echo "单字符串显示所有参数: $*"
echo "当前脚本执行时候的进程号是: $$"
sleep 5 &
echo "上一条命令执行时候的进程号是: $!"
EOF

/bin/bash get_other.sh 1001 1002 hello world
脚本执行命令的第一个内容: /bin/bash
传递给当前脚本的所有参数是: 1001 1002 hello world
单字符串显示所有参数: 1001 1002 hello world
当前脚本执行时候的进程号是: 3438
上一条命令执行时候的进程号是: 3439

# $$ 获取当前的进程号
echo $$
3253

ps aux | grep 3253
root       3253  0.0  0.2 115676  2164 pts/1    Ss   13:32   0:00 -bash
root       3442  0.0  0.0 112808   976 pts/1    S+   14:45   0:00 grep --color=auto 3253

# 杀死当前的进程,则当前session退出
kill -9 3253

# $@ 和 $* 的区别
cat > father.sh << "EOF"
#!/bin/bash
echo "$0: 所有的参数 $@"
echo "$0: 所有的参数 $*"
echo '将 $* 值传递给 child-1.sh 文件'
/bin/bash child-1.sh "$*"

echo '将 $@ 值传递给 child-2.sh 文件'
/bin/bash child-2.sh "$@"
EOF

cat > child-1.sh << "EOF"
#!/bin/bash
echo "$0: 获取所有的参数 $1"
EOF

cat > child-2.sh << "EOF"
#!/bin/bash
echo "$0: 获取所有的参数 $1"
EOF

# 执行 father.sh 脚本
/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

ls *.sh | xargs rm -f