跟老男孩学Linux运维:Shell编程实战.2-阿里云开发者社区

开发者社区> 华章出版社> 正文

跟老男孩学Linux运维:Shell编程实战.2

简介:

4d6ce5e70f97a399af84d65fc38c76e3582d6a67

第3章
Shell变量的核心基础知识与实践
3.1 什么是Shell变量
1.?什么是变量
在小学或初中时,我们开始接触数学方程式,例如:已知x=1,y=x+1,那么y等于多少?
在上述问题中,等号左边的x和y当时被称为未知数,但在Shell编程里它们是变量名,等号右边的1和x+1则是变量的内容(变量的值)。注意,这里的等号符号被称为赋值,而不是等号。
通过上面的例子可以得出一个变量概念的小结论:简单地说,变量就是用一个固定的字符串(也可能是字符、数字等的组合)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他的内容。
变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。
下面是定义变量和打印变量的示例:
[root@oldboy ~]# oldboy="I am oldboy" #<==定义变量,名字为oldboy,对应的内容
    ?为“I am oldboy”。
[root@oldboy ~]# echo $oldboy #<==打印变量的值。
I am oldboy
变量的赋值方式为:先写变量名称,紧接着是“=”这个字符,最后是值,中间无任何空格,通过echo命令加上$oldboy即可输出oldboy变量的值,变量的内容一般要加双引号,以防止出错,特别是当值里的内容之间有空格时。
2.?Shell变量的特性
默认情况下,在bash Shell中是不会区分变量类型的,例如:常见的变量类型为整数、字符串、小数等。这和其他强类型语言(例如:Java/C语言)是有区别的,当然,如果需要指定Shell变量的类型,也可以使用declare显示定义变量的类型,但在一般情况下没有这个需求,Shell开发者在开发脚本时需要自行注意Shell脚本中变量的类型,这对新手来说是个重点也是个难点,别害怕,跟着老男孩走,一切都不是事。
3.?变量类型
变量可分为两类:环境变量(全局变量)和普通变量(局部变量)。
环境变量也可称为全局变量,可以在创建它们的Shell及其派生出来的任意子进程Shell中使用,环境变量又可分为自定义环境变量和bash内置的环境变量。
普通变量也可称为局部变量,只能在创建它们的Shell函数或Shell脚本中使用。普通变量一般由开发者在开发脚本程序时创建。
3.2 环境变量
环境变量一般是指用export内置命令导出的变量,用于定义Shell的运行环境,保证Shell命令的正确执行。Shell通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、Shell脚本和各类应用。
环境变量可以在命令行中设置和创建,但用户退出命令行时这些变量值就会丢失,因此,如果希望永久保存环境变量,可在用户家目录下的.bash_prof?ile或.bashrc(非用户登录模式特有,例如远程SSH)文件中,或者全局配置/etc/bashrc(非用户登录模式特有,例如远程SSH)或/etc/prof?ile文件中定义。在将环境变量放入上述的文件中后,每次用户登录时这些变量都将被初始化。
按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用export命令导出定义,例如:正确的环境变量定义方法为export OLDGIRL=1。
有一些环境变量,比如HOME、PATH、SHELL、UID、USER等,在用户登录之前就已经被/bin/login程序设置好了。通常环境变量被定义并保存在用户家目录下的.bash_prof?ile文件或全局的配置文件/etc/prof?ile中,具体的环境变量说明参见表3-1。
表3-1 部分bash环境变量展示(执行env命令后获得)
变量名 含  义
_= 上一条命令的最后一个参数
BASH=/bin/bash 调用bash实例时使用的全路径名
BASH_VERSINFO=([0]="3"[1]="2"[2]="25" [3]="1"[4]="release"[5]="x86_64-redhat-linux-gnu") 使用2.0以上版本时,展开为版本信息
BASH_VERSION='3.2.25(1)-release' 当前bash实例的版本号
COLORS=/etc/DIR_COLORS 颜色变量
COLUMNS=132 设置该变量,就给Shell编辑模式和选择的命令定义了编辑窗口的宽度
DIRSTACK=() 代表目录栈的当前内容
EUID=0 在Shell启动时被初始化的当前用户的有效ID
GROUPS=() 当前用户所属的组
HISTFILE=/root/.bash_history 历史记录文件的全路径
HISTFILESIZE=50 历史文件能包含的最大行数
HISTSIZE=50 记录在命令行历史文件中的命令行数
HOME=/root 当前用户家目录
HOSTNAME=oldboy 当前主机名称
HOSTTYPE=x86_64 当前操作系统类型
IFS=$'\t\n' 内部字段分隔符,一般是空格符、制表符和换行符,用于划分由命令替换、循环结构中的表和所读取的输入产生的词的字段
INPUTRC=/etc/inputrc readline启动文件的文件名,取代默认的~/.inputrc
JAVA_HOME=/application/jdk1.6.0_10 JAVA HOME环境变量
LANG=zh_CN.UTF-8 字符集
LOGNAME=root 登录用户名称
MACHTYPE=x86_64-redhat-linux-gnu 包含一个描述正在运行bash的系统串
MAILCHECK=60 这个参数定义Shell将隔多长时间(以秒为单位)检查一次由参数MAILPATH或MAILFILE指定的文件,看看是否有邮件到达。默认值为600s
MAIL=/var/spool/mail/root 邮件全路径
OLDPWD=/root 前一个当前工作目录
OPTIND=1 下一个由getopts内置命令处理的参数的序号
OSTYPE=linux-gnu 自动设置成一个串,该串描述正在运行bash的操作系统,默认值由系统来决定
PATH=/usr/lib64/qt-3.3/bin:/usr/kerberos/sbin:
/usr/kerberos/bin
:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/bin:
/server/script/shangxian:/root/bin 全局PATH路径,命令搜索路径。一个由冒号分隔的目录列表,Shell用它来搜索命令。默认路径由系统来决定,并且由安装bash的管理员来设置
PIPESTATUS=([0]="0"[1]="0") 一个数组,包含一列最近在管道执行的前台作业的进程退出的状态值
PPID=1112 父进程的进程ID
PS1='[\u@\h \W]\$ ' 主提示符串,默认值是$
PS2='>' 次提示符串,默认值是>
PS4=`+` 当开启追踪时使用的是调试提示符串,默认值是+,追踪可用set –x开启
PWD=/home 当前用户家目录
RESIN_HOME=/application/resin-3.1.6 这是通过export人为设置的环境变量,java环境会用
SHELL=/bin/bash 登录Shell类型
SHELLOPTS=braceexpand:emacs:hashall:
histexpand:history:interactive-comments:monitor 包含一列开启的Shell选项
SHLVL=1 每启动一个bash实例就将其加1
TERM=vt100 终端设置
TMOUT=3600 退出前等待超时的秒数
UID=0 当前用户的UID,在Shell启动时初始化
USER=root 当前用户的用户名,在Shell启动时初始化

在查看设置的变量时,有3个命令可以显示变量的值:set、env和declare(替代早期的typeset)。set命令输出所有的变量,包括全局变量和局部变量;env命令只显示全局变量;declare命令输出所有的变量、函数、整数和已经导出的变量。set -o命令显示bash Shell的所有参数配置信息。
范例3-1:set、env和declare输出。
[root@oldboy ~]# env|tail
SHLVL=1
HOME=/root
LOGNAME=root
CVS_RSH=ssh
MODULESHOME=/usr/share/Modules
LESSOPEN=||/usr/bin/lesspipe.sh %s
G_BROKEN_FILENAMES=1
BASH_FUNC_module()=() {  eval `/usr/bin/modulecmd bash $*`
}
_=/bin/env
[root@oldboy ~]# declare|tail
_module_not_yet_loaded ()
{
    comm -23 <(_module_avail|sort) <(tr : '\n' <<<${LOADEDMODULES}|sort)
}
module ()
{
    eval `/usr/bin/modulecmd bash $*`
}
[root@oldboy ~]# set|tail
_module_not_yet_loaded ()
{
    comm -23 <(_module_avail|sort) <(tr : '\n' <<<${LOADEDMODULES}|sort)
}
module ()
{
    eval `/usr/bin/modulecmd bash $*`
}
[root@oldboy ~]# set -o|head
allexport       off
braceexpand     on
emacs           on
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      on
history         on
ignoreeof       off
3.2.1 自定义环境变量
1.?设置环境变量
如果想要设置环境变量,就要在给变量赋值之后或在设置变量时使用export命令,具体设置见下文的示例。其实,除了export命令,带-x选项的declare内置命令也可以完成同样的功能(注意:此处不要在变量名前面加$)。
export命令和declare命令的格式如下:
①export 变量名=value
②变量名=value ; export 变量名
③declare –x 变量名=value
提示: 以上为设置环境变量的3种方法。
范例3-2:定义环境变量并赋值的方法。
export NAME=oldboy
declare –x NAME=oldboy
NAME=oldboy ;export NAME
以下是自定义全局环境变量的示例:
[root@oldboy script]# cat /etc/profile|grep OLD
export OLDBOY='oldboy' #<==编辑/etc/profile,然后输出此行并保存
[root@oldboy script]# source /etc/profile #<==或. /etc/profile使其生效
[root@oldboy script]# echo $OLDBOY #<==在变量前加$符号并打印变量值
oldboy
[root@oldboy script]# env|grep OLDBOY #<==查看定义结果
OLDBOY=oldboy
下面来看看让环境变量永久生效的常用设置文件。
(1)用户的环境变量配置
配置如下:
[root@oldboy scripts]# ls /root/.bashrc #<==推荐在此文件中优先设置
/root/.bashrc
[root@oldboy scripts]# ls /root/.bash_profile
/root/.bash_profile
提示: 对于用户的环境变量设置,比较常见的是用户家目录下的.bashrc和.bash_profile。
(2)全局环境变量的配置
常见的全局环境变量的配置文件如下:
/etc/profile
/etc/bashrc #<==推荐在此文件中优先设置
/etc/profile.d/
若要在登录后初始化或显示加载内容,则把脚本文件放在/etc/prof?ile.d/下即可(无须加执行权限)。
2.?设置登录提示的两种方式
第一种是在/etc/motd里增加提示的字符串,如下:
[root@oldboy ~]# cat /etc/motd #<==文件里仅为字符串内容
welcome to oldboy linux Shell training.
登录后显示内容如下:
Last login : Fri Nov  7 15 :36 :56 2016 from 10.0.0.100
welcome to oldboy linux Shell training.
第二种是在/etc/prof?ile.d/下面增加如下脚本。
[root@oldboy ~]# cat /etc/profile.d/oldboy.sh #<==这里是脚本的内容
echo " Here is oldboy training "
登录后显示的内容如下:
Last login : Fri Nov  7 15 :36 :56 2016 from 10.0.0.100
Here is oldboy training
以下是在生产场景下(在Java环境中),自定义环境变量的示例。
export JAVA_HOME=/application/jdk
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOME/bin
export RESIN_HOME=/application/resin
提示: 上述的环境变量设置通常放在/etc/profile全局环境变量里。
如果是写Java的脚本,那么最好是把上述Java环境配置放入脚本内重新定义,特别是作为定时任务执行的脚本。
3.2.2 显示与取消环境变量
1.?通过echo或printf命令打印环境变量
下面我们先来看看常见的系统环境变量。
$HOME:用户登录时进入的目录。
$UID:当前用户的UID(用户标识),相当于id?-u。
$PWD:当前工作目录的绝对路径名。
$SHELL:当前SHELL。
$USER:当前用户。
......
范例3-3:通过echo和printf命令打印环境变量。
[oldboy@oldboy ~]$ echo $HOME
/home/oldboy
[oldboy@oldboy ~]$ echo $UID
504
[oldboy@oldboy ~]$ echo $PWD
/home/oldboy
[oldboy@oldboy ~]$ echo $SHELL
/bin/bash
[oldboy@oldboy ~]$ echo $USER
oldboy
[root@oldboy ~]# printf "$HOME\n"
#<==printf是一个更复杂的格式化打印内容的工具,一般不需要
/root
提示: 在写Shell脚本时可以直接使用系统默认的环境变量,一般情况下是不需要重新定义的,在使用定时任务等执行Shell脚本时建议在脚本中重新定义。
2.?用env或set显示默认的环境变量
用env(printenv)显示默认环境变量的示例如下:
[oldboy@oldboy ~]$ env
HOSTNAME=oldboy
SHELL=/bin/bash
HISTSIZE=1000
LC_ALL=C
MAIL=/var/spool/mail/oldboy
PATH=/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/oldboy/bin
INPUTRC=/etc/inputrc
PWD=/home/oldboy
LANG=zh_cn.gb18030
SHLVL=1
HOME=/home/oldboy
LOGNAME=oldboy
中间和结尾省略若干代码
用set也可以显示环境变量(包括局部变量),如下:
[root@oldboy ~]# set
APACHEERR=hello
BASH=/bin/bash
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="4" [1]="1" [2]="2" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.1.2(1)-release'
COLORS=/etc/DIR_COLORS
COLUMNS=103
中间和结尾省略若干代码
在3.2.1节还提到了一个相关的命令declare,大家还记得吗?
3.?用unset消除本地变量和环境变量
用unset消除本地变量和环境变量的示例如下:
[oldboy@oldboy ~]$ echo $USER
oldboy
[oldboy@oldboy ~]$ unset USER
[oldboy@oldboy ~]$ echo $USER
                                #<==此处为输出的空行
可以看到变量的内容显示为空。
环境变量的知识小结:
变量名通常要大写。
变量可以在自身的Shell及子Shell中使用。
常用export来定义环境变量。
执行env默认可以显示所有的环境变量名称及对应的值。
输出时用“$变量名”,取消时用“unset变量名”。
书写crond定时任务时要注意,脚本要用到的环境变量最好先在所执行的Shell脚本中重新定义。
如果希望环境变量永久生效,则可以将其放在用户环境变量文件或全局环境变量文件里。
3.2.3 环境变量初始化与对应文件的生效顺序
在登录Linux系统并启动一个bash shell时,默认情况下bash会在若干个文件中查找环境变量的设置。这些文件可统称为系统环境文件。bash检查的环境变量文件的情况取决于系统运行Shell的方式。系统运行Shell的方式一般有3种:
1) 通过系统用户登录后默认运行的Shell。
2) 非登录交互式运行Shell。
3) 执行脚本运行非交互式Shell。
当用户登录Linux系统时,Shell会作为登录Shell启动。此时的登录Shell加载环境变量的顺序如图3-1所示。
用户登录系统后首先会加载/etc/profile全局环境变量文件,这是Linux系统上默认的Shell主环境变量文件。系统上每个用户登录都会加载这个文件。
当加载完/etc/profile文件后,才会执行/etc/profile.d目录下的脚本文件,这个目录下的脚本文件有很多,例如:系统的字符集设置(/etc/sysconfig/i18n)等,在后文开发跳板机案例里,我们也把脚本的起始加载放到这个目录下,以便用户登录后即刻运行脚本。
之后开始运行$HOME/.bash_profile(用户环境变量文件),在这个文件中,又会去找$HOME/.bashrc(用户环境变量文件),如果有,则执行,如果没有,则不执行。在$HOME/.bashrc文件中又会去找/etc/bashrc(全局环境变量文件),如果有,则执行,如果没有,则不执行。
如果用户的Shell不是登录时启动的(比如手动敲下bash时启动或者其他不需要输入密码的登录及远程SSH连接情况 ),那么这种非登录Shell只会加载$HOME/.bashrc(用户环境变量文件),并会去找/etc/bashrc(全局环境变量文件)。因此如果希望在非登录Shell下也可读到设置的环境变量等内容,就需要将变量设定等写入$HOME/.bashrc或者/etc/bashrc,而不是$HOME/.bash_profile或/etc/profile。
3.3 普通变量
3.3.1 定义本地变量
本地变量在用户当前Shell生存期的脚本中使用。例如,本地变量oldboy的取值为bingbing,这个值只在用户当前Shell生存期中有意义。如果在Shell中启动另一个进程或退出,那么变量oldboy的值将会无效。
1.?普通变量定义
为普通变量的定义赋值,一般有以下3种写法:
变量名=value #<==赋值时不加引号
变量名='value' #<==赋值时加单引号
变量名="value" #<==赋值时加双引号
2.?在Shell中定义变量名及为变量内容赋值的要求
变量名一般是由字母、数字、下划线组成的,可以以字母或下划线开头,例如:oldboy、oldboy123、oldboy_training。
变量的内容可以用单引号或双引号引起来,也可不加引号,但是这三者的含义是不同的,具体参见后文说明。
3.?普通变量的定义及输出的示例
范例3-4:采用不同的方式对普通变量进行定义,并一一打印输出。
a=192.168.1.2
b='192.168.1.2'
c="192.168.1.2"
echo "a=$a"
echo "b=$b"
echo "c=${c}"
提示:
1)$变量名表示输出变量,可以用$c和${c}两种用法。
2)请在命令行实践以上内容,然后看一看返回的结果有何不同。
思考:在命令行Shell下输入以上内容后会输出什么结果呢?请在看答案之前,先想一想上面a、b、c变量值的输出各是什么,最好自己实践一下。
答案:
a=192.168.1.2
b=192.168.1.2
c=192.168.1.2
可见,将连续的普通字符串的内容赋值给变量,不管用不用引号,或者不管用什么引号,它的内容是什么,打印变量时就会输出什么。
范例3-5:接着上述范例的结果,再在Linux命令行下继续输入如下内容,想一想a、b、c的输出又各是什么结果?
a=192.168.1.2-$a
b='192.168.1.2-$a'
c="192.168.1.2-$a"
echo "a=$a"
echo "b=$b"
echo "c=${c}"
提示: 建议先思考结果是什么,然后再在命令行实践以上内容,看看和思考的结果有何不同。
参考答案:
a=192.168.1.2-192.168.1.2
b=192.168.1.2-$a
c=192.168.1.2-192.168.1.2-192.168.1.2
4.?变量定义的基本技巧总结
这里以范例3-5为例:
a=192.168.1.2-$a
第一种定义a变量的方式是不加任何引号直接定义变量的内容,当内容为简单连续的数字、字符串、路径名时,可以这样用,例如:a=1,b=oldboy等。不加引号时,值里有变量的会被解析后再输出,上述变量定义中因为$a的值被解析为192.168.1.2(范例3-3执行的影响),因此新的a值就是192.168.1.2-192.168.1.2。
b='192.168.1.2-$a'
第二种定义b变量的方式是通过单引号定义。这种定义方式的特点是:输出变量内容时单引号里是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合于定义显示纯字符串的情况,即不希望解析变量、命令等的场景,因此,对于这里的b的值,定义时看到的是什么就输出什么,即192.168.1.2-$a。
c="192.168.1.2-$a"
第三种定义c变量的方式是通过双引号定义变量。这种定义方式的特点是:输出变量内容时引号里的变量及命令会经过解析后再输出内容,而不是把双引号中的变量名及命令(命令需要反引起来)原样输出。这种方式比较适合于字符串中附带有变量及命令且想将其解析后再输出的变量定义。
老男孩经验:
数字内容的变量定义可以不加引号,其他没有特别要求的字符串等定义最好都加上双引号,如果真的需要原样输出就加单引号,定义变量加双引号是最常见的使用场景。
5.?把一个命令的结果作为变量的内容赋值的方法
对需要获取命令结果的变量内容赋值的常见方法有两种:
变量名=`ls` #<==把命令用反引号引起来,不推荐使用这种方法,因为容易和单引号混淆
变量名=$(ls) #<==把命令用$()括起来,推荐使用这种方法
范例3-6:用两种方法把命令的结果赋值给变量。
[oldboy@oldboy ~]$ ls
test.sh
[oldboy@oldboy ~]$ CMD=`ls` #<==其中``为键盘上Tab键上面的那个键输出的字符
[oldboy@oldboy ~]$ echo $CMD
test.sh
[oldboy@oldboy ~]$ CMD1=$(pwd)
[oldboy@oldboy ~]$ echo $CMD1
/home/oldboy #<==打印当前用户所在的目录
提示: 生产场景中把命令的结果作为变量的内容进行赋值的方法在脚本开发时很
常见。
范例3-7:按天打包网站的站点目录程序,生成不同的文件名(此为企业实战案例)。
[root@oldboy scripts]# CMD=$(date +%F) #<==将当前日期(格式为2016-09-10)赋值
   ??给CMD变量
[root@oldboy scripts]# echo $CMD #<==输出变量的值
2016-09-10
[root@oldboy scripts]# echo $(date +%F).tar.gz #<==直接输出时间命令的结果
2016-09-10.tar.gz
[root@oldboy scripts]# echo `date +%F`.tar.gz
2016-09-10.tar.gz
[root@oldboy scripts]# tar zcf etc_$(date +%F).tar.gz /etc 
#<==将时间作为压缩包名打包
tar: 从成员名中删除开头的“/”
tar: 从硬连接目标中删除开头的“/”
[root@oldboy scripts]# ls -l etc_2016-09-10.tar.gz  #<==打包结果,包名中包含
                                                              ?有当前日期
-rw-r--r-- 1 root root 9700163 9月  10 18:39 etc_2016-09-10.tar.gz
[root@oldboy scripts]# H=$(uname -n) #<==获取主机名并赋值给H变量
[root@oldboy scripts]# echo $H
oldboy
[root@oldboy scripts]# tar zcf $H.tar.gz /etc/services  ?#<==将主机名作为压缩包名
                                                          ?打包文件
tar: 从成员名中删除开头的“/”
[root@oldboy scripts]# ls -l oldboy.tar.gz #<==打包结果,包名中包含有主机名
-rw-r--r-- 1 root root 127303 9月  10 18:40 oldboy.tar.gz
局部(普通)变量定义及赋值的经验小结
常规普通变量定义:
若变量内容为连续的数字或字符串,赋值时,变量内容两边可以不加引号,例如a=123。
变量的内容很多时,如果有空格且希望解析内容中的变量,就加双引号,例如a="/etc/rc.local $USER" ,此时输出变量会对内容中的$USER进行解析然后再输出。
希望原样输出变量中的内容时就用单引号引起内容进行赋值,例如:a='$USER'。
希望变量的内容是命令的解析结果的定义及赋值如下:
要使用反引号将赋值的命令括起来,例如:a=`ls`;或者用$()括起来,例如:a=$(ls)。
变量的输出方法如下:
使用“$变量名”即可输出变量的内容,常用“echo $变量名”的方式,也可用printf代替echo输出更复杂的格式内容。
变量定义的技巧及注意事项:
注意命令变量内容前后的字符``(此字符为键盘Tab键上面的那个反引号,不是单引号),例如:“CMD=`ls`”。
在变量名前加$可以取得该变量的值,使用echo或printf命令可以显示变量的值,$A和${A}的写法不同,但效果是一样的。
用echo等命令输出变量的时候,也可用单引号、双引号、反引号,例如:echo $A、echo "$A"、echo '$A',它们的用法和前面变量内容定义的总结是一致的。
$dbname_tname,当变量后面连接有其他字符的时候,必须给变量加上大括号{},例如:$dbname_tname就要改成${dbname}_tname。
有关上述变量问题输出的小故事
故事1:老男孩正在给面授班讲课,发了一段内容,结果引起群里网络班学员的强烈反应,下面是对话内容。
老男孩(31333741)  12:42:54
金庸新著
XXX-学员?12:43:39
老师,金庸又写啥小说了? #<==看到了吧,这引起了误解
老男孩(31333741)?12:42:54
这是一本小说,作者为金庸新,而非金庸,但是给读者造成的感觉是{金庸}
新著。
$dbname_tname变量就类似于这个金庸新著,会引起歧义,因此要改成${dbname}_tname,Shell就会认为只有dbname是变量了。
老男孩(31333741)?12:44:45
如果真的是金庸新著,就要像这样用大括号分隔开,${金庸}新著。
故事2:老男孩运维班20期的李同学在他媳妇看电视剧时发现了这个金庸新著,于是他将电视剧停下来,还截图发给了我。

可见形象的比喻学习对学生的影响非常深远!养成将所有字符串变量用大括号括起来的习惯,在编程时将会减少很多问题。不过老男孩也并不是一直都这么做,因为多输入内容会造成效率不高,但是金庸新著的问题确实要多注意。
3.3.2 变量定义及变量输出说明
有关Shell变量定义、赋值及变量输出加单引号、双引号、反引号与不加引号的简要说明见表3-2。
表3-2 单引号、双引号、反引号与不加引号的知识说明
名 称 解 释
单引号 所见即所得,即输出时会将单引号内的所有内容都原样输出,或者描述为单引号里面看到的是什么就会输出什么,这称为强引用
双引号
(默认) 输出双引号内的所有内容;如果内容中有命令(要反引下)、变量、特殊转义符等,会先把变量、命令、转义字符解析出结果,然后再输出最终内容,推荐使用,这称为弱引用
无引号 赋值时,如果变量内容中有空格,则会造成赋值不完整。而在输出内容时,会将含有空格的字符串视为一个整体来输出;如果内容中有命令(要反引下)、变量等,则会先把变量、命令解析出结果,然后输出最终内容;如果字符串中带有空格等特殊字符,则有可能无法完整地输出,因此需要改加双引号。一般连续的字符串、数字、路径等可以不加任何引号进行赋值和输出,不过最好是用双引号替代无引号的情况,特别是对变量赋值时
反引号 ``一般用于引用命令,执行的时候命令会被执行,相当于$(),赋值和输出都要用``将命令引起来

提示: 这里仅为Linux Shell下的结论,对于awk语言会有点特别,有关情况下文会有测试讲解。
老男孩的建议:
在脚本中定义普通字符串变量时,应尽量把变量的内容用双引号括起来。
单纯数字的变量内容可以不加引号。
希望变量的内容原样输出时需要加单引号。
希望变量值引用命令并获取命令的结果时就用反引号或$()。
以下是单引号、双引号与不加引号的实战演示。
范例3-8:对由反引号引起来的`date`命令或$(date)进行测试。
[root@oldboy ~]# echo 'today is date'
today is date #<==单引号引起内容时,你看到什么就会显示什么
[root@oldboy ~]# echo 'today is `date`'
today is `date` #<==单引号引起内容时,你看到什么就会显示什么,内容中有命令时即使通过
    ?反引号引起来也没有用
[root@oldboy ~]# echo "today is date"
today is date
[root@oldboy ~]# echo "today is `date`"
today is Sun Sep 11 15:12:30 CST 2016
#<==对输出内容加双引号时,如果里面是变量或用反引号引起来的命令,则会先把变量或命令解析成
?具体内容再显示
[root@oldboy ~]# echo "today is $(date)"  #<==?$()的功能和反引号``相同
today is Sun Sep 11 15:12:30 CST 2016
[root@oldboy ~]# echo today is $(date)  ??#<==带空格的内容不加引号,同样可以正确
 ??     地输出,但不建议这么做
today is Sun Sep 11 15:12:30 CST 2016
#<==对于连续的字符串等内容输出一般可以不加引号,但加双引号比较保险,所以推荐使用。
范例3-9:变量定义后,在调用变量输出打印时加引号测试。
[root@oldboy ~]# OLDBOY=testchars  ??#<==创建一个不带引号的变量并赋值。
[root@oldboy ~]# echo $OLDBOY  ??#<==不加引号,显示变量解析后的内容。
testchars
[root@oldboy ~]# echo '$OLDBOY'  ??#<==加单引号,显示变量本身。
$OLDBOY
[root@oldboy ~]# echo "$OLDBOY"  ??#<==加双引号,显示变量内容,引号内可以
?是变量、字符串等。
testchars
范例3-10:使用三剑客命令中的grep过滤字符串时给过滤的内容加引号。
[root@oldboy ~]# cat grep.log  ??#<==待测试的内容。
testchars
oldboy
[root@oldboy ~]# grep "$OLDBOY" grep.log  #<==将$OLDBOY解析为结果后进行过滤。
testchars
[root@oldboy ~]# grep '$OLDBOY' grep.log  #<==将$OLDBOY本身作为结果进行过滤。
[root@oldboy ~]# grep $OLDBOY grep.log    #<==将$OLDBOY解析为结果后进行过滤,同双引号的情况,但不建议这样使用,没有特殊需要时应一律加双引号。
testchars
范例3-11:使用awk调用Shell中的变量,分别针对加引号、不加引号等情况进行测试。
首先在给Shell中的变量赋值时不加任何引号,这里使用awk输出测试结果。
[root@oldboy ~]# ETT=123  ??#<==定义变量ETT并赋值123,没加引号。
[root@oldboy ~]# awk 'BEGIN {print "$ETT"}'
#<==加双引号引用$ETT,却只输出了本身,这个就不符合前文的结论了。
$ETT
[root@oldboy ~]# awk 'BEGIN {print $ETT}'
         #<==不加引号的$ETT,又输出了空的结果,这个就不符合前文的结论了。
[root@oldboy ~]# awk 'BEGIN {print '$ETT'}'
#<==加单引号引用$ETT,又输出了解析后的结果,这个就不符合前文的结论了。
123
 [root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}'
123
以上的结果正好与前面的结论相反,这是awk调用Shell变量的特殊用法。
然后在给Shell中的变量赋值时加单引号,同样使用awk输出测试结果。
[root@oldboy ~]# ETT='oldgirl' #<==定义变量ETT并赋值oldgirl,加单引号。
[root@oldboy ~]# awk 'BEGIN {print "$ETT"}'
$ETT #<==加双引号引用$ETT,则输出本身。
[root@oldboy ~]# awk 'BEGIN {print $ETT}'
#<==对$ETT不加引号,则输出空的结果。
[root@oldboy ~]# awk 'BEGIN {print '$ETT'}'
#<==加单引号引用$ETT,也是输出空的结果,这个和前文的不加引号定义、赋值的结果又不一样。
[root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}'
oldgirl #<==在单引号外再加一层双引号引用$ETT,则输出解析后的结果。
以下在给Shell中的变量赋值时加双引号,也使用awk输出测试结果。
[root@oldboy ~]# ETT="tingting" #<==定义变量ETT并赋值tingting,加双引号,这个
?测试结果同单引号的情况。
[root@oldboy ~]# awk 'BEGIN {print "$ETT"}' #<==加双引号引用$ETT,会输出本身。
$ETT
[root@oldboy ~]# awk 'BEGIN {print $ETT}' #<==不加引号的$ETT,会输出空的结果。

[root@oldboy ~]# awk 'BEGIN {print '$ETT'}' #<==加单引号的$ETT,会输出空的结果。

[root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}' #<==在单引号外部再加双引号引用$ETT,
  ?????会输出正确结果。
tingting
以下在给Shell中的变量赋值时加反引号引用命令,同样使用awk输出测试结果。
[root@oldboy ~]# ETT=`pwd` #<==定义变量ETT并赋值pwd命令,加反引号,这个测试结果更特殊。
[root@oldboy ~]# echo $ETT
/root
[root@oldboy ~]# awk 'BEGIN {print "$ETT"}' #<==加双引号引用$ETT,会输出本身。
$ETT
[root@oldboy ~]# awk 'BEGIN {print $ETT}' #<==不加引号的$ETT,会输出空的结果。

[root@oldboy ~]# awk 'BEGIN {print '$ETT'}' #<==单引号引用$ETT,看起来输出了结
  ???果,却是报错,和外层单引号冲突了。
awk: BEGIN {print /root}
awk:               ^ unterminated regexp
awk: cmd. line:1: BEGIN {print /root}
awk: cmd. line:1:                    ^ unexpected newline or end of string
[root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}' #<==在单引号外部再加双引号引用$ETT,
  ???会输出正确结果。
/root
根据上述范例整理的测试结果见表3-3,供读者参考。
表3-3 测试结果
ETT
   awk ETT=123 ETT='oldgirl' ETT="tingting" ETT=`pwd`
awk加双引号 本身 本身 本身 本身
awk不加引号
awk加单引号 正确输出 报语法错
awk加单引号后再同时加双引号 正确输出 正确输出 正确输出 正确输出

结论:不管变量如何定义、赋值,除了加单引号以外,利用awk直接获取变量的输出,结果都是一样的,因此,在awk取用Shell变量时,我们更多地还是喜欢先用echo加符号输出变量,然后通过管道给awk,进而控制变量的输出结果。举例如下:
[root@oldboy ~]# ETT="oldgirl" #<==最常规的赋值语法
[root@oldboy ~]# echo "$ETT"|awk '{print $0}' #<==用双引号引用$ETT
oldgirl
[root@oldboy ~]# echo '$ETT'|awk '{print $0}' #<==用单引号引用$ETT
$ETT
[root@oldboy ~]# echo $ETT|awk '{print $0}' #<==不加引号引用$ETT
oldgirl
[root@oldboy ~]# ETT=`pwd` #<==命令赋值的语法
[root@oldboy ~]# echo "$ETT"|awk '{print $0}'
/root
[root@oldboy ~]# echo '$ETT'|awk '{print $0}'
$ETT
[root@oldboy ~]# echo $ETT|awk '{print $0}'
/root
这就符合前面给出的普通情况的结论了。不过,这个例子特殊了一点,有关awk调用Shell变量的详情,还可以参考老男孩的博客“一道实用Linux运维问题的9种Shell解答http://oldboy.blog.51cto.com/2561410/760192”。
范例3-12:通过sed指定变量关键字过滤。
[root@oldboy ~]# cat sed.log
testchars
oldboy
[root@oldboy ~]# sed -n /"$OLDBOY"/p sed.log #<==加双引号测试
testchars
[root@oldboy ~]# sed -n /$OLDBOY/p sed.log #<==不加引号测试
testchars
[root@oldboy ~]# sed -n /'$OLDBOY'/p sed.log #<==加单引号测试
  #<==输出本身,但是文件里没有本身匹配的字符串,因此输出为空
注意:sed和grep的测试和前面结论是相符的,唯有awk有些特殊。
提示: 上述内容不需要特意去记,在使用时测试一下就会明白。
关于自定义普通字符串变量的建议
1)内容是纯数字、简单的连续字符(内容中不带任何空格)时,定义时可以不加任何引号,例如:
a.OldboyAge=33
b.NETWORKING=yes
2)没有特殊情况时,字符串一律用双引号定义赋值,特别是多个字符串中间有空格时,例如:
a.NFSD_MODULE="no load"
b.MyName="Oldboy is a handsome boy."
3)当变量里的内容需要原样输出时,要用单引号(' '),这样的需求极少,例如:
a.OLDBOY_NAME='OLDBOY'
3.4 变量定义技巧总结
可以多学习和模仿操作系统自带的/etc/init.d/functions函数库脚本的定义思路,多学习Linux系统脚本中的定义,有经验的读者最终应形成一套适合自己的规范和习惯。
(1)变量名及变量内容定义小结
变量名只能为字母、数字或下划线,只能以字母或下划线开头。
变量名的定义要有一定的规范,并且要见名知意。
示例:
OldboyAge=1 #<==每个单词的首字母大写的写法
oldboy_age=1 #<==单词之间用"_"的写法
oldboyAgeSex=1 #<==驼峰语法: 首个单词的首字母小写,其余单词首字母大写
OLDBOYAGE=1 #<==单词全大写的写法
一般的变量定义、赋值常用双引号;简单连续的字符串可以不加引号;希望原样输出时使用单引号。
希望变量的内容是命令的解析结果时,要用反引号``,或者用$()把命令括起来再赋值。
(2)Shell定义变量时使用“=”的知识
“a=1”里等号是赋值的意思;比较变量是否相等时也可以用“=”或“==”。
(3)打印输出及使用变量的知识
打印输出或使用变量时,变量名前要接$符号;变量名后面紧接其他字符的时候,要用大括号将变量部分单独括起来,以防止出现“金庸新著”的问题;在unset、export、(())等场景中使用但不打印变量时不加$,这个有些例外。
打印输出或使用变量时,一般加双引号或不加引号;如果是字符串变量,最好加双引号;希望原样输出时使用单引号。
关于变量命名的更多规范可参考第14章。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接