Linux shell 局部变量与全局变量

bash 变量默认都是全局变量,脚本内、函数内都可以调用,无论在什么位置(函数体中也一样),即函数体外可以调用函数体内的变量

local一般用于局部变量声明,多在函数体内使用,不可以在函数外引用

如果要在函数体里设置局部变量,则要使用 local

 

1、全局变量

在脚本中定义的变量都是全局变量,包括脚本中的函数,函数中使用的变量依然在全局生效。

声明变量时,使用“declare”,可以通过选项在声明的时候给予某些属性。

declare 选项:

-p:
-a:数组索引
-A:数组变量
-f:仅仅代表函数名
-i:整型数
-l:小写字母(自动转)
-r:只读变量声明
-t:跟踪属性,调试用
-u:大些字母
-x:通过环境变量导出的变量声明

 

定义一个数组、赋值、输出数组:

declare -a arr
arr=(1 8 3 6 12)
echo ${#arr[@]}         # 5
echo ${arr[@]}          # 1 8 3 6 12
echo ${arr[*]}          # 1 8 3 6 12

 

2、局部变量

通过“local”关键字在函数内部定义局部变量,可以接受“declare”的一切选项,最好在函数中定义局部变量时使用。

当在函数中使用local时,它会导致变量名使可见范围仅限于该函数及其子函数。

示例代码:

vim test_shell_var.sh

#!/bin/bash
#
# mimvp.com
# 2018-09-18

mimvp='macro_blog'
function test_macro() {
    echo $mimvp                 # macro_blog

    mimvp='macro_proxy'
    echo $mimvp                 # macro_proxy
}
test_macro
echo $mimvp                     # macro_proxy


mimvp='local_blog'
function test_local() {
    echo $mimvp                 # local_blog

    local mimvp
    mimvp='local_proxy'
    echo $mimvp                 # local_proxy
}
test_local
echo $mimvp                     # local_blog

运行结果:

macro_blog
macro_proxy

macro_proxy
local_blog
local_proxy

local_blog

 

变量类型:全局变量(环境变量)和局部变量(本地变量) 

全局变量(环境变量),可以在定义它们的shell及其派生出来的任意子进程的shell中使用。

局部变量(本地变量),只能在定义它们的函数/脚本内部中使用。

还有一些变量是用户创建的,其他的则是专用的shell变量。

 

1、全局变量(环境变量)

全局变量(环境变量)可用于定义shell的运行环境,环境变量可以在配置文件中定义与修改,也可以在命令行中进行临时设置,但是命令行中的修改操作在终端重启时就会丢失,因此最好在配置文件中修改:"~/.bash_profile" 文件,或者全局配置 "/etc/profile"、"/etc/bashrc"、"/etc/profile.d"文件中定义。将环境变量放在profile文件中,每次用户登录时这些变量值将被初始化。比如HOME、USER、SHELL、UID等再用户登录之前就已经被/bin/login程序设置好了。

常见系统环境变量:

TMOUT:设置自动退出的误操作等待时间,如 18000
HOSTTYPE:系统文件系统类型,如 x86_64​
HISTSIZE:历史命令记录条数,如 1000
HOME:用户登录时进入的目录,如 /root​
UID:当前用户的id,如 0 (root)
SHELL:当前shell解释器,如 /bin/bash
PWD:当前所在路径(每改变一次目录,该值就会改变) ,如 /home/mimvp , /root
PATH:可执行文件默认路径,如 /sbin:/bin:/usr/sbin:/usr/bin

可以用echo来显示查看全局变量,例如:

echo $HOME       # /home/mimvp

echo $PATH         # /usr/local/php/bin:/usr/local/httpd/bin:/home/java/jdk-10.0.2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

env(或printenv)、set 也可以用来查看系统的环境变量,但不是单独查看,例如:

[root@mimvp-sz2 script]# env
XDG_SESSION_ID=11843
HOSTNAME=mimvp-sz2
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=106.39.148.162 23363 22
NGINX_ROOT=/usr/local/nginx
QTDIR=/usr/lib64/qt-3.3
QTINC=/usr/lib64/qt-3.3/include
SSH_TTY=/dev/pts/1
.....

自定义环境变量,采用 export: 

1)export 变量名=value 

2)变量名=value;export 变量名 

3)declare -x 变量名=value 

命令行的三种方式测试如下: 

# export TEST='mimvp.com'
# echo $TEST
mimvp.com
#
# TEST2='www.mimvp.com'; export TEST2
# echo $TEST2
www.mimvp.com
#
# declare -x TEST3='blog.mimvp.com'
You have new mail in /var/spool/mail/root
# echo $TEST3
blog.mimvp.com
#
# env | grep -i "TEST"
TEST=mimvp.com
TEST2=www.mimvp.com
TEST3=blog.mimvp.com

临时取消环境变量用 unset

例如:unset USER,要永久生效还是要写到配置文件中

# unset TEST
[root@mimvp-sz2 script]# env | grep -i "TEST"
TEST2=www.mimvp.com
TEST3=blog.mimvp.com

这里依旧是临时生效,在shell终端关闭后就消失了,若要永久生效,就需要写到配置文件中

注意:写到配置文件中后,需要运行一遍配置文件的脚本才可生效,或重启服务器后生效

 

2、局部变量本地变量

本地变量在用户当前的shell生存期的脚本中使用。

在一个函数中将某个变量声明为local,则该变量就是一个局部变量,只在本函数中有效。 

定义:

变量名=value 
变量名='value'
变量名="value"

shell中变量名的要求:一般遵循字母、数字、下滑线组成,不能以数字开头

以下脚本执行后(交互式非交互式都可以测试)输出什么(c与c与{c}等同)?

a=192.168.1.1
b='192.168.1.2'
c="192.168.1.3"
echo "A=$a"
echo "B=$b"
echo "C=${c}"
a=192.168.1.1-$b
b='192.168.1.2-$b'
c="192.168.1.3-$b"
echo "A=$a"
echo "B=$b"
echo "C=${c}"

输出结果如下:

A=192.168.1.1
B=192.168.1.2
C=192.168.1.3
A=192.168.1.1-192.168.1.2
B=192.168.1.2-$b
C=192.168.1.3-192.168.1.2-$b

总结分析: 

单引号与双引号的区别在于:

1)单引号内若存在变量,存在的变量当做字符串不会被解析,原样输出

2)双引号中若存在变量,该变量会被解析出其具体的值再加入到字符串中。

① 不加引号可以直接定义内容包含数字、字符串、路径名等,适合定义数字

② 单引号适合于纯定义字符串,

③ 双引号适合于字符串的内容中包含有变量的内容的定义。

使用习惯:

数字以及不带空格的简单字符串不加引号,其它长的特别是有空格的字符串加双引号;

遇到“$变量名”,但不想解析的加单引号,但一般出现$都是为了解析变量,所以单引号较少使用

注意:单引号与双引号的特点不具有普遍性,如下: 

在普通shell中:

ETT=123
echo '$ETT'	# 打印$ETT(单引号不解析)
echo "$ETT"    # 打印123(双引号解析)

而在awk中调用shell变量:

awk 'BEGIN {print '$ETT'}'   # 打印123(单引号解析)
awk 'BEGIN {print "$ETT"}'  # 打印$ETT(双引号不解析)

虽然在awk中不具有普遍性,但是在普通Shell中还是具有普遍性的。

 

3、局部变量的其它问题

用反引号将命令的结果作为变量名是常用的方法:cmd=`date +%F`

用$符号将命令的结果作为变量名也比较常用:cmd=$(date +%F)

变量在大括号上的使用:在以时间、主机名等为包名一部分来打包的时候常用

用时间作为文件名的一部分打包

cmd=$(date +%F)    # 由于`date +%F`的反引号不容易辨认,就不太使用`date +%F`
tar -zcf code_$(date+ %F)_mimvp.tar.gz /etc/passwd   # 没有问题
tar -zcf code_`date +%F`_mimvp.tar.gz /etc/passwd     # 没有问题
tar -zcf code_mimvp_$cmd.tar.gz /etc/passwd              # 没有问题
tar -zcf code_${cmd}_mimvp.tar.gz /etc/passwd            # 不会有歧义
tar -zcf code_$cmd_mimvp.tar.gz /etc/passwd	       # 会有歧义,不清楚是应该解析$cmd还是cmd_mimvp

上面命令,对后两个命令的测试结果如下: 

# cmd=$(date +%F)
# echo $cmd
2018-09-20
#
# tar -zcPf code_${cmd}_mimvp.tar.gz /etc/passwd  
# ll code*  
-rw-r--r-- 1 root root 846 Sep 20 14:28 code_2018-09-20_mimvp.tar.gz
#
# tar -zcPf code_$cmd_mimvp.tar.gz /etc/passwd  
# ll code* 
-rw-r--r-- 1 root root 846 Sep 20 14:28 code_2018-09-20_mimvp.tar.gz
-rw-r--r-- 1 root root 846 Sep 20 14:29 code_.tar.gz

说明:不加{} 会产生歧义,不知道变量是 $cmd 或 $cmd_mimvp,所以最后为空

 

用主机名与时间打包

cmd=$(date +%F)
host=$(hostname)
tar -zcf code_${cmd}_${host}.tar.gz /etc/passwd   

运行日志: 

# echo $cmd
2018-09-20
# host=$(hostname)
# echo $host
mimvp-sz2
# tar -zcPf code_${cmd}_${host}.tar.gz /etc/passwd
# ll code*
-rw-r--r-- 1 root root 846 Sep 20 14:34 code_2018-09-20_mimvp-sz2.tar.gz

小结:编写shell脚本,养成将字符串变量用{}括起来使用的好习惯,防止不易发现的歧义或错误

 

4、Shell 特殊变量

$0  $n  $#  $*  $@  $$  $?  $_

$0:获取脚本的路径
$n:获取当前执行shell脚本的第n个参数, 例如第一个参数 $1

$#:获取当前shell命令行中参数的总个数, 格式 ${#arr[*]}
$*:获取当前执行的shell的所有参数,将所有的命令行参数视为一个字符串:"$1$2$3..."
$@:获取当前执行的shell的所有参数,将所有的命令行的每个参数视为单个的字符串:"$1" "$2" "$3" 

$$:获取当前的shell进程号
$?:获取执行上一个指令的返回值,0为成功,非零为失败,可以对上一个命令执行是否成功进行判断
$_:在此之前执行的命令或脚本的最后一个参数

$0:获取脚本的路径,获取当前执行的shell脚本的文件名,执行时给定的是完整路径则获取到的也是完整路径

两个命令与$0的组合测试:获取一个带路径的文件的路径名与文件名两部分

dirname:获取目录名
basename:获取文件名

测试 $0 

vim test.sh

#!/bin/bash
#
# mimvp.com
# 2019.09.18

dirname $0
basename $0

运行 test.sh

# sh test.sh 
.
test.sh
# 
# sh `pwd`/test.sh
/root/script
test.sh

说明:圆点 . 表示当前目录,`pwd`表示获取当前目录的绝对路径

 

$n:获取当前执行的shell脚本的第n个参数,

1)如果n=0则获取的是脚本的文件名

2)如果n>9则需要用大括号括起来,例如:${21}

测试 $n:

# seq 9 | sed 's#[0-9]#echo $&#g' > test2.sh 
# cat test2.sh 
echo $1
echo $2
echo $3
echo $4
echo $5
echo $6
echo $7
echo $8
echo $9
# sh test2.sh `seq -s " " 10`
1
2
3
4
5
6
7
8
9

说明:

seq 9 是表示生成一个数组

# seq 9
1
2
3
4
5
6
7
8
9

sed 's#[0-9]#echo $&#g'  是表示把每一行的匹配的数字,替换为字符串 echo $[0-9] 对应的数字

# echo 5 | sed 's#[0-9]#echo $&#g'
echo $5

 

更多$参数:

$# :获取当前shell命令行中参数的总个数

$* :获取当前执行的shell的所有参数,将所有的命令行参数视为一个字符串 :"$1$2$3..."

$@ :获取当前执行的shell的所有参数,将所有的命令行的每个参数视为单个的字符串 :"$1" "$2" "$3" ...  这是将参数传递给其它程序的最佳方式,因为它会保留所有内嵌在每个参数里的任何空格分隔

 

测试$ 如下: 

vim test3.sh 

#!/bin/bash
#
# mimvp.com
# 2018-09-18

echo $#
echo $*
echo $@

运行日志:

# sh test3.sh `seq -s " " 10`
10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10

 

获取状态变量:

$$:获取当前的shell进程号

$?:获取执行上一个指令的返回值,0为成功,非零为失败,可以对上一个命令执行是否成功进行判断

$_:在此之前执行的命令或脚本的最后一个参数

$?变量其实获取的是上一个程序返回给父进程shell的返回值(该值在0-255之间:0表示运行成功,2表示权限拒绝,1~125为运行失败原因是脚本命令、系统命令错误或参数传递错误,126为找到该命令但是无法执行,127为无该命令/程序,>128表示命令被系统强制结束)

$?的不同返回值测试: 

# tar -zcPf code_$(date +%F).tar.gz /etc/passwd
# echo $?
0
# tar -zcPf code_$(date +%F).tar.gz /etc/
^C   【Ctrl+C 迅速中断执行】
# echo $?
130

说明:成功执行压缩命令,返回 $? 为 0(成功);中断执行命令,返回错误码 130

 

5、Shell 脚本追踪

检查脚本语法、调试执行脚本

$ bash -n tmp_shell.sh 
$ bash -x tmp_shell.sh  
+ declare -a arr
+ arr=(1 8 3 6 12)
+ echo 5
5
+ echo 1 8 3 6 12
1 8 3 6 12
+ echo 1 8 3 6 12
1 8 3 6 12

 

shell 脚本追踪

在测试脚本时,可以使用set命令进行运行时的追踪,在脚本中加入一行“set -x”;以“+”开头的行,就是获得的追踪内容(程序的执行过程)

vim tmp_shell.sh

#!/bin/bash
# mimvp.com 2016.05.10

set -x

declare -a arr
arr=(1 8 3 6 12)
echo ${#arr[@]}         # 5
echo ${arr[@]}          # 1 8 3 6 12
echo ${arr[*]}          # 1 8 3 6 12

运行结果:

$ sh tmp_shell.sh  
+ declare -a arr
+ arr=(1 8 3 6 12)
+ echo 5
5
+ echo 1 8 3 6 12
1 8 3 6 12
+ echo 1 8 3 6 12
1 8 3 6 12

说明:脚本文件内添加 set -x 与脚本执行时 bash +x tmp_shell.sh 效果是一样的,推荐使用 bash +x tmp_shell.sh 

 

 

参考推荐

Linux cut 命令

linux 命令快捷键

Linux Shell 常用命令与目录分区

Linux shell 脚本通过expect实现自动输入密码

nohup、&、disown、setsid、screen、jobs 后台运行命令区别

cp、tar 命令排除文件和子目录

zip、tar 命令加密和解密压缩文件

Linux下tar、bz、gz等压缩包的压缩和解压

shell命令curl 检测代理是否可用

Linux 之 shell 比较运算符

Linux 之 shell 比较运算符