2.4 Bash的变量和运算符
2.4.1 什么是变量
什么是变量呢?从字面上来看就是可以变的量。举个例子,我们小时候都做过数学的应用题,在应用题中经常定义x的值是某个数,如果换了一道题,还是定义x的值,但是x的值就不和第一道题相同了,那么这个x就是变量。变量是计算机内存的单元,其中存放的值可以改变。当Shell脚本需要保存一些信息时,如一个文件名或一个数字,就把它存放在一个变量中。每个变量都有一个名字,所以很容易引用它。变量可以定制用户本身的工作环境。使用变量可以保存有用信息,使系统获知用户相关设置。变量也可以用于保存暂时信息。
那么,应该如何设置变量呢?其实非常简单,命令如下:
[root@localhost ~]# name=sc #定义变量name的值 [root@localhost ~]# echo $name sc #查询变量的值
在定义变量时,有一些规则需要遵守。
· 变量名可以由字母、数字和下画线组成,但是不能以数字开头。如果变量名是“2name”,则是错误的。
· 在Bash中,变量的默认类型都是字符串型,如果要进行数值运算,则必须指定变量类型为数值型。比如:
[root@localhost ~]# aa=1+2 [root@localhost ~]# echo $aa 1+2
看到了吧,变量aa的值不是“3”,而是“1+2”。因为在Bash中,变量类型是字符串型,所以认为“1+2”只是一个字符串,而不会进行数值运算(我们将在2.4.7节中介绍数值运算方法)。
· 变量用等号“=”连接值,“=”左右两侧不能有空格。这是Shell语言特有的格式要求。在绝大多数的其他语言中,“=”左右两侧是可以加入空格的。但是在Shell中命令的执行格式是“命令 [选项] [参数]”,如果在“=”左右两侧加入空格,那么Linux会误以为这是系统命令,是会报错的。
· 变量值中如果有空格,则需要使用单引号或双引号包含,如test="hello world! "。双引号括起来的内容“$”“\”和反引号都拥有特殊含义,而单引号括起来的内容都是普通字符。
· 在变量值中,可以使用转义符“\”。
· 如果需要增加变量值,那么可以进行变量叠加。例如:
[root@localhost ~]# test=123 [root@localhost ~]# test="$test"456 [root@localhost ~]# echo $test 123456 #叠加变量test,变量值变成了123456 [root@localhost ~]# test=${test}789 [root@localhost ~]# echo $test 123456789 #再叠加变量test,变量值变成了123456789
变量叠加可以使用两种格式:"$变量名"或${变量名}。
· 如果要把命令的执行结果作为变量值赋予变量,则需要使用反引号或$()包含命令。例如:
[root@localhost ~]# test=$(date) [root@localhost ~]# echo $test 2013年 10月 21日 星期一 20:27:50 CST
· 环境变量名建议大写,便于区分。
知道了变量的基本概念和定义变量的规则,那么我们来看看变量的分类。好在在Bash中变量的类别不多,只有4种。
· 用户自定义变量:这种变量是最常见的变量,由用户自由定义变量名和变量值。
· 环境变量:这种变量中主要保存的是和系统操作环境相关的数据,比如当前登录用户、用户的家目录、命令的提示符等。在Windows中,同一台计算机可以有多个用户登录,而且每个用户都可以定义自己的桌面样式和分辨率,这些其实就是Windows的操作环境,可以当作Windows的环境变量来理解。环境变量的变量名可以自由定义,但是一般对系统起作用的环境变量的变量名是由系统预先设定的。
· 位置参数变量:这种变量主要是用来向脚本中传递参数或数据的,变量名不能自定义,变量的作用是固定的。
· 预定义变量:Bash中已经定义好的变量,变量名不能自定义,变量的作用也是固定的。
我们分别来学习这几种变量吧!
2.4.2 用户自定义变量
1.变量定义
用户自定义变量是最常用的变量类型,其特点是变量名和变量值都是由用户自由定义的。那么,该如何定义变量呢?很简单,只需执行“变量名=变量值”命令即可,不过要遵守变量定义规则。例如:
[root@localhost ~]# name="shen chao"
变量的定义就是这么简单,但是如果我们不遵守变量定义规则,就会报错。比如:
[root@localhost ~]# 2name="shen chao" -bash: 2name=shen chao: command not found #变量名不能以数字开头 [root@localhost ~]# name = "shenchao" -bash: name: command not found #等号左右两侧不能有空格 [root@localhost ~]# name=shen chao -bash: chao: command not found #变量的值如果有空格,则必须用引号包含
我们再看看如何进行变量叠加。例如:
[root@localhost ~]# aa=123 #定义变量aa的值是123 [root@localhost ~]# aa="$aa"456 #重复定义变量aa的值是源aa的值加上456 [root@localhost ~]# echo $aa 123456 #aa的值已经变成了123456 [root@localhost ~]# aa=${aa}789 [root@localhost ~]# echo $aa 123456789 #在进行变量叠加时也可以使用${变量名}格式
这里要小心,在进行变量叠加时,变量名需要用双引号或${}包含。
在定义变量时,也可以使用特殊字符,如双引号、单引号、反引号、小括号、大括号等。
2.变量调用
当我们需要提取变量中的内容时,需要在变量名之前加入“$”符号。也就是说,我们需要调用变量时,需要在变量名之前加入“$”符号。那么最简单的变量调用就是通过echo命令输出变量的值。命令如下:
[root@localhost ~]# name="shen chao" #定义变量name [root@localhost ~]# echo $name shen chao #输出变量name的值
就这么简单。不过,不仅通过echo命令输出变量的值时才需要在变量名前加入“$”符号,只要需要调用变量的值,就需要在变量名前加入“$”符号。
3.变量查看
我们可以通过echo命令查询已经设定的变量的值,这种查询是已知变量名查询变量值。但是如果我不知道变量名,那么可以查询系统中已经存在的变量吗?当然可以,只需使用set命令即可。set命令可以用来查看系统中的所有变量(用户自定义变量和环境变量)和设定Shell的执行环境。命令格式如下:
[root@localhost ~]# set [选项] 选项: -u: 如果设定此选项,则在调用未声明的变量时会报错(默认无任何提示) -x: 如果设定此选项,则在命令执行之前会先把命令输出一次
举几个例子:
[root@localhost ~]# set BASH=/bin/bash …省略部分输出… name='shen chao' #直接使用set命令,会查询系统中所有的变量,包含用户自定义变量和环境变量 [root@localhost ~]# set -u [root@localhost ~]# echo $file -bash: file: unbound variable #当设置了-u选项后,如果调用没有设定的变量则会报错。默认是没有任何输出的 [root@localhost ~]# set -x [root@localhost ~]# ls + ls --color=auto anaconda-ks.cfg install.log install.log.syslog sh tdir test testfile #如果设定了-x选项,则会在每条命令执行之前先把命令输出一次
set命令的选项和功能众多,不过我们更常用的还是使用set命令查看变量。
4.变量删除
要想删除自定义变量,可以使用unset命令。命令格式如下:
[root@localhost ~]# unset变量名
这里只是清空变量,而不是调用变量的值,所以在变量名前不需要加入“$”符号。举个例子:
[root@localhost ~]# unset name #删除name变量
这条命令执行之后,再查询变量,就会发现这个变量已经为空了。
2.4.3 环境变量
环境变量和用户自定义变量最主要的区别在于,环境变量是全局变量,而用户自定义变量是局部变量。用户自定义变量只在当前的Shell中生效,而环境变量会在当前Shell和这个Shell的所有子Shell中生效。如果把环境变量写入相应的配置文件,那么这个环境变量就会在所有的Shell中生效(这是有区别的,如果环境变量不写入配置文件,那么当前Shell一旦终止,这个环境变量当然会消失,而只有写入配置文件才会永久地、在所有Shell中生效)。
在Linux中一般通过环境变量配置操作系统的环境,如提示符、查找命令的路径、用户家目录等,这些系统默认的环境变量的变量名是固定的,我们只能修改变量的值。当然,我们也可以手工定义环境变量,不过这些自定义的环境变量就不能修改操作系统环境,而只是一个全局变量而已。
1.环境变量设置
环境变量和用户自定义变量的设置方法相比,只需通过export命令将变量声明为环境变量即可。命令如下:
[root@localhost ~]# export age="18" #使用export声明的变量就是环境变量
这样年龄就是环境变量了。当然也可以先把变量声明为本地变量,再用export声明为环境变量,命令如下:
[root@localhost ~]# gender=male [root@localhost ~]# export gender
这样性别也被声明为环境变量了。我们说过,用户自定义变量和环境变量的区别就是:用户自定义变量只能在当前Shell中有效,而环境变量在当前Shell和所有子Shell中有效。比如:
[root@localhost ~]# name="shen chao" ←把姓名声明为本地变量 [root@localhost ~]# export age="18" ←把年龄声明为环境变量 [root@localhost ~]# gender=male ←把性别声明为本地变量 [root@localhost ~]# export gender ←再把性别升级为环境变量
然后我们查询一下这些变量。
[root@localhost ~]# set …省略部分内容… gender=male name='shen chao' age=18
在当前Shell中可以看到这三个变量。
[root@localhost ~]# bash #再调用一次bash,也就是进入子Shell [root@localhost ~]# set #再次查询变量 …省略部分输出… age=18 gender=male #在子Shell中只能看到环境变量“age”和“gender”,而不能查询到用户自定义变量“name”
可以看到,在子Shell中只能看到环境变量“age”和“gender”,这就是环境变量和用户自定义变量的区别。
2.环境变量查询和删除
先来说说环境变量的查询吧。其实set既然可以查询所有的变量,当然也可以查询环境变量了,我们刚刚的实验就是使用set命令进行环境变量查询的。当然也可以使用env命令进行环境变量的查询,命令如下:
[root@localhost ~]# env HOSTNAME=localhost.localdomain SELINUX_ROLE_REQUESTED= SHELL=/bin/bash …省略部分输出…
env和set命令的区别是,set命令可以查看所有变量,而env命令只能查看环境变量。我们可以发现系统默认有很多的环境变量,这些环境变量都代表什么含义呢?我们稍后会有详细介绍。
再来说说环境变量的删除。其实环境变量的删除方法和用户自定义变量是一样的,都使用unset命令,命令如下:
[root@localhost ~]# unset gender [root@localhost ~]# env | grep gender #删除环境变量gender
3.系统默认环境变量
系统中默认有很多的环境变量,我们来详细了解一下这些系统默认的环境变量的作用。
[root@localhost ~]# env HOSTNAME=localhost.localdomain ←主机名 SHELL=/bin/bash ←当前的Shell TERM=linux ←终端环境 HISTSIZE=1000 ←历史命令条数 SSH_CLIENT=192.168.4.1594824 22 ←当前操作环境是用ssh连接的,这里记录客户端IP SSH_TTY=/dev/pts/1 ←ssh连接的终端是pts/1 USER=root ←当前登录的用户 LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01 :cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:o w=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.l zh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31 :*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=0 1;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*. ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=0 1;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35: *.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.sv gz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=0 1;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35 :*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc =01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35: *.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv =01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01; 36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.a xa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: ←定义颜色显示 age=18 ←我们刚刚定义的环境变量 PATH=/usr/lib/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin: /usr/bin:/root/bin ←系统查找命令的路径 MAIL=/var/spool/mail/root ←用户邮箱 PWD=/root ←当前所在目录 LANG=zh_CN.UTF-8 ←语系 HOME=/root ←当前登录用户的家目录 SHLVL=2 ←当前在第二层子Shell中。还记得我们刚刚进入了 一个子Shell吗?如果是第一层Shell,那么这里是1 LOGNAME=root ←登录用户 _=/bin/env ←上次执行命令的最后一个参数或命令本身
env命令可以查询到所有的环境变量,还有一些变量虽然不是环境变量,但是是和Bash操作接口相关的变量,这些变量也对我们的Bash操作终端起到了重要的作用。这些变量就只能用set命令来查看了,这里只列出重要的内容,如下:
[root@localhost ~]# set BASH=/bin/bash ←Bash的位置 BASH_VERSINFO=([0]="4" [1]="1" [2]="2" [3]="1" [4]="release" [5]="i386-redhat-linux-gnu") ←Bash的信息 BASH_VERSION='4.1.2(1)-release' ←Bash的版本 COLORS=/etc/DIR_COLORS ←颜色记录文件 HISTFILE=/root/.bash_history ←历史命令保存文件 HISTFILESIZE=1000 ←在文件中记录的历史命令最大条数 HISTSIZE=1000 ←在缓存中记录的历史命令最大条数 LANG=zh_CN.UTF-8 ←语系 MACHTYPE=i386-redhat-linux-gnu ←软件类型是i386兼容类型 MAILCHECK=60 ←每隔60秒去扫描新邮件 PPID=2166 ←父Shell的PID。当前Shell是一个子Shell PS1='[\u@\h \W]\$ ' ←命令提示符 PS2='> ' ←如果命令在一行中没有输入完成,第二行命令的提示符 UID=0 ←当前用户的UID
set命令是可以查询所有的变量的,当然也包含环境变量,所以在这里并没有列出刚刚在env命令中介绍过的环境变量。其实这些和Bash操作接口相关的变量,我们一般也当作环境变量来对待,因为它们确实也是用来定义我们的操作环境的。下面解释一些重要的环境变量。
1)PATH变量:系统查找命令的路径
在2.2节中我们说过,程序脚本要想在Linux中运行,需要使用绝对路径或相对路径指定这个脚本所在的位置。但是为什么系统命令都没有指定路径而是直接执行的?比如,ls命令并没有输入“/bin/ls”来执行,而是直接执行“ls”命令。这就是PATH环境变量的功能了。
先查询一下PATH环境变量的值,如下:
[root@localhost ~]# echo $PATH /usr/lib/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/ bin:/root/bin
PATH变量的值是用“:”分隔的路径,这些路径就是系统查找命令的路径。也就是说,我们输入了一个程序名,如果没有写入路径,系统就会到PATH变量定义的路径中去寻找是否有可以执行的程序,如果找到则执行,否则会报“命令没有发现”的错误。
那么,是不是我们把自己写的脚本复制到PATH变量定义的路径中也可以不输入路径而直接执行呢?当然是可以的,我们试试吧,就拿最开始的hello.sh来举例吧。
[root@localhost ~]# cp /root/sh/hello.sh /bin/ #复制hello.sh到/bin/目录中 [root@localhost ~]# hello.sh Mr. Shen Chao is the most honest man in LampBrother #hello.sh可以直接执行了
我们只要把程序脚本复制到PATH变量定义的任意路径中,比如/bin/目录下,以后这个脚本就可以直接执行了,不用再指定绝对路径或相对路径。
如果我们把自己写的所有程序脚本都放在/bin/目录下,那么有时会搞不清系统命令和自己写的程序(其实超哥是很反对改变系统目录的结构的)。我们是不是可以修改PATH变量的值,而不把程序脚本复制到/bin/目录中?当然是可以的,通过变量的叠加就可以实现了。
[root@localhost ~]# PATH="$PATH":/root/sh #在变量PATH的后面,加入/root/sh目录 [root@localhost ~]# echo $PATH /usr/lib/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/ bin:/root/bin:/root/sh #查询PATH的值,变量叠加生效了
当然,这样定义的PATH变量只能临时生效,一旦重启或注销系统就会消失。如果想要永久生效,则需要写入环境变量配置文件,我们在2.5节中再详细介绍。
2)PS1变量:命令提示符设置
PS1是一个很有意思的变量,是用来定义命令行的提示符的,可以按照我们自己的需求来定义自己喜欢的提示符。PS1可以支持以下这些选项。
· \d:显示日期,格式为“星期 月 日”。
· \H:显示完整的主机名。如默认主机名“localhost.localdomain”。
· \h:显示简写的主机名。如默认主机名“localhost”。
· \t:显示24小时制时间,格式为“HH:MM:SS”。
· \T:显示12小时制时间,格式为“HH:MM:SS”。
· \A:显示24小时制时间,格式为“HH:MM”。
· \@:显示12小时制时间,格式为“HH:MM am/pm”。
· \u:显示当前用户名。
· \v:显示Bash的版本信息。
· \w:显示当前所在目录的完整名称。
· \W:显示当前所在目录的最后一个目录。
· \#:执行的第几条命令。
· \$:提示符。如果是root用户,则会显示提示符为“#”;如果是普通用户,则会显示提示符为“$”。
这些选项该怎么用呢?我们先看看PS1变量的默认值,如下:
[root@localhost ~]# echo $PS1 [\u@\h \W]\$ #默认的提示符是显示“[用户名@简写主机名 最后所在目录]提示符”
在PS1变量中,如果是可以解释的符号,如“\u”“\h”等,则显示这个符号的作用;如果是不能解释的符号,如“@”或“空格”,则原符号输出。我们修改一下PS1变量,看看会出现什么情况。
[root@localhost ~]# PS1='[\u@\t \w]\$ ' #修改提示符为’[用户名@当前时间 当前所在完整目录]提示符’ [root@04:46:40~]#cd /usr/local/src/ #切换到当前所在目录,因为家目录是看不出来区别的 [root@04:47:29 /usr/local/src]# #看到了吗?提示符按照我们的设计发生了变化
这里要小心,PS1变量的值要用单引号包含,否则设置不生效。再举个例子:
[root@04:50:08 /usr/local/src]#PS1='[\u@\@ \h \# \W]\$'
[root@04:53 上午localhost 31 src]#
#提示符又变了。\@:时间格式是HH:MM am/pm; \#:会显示执行了多少条命令
PS1变量可以自由定制,好像看到了一点Linux可以自由定制和修改的影子,还是很有意思的。不过说实话,一个提示符已经使用习惯了,如果换一个还是非常别扭的,还是改回默认的提示符吧,命令如下:
[root@04:53 上午localhost 31 src]#PS1='[\u@\h \W]\$ ' [root@localhost src]#
注意:这些提示符的修改同样是临时生效的,一旦注销或重启系统就会消失。要想永久生效,必须写入环境变量配置文件。
3)LANG语系变量
LANG变量定义了Linux系统的主语系环境,这个变量的默认值如下:
[root@localhost src]# echo $LANG zh_CN.UTF-8
这是因为我们在安装Linux时选择的是中文安装,所以默认的主语系变量是“zh_CN.UTF-8”。那么,Linux系统中到底支持多少种语系呢?我们可以使用以下命令查询:
[root@localhost src]# locale -a | more aa_DJ aa_DJ.iso88591 aa_DJ.utf8 aa_ER …省略部分输出… #查询支持的语系 [root@localhost src]# locale -a | wc -l 735 #实在太多了,统计一下有多少个吧
既然Linux系统支持这么多种语系,那么当前系统使用的到底是什么语系呢?使用locale命令直接查询,命令如下:
[root@localhost src]# locale LANG=zh_CN.UTF-8 LC_CTYPE="zh_CN.UTF-8" LC_NUMERIC="zh_CN.UTF-8" LC_TIME="zh_CN.UTF-8" LC_COLLATE="zh_CN.UTF-8" LC_MONETARY="zh_CN.UTF-8" LC_MESSAGES="zh_CN.UTF-8" LC_PAPER="zh_CN.UTF-8" LC_NAME="zh_CN.UTF-8" LC_ADDRESS="zh_CN.UTF-8" LC_TELEPHONE="zh_CN.UTF-8" LC_MEASUREMENT="zh_CN.UTF-8" LC_IDENTIFICATION="zh_CN.UTF-8" LC_ALL=
在Linux系统中,语系主要是通过这些变量来设置的,这里只需知道LANG和LC_ALL变量即可,其他的变量会依赖这两个变量的值而发生变化。LANG是定义系统主语系的变量,LC_ALL是定义整体语系的变量,一般使用LANG变量来定义系统语系。
我们还要通过文件/etc/sysconfig/i18n定义系统的默认语系,查看一下这个文件的内容,如下:
[root@localhost src]# cat /etc/sysconfig/i18n LANG="zh_CN.UTF-8"
这又是当前系统语系,又是默认语系,有没有快晕倒的感觉?解释一下吧,我们可以这样理解:默认语系是下次重启之后系统所使用的语系;而当前系统语系是当前系统所使用的语系。如果系统重启,则会从默认语系配置文件/etc/sysconfig/i18n中读出语系,然后赋予变量LANG,让这个语系生效。也就是说,LANG变量定义的语系只对当前系统生效;要想永久生效,就要修改/etc/sysconfig/i18n文件。
说到这里,我们需要解释一下Linux中文支持的问题。是不是只要定义了语系为中文语系,如zh_CN.UTF-8,就可以正确显示中文了呢?这要分情况,如果是在图形界面中,或者使用远程连接工具(如SecureCRT、Xshell等),那么,只要正确设置了语系,是可以正确显示中文的。当然,远程连接工具也要配置正确的语系环境。
如果是纯字符界面(本地终端tty1~tty6),则是不能显示中文的,因为Linux的纯字符界面是不能显示中文这么复杂的编码的。如果非要在纯字符界面中显示中文,那么只能安装中文插件,如zhcon等。举个例子,先来看看在远程连接工具中显示中文的情况,如下:
[root@localhost src]# echo $LANG zh_CN.UTF-8 #当前使用远程连接工具,只要语系正确,就可以正确显示中文 [root@localhost src]# df 文件系统 1K-块 已用 可用 已用% 挂载点 /dev/sda3 19923216 1813532 17097616 10% / tmpfs 312672 0 312672 0% /dev/shm /dev/sda1 198337 26359 161738 15% /boot #使用df命令可以看到中文是正常显示的
但如果是纯字符界面呢?虽然Linux是中文安装的,但纯字符界面的语系却是“en_US. UTF-8”,如图2-2所示。
图2-2 纯字符界面的语系
我们更改语系为中文,看看会出现什么情况,如图2-3所示。
图2-3 纯字符界面设置中文语系
如果我们非要在纯字符界面中设置中文语系,就会出现乱码。怎么解决呢?安装zhcon中文插件吧,安装并不复杂,查询一下安装说明应该可以轻松地安装。
2.4.4 位置参数变量
在Linux的命令行中,当一条命令或脚本执行时,后面可以跟多个参数,我们使用位置参数变量来表示这些参数。其中,$0代表命令行本身,$1代表第1个参数,$2代表第2个参数,依次类推。当参数个数超过10个时,就要用大括号把这个数字括起来,例如,${10}代表第10个参数,${14}则代表第14个参数。举个例子:
[root@localhost ~]# ls anaconda-ks.cfg install.log install.log.syslog
如果执行这样一条命令,则$0的值就是ls命令本身,$1的值就是anaconda-ks.cfg这个文件,$2是install.log文件,$3是install.log.syslog文件。
在Shell中可以识别的位置参数变量如表2-9所示。
表2-9 位置参数变量
位置参数变量主要用于向命令或程序脚本中传递信息,比如,我们想要写一个计算器,总要告诉程序应该运算哪个字符吧,这时就需要通过位置参数变量向脚本中传递数值。那就先写一个加法计算器吧,命令如下:
[root@localhost ~]# cd sh/ [root@localhost sh]# vi count.sh #! /bin/bash # Author: shenchao(E-mail: shenchao@lampbrother.net) num1=$1 #给num1变量赋值是第一个参数 num2=$2 #给num2变量赋值是第二个参数 sum=$(( $num1 + $num2)) #变量sum的和是num1加num2 #Shell中的运算还是不太一样的,我们将在2.4.7节中进行详细介绍 echo $sum #打印变量sum的值
在Shell中,数值运算是必须使用特殊格式的,我们在2.4.7节中再详细介绍,这里大家就照着例子先执行。执行一下这个脚本吧:
[root@localhost sh]# chmod 755 count.sh #给脚本文件赋予执行权限 [root@localhost sh]# ./count.sh 11 22 33 #这个脚本就会把第一个参数和第二个参数相加
还有几个位置参数变量是干什么的呢?我们再写一个脚本来说明一下,如下:
[root@localhost sh]# vi parameter.sh #! /bin/bash # Author: shenchao(E-mail: shenchao@lampbrother.net) echo "A total of $# parameters" #使用$#代表所有参数的个数 echo "The parameters is: $*" #使用$*代表所有的参数 echo "The parameters is: $@" #使用$@也代表所有的参数
执行一下这个脚本:
[root@localhost sh]# chmod 755 parameter.sh [root@localhost sh]# ./parameter.sh 11 22 33 A total of 3 parameters #因为输入了三个参数,所以$#显示的值是3 The parameters is: 11 22 33 #输出了所有参数 The parameters is: 11 22 33 #也输出了所有参数
那么“$*”和“$@”有区别吗?还是有区别的,$*会把接收到的所有参数当成一个整体对待,而$@则会区别对待接收到的所有参数。还是举个例子吧:
[root@localhost sh]# vi parameter2.sh #! /bin/bash # Author: shenchao(E-mail: shenchao@lampbrother.net) for i in "$*" #定义for循环,in后面有几个值,for就会循环多少次,注意“$*”要用双引号括起来 #每次循环都会把in后面的值赋予变量i #Shell把“$*”中的所有参数看成一个整体,所以这个for循环只会循环一次 do echo "The parameters is: $i" #打印变量$i的值 done x=0 #定义变量x的值为0 for y in "$@" #同样,in后面有几个值,for就会循环几次,每次都把值赋予变量y #因为Shell会把“$@”中的每个参数都看成独立的,所以“$@”中有几个参数,就会循环几次 do echo "The parameter$x is: $y" #输出变量y的值 x=$(( $x +1 )) #让变量x每次循环都加1,是为了输出时看得更清楚 done echo "x is: $x"
在这个脚本中我们用到了for循环,关于for循环,我们在第3章中还会有详细介绍。执行一下这个脚本:
[root@localhost sh]# chmod 755 parameter2.sh [root@localhost sh]# ./parameter2.sh 11 22 33 The parameters is: 11 22 33 #这是第一个for的结果,“$*”被看作一个整体,所以只会循环一次 The parameter1 is: 11 The parameter2 is: 22 The parameter3 is: 33 #这是第二个for的结果,“$@”中的每个变量被区别对待,所以会循环三次 x is: 3 #x的值是3,证明循环了三次
2.4.5 预定义变量
预定义变量是在Shell一开始时就定义的变量,这一点和默认环境变量有些类似。不同的是,预定义变量不能重新定义,用户只能根据Shell的定义来使用这些变量。其实,严格来说,位置参数变量也是预定义变量的一种,只是位置参数变量的作用比较统一,所以我们把位置参数变量单独划分为一类变量。
那么,预定义变量有哪些呢?我们通过表2-10来说明一下。
表2-10 预定义变量
我们先来看看“$? ”这个变量,看起来不好理解,还是举个例子吧,如下:
[root@localhost sh]# ls count.sh hello.sh parameter2.sh parameter.sh #ls命令正确执行 [root@localhost sh]# echo $? 0 #预定义变量“$? ”的值是0,证明上一条命令正确执行 [root@localhost sh]# ls install.log ls: 无法访问install.log: 没有那个文件或目录 #当前目录中没有install.log文件,所以ls命令报错了 [root@localhost sh]# echo $? 2 #变量“$? ”返回一个非0的值,证明上一条命令没有正确执行 #至于错误的返回值到底是多少,是在编写ls命令时定义好的,如果碰到文件不存在就返回数值2
这里需要用到进程号(PID)的概念,我们会在第6章中详细介绍。这里大家可以理解为在系统中每个进程都有一个ID,我们把这个ID称作PID,系统是通过PID来区分不同的进程的。
接下来说明一下“$$”和“$! ”这两个预定义变量,我们写一个脚本吧,如下:
[root@localhost sh]# vi variable.sh #! /bin/bash # Author: shenchao(E-mail: shenchao@lampbrother.net) echo "The current process is $$" #输出当前进程的PID #这个PID就是variable.sh脚本执行时生成的进程的PID find /root -name hello.sh & #使用find命令在/root目录下查找hello.sh文件 #符号“&”的意思是把命令放入后台执行 echo "The last one Daemon process is $! " #输出这个后台执行命令的进程的PID,也就是输出find命令的PID
执行一下这个命令:
[root@localhost sh]# chmod 755 variable.sh #赋予执行权限 [root@localhost sh]# ./variable.sh The current process is 26970 #脚本variable.sh执行时,PID是26970 The last one Daemon process is 26971 #find命令执行时,PID是26971
这里需要注意的是,不论是脚本variable.sh,还是find命令,一旦执行完毕就会停止,所以使用ps命令是查看不到这两个进程号的。
一般情况下使用“$? ”变量来判断上一条命令是否正确执行,我们后面要讲的test测试命令也是通过“$? ”变量来判断上一条命令是否正确执行的。使用“$$”变量来给临时文件命名,以保证临时文件名不会重复。
2.4.6 接收键盘输入
我们刚刚讲过的位置参数变量是可以把用户的输入用参数的方式输入脚本的,不过这种输入方式只有写这个脚本的人才能确定需要输入几个参数,每个参数应该输入什么类型的数据,并不适合普通用户使用。
除位置参数变量外,我们也可以使用read命令向脚本中传入数据。read命令接收标准输入(键盘)的输入,或者其他文件描述符的输入。得到输入后,read命令将数据放入一个标准变量中。命令格式如下:
[root@localhost ~]# read [选项] [变量名] 选项: -p "提示信息": 在等待read输入时,输出提示信息 -t秒数: read命令会一直等待用户输入,使用此选项可以指定等待时间 -n字符数: read命令只接收指定的字符数就会执行 -s: 隐藏输入的数据,适用于机密信息的输入 变量名: 变量名可以自定义。如果不指定变量名,则会把输入保存到默认变量REPLY中 如果只提供了一个变量名,则将整个输入行赋予该变量 如果提供了一个以上的变量名,则输入行分为若干字,一个接一个地赋予各个变量,而命令行上的最后 一个变量取得剩余的所有字
还是写一个例子来解释一下read命令,如下:
[root@localhost sh]# vi read.sh
#! /bin/bash
# Author: shenchao(E-mail: shenchao@lampbrother.net)
read -t 30-p "Please input your name: " name
#提示“请输入姓名”并等待30秒,把用户的输入保存到变量name中
echo "Name is $name"
#看看变量“$name”中是否保存了你的输入
read -s -t 30-p "Please enter your age: " age
#提示“请输入年龄”并等待30秒,把用户的输入保存到变量age中
#年龄是隐私,所以我们用“-s”选项隐藏输入
echo -e "\n"
#调整输出格式,如果不输出换行,则一会儿的年龄输出不会换行
echo "Age is $age"
read -n 1-t 30-p "Please select your gender[M/F]: " gender
#提示“请选择性别”并等待30秒,把用户的输入保存到变量gender中
#使用“-n 1”选项只接收一个输入字符就会执行(无须按回车键)
echo -e "\n"
echo "Sex is $gender"
执行一下这个脚本:
[root@localhost sh]# chmod 755 read.sh
#赋予执行权限
[root@localhost sh]# ./read.sh
#运行脚本
Please input your name: shen chao
#在read的提示界面输入姓名
Name is shen chao
#“$name”变量中保存了我们的输入
Please enter your age:
#因为加入了“-s”选项,所以输入不会显示在命令行上
Age is 18
#“$age”变量中保存了我们的输入
Please select your gender[M/F]: M
#因为加入了“-n 1”选项,所以只能输入一个字符
Sex is M
#“$gender”变量中保存了我们的输入
read命令并不难,却是接收键盘输入的重要方法,要熟练使用。
2.4.7 Shell的运算符
1.数值运算
Shell编程和其他语言还是有很多不一样的地方的,其中超哥最不习惯的是:在Shell中所有的变量默认都是“字符串型”。也就是说,如果不手工指定变量的类型,那么所有的数值都是不能进行运算的。比如:
[root@localhost sh]# aa=11 [root@localhost sh]# bb=22 #给变量aa和bb赋值 [root@localhost sh]# cc=$aa+$bb #我想让cc的值是aa和bb的和 [root@localhost sh]# echo $cc 11+22 #但是cc的值却是“11+22”这个字符串,并没有进行数值运算
如果需要进行数值运算,则可以采用以下三种方法中的任意一种。
1)使用declare声明变量类型
既然所有变量的默认类型是字符串型,那么只要把变量声明为整数型不就可以参与运算了吗?使用declare命令就可以声明变量的类型。命令格式如下:
[root@localhost ~]# declare [+/-][选项] 变量名 选项: -: 给变量设定类型属性 +: 取消变量的类型属性 -a: 将变量声明为数组型 -i: 将变量声明为整数型(integer) -r: 将变量声明为只读变量。注意,一旦设置为只读变量,既不能修改变量的值, 也不能删除变量,甚至不能通过+r取消只读属性 -x: 将变量声明为环境变量 -p: 显示指定变量的被声明的类型
例子1:数值运算
只要把变量声明为整数型就可以参与运算了吗?试试吧:
[root@localhost ~]# aa=11 [root@localhost ~]# bb=22 #给变量aa和bb赋值 [root@localhost ~]# declare -i cc=$aa+$bb #声明变量cc的类型是整数型,它的值是aa和bb的和 [root@localhost ~]# echo $cc 33 #这下终于可以相加了
这样运算好麻烦!没有办法,Shell在数值运算方面确实是比较麻烦的,习惯就好了。
例子2:数组变量类型
只有在写一些较为复杂的程序时才会用到数组,大家不用着急学习数组,当有需要的时候再回来详细学习。那么,数组是什么呢?所谓数组,就是相同数据类型的元素按一定顺序排列的集合,也就是把有限个类型相同的变量用一个名字命名,然后用编号区分它们的变量的集合,我们把这个名字称为数组名,把编号称为下标。组成数组的各个变量被称为数组的分量,又称数组的元素、下标变量。
一看定义就一头雾水,更加不明白数组是什么了。那么换一种说法,变量和数组都是用来保存数据的,只是变量只能被赋予一个数据值,一旦重复赋值,后一个值就会覆盖前一个值;而数组可以被赋予一组相同类型的数据值。大家可以把变量想象成一间小办公室,这间办公室里只能容纳一个人办公,办公室名就是变量名;而数组是一间大办公室,可以容纳很多人同时办公,在这间大办公室里办公的每个人是通过不同的座位号来区分的,这个座位号就是数组的下标,而大办公室的名字就是数组名。
还是举个例子吧:
[root@localhost ~]# name[0]="shen chao" #数组中第一个变量是沈超 [root@localhost ~]# name[1]="li ming" #数组中第二个变量是李明 [root@localhost ~]# name[2]="gao luo feng" #数组中第三个变量是高洛峰 [root@localhost ~]# echo ${name} shen chao #输出数组的内容。如果只写数组名,那么只会输出第一个下标变量 [root@localhost ~]# echo ${name[*]} shen chao li ming gao luo feng #输出数组所有的内容
注意:数组的下标是从0开始的,在调用数组的元素时,需要使用“${数组[下标]}”方式来读取。
不过,在刚刚的例子中,我们并没有把name变量声明为数组型。其实只要我们在定义变量时采用了“变量名[下标]”的格式,这个变量就会被系统认为是数组型了,不用强制声明。
例子3:环境变量
其实也可以使用declare命令把变量声明为环境变量,它和export命令的作用是一样的。命令如下:
[root@localhost ~]# declare -x test=123 #把变量test声明为环境变量
例子4:只读属性
一旦给变量设定了只读属性,那么这个变量既不能修改变量的值,也不能删除变量,甚至不能使用“+r”选项取消只读属性。命令如下:
[root@localhost ~]# declare -r test #给test变量赋予只读属性 [root@localhost ~]# test=456 -bash: test: readonly variable #test变量的值就不能修改了 [root@localhost ~]# declare +r test -bash: declare: test: readonly variable #也不能取消只读属性 [root@localhost ~]# unset test -bash: unset: test: cannot unset: readonly variable #也不能删除变量
不过,还好这个变量只是命令行声明的,所以只要重新登录或重启,这个变量就会消失了。
例子5:查询变量属性和取消变量属性
变量属性的查询使用“-p”选项,变量属性的取消使用“+”选项。命令如下:
[root@localhost ~]# declare -p cc declare -i cc="33" #cc变量是int型 [root@localhost ~]# declare -p name declare -a name='([0]="shen chao" [1]="li ming" [2]="gao luo feng")' #name变量是数组型 [root@localhost ~]# declare -p test declare -rx test="123" #test变量是环境变量和只读变量 [root@localhost ~]# declare +x test #取消test变量的环境变量属性 [root@localhost ~]# declare -p test declare -r test="123" #注意:只读变量属性是不能被取消的
2)使用expr或let数值运算工具
进行数值运算的第二种方法是使用expr命令,这个命令就没有declare命令那么复杂了。命令如下:
[root@localhost ~]# aa=11 [root@localhost ~]# bb=22 #给变量aa和bb赋值 [root@localhost ~]# dd=$(expr $aa + $bb) #dd的值是aa和bb的和。注意“+”号左右两侧必须有空格 [root@localhost ~]# echo $dd 33
在使用expr命令进行运算时,要注意“+”号左右两侧必须有空格,否则运算不执行。
至于let命令,和expr命令基本类似,都是Linux中的运算命令。命令如下:
[root@localhost ~]# aa=11 [root@localhost ~]# bb=22 #给变量aa和bb赋值 [root@localhost ~]# let ee=$aa+$bb [root@localhost ~]# echo $ee 33 #变量ee的值是aa和bb的和 [root@localhost ~]# n=20 #定义变量n [root@localhost ~]# let n+=1 #变量n的值等于变量本身再加1 [root@localhost ~]# echo $n 21
expr和let命令大家可以按照习惯使用,不过let命令对格式的要求要比expr命令对格式的要求宽松,所以推荐使用let命令进行数值运算。
3)使用“$((运算式))”或“$[运算式]”方式运算
其实这是一种方式,“$(())”和“$[]”这两种括号按照个人习惯使用即可。命令如下:
[root@localhost ~]# aa=11 [root@localhost ~]# bb=22 [root@localhost ~]# ff=$(( $aa+$bb )) [root@localhost ~]# echo $ff 33 #变量ff的值是aa和bb的和 [root@localhost ~]# gg=$[ $aa+$bb ] [root@localhost ~]# echo $gg 33 #变量gg的值是aa和bb的和
这三种数值运算方式,大家可以按照自己的习惯来选择使用。不过我们推荐使用“$((运算式))”,这种方式更加简单,也更加常用。
2.Shell中常用的运算符
我们通过表2-11来说明一下Shell中常用的运算符。
表2-11 Shell中常用的运算符
运算符优先级表明在每个表达式或子表达式中哪个运算对象首先被求值,数值越大优先级越高,具有较高优先级的运算符先于具有较低优先级的运算符进行求值运算。
还是举几个例子来说明。
例子1:加减乘除
[root@localhost ~]# aa=$(( (11+3)*3/2 )) #虽然乘和除的优先级高于加,但是通过小括号可以调整运算优先级 [root@localhost ~]# echo $aa 21
例子2:取模运算
[root@localhost ~]# bb=$(( 14%3 )) [root@localhost ~]# echo $bb 2 #14不能被3整除,余数是2
例子3:逻辑与
[root@localhost ~]# cc=$(( 1 && 0 )) [root@localhost ~]# echo $cc 0 #逻辑与运算只有相与的两边都是1,与的结果才是1;否则与的结果是0
2.4.8 变量测试与内容置换
在脚本中,我们有时需要判断变量是否存在或是否被赋予了值,如果变量已经存在并且被赋予了值,则不改变变量;如果变量不存在或没有被赋值,则赋予其新值。这时我们就可以使用变量测试与内容置换。我们在脚本中可以使用条件判断语句if来替代这种测试方法,不过使用Shell自带的变量置换更加方便,但是这种方法容易记混,我们通过表2-12来进行说明。
表2-12 变量测试与内容置换
如果大括号内没有“:”,则变量y为空或没有被设置,处理方法是不同的;如果大括号内有“:”,则变量y不论是为空,还是没有被设置,处理方法是一样的。
如果大括号内是“-”或“+”,则在改变变量x的值的时候,变量y的值是不改变的;如果大括号内是“=”,则在改变变量x的值的同时,变量y的值也会改变。
如果大括号内是“? ”,则当变量y不存在或为空时,会把“新值”当成报错输出到屏幕上。
举几个例子来说明一下。
例子1:
[root@localhost ~]# unset y #删除变量y [root@localhost ~]# x=${y-new} #进行测试 [root@localhost ~]# echo $x new #因为变量y不存在,所以x=new [root@localhost ~]# echo $y #变量y还是不存在的
和表2-12对比一下,是不是可以看懂了?这是变量y不存在的情况,如果变量y为空呢?
[root@localhost ~]# y="" #给变量y赋值为空 [root@localhost ~]# x=${y-new} #进行测试 [root@localhost ~]# echo $x [root@localhost ~]# echo $y #变量x和y都为空
如果变量y有值呢?
[root@localhost ~]# y=old #给变量y赋值 [root@localhost ~]# x=${y-new} #进行测试 [root@localhost ~]# echo $x old [root@localhost ~]# echo $y old #变量x和y的值都是old
例子2:
如果大括号内是“=”,则又是什么情况呢?先测试一下变量y没有被设置的情况,如下:
[root@localhost ~]# unset y #删除变量y [root@localhost ~]# x=${y:=new} #进行测试 [root@localhost ~]# echo $x new [root@localhost ~]# echo $y new #变量x和y的值都是new
一旦使用了“=”,那么会同时处理变量x和y,而不像例子1那样只改变变量x的值。如果变量y为空,则又是什么情况呢?
[root@localhost ~]# y="" #设定变量y为空 [root@localhost ~]# x=${y:=new} #进行测试 [root@localhost ~]# echo $x new [root@localhost ~]# echo $y new #变量x和y的值都是new
一旦在大括号中使用“:”,那么变量y为空或者没有被设置,处理方法是一样的。如果变量y已经被赋值了,则又是什么情况?
[root@localhost ~]# y=old #给变量y赋值 [root@localhost ~]# x=${y:=new} #进行测试 [root@localhost ~]# echo $x old [root@localhost ~]# echo $y old #变量x和y的值都是old
例子3:
再测试一下大括号中是“? ”的情况。
[root@localhost ~]# unset y #删除变量y [root@localhost ~]# x=${y? new} -bash: y: new #会把值“new”输出到屏幕上
如果变量y已经被赋值了呢?
[root@localhost ~]# y=old #给变量y赋值 [root@localhost ~]# x=${y? new} #进行测试 [root@localhost ~]# echo $x old [root@localhost ~]# echo $y old #变量x和y的值都是old
这些内容实在让人头疼啊,如果在脚本中用到了,则参考表2-12即可。