Shell变量(1)Bashshell编程与其他编程语言类似,也包括变量(存放字符串和值的容器,可以修改、比较、传递)。引用bash变量时可以使用一些非常特殊的运算符。bash还具有内置变量,可以提供有关脚本中其他变量的重要信息。这里介绍了bash变量和一些特殊的变量引用机制,展示了如何在您自己的脚本中使用它们。1.shell变量基础知识bash脚本中的变量名通常全部大写,但这不是强制的,只是一种常见的做法。变量不需要提前声明,直接使用即可。变量基本上是字符串类型,尽管一些运算符可以将变量内容视为数字。变量的实际用法如下所示。#常用脚本使用shell变量MYVAR="something"echo$MYVAR#写法类似,只是没有引号MY_2ND=anotheroneecho$MY_2ND#因为包含空客,所以需要用引号:MYOTHER="morestufftoecho"echo$MYOTHERbashvariable的语法有两点很重要,但乍一看可能不是那么明显。首先,赋值语法name=value看起来相当直观,但是=不能被任何空白字符包围。如果=两边都允许有空白字符,那么变量赋值就会变成这样:MYVAR=something这时,shell很难区分是调用命令还是给变量赋值。对于可以将=作为参数的命令尤其如此,例如test。所以,让我们保持简单:在分配变量时,shell不允许在=周围使用空格。此规则的另一方面也值得注意,不要在文件名中使用=。要注意的第二件事是在引用变量时使用$符号。给变量赋值时,变量名前不需要加$,但取变量值时需要加$。出现在表达式$(())中的变量是一个例外。原因很简单,就是为了消除歧义。如下:MYVAR=somethingechoMYVAR现在是MYVAR你能分清哪个是字符串MYVAR,哪个是变量MYVAR吗?bash中的一切都是字符串,因此需要使用$来表示变量引用。2.录制脚本在详细讨论shell脚本或变量之前,我们不得不说说如何录制脚本。毕竟,您需要能够阅读自己的脚本,即使是在编写脚本几个月之后。带有注释的文档脚本。#代表注释的开始。该行的所有后续字符都将被shell忽略。##这是一行注释##多用注释#注释是你的好朋友如果你是java开发者,你会发现这就是我们通常所说的代码注释。3.如果您需要将变量与其他文本一起输出,请将变量名称与周围的文本分开。你用$来引用变量,但是你如何区分变量名和它后面的其他文本呢?比如你想使用shell变量作为文件名的一部分,如下:forFNin12345dosomescript/tmp/rep$FNport.txt#执行一个脚本,使用文件作为执行参数,比如作为catdoneshell你会如何理解这段代码?它将考虑以$开头并以点结尾的变量名称。换句话说,它将$FNport视为变量名,而不是我们预期的$FN。那么,我们如何让shell知道我们的变量是FN呢?使用完整的变量引用语法,不仅包括$,还包括变量名的花括号,如下所示:somescript/tmp/rep${FN}port.txt因为shell变量名只能包含字母、数字和下划线,所以在很多情况下不需要使用花括号。任何空格或标点符号(下划线除外)都足以指示变量名的结束位置。但只要有疑问,就应该使用花括号。4.导出变量你在脚本中定义了一个变量,但是在调用其他脚本时,脚本并不知道这个变量的存在。要解决此问题,我们需要导出传递给其他脚本的变量。如下:exportMYVARexportNAME=value要查看所有导出的变量,键入命令env(或内置命令export-p)列出每个变量及其值。这些变量在脚本运行时可用,其中许多已经由bash启动脚本设置,例如$PATH。变量赋值后可以导出,但这种方法不适用于较旧的shell版本。然后导出后就可以随意给变量赋值,不用重复导出了。因此,有时你会看到如下语句:#exportvariableexportFNAMEexportSIZEexportMAX#assignvaluetovariableMAX=2048SIZE=64FNAME=/tmp/scratch注意导出的变量实际上是按值调用的。在被调用脚本中修改导出变量的值不会更改调用脚本中变量的值。对于导出的变量,我们如何删除它们?#删除变量unsetmyvarShellvariable(2)你希望用户在调用脚本时能够指定参数。要求用户设置shell变量是可能的,但这似乎不灵活。您还需要将数据传递给其他脚本。这对于环境变量是可能的,但会将两个脚本紧密地联系在一起。所以这里我们可以使用脚本参数。1.在shell脚本中使用参数使用命令行参数。在命令行上,脚本名称后出现的任何单词都可以在脚本中作为编号变量访问。假设以下脚本simplest.sh。#一个简单的shell脚本echo$1此脚本将显示在命令行上调用时指定的第一个参数。让我们看一个实际的用法。$catsimplest.sh#一个简单的shell脚本echo${1}$./simplest.shyouseewhatImeanyou$./simplest.shonemoretimeone$其他参数的可用形式是${2},${3},${4},${5}等。个位数不需要花括号,除非是为了区分变量名和后面出现的文本。典型的脚本只使用少量参数,但如果涉及到${10},则必须使用花括号,否则shell会将$10解释为${1}后跟字符串0,如下所示。$cattricky.shecho$1$10${10}$./tricky.shIIIIIIIVVVIVIIVIIIIXXXIII0X#注意输出的第二个$第十个参数的值为X,但是如果在在脚本中写成$10,那么在echo语句中得到的就是第一个参数$1,后面跟着一个字符串0。因为第三个用了${},三个${10}就可以正常输出X了。2.遍历传入脚本的参数:$*如果要对指定的一系列参数进行一些操作。在写shell脚本的时候,处理单个参数不是问题,用$1来引用这个参数就可以了。但是,如果您面对大量文件怎么办?您可能想这样调用脚本。./actall*.txtshell进行模式匹配,生成与*.txt模式匹配的文件名列表(文件名以.txt结尾)。对于脚本,我们永远无法估计传入的参数个数,那么我们就无法通过${number}获取所有参数,那么${number}方法将不再适用。特殊的shell变量$*可以引用所有参数,可以在for循环中使用,如下所示:#!/usr/bin/envbash#示例文件:actall.sh#批量修改文件权限#forFNin$*doechochanging$FNchmod0750$FNdone我们选择了变量$FN;使用另一个变量名没有错。$*指出现在命令行上的所有参数。如果用户输入./actallabc.txtanother.txtallmynotes.txt调用脚本,$1等于abc.txt,$2等于another.txt,$3等于allmynotes.txt,$等于整个参数列表。也就是说,shell将for语句中的$替换掉后,脚本就变成了这样:列表,并将其赋值给变量$FN,然后执行do和done之间的语句。对列表中的其他值重复该过程。3.处理包含空格的参数:""你写了一个接受文件名作为参数的脚本,一切似乎都正常,但有一次脚本有问题,结果是因为文件名中有空格.您必须小心引用所有可能包含文件名的命令参数。引用变量时,用双引号将其括起来。在shell脚本中,以前写ls-l$1很简单,现在最好是引用参数并将其重写为ls-l"$1"。否则,如果参数包含空格,它会被shell解析为两个单词,只有部分文件名会包含在$1中。如下:$catsimpls.sh#Asimpleshellscriptls-l${1}$$./simple.shOhtheWastes:Oh:Nosuchfileordirectory$如果不把文件名放在引号里时调用脚本,然后bash将看到3个参数并将$1替换为第一个参数(哦)。当ls命令只带一个参数运行时哦,结果是找不到文件。接下来,我们在调用脚本时在文件名两边加上引号。$./simpls.sh"OhtheWaste"ls:Oh:Nosuchfileordirectoryls:the:Nosuchfileordirectoryls:Waste:Nosuchfileordirectory$还是不行。bash获取一个3字的文件名,并用该文件名替换ls命令中的$1。到目前为止,一切都很好。但是,我们没有将脚本中的变量引用放在引号中,因此ls将文件名中的每个单词视为一个单独的参数(作为一个单独的文件名)。结果还是找不到这些文件,只好执行命令:ls-lOhtheWaste所以我们要把我们的变量引用放到引号里,修改脚本内容如下:$catsimpls.sh#A简单的shell脚本,这里要注意${1}和第一个脚本的区别是多了双引号ls-l"${1}"$$./simple.sh"OhtheWaste"$4,处理参数包含空格的lists:$@对于第2段,我们可以通过$*来获取参数列表,那么如果此时我们传入的参数列表包含空格,会不会有什么问题呢?如下:$./actall.sh"OhtheWaste"changingOhchmod:Cannotaccess"Oh":Nosuchfileordirectorychangingthechmod:Cannotaccess"the":NosuchfileordirectorychangingWastechmod:Cannotaccess"Waste"":Nosuchfileordirectory$按照上一节的建议,你给变量加上引号,但仍然报错。如下:#!/usr/bin/envbash#示例文件:actall.sh#批量修改文件权限#forFNin$*doechochanging"$FN"chmod0750"$FN"done如果有空格在文件名中,会报错,报错的原因与for循环中使用的$*有关。在这个例子中,我们需要使用一个不同但相关的shell变量$@。如果变量出现在引号中,您将获得一个命令行参数列表,每个参数都被单独引用。修改后的shell脚本如下:#!/usr/bin/envbash#示例文件:chmod_all.2#选择更好的方式在文件名包含空格时加引号,批量修改文件权限#forFNin"$@"dochmod0750"$FN"done没有引号,$*与$@相同。但是当两者出现在引号中时,bash就会区别对待。“$*”获取的是整个参数列表,而“$@”获取的不是字符串,而是每个参数对应的带引号的字符串列表。如果您知道文件名中没有空格,那么坚持使用旧的$*语法几乎没问题。对于那些比较健壮的脚本,为了安全起见,建议使用“$@”Shell变量(三)1.统计参数个数你想知道调用脚本时使用了多少个参数。使用shell内置变量$#。如下所示,严格要求3个参数的脚本:#!/usr/bin/envbash#Examplefile:check_arg_count##检查正确的参数个数:#使用以下语法或:if[$#-lt3]if(($#<3))thenprintf"%b""错误。没有足够的参数。\n">&2printf"%b""用法:myscriptfile1opfile2\n">&2exit1elif(($#>3))thenprintf"%b""错误。参数太多。\n">&2printf"%b""用法:myscriptfile1opfile2\n">&2exit2elseprintf"%b""参数计数correct.Proceeding...\n"fi下面是参数过多,参数个数恰到好处时的运行情况。$./myscriptmyfileiscopyedintoyourfileError.争论太多。用法:myscriptfile1opfile2$./myscriptmyfilecopyyourfileArgument计数正确。Proceeding...我们使用if来测试提供的参数个数(存储在$#中)是否大于3。如果答案是是,则输出错误消息,提醒用户正确使用脚本,然后退出。标准错误消息被重定向到标准错误(>&2)。这种方法服务于标准错误的目的:作为所有错误消息的管道。2.丢弃参数:所有shift官方脚本都可能有两类参数:修改脚本行为的选项和待处理的真实参数。您需要一种在处理选项后丢弃选项的方法。例如有如下脚本:forFNin"$@"doechochanging$FNchmod0750"$FN"done脚本内容很简单,会显示正在处理的文件名,然后修改文件权限。但有时您希望脚本在不显示文件名的情况下静默运行,而其他时候您希望显示文件名。如何在保持for循环的同时添加一个选项来关闭文件名的显示?使用shift删除处理过的参数,如下:#自定义变量VERBOSE=0#确定第一个参数的值if[[$1=-v]]then#使用变量保存参数值VERBOSE=1#删除参数shiftfi#这个时候for得到的参数已经小于$1了,从$2开始读取forFNin"$@"doif((VERBOSE==1))thenechochanging$FNfichmod0750"$FN"done我们添加了标记变量$VERBOSE以了解是否应输出处理后的文件名。但是一旦shell脚本找到-v选项并设置标志,我们就不需要在参数列表中使用-v了。shift语句告诉bash将命令行参数移动一个位置,以便$2变为$1,$3变为$2,依此类推,从而丢弃第一个参数($1)。这样,当for循环开始时,-v不再出现在参数列表($@)中,只有它后面的参数。运行结果如下:#执行脚本,带-v参数,输出修改后的文件名$./shift_test.sh-verror.outchangingerror.out#执行脚本,不带-v参数,安静执行,不输出文件名$./shift_test.sherror.out$3.获取默认值:${:-}有一个可以接受命令行参数的shell脚本。例如,你希望能够提供默认值,这样就不用每次都要求用户输入那些经常使用的值。使用${:-}语法来引用参数并提供默认值,如下所示:FILEDIR=${1:-/tmp}引用shell变量时可以使用各种特殊运算符。:-运算符表示如果指定的参数(此处为$1)不存在或为空,则将运算符后面的内容(在本例中为/tmp)用作值。否则,使用已经设置的参数值。该运算符可以与任何shell变量一起使用,不限于位置参数$1、$2、$3等。当然,您还可以使用更多代码来实现:使用if语句来检查变量是否为空存在,但在shell脚本中,这样的处理是司空见惯的,而:-运算符可谓是一种流行的便利。.4.设置环境变量的默认值:${HOME:=/tmp}你的脚本依赖于一些常用的(比如$USER)或者业务特定的环境变量。要构建健壮的shell脚本,请确保这些变量具有合理的默认值。那么如何确定呢?首次引用shell变量时,如果该变量没有值,则使用赋值运算符为其赋值,如下所示:cd${HOME:=/tmp}示例中引用的$HOME返回其当前值值,除非它为空或根本未设置。对于后两种情况(为空或未设置),返回/tmp,这个值也会赋值给$HOME,如果后面引用$HOME,会返回这个新值。如下图,注意:下面的例子会改变环境变量HOME,请谨慎执行$echo${HOME:=/tmp}/home/uid002$unsetHOME#删除环境变量值$echo${HOME:=/tmp}#重新获取,如果此时不存在,则重新赋值并返回一个新值/tmp$echo$HOME#此时查看变量,并输出新设置的值/tmp$cd;pwd/tmp$赋值运算符有一个重要的例外:不能为$1或$等位置参数赋值。在这种情况下,您可以使用:-(如${1:-default*}),它只返回一个值,但不执行赋值。${VAR:=*value*}和${VAR:-value}形式上的区别可能会帮助你记住这两个令人抓狂的符号。:=执行赋值,返回运算符右侧的值。:-做前者的一半:返回一个值,但不分配它。因此,它的符号只是等号的一半(一个破折号,而不是两个破折号)。Shell变量(四)1.获取某个数的绝对值变量中的值可以是负数、零或正数。你想得到它的大小(也就是绝对值),但是bash好像没有绝对值的功能。但是,我们可以利用字符串操作。如下:${MYVAR#-}这是一个简单的字符串操作。#从字符串的开头搜索减号(-)。如果找到,删除它;如果找不到,则保留原始值。无论哪种情况,最终结果都是不带负号的绝对值。但是,我们也可以使用if/then/else在数学上进行计算。如下:#通过判断值与0的关系,乘以-1if((MYVAR<0))thenletMYVAR=MYVAR*-1fi对比上面两种方法,很明显第一种方法更简单,所以推荐第一种。2.选择CSV的替换值你想做一个用逗号分隔的值列表,但是你不想在开头或结尾有逗号,这也是我们日常工作中的常见需求。如果您在循环内使用LIST="${LIST},${NEWVAL}"构建列表,您会在第一个循环后得到一个前导逗号(当LIST为空时)。您可以对LIST进行特殊初始化,以便它在进入循环之前获取第一个值,但如果这不切实际,或者为了避免重复代码(用于获取新值),您可以更改Usethe${:+}syntaxin庆典。如下:LIST="${LIST}${LIST:+,}${NEWVAL}"如果{LIST}为空或不存在,那么$LIST的两个表达式什么都不产生。这意味着在第一次循环之后,只有NEWVAL的值被保存在LIST中。如果LIST不为空,则第二个表达式${LIST:+,}将替换为分隔前一个值和新值的逗号。下面的代码片段读取并构建一个CSV列表。LIST=""forNEWVALin"$@"doLIST="${LIST}${LIST:+,}${NEWVAL}"doneecho${LIST}3.使用数组变量到目前为止,我们已经看到了不同的脚本使用更少的变量,但bash可以处理数组吗?当然可以,bash有一种特殊的一维数组语法。如果您在编写脚本时已经知道确切的值,则初始化数组很容易。格式如下:MYRA=(firstsecondthirdhome)注意:数组用的是(),与java中的数组符号[]不同。括号中列表中的每个单词对应一个数组元素。你可以这样引用每个元素:echorunnerson${MYRA[0]}and${MYRA[2]}输出如下:runnersonfirstandthird注意:如果你只写$MYRA,你只会获取第一个数组元素,相当于${MYRA[0]}。4.转换大小写bash4.0中的几个操作符可以在引用变量名时转换它们的大小写。如果变量$FN包含需要转换为小写的文件名(字符串),则${FN,,}将返回全部小写的字符串。同样,${FN^^}返回全部大写的字符串。甚至还有${FN~~},它可以切换大小写,将所有小写字母转换为大写字母,将所有大写字母转换为小写字母。下面的for循环会将所有参数更改为小写。forFNin"$@"doecho"${FN}"转换为小写的结果是:"${FN,,}"完成或写成单行脚本:forFNin"$@";doecho"${FN}"转换为小写的结果是:"${FN,,}";完成5。为不存在的参数输出错误信息有时需要强制用户指定某个值,否则无法继续。用户有可能会错过一个参数,因为他们真的不知道如何调用脚本。您希望能够给用户一些提示,这样他们就不必猜测了。有没有比成堆的if语句更简洁的方法来检查单个参数?引用参数时使用${:?}语法。如果指定的参数不存在或为空,bash会打印一条错误消息并退出。#!/usr/bin/envbash#示例文件:check_unset_parms#USAGE="usage:myscriptscratchdirsourcefileconversion"FILEDIR=${1:"Error.Youmustsupplyascratchdirectory."}FILESRC=${2:"Error.Youmustsupplyasourcefile."}CVTTYPE=${3:"Error.${USAGE}"}如果在执行脚本时没有指定足够的参数,会出现如下结果。$./myscript/tmp/dev/null./myScript.sh:第11行:3:错误。用法:myscriptscratchdirsourcefileconversion$bash会测试每个参数,如果参数不存在或为空,则输出错误信息并退出。$3对应的错误消息中使用了另一个shell变量。如果$3不存在,则错误消息包含短语“Error.”,即变量$USAGE的值。另一方面,${:?}生成一条包含shell脚本文件名和行号的错误消息。本文由传智教育博学谷教研组发布。如果本文对您有帮助,请关注并点赞;有什么建议也可以留言或私信。您的支持是我坚持创作的动力。转载请注明出处!
