shell的变量和分类

什么是变量

变量,就是一个容器用来存储数据也是一段内存空间(内存是编制的存储单元);通过变量赋值在变量中存储数据,然后可以通过变量名访问到的存储信息的内存空间地址。

变量是脚本语言的核心,shell 脚本又是无类型的,所以要使用变量就需要引用。

数据类型

数据类型是指用来事先定义数据的存储格式和存储长度,参与的运算。

数据类型的重要性:有一种系统攻击叫缓冲区溢出,如声明一个数据类型为整型并申请 1 字节空间。当在这个变量中存储 256 时,这个整型数据时就会产生溢出,因为 1 字节是 8 个位存储单元的存储范围是 0-255。而 256 就存储不了也就会产生溢出占用其他进程空间,解决缓冲溢出最简单的方法就是当用户存储一个数据时先检查数据是否可以存储下。

对于解释型语言来说,它的数据类型都是弱类型的。不管变量中的数据有没有类型都可以,因为解释器能理解,而且由解释器在另外一个层次给予避免。

对于编译型语言来说,一旦数据类型出了问题只能靠程序自身来解决,它没有额外的一层保护机制,编译器也可以在编译时检查明显错误,但是对于后期用户输入进来的数据就无法检查了,所以但凡编译型语言都是强类型编译必须事先严格定义变量中的数据类型。

常用的数据类型有字符型,数值型,时间日期型,布尔型等等。

Bash 对变量的机制

  • 所有变量都看作为字符型
  • 它不支持浮点型数据,如果想要支持则需借助外部机制才能实现
  • 变量无需事先声明,也就是说变量赋值和变量声明是同时进行的

Tips: 变量引用机制:把变量名出现的位置,直接替换为其所指向的内存空间中的数据

变量的命名规范

  • 不能以数字开头,只能包含数字、字母、下划线,且变量名中不能出现空格
  • 不能使用下划线以外的标点符号
  • 不能使用程序语言的保留字(if else while 等),保留关键字可以尝试 help if 这种方式查看
  • 变量名最好做到见名知义

Tips: 命名规则针对的是自定义变量

变量分类

本地变量

本地变量作用域只对当前 shell 进程生效,对子 shell、其他 shell 进程无效。Bash 默认变量都是本地变量。

1
2
3
4
5
6
7
8
# 定义本地变量
set VAR_NAME=value

# set一般都可以省略,如下
VAR_NAME=VALUE

# 引用变量,其中{}一般可省略,直接echo $VAR_NAME;
echo ${VAR_NAME}

局部变量

局部变量作用域对当前代码段有效,在一个脚本的函数中的变量跟函数外的变量名同名,就可以把函数内的变量定义为局部变量,这样就不会跟其他变量冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

[user1@study ~]$ cat test.sh
#!/bin/bash

name='Tom'

func(){
# 定义局部变量
local name='Jerry'
echo ${name}
}

func
[user1@study ~]$ bash test.sh
Jerry
[user1@study ~]$

环境变量

环境变量用来定义每一个用户的操作环境,变量作用域只对当前用户 shell 进程及其子 shell 生效,并且机器重启变量失效。如我们常用的 PATH 变量就是一个环境变量,不管机器有没有重启,PATH 变量重来没有失效过,这是因为针对环境变量有特定的环境变量配置文件,每一次用户登录就会加载此配置文件,同理在此文件中的变量就会生效。

定义环境变量

1
2
3
4
5
6
7
8
9
# 定义环境变量,定义不存在的变量为环境变量;
export VAR_NAME=value
# 或如下方式
declare -x VAR_NAME=VALUE
# 或如下方式
VARNAME=VALUE ; export VARNAME

# 定义环境变量,定义已存在的变量为环境变量;
$ export VAR_NAME

查看当前系统环境变量

1
2
3
4
5
set # 显示(设置)所有shell变量(包括环境变量及本地变量)
env # 显示(设置)当前shell的环境变量
printenv # 同env
export # 显示(设置)导出成当前shell环境变量的变量。
declare -x # 同export

常见的系统环境变量

变量名称 含义
SHELL 当前用户用的是哪种Shell
BASH bash的路径
BASH_VERSION bash的版本号
LANG 字符集,是和语言相关的环境变量,使用多种语言的用户可以修改此环境变量
HOSTNAME 主机的名称,许多应用程序如果要用到主机名的话,通常是从这个环境变量中来取得的
HOSTTYPE 主机架构类型,用来识别系统硬件
MACHTYPE 平台类型,系统平台依赖的编译平台
OSTYPE 系统类型
LOGNAME 当前用户的登录名
USER 当前的用户
UID 当前的用户的ID号
EUID 有效用户的ID号
HOME 当前用户的家目录
PWD 当前目录
OLDPWD 上次使用的目录
PATH 包含一系列由冒号分隔开的目录,系统从这些目录里寻找可执行文件。若输入的可执行文件不在这些目录中,系统就无法执行它(除非输入绝对路径)
LDPATH 包含一系列用冒号隔开的目录,动态链接器将在这些目录里查找库文件
MANPATH 包含一系列用冒号隔开的目录,命令man会在这些目录里搜索man页面,在/etc/man.config/etc/man_db.conf中定义
INFODIR 包含一系列用冒号隔开的目录,命令info将在这些目录里搜索info页面
PAGER 包含浏览文件内容的程序的路径(例如less或者more)
EDITOR 包含修改文件内容的程序(文件编辑器)的路径(比如nano或者vi)
KDEDIRS 包含一系列用冒号隔开的目录,里面放的是KDE相关的资料
MAIL 当前用户的邮件存放目录
HISTSIZE 保存历史命令记录的条数
PS1 命令提示符,对于root用户是#,对于普通用户是$
PS2 是附属提示符,默认是“>”。可以通过修改此环境变量来修改当前的命令符
PS3 第三提示符,用于select命令中
PS4 第四提示符,当使用-X选项调用脚本时,显示的提示符,默认为+号

举个例子,PS2 我们已经知道它是附属提示符,默认是“>”。我们改成其他的来试试。

1
2
3
4
5
6
[user1@practice ~]$ export PS2='........'
[user1@practice ~]$ if true;then
........echo "${USER}";
........fi
user1
[user1@practice ~]$

位置变量

传递给脚本或函数的位置参数就是位置变量,引用方式为 $1,$2,…$9,${10},${11}…${n},位置变量都是只读的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[user1@study ~]$ cat test.sh 
#!/bin/bash

echo "1: ${1}"
echo "2: ${2}"
echo "3: ${3}"
echo "4: ${4}"

[user1@study ~]$ bash test.sh Tom Jerry yes no
1: Tom
2: Jerry
3: yes
4: no
[user1@study ~]$

shift 用来把脚本或函数的位置变量列表向左移动指定的位数(n),如果 shift 后没有参数,则将位置变量向左移动一位。一旦移位发生,被移出列表的位置变量将被永远删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[user1@study ~]$ cat test.sh 
#!/bin/bash

echo "$1"
shift
# 移除第一个位置参数,并把后面的第二个位置参数放到第一个位置,以此类推
echo "$1"

shift 5
# 从当前的位置参数开始,移除 5 个并将之后的位置参数顶替上来
echo "arg1: ${1}"
echo "arg2: ${2}"
echo "arg3: ${3}"

[user1@study ~]$ bash test.sh 1 2 3 4 5 6 7 8 9 10
1
2
arg1: 7
arg2: 8
arg3: 9
[user1@study ~]$

特殊变量

在 shell 中,会对一些参数做特殊处理,而这些参数只能被引用不能被赋值

$0 代表当前脚本文件的文件名

1
2
3
4
5
6
7
8
[user1@study ~]$ cat test.sh 
#!/bin/bash

echo "Usage : ${0} { start | stop }"

[user1@study ~]$ bash test.sh
Usage : test.sh { start | stop }
[user1@study ~]$

$$ 取得当前 shell 脚本或终端的进程 ID

1
2
3
4
5
6
7
[user1@study ~]$ ps -ef | grep bash
root 1211 1209 0 13:28 pts/0 00:00:00 -bash
user1 1315 1314 0 14:42 pts/0 00:00:00 -bash
user1 1417 1315 0 16:04 pts/0 00:00:00 grep --color=auto bash
[user1@study ~]$ echo $$
1315
[user1@study ~]$

$! 取得上一条在后台执行命令的进程ID

1
2
3
4
5
6
7
8
[user1@study ~]$ sleep 50s &
[1] 1419
[user1@study ~]$ echo $!
1419
[user1@study ~]$ ps -ef | grep sleep
user1 1419 1315 0 16:07 pts/0 00:00:00 sleep 50s
user1 1421 1315 0 16:07 pts/0 00:00:00 grep --color=auto sleep
[user1@study ~]$

$# 取得位置参数的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
[user1@study ~]$ cat test.sh 
#!/bin/bash

echo "total: $#"

[user1@study ~]$
[user1@study ~]$ bash test.sh
total: 0
[user1@study ~]$ bash test.sh Tom Jerry yes no
total: 4
[user1@study ~]$ bash test.sh Tom Jerry yes
total: 3
[user1@study ~]$
1
${!#}

取得位置参数中的最后一个参数,将 $# 的值赋值给一个变量而后使用 ! 也可以达到同样的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[user1@study ~]$ cat test.sh           
#!/bin/bash

echo "total: $#"
echo "First: $1"
echo "Last: ${!#}"
x=$#
echo "Last: ${!x}"

[user1@study ~]$ bash test.sh
total: 0
First:
Last: test.sh
Last: test.sh
[user1@study ~]$ bash test.sh 1 2 3 4 5
total: 5
First: 1
Last: 5
Last: 5
[user1@study ~]$

$*$@ 都指的是所有的位置变量,区别是 $* 是作为一个整体字符串,而 $@ 是把每个位置变量都分别作为独立的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[user1@study ~]$ cat test.sh                  
#!/bin/bash

echo "total: $#"

echo '$*'
echo '-----------------------'
for i in "$*";do echo ${i};done
echo '-----------------------'

echo '$@'
echo '***********************'
for i in "$@";do echo ${i};done
echo '***********************'

[user1@study ~]$ bash test.sh Tom Jerry yes no
total: 4
$*
-----------------------
Tom Jerry yes no
-----------------------
$@
***********************
Tom
Jerry
yes
no
***********************
[user1@study ~]$

$- 指的是当前 shell 进程使用了哪些选项,与 set 命令的功能有些类似,要注意不是 shell 脚本的位置参数,而是针对进程级别的设置的选项

1
2
3
[user1@study ~]$ echo $-
himBH
[user1@study ~]$

$_ 取得上一个命令或脚本的最后一个参数

1
2
3
4
5
6
7
8
9
10
[user1@study ~]$ cat test.sh 
#!/bin/bash

echo {1..5}
echo $_

[user1@study ~]$ bash test.sh
1 2 3 4 5
5
[user1@study ~]$

$? 指的是上一个命令或脚本的执行状态返回值,执行正常则其值为 0,否则为非 0 数字

1
2
3
4
5
6
7
[user1@study ~]$ echo '123abc' | grep 123 &>/dev/null
[user1@study ~]$ echo $?
0
[user1@study ~]$ echo '123abc' | grep '456' &>/dev/null
[user1@study ~]$ echo $?
1
[user1@study ~]$
有钱任性,请我吃包辣条
0%