变量是编程最重要知识点之一,从根本上讲,编程就是对数据的操作,让数据按我们设定的逻辑进行运算。变量是用于存储数据的内存地址的人性化表示方式。在shell中,数据类型比较简单,它属于弱数据类型的编程语言,简而言之它自动帮你处理了有关数据类型的工作。
顾名思义,变量就是可以变化的量。很多书上这么写的,笔者也学习一下,但这是一句没什么意义的话,纯粹是为了让人更迷糊的说法。
变量是内存地址的别名,别名就是外号的文雅说法。这是某本知名C/C++语言著作上的说法(具体是哪本书我忘记了),当然外号这句是笔者加的,从这个角度比较容易解释什么是变量。
都知道计算机系统中内存是必不可少的,数据是暂时存储在内存当中的。所以当你用PS修图的时候,如果没有保存就突然停电,来电以后你就要找出原图重新开始修1。这就是因为内存中的数据没有被保存到我们的硬盘上。
既然数据是先存储在内存中的,不管内存有多大,把内存细分成字节来管理(1G内存约有1千兆个字节,兆表示10的6次方)。这个数量是极其庞大的,大到我们光用二进制写一个内存地址就要写很久,程序在退休前肯定写不完,还得找个继承人接着写!更关键的是你无法记住这么长的一串数字表示的地址。所以我们要有一个好记的简短的名字来给我们所使用到的内存地址命名。
当然实际情况肯定是比这复杂多了,内存实际是由系统管理的,系统分配给你哪块就用哪块。变量只是给这部分内存起了一个名字。希望这么解释变量能让读者有个概念,变量不是什么稀奇的玩意,就是一个内存地址的名字。
那为什么那么多书都说变量是可以变化的量呢?那些作者比笔者我傻吗?肯定不是的,看下一节的演示你就明白,为什么说它是可以变化的量了。
对啊,我不用变量不就行了?要这种抽象的概念来做什么呢?假设我们要求计算机计算1+2等于多少,不用变量你输入echo $((1+2))
,计算机也会告诉你是3,没毛病。那我们要学习高斯同学的1加到100呢?你把1到100都写一遍吗?显然没有这个必要,加数是有规律的,每次加1,那么我们可以在变量被使用后,每次让它加上1,就是下一次计算的加数了。计算出的和也是一个道理,先让和是1+2的结果,下一次计算,我们让和加上3,以此类推,就轻松的得出结果了。当然这个高斯同学小学时的例子太简单,我们来个复杂点的著名例子:
有数列1,1,2,3,5,8,13 … 我们想知道第37个数是什么,之所以要37个是怕读者太聪明,口算得出结果~ 显然它的规律是:每个数都是前两个数的和。我们可以编写程序来计算:
#!/bin/sh
a=1
b=1
i=3
while [ $i -lt 38 ]
do
tmp=$a
a=$b
b=$tmp+$b
i=$i+1
done
echo $b
我就不信有口算这么厉害的人,没算出来吧~ 我们看看具体逻辑细节:
i
小于38,它就一直在do
和done
之间循环。a
的值赋值给变量tmp
(用于记录变量a的值,下行a的值会改变)。b
的值给变量a,把变量tmp
和b
的值加起来给b。i
加上1表示计算了一次,直到变量i
等于37,while循环条件不满足了,程序跳出循环。echo
显示结果这个程序的4个变量在运行时不停的改变自身存储的值。正是因为变量的值在程序的运算过程中不停的变化,我们才叫它变量!变量的存在才让程序变得简洁、灵活。
通常,我们将变量分为局部变量和全局变量。在函数内部被定义的变量,我们叫它局部变量,它只在函数内部作用。当函数被调用结束时,这个变量也就销毁了。相对的在函数外部定义的变量,我们称之为全局变量,这种变量在整个程序运行期间起作用。
这里引出了函数和调用的概念,函数我们可以认为是一个功能模块,它是为了实现某一个功能而定义的代码集合。局部变量只在定义它的函数内部有效,这个函数就是它的作用域。
如果局部变量和全局变量重名,全局变量会在同名局部变量的函数内被屏蔽。因为全局和局部是个相对的概念,相对于整个系统来说,每个程序内定义的变量都是局部变量。
这么分是有意义的:主要是为了节省内存空间的占用,局部变量用完就销毁的特性避免内存被无意义的数据填满。同时也能让程序员不用关心程序的其它部分定义了什么变量,不然多个程序员协作,大家就天天为了变量命名权打架了。
通常程序员会把全局变量写在程序的开始部分,这个开始并不一定是程序代码的最上部,而是指程序执行的入口部分,很多时候这个入口都写在程序的最下面部分。当然写在代码最前面也是一种好习惯。这么做是为了阅读程序方便,实际上只要变量在被使用前定义就行。
大多数编程语言规定,变量名只能用大小写字母和数字、下划线_ 组成,且数字不能位于变量名开头。
我们用几个例子来看一下:
age=23 # 很好的变量名,简单又有意义
age_1=24 # 较好的变量名,还可以命名多个类似的
_age_=23 # 通常程序员喜欢用下划线在前的变量表示在内部调用的
_tmp=23 # 也有程序员喜欢用这种变量名表示临时的
years_old=23 # 有很多程序员喜欢这样命名,也常用于函数命名
yearsOld=23 # 这叫驼峰命名,也是很常用的,也是通常用于函数名
AGE=23 # 符合规则,但通常不用全大写定义变量
abc=23 # 符合规则,但没有意义,不建议使用
1a=23 # 错误的命名方式
既然有变量,相应的就有常量。变量是可以变化的量,常量就是通常不变的量。比如Linux系统本身就定义了一些常量:
echo $HOME # 家目录,和~的作用一样
/root
echo $PWD
/tmp/home/root
echo $PATH
/koolshare/bin:/koolshare/scripts:/opt/bin:/opt/sbin:/bin:/usr/bin:/sbin:/usr/sbin:/home/admin:/mmc/sbin:/mmc/bin:/mmc/usr/sbin:/mmc/usr/bin:/opt/sbin:/opt/bin:/opt/usr/sbin:/opt/usr/bin
echo $SHELL
/bin/sh
echo $IFS
# 这里有一个空格
echo $USER
admin
echo $UID
0 # 路由器上这个命令可能没有显示
echo $HOSTNAME
RT-AC68U-F2A7
以下是一些常用的系统已定义常量:
~
的作用一样我们在程序中也可以自定义常量,它和变量没有本质的区别。命名规则也是一样,只是通常用全大写来命名。这只是一个概念,表示这是个常量,我们不是不能修改它的值,只是不想修改它。包括系统定义的常量,我们也是可以修改的,最常见之一就是IFS:
admin@RT-AC68U-F2A7:/tmp# touch test.txt
admin@RT-AC68U-F2A7:/tmp# echo "this is a test!" > test.txt
admin@RT-AC68U-F2A7:/tmp# cat test.txt
this is a test!
admin@RT-AC68U-F2A7:/tmp# for i in $(cat test.txt)
> do
> echo $i
> done
this
is
a
test!
上述代码中touch
是一个新建空白文件命令,如果要建立的文件已经存在,命令会改变这个文件的最后访问时间记录,并不会改变文件内容。>
重定向符,用于将标准输出(stdout)重定向到文件中。如果目标文件不存在则创建新文件;若已经存在同名文件,会被覆盖。最上面的touch
是可以不写的,只是作者的习惯。
我们把这个字符串改一改再来测试:
admin@RT-AC68U-F2A7:/tmp# echo "this,is,a,test!" > test.txt
admin@RT-AC68U-F2A7:/tmp# cat test.txt
this,is,a,test!
admin@RT-AC68U-F2A7:/tmp# for i in $(cat test.txt)
> do
> echo $i
> done
this,is,a,test!
上面这个例子,很好的说明了IFS的作用,默认以空格来分割字符串,当我们的字符串改成以逗号分隔时,因为字符串中没有空格,所以不进行分割了。下面我们就修改这个IFS常量:
admin@RT-AC68U-F2A7:/tmp# cat test.txt
this,is,a,test!
admin@RT-AC68U-F2A7:/tmp# IFS_OLD=$IFS
admin@RT-AC68U-F2A7:/tmp# IFS=","
admin@RT-AC68U-F2A7:/tmp# for i in $(cat test.txt)
> do
> echo $i
> done
this
is
a
test!
admin@RT-AC68U-F2A7:/tmp# IFS=$IFS_OLD
这个例子说明了,常量是可以修改的,即使它是系统定义的常量。通常我们修改了IFS后要第一时间给改回去。所以程序第一步就是将原IFS值先保存在IFS_OLD中,最后又改回去了。
如果我们实在不想一个变量或常量被无意中修改,在c/c++中有静态变量的概念,shell中也有只读变量可以做到。你可以定义一个通常不能改变的变量:
admin@RT-AC68U-F2A7:/tmp/home/root# readonly age=23
admin@RT-AC68U-F2A7:/tmp/home/root# echo $age
23
admin@RT-AC68U-F2A7:/tmp/home/root# age=24
-sh: age: is read only
admin@RT-AC68U-F2A7:/tmp/home/root# unset age
admin@RT-AC68U-F2A7:/tmp/home/root# echo $age
23
admin@RT-AC68U-F2A7:/tmp/home/root# unset -v age
admin@RT-AC68U-F2A7:/tmp/home/root# unset age
admin@RT-AC68U-F2A7:/tmp/home/root# echo $age
23
unset
用于删除一个变量。这样定义的变量是只读的,一般情况不能修改,在shell中还设计成很难修改,删除都不能。当然它只在内存中,你重启系统就没了。有人说服务器让你随便重启吗?对的,所以少用这个定义变量的方法。还有人说用unset -v 变量名
可以解除只读属性,至少在路由的这个2.6版内核中是不可行的,这真是够顽强的。笔者也没在多个发行版Linux中测试过这个,估计在ubuntu20.4中是不可行的,笔者查过这个命令的帮助文件:
sal@sal-laptop:~$ uname -a
Linux sal-laptop 5.4.0-169-generic #187-Ubuntu SMP Thu Nov 23 14:52:28 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
sal@sal-laptop:~$ unset --help
unset: unset [-f] [-v] [-n] [名称 ...]
取消设定 shell 变量和函数的值和属性。
对每一个 NAME 名称,删除对应的变量或函数。
选项:
-f 将每个 NAME 视为函数
-v 将每个 NAME 视为变量
-n 将每个 NAME 视为名称引用,只取消其本身而非其指向的变量
不带选项时,unset 首先尝试取消设定一个变量,如果失败,再尝试取消设定一个函数。
某些变量不可以被取消设定;参见 `readonly'。
退出状态:
返回成功,除非使用了无效的选项或者 NAME 名称为只读。
从这个帮助文件可以看出,函数也是用unset
来删除的,所以函数和变量是有共通之处的。从本质上讲,变量和函数都是用户在内存中存储的一串0和1。
本章介绍了变量和常量的概念,需要了解变量和常量的命名规则以及通常用法,这个规则在绝大多数的编程语言中是通用的。
返回专栏目录 <<<
某些软件设计了随时保存临时文件的功能,在我们使用软件的过程中定时或检测到活动就保存到硬盘上的一个文件中。当停电后重启电脑,可能可以找到一个临时文件来恢复大部分数据。 ??