转自:我的博客www.javafan.cn 请尊重原创
上篇我们学会了如何使用及定义变量。按照尿性,一般接下来就该学基本数据类型的运算了。没错,本篇就仍是这么俗套的来讲讲这无聊但又必学的基本数据类型的运算了。
上述操作符与其它语言相比,并无特殊之处。
在shell中,对于基本数据类型的运算主要分为两种,整数运算和浮点数(小数)运算。下面就分别来看看这两种运算:
在shell中,有两种方式能实现整数运算,一种是使用expr命令, 另外一种是通过方括号($[])来实现。下面分别来看看:
注意:
在以上的乘法(*)中,我们用了反斜线()来转义,不然会报错。
运算符前后必须还有空格,否则会被直接当作字符串返回。
如果要将计算结果保存到变量,就需要用到我们上篇文章讲到的那两种方式($() 或者 ``)来替换命令了。
这种种迹象无不让人吐槽啊。幸好还有一种实现方式,那就是接下来要看的方括号。
看了这种运算,再回看expr, 是不是觉得要升天,终于正常了。expr的那几个注意事项,在这儿都不算事儿。所以,如果要图简单,还是用这种方式吧。
在shell中,做浮点运算一般是用bash的计算器(bc)。在shell脚本中,一般我们的使用方法是:
variable=$(echo "options; expression" | bc)
options是bc的一些选项,例如: 可以通过scale去设置保留的小数位数。具体有哪些参数,可以man bc进行查看
expression就是我们具体的表达式,例如 10 * 3
" | " 这个符号,对于熟悉linux系统的人来说,这个再熟悉不过了。它叫做管道, 之所以会叫做管道,其实很形象,你可以把它看作一根水管,水管一头接入前一个命令的返回结果, 一头接入下一个命令。表示将前一个命令的执行结果作为后一个命令的参数输入。以上,表示将我们的表达式作为bc的参数输入。
#!/bin/bash #表示 10/3, 保留2位小数,将结果赋值给了num, 输出3.33 num=$(echo "scale=2; 10 / 3" | bc) echo $num本篇比较无聊,简单介绍了shell的基本运算符及其运算。shell运算主要分为整型和浮点型的运算。整型又有两种实现方式,浮点型是通过使用bash内置的计算器(bc)来实现的。
好啦,到此,我们学习了shell的变量的使用,也学习了基本数据类型变量的计算啦!挺简单吧
通过前两篇文章,我们掌握了shell的一些基本写法和变量的使用,以及基本数据类型的运算。那么,本次就将要学习shell的结构化命令了,也就是我们其它编程语言中的条件选择语句及循环语句。
不过,在学习shell结构化命令的时候,我们又会发现它与其它编程的语言相比存在不小的区别。下面就开始看看吧:
在条件选择语句中,主要包含以下几种写法:
吃瓜群众表示一脸懵比:if语句后面接的是命令,我们其它编程语言中,这儿都是接返回布尔值(true,false)的表达式。
那么这到底是怎么回事呢?
在shell脚本的if其实是根据紧跟后面的那个命令的退出状态码来判断是否执行then后面的语句的。
关于退出状态码,你只需要记住:正常退出(命令执行正常)的状态码是0, 非正常退出的状态码不是0(有不少)。
以上语句的语义为: 如果if后面的命令执行正常(状态码0),那么就执行then后面的语句。否则不执行。 fi代表if语句的结束。
#!/bin/bash #这儿由于pwd是linux内置的命令,因此执行后会正常退出(状态码0),所以会执行then中的语句 #如果此处替换为一个不存在的命令(例如: pw),那么就会非正常退出,不会执行then中的语句 if pwd then echo 执行then里面的语句 fiif-then还可以简写为
if command; then commands fi因此,以上代码还可以写成以下:
#!/bin/bash if pwd; then echo 执行then里面的语句 fi以上,如果我要判断处理异常退出(状态码非0)情况,该怎么办?
别着急: else 来帮你。
与if-then语句相比,这回多了个else语句,else语句用来判断if后面的命令非正常退出的情况。
#!/bin/bash if pwd then echo 正常退出 else echo 非正常退出 fi甚至,我们还可以变形写出更多的else:
if command1 then commands elif command2 then command3 fi但是上面就只能根据退出状态码判断,不能写表达式,你还让我怎么写? 我各个编程语言直接吊打你!
不要慌,客官,请接着往下看:
test命令用于if-then或者if-then-else语句中,主要用于判断列出的条件是否成立,如果成立,就会退出并返回退出状态码0,否则返回非0。
这意味着我们可以通过test命令来写表达式命令了。不过,对于已习惯其它编程语言的程序猿们(没学过的除外),不要高兴得太早,前方有坑,至于是什么坑,待会儿就能看到。
先看看test命令的基本用法吧:
直接用:
test condition结合if-then语句用
if test condition then commands fi结合if-then-else语句用
if test condition then commands else commands fi条件成立就执行then语句,否则else语句。
test命令只能判断一下三类条件:
数值比较字符串比较文件比较特别提醒: 以上表格不用你去记住,在命令行下面, 执行man test就能看到这些了。后面的对与另外两种比较的同理
#!/bin/bash num1=100 num2=200 if test $num1 -eq $num2 then echo num1等于num2 else echo num2不等于num2 fi好好的标准的数学比较符号不能使用,难道非得写这种文本形式?是不是觉得很别扭?不着急,还有替代方案:
双括号命令允许你在比较过程中使用高级数学表达式。关键是使用双括号,咱就可以用数学比较符号啦(等于==, 大于>, 小于< 等等都能使用啦)。使用方法:
(( expression ))注意:括号里面两边都需要有空格
#!/bin/bash num1=100 num2=200 if (( num1 > num2 )) then echo "num1 > num2" else echo "num2 <= num2"程序猿们,要骂的就尽情释放吧。我反正是骂了。
test命令和测试表达式使用标准的数学比较符号来表示字符串比较,而用文本代码来表 示数值比较。这与其它语言相比都不一样。
#!/bin/bash var1=test var2=Test if test $var1 = $str2 then echo 相等 else echo 不相等 fi注意,在使用大于(>)或小于(<)符号时,需要转义(\>)(\<),不然会把这两种符号时别为重定向(后面文章才会讲到)。
吐槽模式开启:我要用个比较符号,还要转义,很蛋疼的设计!
不要慌,大招一般都在后面:
双方括号命令提供了针对字符串比较的高级特性。它不仅解决了使用test所带来的一系列毛病,还提供了一些test命令所没有的高级用法。双方括号命令的格式如下:
[[ expression ]]注意,可能有些shell不支持此种写法。不过bash完美支持。以上写法注意括号内两边都有空格。
#!/bin/bash var1=test var2=Test if [[ $test < $test2 ]] then echo "test1 < test2" else echo "test1 >= test2" fi这下终于不用转义了。
对于文件的比较,其实跟上面差不多,都是用test命令。由于篇幅有限,我这儿就不多写了。通过man test命令可以看到具体的用法。
在使用if-then-else语句中,如果碰到条件很多的情况,如下:
#!/bin/bash num=3 if (( $num == 1 )) then echo "num=1" elif (( $num == 2 )) then echo "num=2" elif (( $num == 3 )) then echo "num=3" elif (( $num == 4 )) then echo "num=4" fi如果再多点条件,看起来是不是很多?此时,其实还有一种替代方案,那就是使用case.
case variable in pattern1 | pattern2) commands1;; pattern3) commands2;; *) default commands;; esac将以上代码替换为case:
#!/bin/bash case $num in 1) echo "num=1";; 2) echo "num=2";; 3) echo "num=3";; 4) echo "num=4";; *) echo "defaul";; esac本篇主要讲了条件语句。shell中的条件语句与其他编程语言相比有不小的区别,最大的区别就在于条件语句后接的是命令,而不是布尔值, 是根据命令执行退出的状态码来决定是否进入then语句的。这点需要牢记。
上篇我们学习了shell中条件选择语句的用法。接下来本篇就来学习循环语句。在shell中,循环是通过for, while, until命令来实现的。下面就分别来看看吧。
for循环有两种形式:
基本格式如下:
for var in list do commands donelist代表要循环的值,在每次循环的时候,会把当前的值赋值给var(变量名而已,随意定), 这样在循环体中就可以直接通过$var获取当前值了。
先来一个例子吧:
#!/bin/bash for str in a b c d e do echo $str done以上会根据空格将abcde分割,然后依次输出出来。
如果以上例子不是以空格分割,而是以逗号(,)分割呢?
#!/bin/bash list="a,b,c,d,e" for str in $list do echo $str done结果输出a,b,c,d,e
造成这个结果的原因是:for...in循环默认是循环一组通过空格或制表符(tab键)或换行符(Enter键)分割的值。这个其实是由内部字段分隔符配置的,它是由系统环境变量IFS定义的。当然,既然是由环境变量定义的,那当然也就能修改啊。
以上第一个循环会分别输出abcde几个值。而第二个循环会输出a b c d e(即未处理)。因为我们把IFS的值设置为逗号了, 当然,不一定要是逗号,想设置什么,你说了算!
bash中c语言风格的for循环遵循如下格式:
for (( variable assignment ; condition ; iteration process ))
一个例子足以说明:
#!/bin/bash for (( i = 0; i <= 10; i++ )) do echo $i done上面例子循环11次,从0到10依次输出。稍微有过编程基础的都对此应该很熟悉。就不做详细阐述了。
如果你习惯了其它语言的while循环,那么到这儿你又会发现这个while循环有点变态了。与其它编程语言while的不同在于:在bash中的while语句,看起来似乎是结合了if-then语句(参考上一篇)和for循环语句。其基本格式如下:
while test command do other commands done与if-then语句一样,后面接test命令,如果test后面的命令的退出状态码为0. 那么就进入循环,执行do后面的逻辑。要注意在do后面的逻辑中写条件,避免死循环。
既然是接test命令,那么一切都可以参考if-then的test
示例一:
#!/bin/bash flag=0 while test $flag -le 10 do echo $flag # 如果没有这句,那么flag的值一直为0,就会无限循环执行 flag=$[$flag + 1] done以上判断flag是否大于或者等于10, 如果满足条件,那么输出当前flag的值,然后再将flag的值加1。最终输出的结果为0到10的结果。
结合上一篇文章test的写法,我们还可以将以上示例变形为如下:
示例二:
#!/bin/bash flag=0 while [ $flag -le 10 ] do echo $flag flag=$[$flag + 1] done示例三:
flag=0 while (( $flag <= 10 )) do echo $flag flag=$[$flag + 1] doneuntil语句基本格式如下:
until test commands do other commands done在掌握while循环语句之后, until语句就很简单了。until语句就是与while语句恰好相反, while语句是在test命令退出状态码为0的时候执行循环, 而until语句是在test命令退出状态码不为0的时候执行。
示例:
#!/bin/bash flag=0 until (( $flag > 10 )) do echo $flag flag=$[ $flag + 1 ] done以上输出0到10的值。until后面的条件与上面while例子完全相反。
好啦,到此,我们学完了shell的循环语句啦。不过上面我们写的循环语句都是根据条件执行完毕,如果我们在执行的过程中想退出,该怎么办?接下来就继续看看怎么控制循环语句。
与其它编程语言一样,shell是通过break和continue命令来控制循环的。下面就分别来看看二者的基本用法:
示例一:
#!/bin/bash for (( flag=0; flag <= 10; flag++ )) do if (( $flag == 5 )) then break fi echo $flag done以上当flag的值为5的时候,退出循环。输出结果为0-4的值。
break用于跳出内层循环。示例二:
#!/bin/bash flag=0 while (( $flag < 10 )) do for (( innerFlag=0; innerFlag < 5; innerFlag++ )) do if (( $innerFlag == 2 )) then break fi echo "innerFlag=$innerFlag" done echo "outerFlag=$flag" done以上代码在执行内部循环for的时候,当innerFlag值为2的时候就会跳出到外层的while循环, 由于外层循环一直flag都为0, 所以while会成为一个死循环,不停的输出:
...
innerFlag=0
innerFlag=1
outerFlag=0
...
break用于跳出外层循环break 可后接数字,用于表示退出当前循环的外层的第几层循环。
示例三:
#!/bin/bash flag=0 while (( $flag < 10 )) do for (( innerFlag=0; innerFlag < 5; innerFlag++ )) do if (( $innerFlag == 2 )) then # 2表示外面一层循环 break 2 fi echo "innerFlag=$innerFlag" done echo "outerFlag=$flag" done与上面例子相比,本例就只是在break后面跟了个数字2,表示退出外面的第一层循环。最终输出:
innerFlag=0
innerFlag=1
continue表示终止当前的一次循环,进入下一次循环,注意,continue后面的语句不会执行。
continue的语法与break一样,因此,就只做一个示例演示啦。
示例:
flag=0 while (( $flag <= 10 )) do if (( $flag == 5 )) then flag=$[$flag+1] continue fi echo "outerFlag=$flag" for (( innerFlag=11; innerFlag < 20; innerFlag++ )) do if (( $innerFlag == 16 )) then flag=$[$flag+1] continue 2 fi echo "innerFlag=$innerFlag" done done以上例子: 当for循环中innerFlag的值为16的时候会跳到外层while循环,当外层循环的flag的值为5的时候,会直接跳过本次循环,然后进入下一次循环,因此在输出的结果中,不会出现outerFlag=5的情况。
通过前几篇文章的学习,我们学会了shell的基本语法。在linux的实际操作中,我们经常看到命令会有很多参数,例如:ls -al 等等,那么这个参数是怎么处理的呢? 接下来我们就来看看shell脚本对于用户输入参数的处理。
bash shell可根据参数位置获取参数。通过 $1 到 $9 获取第1到第9个的命令行参数。$0为shell名。如果参数超过9个,那么就只能通过${}来获取了, 例如获取第10个参数,那么可以写为${10}。
示例一:
#!/bin/bash #testinput.sh echo "file name: $0" echo "base file name: $(basename $0)" echo "param1: $1" echo "param2: ${2}"运行上面的的shell
./testinput.sh 12 34最终得到的结果如下:
file name: ./testinput4.sh
base file name: testinput4.sh
param1: 12
param2: 34
成功的得到文件名和命令行输入的参数(命令行参数以空格分隔,如果参数包含了空格,那么久必须添加引号了)
$0默认会获取到当前shell文件的名称,但是,它也包含(./),如果你以完整路径运行,那么这还会包含目录名。因此,上面通过basename命令来获取单纯的文件名$(basename $0)。
试想一下,假如我们写的shell的这个参数很多,那如果像上面那样一个一个去获取参数,那岂不是要写疯!下面就来看看如何解决这种情况。
既然bash shell通过位置可获取参数,那意味着如果我们知道参数的总个数就可以通过循环依次获取参数。那么如何获取参数总个数呢?
在bash shell中通过 $# 可获取参数总数。
示例:(循环获取参数)
#!/bin/bash for (( index=0; index <= $#; index++ )) do echo ${!index} done以上示例,我们通过 $# 获取总参数个数。然后通过循环获取每个位置的参数。注意: 按照正常的理解,上面的 ${!index} 应该是 ${$index}才对, 对吧? 但是,由于${}内不能再写$符号,bash shell在这个地方是用了!符号,所以以上才写为了${!index}。
在bash shell中还可以通过 $* 和 $@ 来获取所有参数。但是这两者之间有着很大的区别:
$* 会将命令行上提供的所有参数当作一个单词保存, 我们得到的值也就相当于是个字符串整体。
$@ 会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。
可能文字看起来描述的不太清楚,那么还是通过示例来看二者的区别吧:
#!/bin/bash #testinput.sh var1=$* var2=$@ echo "var1: $var1" echo "var2: $var2" countvar1=1 countvar2=1 for param in "$*" do echo "first loop param$countvar1: $param" countvar1=$[ $countvar1 + 1 ] done echo "countvar1: $countvar1" for param in "$@" do echo "second param$countvar2: $param" countvar2=$[ $countvar2 + 1 ] done echo "countvar2: $countvar2"执行上面的示例:
./testinput.sh 12 34 56 78上面示例的输出结果为:
var1: 12 34 56 78
var2: 12 34 56 78
param1: 12 34 56 78
countvar1: 2
param1: 12
param2: 34
param3: 56
param4: 78
countvar2: 5
通过上面的结果可见,直接输出看起来二者结果一样,但是通过for循环就可看出二者的区别了。上一篇文章我们讲到for循环会通过IFS定义的值进行分割,因此默认情况下,如果我们上面在for循环处不加引号,那么根据IFS中所定义的空格分割,最终也会导致看不出二者区别。
有时候,我们在shell执行过程中获取用户的输入,以此与用户进行交互。这是通过read命令来实现的。下面就来看看其用法:
示例一:
#!/bin/bash echo -n "yes or no(y/n)?" read choice echo "your choice: $choice"运行以上示例,首先会输出”yes or no(y/n)?“, 然后会等待用户输入(-n参数表示不换行,因此会在本行等待用户输入),当用户输入后,会把用户输入的值赋值给choice变量, 然后最终输出 “your choice: (你输入的内容)”。
事实上,我们可以不指定read后面的变量名,如果我们不指定, read命令会将它收到的任何数据都放进特殊环境变量REPLY中。如下:
示例二:
#!/bin/bash echo -n "yes or no(y/n)?" read echo "your choice: $REPLY"以上示例与示例一是等价的。
有时候,我们需要用户输入多个参数,当然,shell是支持一次接受多个参数输入的。
示例三:
#!/bin/bash read -p "what's your name?" first last echo first: $first echo last: $last以上示例首先输出“what's your name?”, 然后在本行等待用户输入(此处用read -p实现以上示例的echo -n + read命令的不换行效果),输入的参数以空格分隔,shell会把输入的值依次赋值给first和last两个变量。如果输入的值过多,假如我输入了3个值,那么shell会把剩下的值都赋值给最后一个变量(即第二三两个的值都会赋值给last变量)。
细想一下,有个问题,假如用户一直不输入,怎么办?一直等待?
我们可以通过read -t 来指定超时时间(单位为秒),如果用户在指定时间内没输入,那么read命令就会返回一个非0的状态码。
示例四:
#/bin/bash if read -t 5 -p "Please enter your name: " name then echo "Hello $name" else echo "Sorry, timeout! " fi运行以上示例,如果超过5秒没输入,那么就会执行else里面的。
本篇简单的介绍了shell的输入参数以及接收用户输入。大家可以举一反三,结合之前所学的基础知识,可以写一些小的脚本应用了。