13种Shell逻辑与算术,能写出5种算你赢!

news2025/1/12 0:45:50

相较于最初的 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中的赋值运算符

file

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 循环而言,每个值都是字符串。

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

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DHT11温湿度传感器初识

目录 一、产品概述 1、接线方式 2、特点 3、数据传送逻辑 二、发送时序检测模块是否存在 1、C51单片机&#xff08;主机&#xff09;时序分析 2、编写代码检测模块是否存在 3、读取DHT11数据的时序分析 三、温湿度通过串口传到PC显示 四、温湿度检测小系统——使数据…

Discrete Opinion Tree Induction for Aspect-based Sentiment Analysis 论文阅读笔记

一、作者 Chenhua Chen、Zhiyang Teng、Zhongqing Wang、Yue Zhang School of Engineering, Westlake University, China Institute of Advanced Technology, Westlake Institute for Advanced Study Soochow University 二、背景 如何为每一个方面词定位相应的意见上下文…

反补码运算之 “1 - 1 = - 1 ” ?

我们在研究数据的二进制表示时遇到这样一个问题&#xff1a; 由于计算中的CPU只有加法器&#xff0c;没有减法器&#xff0c;所以在计算机采用原码做减法时对于&#xff1a;1 - 1 0 相当于1 &#xff08;-1&#xff09;&#xff0c;用二进制表示为&#xff1a;000110011001。…

儿童感染新冠病毒后发高烧如何应对?专家来支招

儿童感染新冠病毒后发热较多&#xff0c;但肺炎发展较少儿童感染新冠病毒后发高烧&#xff0c;是重症吗&#xff1f;徐红梅&#xff1a;从目前的接待情况来看&#xff0c;儿童感染新冠病毒后比成人发烧。超过一半的儿童在感染后有发烧症状&#xff0c;伴有咳嗽、鼻塞、流鼻涕、…

JavaWeb的一些学习总结

Web系统就是&#xff1a;前端负责貌美如花&#xff0c;后端负责坚如磐石 企业在做Web系统时&#xff0c;成本与收益的对比是不可忽视的&#xff0c;企业做项目一定是要盈利的&#xff01; 企业做项目最求的不是技术的新和潮&#xff0c;追求的是低成本和稳还有好用度&#xff…

函数的设计

一、默认参数 C允许在函数定义或声明时&#xff0c;为形参指定默认值&#xff0c;即默认参数&#xff08;default argument&#xff09;。 &#xff08;1&#xff09;函数定义与函数声明只能设置一次默认参数。 &#xff08;2&#xff09;可以设置多个默认参数&#xff0c;设…

【精华】搞定JVM调优学习

JVM 介绍 1. 什么是 JVM JVM 是 Java Virtual Machine&#xff08;Java 虚拟机&#xff09;的缩写。一台执行 Java 程序的机器。 2 .JAVA 语言的执行原理 计算机语言&#xff1a; 计算机能够直接执行的指令。这种指令和系统及硬件有关。 计算机高级语言&#xff1a; 在遵循…

「数据」驱动行业拐点,毫末智行冲刺自动驾驶3.0时代

“毫末预计&#xff0c;到2025年中国高阶辅助驾驶搭载率将达到70%。而在汽车新消费领域&#xff0c;中国汽车市场增换购消费比例将达到60%&#xff0c;智能驾驶功能成为必选因素&#xff0c;并迎来商业化的加速发展。”1月5日&#xff0c;第七届HAOMO AI DAY上&#xff0c;毫末…

黑马学ElasticSearch(五)

目录&#xff1a; &#xff08;1&#xff09;DSL查询语法-DSL查询分类和基本语法 &#xff08;2&#xff09;DSL查询语法-全文检索查询 &#xff08;3&#xff09;DSL查询语法-精确查询 &#xff08;4&#xff09;DSL查询语法-地理查询 &#xff08;5&#xff09;DSL查询语…

计算机网络(一)

计算机网络1 概述1.1 计算机网络的作用1.2 因特网概述1.2.1 网络的网络1.2.2 Internet和internet的区别1.2.3 因特网发展的三个阶段1.2.4 ISP介绍1.2.5 ISP分类1.2.5.1 主干ISP1.2.5.2 地区ISP1.2.5.3 本地ISP1.2.6 因特网交换点 IXP1.3 因特网的组成1.3.1 因特网的边缘部分1.3…

资产管理4大难点,如何破解?

随着企业业务扩大、人员增多&#xff0c;固定资产的数量和种类也会随着增加。此时&#xff0c;如何高效管理企业资产就成为很多企业亟待解决的一大难题。 传统资产管理4大难点 01.资产管理部门需要联系采购部门、使用部门、财务部门等收集数据&#xff0c;汇总难且工作量大&…

vacuum移除不必要的CLOG文件

迫切模式弥补了惰性模式的缺陷。它会扫描所有页面&#xff0c;检查表中的所有元组&#xff0c;更新相关的系统视图&#xff0c;并在可能时删除不必要的CLOG文件与页面。当满足以下条件时&#xff0c;会执行迫切模式。pg_database.datfrozenxid<(OldestXmin-vacuum_freeze_ta…

【多线程】【C++ 知识点】pthread_join学习

目录pthread_join进程id和线程idpthread_join pthread_join() 主线程会进入阻塞装题&#xff0c;pthread_join()之后的代码&#xff0c;只有等待子进程退出之后才能执行。 代码块A pthread_create(&id, NULL, Fn, NULL);pthread_create(&id_1, NULL, Fn, NULL);pthre…

jdk1.8 更替为 oepnJdk8遇到的坑

背景&#xff1a;客户服务器因为说jdk要收费&#xff0c;所以要求将jdk1.8替换为openJdk&#xff0c;本地测试ok&#xff0c;则将服务器的jdk替换为openJdk8&#xff0c;出现一个登录异常&#xff0c;调查发现是一个sso登录的问题&#xff08;单点登录&#xff09;&#xff0c;…

Rhce第一次作业

chrony服务部署&#xff1a;两台机器a: 第一台机器从阿里云同步时间&#xff0c;第二台机器从第一台机器同步时间1.查看防火墙是否关闭&#xff0c;若未关闭&#xff0c;关闭防火墙2.打开chrony配置文件3.向配置文件中写入阿里云时间服务器&#xff0c;并允许两台机器所在的网段…

从0到1完成一个Vue后台管理项目(十八、基础地图绘制)

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; 从0到1完成一个Vu…

spring restTemplate的坑----会对String类型的url中的特殊字符进行转义

&#x1f4e2; &#x1f4e2; &#x1f4e2; &#x1f4e3; &#x1f4e3; &#x1f4e3;哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步 &#x1f91d; &#x1f91d;一位上进心十足的【Java ToB端…

基于OpenCV实现两种方法测量圆弧长度(步骤 + 源码)

导 读 本文主要介绍基于OpenCV实现两种方法测量圆弧长度(步骤 + 源码)。 背景介绍 要求:如上所示,分别用OpenCV计算出图1和图2中圆弧的长度。因为OpenCV中没有提供现成计算圆弧的方法,所以需要自己编写,本文将提供2种不同的方法来实现,仅供参考。 实现步骤 首…

mmsegmentation 训练Binary segmentation

1.一天最无聊的事从搭环境开始 1.conda create -n swin python3.7 2.conda activate swin 3.conda install pytorch1.7.0 torchvision0.8.0 torchaudio0.7.0 cudatoolkit11.0 4.pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu110/torch1.7.0/index.h…

Keil + STM32学习嵌入式数据结构-01

视频链接 初识数据结构&#xff0c;十天搞定嵌入式数据结构_哔哩哔哩_bilibili 课程目的 学会嵌入式经常使用的数据结构 具备基础知识 具有C语言基础&#xff08;结构体、指针、内存&#xff08;malloc)&#xff09; 具有数据结构的基础知识&#xff0c;因此提及到的基础…