乐趣区

关于后端:13种Shell逻辑与算术能写出5种算你赢

相较于最后的 Bourne shell,古代 bash 版本的最大改良之一体现在算术方面。晚期的 shell 版本没有内建的算术性能,哪怕是给变量加 1,也得调用独自的程序来实现。

1、算术办法一:$(())

只有都是整数运算,就能够在 $(()) 的算术表达式内应用所有的规范运算符。还有一个额定的运算符:能够用 ** 进行幂运算,如下:

COUNT=$((COUNT + 5 + MAX * 2))

或者:

MAX=$((2**8))

$(()) 表达式内不须要应用空格,不过在运算符和操作数两边加上空格也不妨(但 ** 必须写在一起)。然而 = 两边绝不能呈现空格,这和 bash 变量赋值的规定一样。如果你按以下形式写:

COUNT = $((COUNT+5))   # 留神 = 号两边多了空格,可不像你想的那样!

那么,bash 会尝试运行一个名为 COUNT 的程序,其第一个参数为 =,第二个参数为 $COUNT 与 5 之和。记住,别在赋值号两边加空格!

另一个怪异之处是,通常呈现在 shell 变量前示意取值的 $ 符号(如 $COUNT 或 $MAX)在双括号外部是不须要的。例如,咱们能够写:

$((COUNT + 5 + MAX * 2))

shell 变量前并没有 $ 符号,实际上,内部的 $ 利用于整个表达式。但如果用到了地位参数(如 $2),那么 $ 还是少不了的,因为只有这样能力辨别地位参数与数字常量(如 2)。以下是一个示例。

COUNT=$((COUNT + $2 + OFFSET))

也能够用逗号运算符造成级联赋值,如下图:

echo $((X+=5 , Y*=3))

该表达式执行两次赋值操作,而后由 echo 显示出第二个子表达式的后果(因为逗号运算符返回其第二个操作数的值)。

2、算术办法二:let

除去应用 $(())可进行算术运算外,还能够应用 let 语句,如下:

let COUNT=COUNT+5

同 $(())一样,在应用变量时不须要应用 $ 符号。然而,当咱们须要应用 let 进行 COUNT=$((COUNT + 5 + MAX * 2))格局的运算时,须要应用到引号‘’, 如下:

let COUNT+='5+MAX*2'

let 语句和 $(()) 语法的另一处重要区别在于两者解决空白字符(空格字符)的形式不同。对 let 语句来说,要么增加引号,要么赋值运算符(=)和其余运算符两边不能呈现空格。必须将运算符和操作数放在一起造成一个单词。以下两种写法都没问题。

let i=2+2
let "i = 2 + 2"

$(()) 语法就宽松多了,它容许各种空白字符呈现在双括号内。这种写法不易出错,代码的可读性也要好得多,是咱们执行 bash 整数运算时的首选形式。

3、bash 中的赋值运算符

4、条件分支 if

条件判断,逻辑分支是任何一个语言都会遇到的问题,bash 中同其余语言相似,都是应用 if 进行条件判断,如下:

if [$# -lt 3]
then
 printf "%b" "Error. Not enough arguments.\n"
 printf "%b" "usage: myscript file1 op file2\n"
 exit 1
fi

或者:

if (($# < 3))
then
 printf "%b" "Error. Not enough arguments.\n"
 printf "%b" "usage: myscript file1 op file2\n"
 exit 1
fi

以下是一个带有 elif(bash 中的 else-if)和 else 子句的残缺 if 语句。如下:

if (($# < 3))
then
 printf "%b" "Error. Not enough arguments.\n"
 printf "%b" "usage: myscript file1 op file2\n"
 exit 1
elif (($# > 3))
then
 printf "%b" "Error. Too many arguments.\n"
 printf "%b" "usage: myscript file1 op file2\n"
 exit 2
else
 printf "%b" "Argument count correct. Proceeding...\n"
fi

对于 if,咱们有两个问题须要明确,别离是:

  • if 语句的根本构造
  • if 表达式的不同语法(括号或方括号,运算符或选项)

5、if 的根本构造

依照 bash 手册页中的形容,if 语句的个别模式如下所示。

if list; then list; [elif list; then list;] ... [else list;]
fi

[和] 用于划分语句中的可选局部(例如,有些 if 语句中就没有 else 子句)。咱们先来看看不带任何可选局部的 if 语句。

最简略的 if 语句模式如下所示。

if list; then list; fi

在 bash 中,和换行符一样,分号的作用也是完结某个语句。咱们能够用分号将解决方案局部中的示例塞进更少的行中,但应用换行符的可读性更好。then list 的存在看起来是有意义的,其中的语句在 if 条件为真的状况下执行(咱们也能够从其余编程语言中猜想进去)。然而,if list 算是怎么回事?难道不应该是 if expression 吗?

没错,但这是 shell,一个命令处理器。它的次要工作就是执行命令。因而,if 前面的 list 就是搁置命令列表的中央。你可能会问,决定分支走向(then 子句或 else 子句)的是什么呢?答案是list 中最初一个命令的返回值

咱们通过一个有点奇怪的示例来阐明这一点。如下:

$ cat trythis.sh // 查看脚本内容,如下所示

if ls; pwd; cd $1; 

then

 echo success

else

 echo failed

fi

// 执行脚本,传递一个参数
$ bash ./trythis.sh /tmp

在这个奇怪的脚本中,shell 会在抉择分支前执行 3 个命令(ls、pwd、cd),其中 cd 命令的参数是调用该脚本时所提供的第一个命令行参数。如果没有提供参数,那就只执行 cd,返回到主目录中。后果会是怎么?你能够本人试试。最终是显示“success”还是“failed”,取决于 cd 命令是否执行胜利。在示例中,cd 是 if 语句命令列表中的最初一个命令。如果 cd 执行失败,就转到 else 子句;但如果执行胜利,则抉择 then 子句。

6、if 中的 [] 和 (())

咱们一起来看上面的例子:

if test $# -lt 3
then
 echo try again.
fi

后面讲到 if 前面是 list 是命令列表,尽管此处不是命令列表,但有没有从中看出起码相似于单个 shell 命令(内建命令 test 承受参数并比拟参数值)的货色?

在本章结尾,咱们给出的第一个示例中结尾的 if [$# -lt 3] 看起来很像 test 命令。这是因为 [其实只是雷同命令的不同名称而已。(出于可读性和好看方面的思考,调用 [ 时还要求将] 作为最初一个参数。)因而,对于该语法,if 语句中的表达式其实就是一个只蕴含单个命令(test 命令)的列表。

在晚期的 Unix 中,test 是一个独立的可执行文件,[只是指向该文件的链接。当初两者仍以可执行文件的模式存在,但 bash 也将它们实现为内建命令。

那么 if (($# < 3)) 又是什么意思?

双括号是复合命令的一种。因为它会对其中的算术表达式求值,所以能在 if 语句中派上用场。这是一处比拟新的 bash 改良,专门用于有 if 语句的场合。

可用于 if 语句的这两种语法之间的重要区别在于 测试的表达方式及其可能测试的对象品种

双括号仅限于算术表达式,方括号 还能够 测试文件个性,但后者的算术测试语法远不如前者不便,尤其是用括号将表达式划分成若干子表达式时。

当咱们应用 [] 时,肯定要留神空格是必须存在的,如下图:

if [-d "/opt/"]

7、测试文件的个性

为了进步脚本的稳健性,你心愿在读取输出文件前先查看该文件是否存在;另外,还想在写入输入文件前确认其是否具备写权限,在用 cd 切换目录前看看到底有没有这个目录。这些该如何在 bash 脚本中实现呢?如下所示:

#!/usr/bin/env bash
# 实例文件:checkfile
#
DIRPLACE=/tmp
INFILE=/home/yucca/amazing.data
OUTFILE=/home/yucca/more.results

if [-d "$DIRPLACE"] // 判断是否目录
then
    cd $DIRPLACE
    if [-e "$INFILE"] // 判断文件是否存在
    then
        if [-w "$OUTFILE"] // 判断文件是否领有写权限
        then
            doscience < "$INFILE" >> "$OUTFILE"
        else
               echo "cannot write to $OUTFILE"
        fi
    else
        echo "cannot read from $INFILE"
    fi
else
    echo "cannot cd into $DIRPLACE"
fi

将各种文件名援用全都放入了引号,以防路径名中蕴含空格。在下面的例子,咱们应用了测试文件是否是目录(-d)、文件是否存在(-e)、文件是否有写权限(-w), 咱们也能够测试一些别的文件个性,其中有 3 个个性要用到双目运算符(承受两个文件名)。

  • FILE1 -nt FILE2 是否更新(查看文件的批改工夫)。现有文件要比不存在的文件“新”。
  • FILE1 -ot FILE2 是否更旧。同样,不存在的文件要比现有文件“旧”。
  • FILE1 -ef FILE2 具备雷同设施和 inode 编号(即使由不同链接所指向,也视为雷同的文件)

后面应用的 -e、-d、- w 都属于单目运算符,其模式为 option filename,例如,if [-e myfile]

8、测试多个个性

后面,咱们测试每个个性都是应用独自一个 if 语句,那么咱们测试多个个性时,必须嵌套 if 语句吗?

应用 -a(逻辑与)和 -o(逻辑或)运算符将多个测试条件组合成一个表达式。例如:

if [-r $FILE -a -w $FILE]

该 if 语句会测试指定文件是否可读 并且 可写。

测试时,为啥不加上 - e 呢?因为所有的文件测试条件都隐含了该文件存在的测试,所以测试文件可读性时不必测试文件是否存在。如果文件不存在,天然也就不可读。这些逻辑运算符(-a 示意 AND,-o 示意 OR)可用于所有的测试条件,并不局限于文件测试。

同一个语句中能够呈现多个 AND/OR。你可能要用括号来取得正确的优先级,比方 a and (b or c),但肯定要记得在括号前加上反斜杠或将括号放进引号,以打消其非凡含意。如下:

if [-r "$FN" -a \( -f "$FN" -o -p "$FN" \) ]

9、测试字符串个性

你心愿在应用字符串前先检查一下它们的值。这些字符串能够是用户输出、读入的文件或传入脚本的环境变量。如何用 bash 脚本实现呢?

你能够在 if 语句中应用单方括号模式的 test 命令进行一些简略的测试,其中包含查看变量是否蕴含文本以及两个变量中的字符串是否雷同。如下脚本所示:

# 应用命令行参数
VAR="$1"
#
# if ["$VAR"]这种模式通常也管用,但并不是一种好的写法,加上 - n 会更清晰

if [-n "$VAR"]
then
    echo has text
else
    echo zero length
fi

if [-z "$VAR"]
then
    echo zero length
else echo has text
fi

长度为 0 的变量有两种:设置为空串的变量和不存在的变量。示例中的测试并不辨别这两种状况。它只关怀变量中是否有字符存在。

重要的是要将 $VAR 放进引号,否则测试会被一些怪异的用户输出烦扰。如果 $VAR 的值是 x -a 7 -lt 5 且没有应用引号,那么下列语句:

if [-z $VAR]

就会变成(在变量扩大之后):

if [-z x -a 7 -lt 5]

10、测试等量关系

你想要查看两个 shell 变量是否相等,然而存在两种测试运算符:-eq 和 =(或 ==)。该用哪个呢?

你须要的比拟类型决定了该用哪种运算符。

  • 如果是进行数值比拟,能够应用 -eq 运算符。
  • 如果是进行字符串比拟,则应用 =(或 ==)运算符。

上面,咱们通过一个简略的脚本例子来演示,如下:

#
# 陈词滥调的字符串与数值比拟
#

VAR1="05"
VAR2="5"
printf "%s" "do they -eq as equal?"
if ["$VAR1" -eq "$VAR2"]
then
    echo YES
else
    echo NO
fi

printf "%s" "do they = as equal?"

if ["$VAR1" = "$VAR2"]
then
    echo YES
else
    echo NO
fi

如果,咱们运行脚本,则会失去如下后果:

$ ./ 脚本名
do they -eq as equal? YES
do they = as equal? NO
$

只管两个变量的数值相等(5),但从字符角度来看,前导字符 0 和空白字符意味着这两个字符串并不相同。

= 和 == 都能够应用,但 = 合乎 POSIX 规范,可移植性更好。

应用 if,咱们能够在脚本中进行分支判断。然而对于零碎而言,循环同分支一样是常见需要。所以 Shell 一样反对循环操作

11、循环一段时间

对于算术条件,应用 while 循环:

while ((COUNT < MAX)) // 判断条件是否成立
do // 语法要求,以 do 开始
 some stuff
 let COUNT++
done // 语法要求,以 done 完结

对于文件系统相干的条件:

while [-z "$LOCKFILE"]
do
 some things
done

第一个 while 语句中的双括号界定了算术表达式,这很像 shell 变量赋值中用到的 $(())。双括号内呈现的变量名示意取值。也就是说,不须要写成 $VAR,间接在括号中应用 VAR 就行了。

while [-z”$LOCKFILE”] 中的方括号和 if 语句中的一样,等同于应用 test 命令。

应用(()) 时,shell 会对其中的表达式求值,如果后果为非 0,那么 (()) 就返回 0;如果后果为 0,则返回 1。这意味着咱们能够像 Java 或 C 程序员那些书写表达式,但 while 语句沿用的仍旧是 bash 那一套,视 0 为真。实际上,这意味着咱们能够编写一个有限循环:

while ((1))
do

 ...dosomething

done

12、循环若干次

如果须要循环够肯定次数。能够应用 while 循环,在计数时进行测试,不过编程语言中的 for 循环正是针对这种状况设计的。那么,如何在 bash 中实现呢?

应用 for 循环语法的一种特例,看起来和 C 语言中的差不多,但应用的是双括号。

for ((i=0 ; i < 10 ; i++)) ; do echo $i ; done

在晚期的 shell 版本中,for 循环只能依照固定的列表项进行迭代。和文件名之类的打交道时,shell 脚本是面向单词的,就此而言,这算得上是一个不错的翻新。但如果须要计数,用户会发现自己可能写出了如下代码。

for i in 1 2 3 4 5 6 7 8 9 10
do
 echo $i
done

看起来还行,尤其是循环次数不多时。可是说实话,换成 500 次循环可就不好使了。

bash 2.04 版开始引入一种 for 循环的变体,语法与 C 语言相似。其个别模式如下所示。

for ((expr1 ; expr2 ; expr3)) ; do list ; done

双括号表明这是算术表达式,在其中援用变量时,不必加 $(但 $1 等地位参数除外),只有是 bash 中呈现双括号的中央,均是如此。该表达式是整数表达式,能够应用包含逗号(用于在一个表达式中放入多个操作)在内的大量运算符。

for ((i=0, j=0 ; i+j < 10 ; i++, j++))
do
 echo $((i*j))
done

for 循环先初始化了两个变量($i 和 $j),而后在第二个更简单的子表达式中对 $i 和 $j 求和,接着判断是否小于 10。第三个子表达式再次用逗号运算符累加这两个变量。

13、在循环中应用浮点值

带有算术表达式的 for 循环只能执行整数运算。如果是浮点值,该怎么办呢?

如果零碎提供了 seq 命令,则能够用它来生成浮点值。

for fp in $(seq 1.0 .01 1.1)
do
 echo $fp; other stuff too
done

seq 命令会生成一系列浮点值,每行一个。该命令的参数顺次是起始值、增量、完结值。$() 在子 shell 中执行命令,返回后果中的换行符会被空白字符替换,因而,就 for 循环而言,每个值都是字符串。

本文由 传智教育博学谷 教研团队公布。

如果本文对您有帮忙,欢送 关注 点赞 ;如果您有任何倡议也可 留言评论 私信,您的反对是我保持创作的能源。

转载请注明出处!

退出移动版