Shell:结构化语句

Posted 时间之旅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shell:结构化语句相关的知识,希望对你有一定的参考价值。

  • ver: 1.0
  • Blog:博客园 个人
  • 本文介绍了Shell常用的结构化语句。

数组

数组(Array)是若干数据的集合,其中的每一份数据都称为元素(Element)。

Bash只支持一维数组(不支持多维数组),初始化时不需要定义数组大小,理论上可以存放无限量的数据。

与大部分编程语言类似,数组元素的下标由0开始。

Shell数组用括号来表示,元素用"空格"符号分割开。格式如下:

array_name=(ele1  ele2  ele3 ... elen)

Tips:赋值号=两边不能有空格,必须紧挨着数组名和数组元素。

获取数组中的元素要使用下标[],下标可以是一个整数,也可以是一个结果为整数的表达式;当然,下标必须大于等于0。格式如下:

${array_name[index]}

Tips:array_name是数组名,index是下标。

Shell是弱类型的,它并不要求所有数组元素的类型必须相同,例如:

arr=(10 24 \'ddd\' \'ab22\' 5)

获取数组所有元素

使用@*可以获取数组中的所有元素,格式如下:

${array_name[*]}
${array_name[@]}

获取数组元素个数

使用#来获取数组元素的个数,格式如下:

${#array_name[@]}
${#array_name[*]}

数组合并

数组合并,就是将两个或两个以上的数组合并成一个个数组,格式如下:

array_new=(${array1[@]}  ${array2[@]}...${arrayn[@]})
array_new=(${array1[*]}  ${array2[*]}...${arrayn[*]})

删除数组元素

使用unset关键字来删除数组元素,格式如下:

unset array_name[index]

如果不写下标,则代表删除整个数组所有元素,格式如下:

unset array_name

实例

测试ip是否ping通

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Create Time: 2020/06/13
# Test network connectivity

arr_num=(3 11 25 32 200)
for i in ${arr_num[*]}; do
  ip=192.168.110.${i}
  ping -c 1 $ip >/dev/null 2>&1
  if [ $? -eq 0 ]; then
    echo "${ip} is ok."
  else
    echo "${ip} is unreachable."
  fi
done

选择结构

Shell中的选择结构(分支结构)有两种形式,分别是 if-elsecase-in语句,它们都根据命令的退出状态来判断条件是否成立。

if-else语句

基本格式

基本结构格式:

if condition; then
  statement(s)
fi

condition是判断条件,如果condition成立(返回True),那么then后边的语句将会被执行;如果 condition不成立(返回False),那么不会执行任何语句。

Tips:最后必须以fi来闭合,fi就是if 倒过来拼写。

if-else

如果有两个分支,就可以使用if-else语句,格式:

if condition; then
  statement1
else
  statement2
fi

如果condition成立,那么then后边的 statement1语句将会被执行;否则,执行else 后边的statement2语句。

if-elif-else

当分支比较多时,可以使用if-elif-else 结构,格式:

if condition1; then
  statement1
elif condition2;then
  statement2
...
else
  statementn
fi

Tips:ifelif后边都得跟着then

语句的执行逻辑:

  • 如果condition1成立,那么执行statement1,如果不成立,则执行elif语句;
  • 如果elif语句不成立,则执行else语句;

case-in语句

当分支较多,且判断条件比较简单时,推荐使用case-in语句。格式如下:

case expression in
  pattern1)
    statement1
    ;;
  pattern2)
    statement2
    ;;
  ...
  *)
    statementn
esac
  • expression表示表达式,既可以是一个变量、一个数字、一个字符串,还可以是一个数学计算表达式,或者是命令的执行结果,只要能够得到expression的值就可以。
  • pattern表示匹配模式,可以是一个数字、一个字符串,甚至是一个简单的正则表达式。

case会将expression的值与 pattern1pattern2...patternn逐个进行匹配:

  • 如果expression和某个模式(比如 pattern2)匹配成功,就会执行这模式(比如 pattern2)后面对应的所有语句(该语句可以有一条,也可以有多条),直到遇见双分号;;才停止;然后整个case-in语句就执行完了,程序会跳出整个 case-in语句,执行esac后面的其它语句。
  • 如果 expression 没有匹配到任何一个模式,那么就执行*)后面的语句(*表示其它所有值),直到遇见双分号;;或者esac才结束。*)相当于多个if分支语句中最后的else部分。

Tips:分支*)并不是什么语法规定,它只是一个正则表达式,*表示任意字符串,所以不管expression的值是什么,*)总能匹配成功。因此,可以没有*)部分,如果expression没有匹配到任何一个模式,那么就不执行任何操作。

除最后一个分支外(这个分支可以是普通分支,也可以是*)分支),其它的每个分支都必须以;;结尾,;;代表一个分支的结束,不写的话会有语法错误。最后一个分支可以写;;,也可以不写,因为无论如何,执行到esac都会结束整个case-in语句。

case-inpattern部分支持简单的正则表达式,具体来说,可以使用以下几种格式:

格式 说明
* 表示任意字符串。
[abc] 表示 a、b、c 三个字符中的任意一个。比如,[15ZH] 表示 1、5、Z、H 四个字符中的任意一个。
[m-n] 表示从 m 到 n 的任意一个字符。比如,[0-9] 表示任意一个数字,[0-9a-zA-Z] 表示字母或数字。
| 表示多重选择,类似逻辑运算中的或运算。比如,abc | xyz 表示匹配字符串 "abc" 或者 "xyz"。

;;&终止每个条件块,例如:

read -n 1 -p "Type a character > "
echo
case $REPLY in
  [[:upper:]])    echo "\'$REPLY\' is upper case." ;;&
  [[:lower:]])    echo "\'$REPLY\' is lower case." ;;&
  [[:alpha:]])    echo "\'$REPLY\' is alphabetic." ;;&
  [[:digit:]])    echo "\'$REPLY\' is a digit." ;;&
  [[:graph:]])    echo "\'$REPLY\' is a visible character." ;;&
  [[:punct:]])    echo "\'$REPLY\' is a punctuation symbol." ;;&
  [[:space:]])    echo "\'$REPLY\' is a whitespace character." ;;&
  [[:xdigit:]])   echo "\'$REPLY\' is a hexadecimal digit." ;;&
esac

输出结果如下:

Type a character > a
\'a\' is lower case.
\'a\' is alphabetic.
\'a\' is a visible character.
\'a\' is a hexadecimal digit.

循环结构

循环结构语句大致分为4种:

  • while
  • until
  • for
  • select

while语句

当条件满足时,while重复地执行一组语句,当条件不满足时,就退出while循环。格式如下:

while condition; do
  statements
done

执行流程如下:

  • 先对condition进行判断,如果该条件成立,就进入循环,执while循环体中的语句,也就是dodone之间的语句。这样就完成了一次循环。
  • 每一次执行到done的时候都会重新判断condition是否成立,如果成立,就进入下一次循环,继续执行dodone之间的语句,如果不成立,就结束整个while循环,执行done后面的其它Shell代码。
  • 如果一开始condition就不成立,那么程序就不会进入循环体。

死循环

格式一:

while true; do
  statements
done

格式二:

while [ 1 ]; do
  statements
done

until语句

until循环当判断条件不成立时才进行循环,一旦判断条件成立,就终止循环。

until condition; do
  statements
done

until循环的执行流程为:

  • 先对condition进行判断,如果该条件不成立,就进入循环,执行until循环体中的语句(dodone之间的语句),这样就完成了一次循环。
  • 每一次执行到done的时候都会重新判断condition是否成立,如果不成立,就进入下一次循环,继续执行循环体中的语句,如果成立,就结束整个 until循环,执行done后面的其它Shell代码。

for语句

C语言风格的 for 循环

格式如下:

for((exp1; exp2; exp3)); do
  statements
done

exp1exp2exp3是三个表达式,其中exp2是判断条件,for循环根据exp2的结果来决定是否继续下一次循环;

它的运行过程为:

  1. 先执行exp1
  2. 再执行exp2,如果它的判断结果是成立的,则执行循环体中的语句,否则结束整个for循环。
  3. 执行完循环体后再执行exp3
  4. 重复执行2、3步骤,直到exp2的判断结果不成立,就结束循环。

for-in循环

格式如下:

for variable in value_list; do
  statements
done

variable表示变量,value_list表示取值列表。

每次循环都会从value_list中取出一个值赋给变量 variable,然后进入循环体(dodone之间的部分),执行循环体中的statements。直到取完value_list中的所有值,循环就结束了。

value_list

  • 具体的值,例如:
for i in 1 2 3 \'dd\';do echo $i;done
  • {start..end},例如:
# 求1到10的和
sum=0
for i in {1..10}; do
  sum=$((sum+i))
done
echo $sum
  • 命令的执行结果,例如:
# 求100以内偶数的和
for i in $(seq 2 2 100); do
  sum=$((sum+i))
done
echo $sum
  • 通配符,例如:
# 打印当前路径.log结尾的文件
for i in *.log; do echo $i;done

Tips:若当前路径无.log结尾的文件,则会打印*.log

  • 特殊变量,$*$@等,例如:
for i in $@; do
  sum=$((sum+i))
done
echo $sum

select-in语句

select-in循环用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能,非常适合终端(Terminal)这样的交互场景。格式如下:

select variable in value_list; do
  statements
done

variable表示变量,value_list表示取值列表。

例如:

echo "选择你要学习的科目:"
select i in \'Linux\' \'Python\' \'Java\' \'C++\' \'php\'; do
  echo "你选择了${i}。"
done

结果如下:

选择你要学习的科目:
1) Linux
2) Python
3) Java
4) C++
5) PHP
#? 5
你选择了PHP。
#? 2
你选择了Python。
#? 666
你选择了。

Tips:select是死循环,输入空值或者输入的值无效,都不会结束循环,只有遇到break语句,或者按下Ctrl+D组合键才能结束循环。

例如:

echo "选择你要学习的科目:"
select i in \'Linux\' \'Python\' \'Java\' \'C++\' \'PHP\'; do
  echo "你选择了${i}。"
  break
done

结果如下:

选择你要学习的科目:
1) Linux
2) Python
3) Java
4) C++
5) PHP
#? 5
你选择了PHP。

select-in语句常和case-in语句一起使用。

break

格式如下:

break n

n表示跳出循环的层数,如果省略n,则表示跳出当前。

continue

格式如下:

continue n

n表示循环的层数:

  • 如果省略n,则表示continue只对当前层次的循环语句有效,遇到continue会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
  • 如果带上n,比如n的值为2,那么continue 对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带ncontinue

break的区别:

  • break用来结束当前整个循环;
  • continue用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环;

函数

函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接调取即可。

函数定义

格式如下:

function func_name() {
    statements
    [return value]
}

Tips:关键词function是可选的,但必须在一个项目中保持一致。

说明:

  • function是Shell中的关键字,专门用来定义函数,可以不写,但要求在整个项目脚本中保持一致,即统一不写或都写;
  • func_name是函数名,按照约定规范,函数名后面必须带上()
  • statements是函数要执行的代码,也就是一组语句;
  • return value表示函数的返回值,其中return 是Shell关键字,专门用在函数中返回一个值;这一部分可以写也可以不写。

函数调用

调用Shell函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可,格式如下:

func_name

如果传递参数,那么多个参数之间以空格分隔:

func_name param1 param2 param3...

Tips:不管是哪种调用方式,函数名字后面都不需要带()

函数参数

函数参数是Shell位置参数的一种,在函数内部可以使用$n来接收,例如,$1表示第一个参数,$2表示第二个参数,依次类推。

除了$n,还有另外三个比较重要的变量:

  • $#可以获取传递的参数的个数;
  • $@或者$*可以一次性获取所有的参数

以上是关于Shell:结构化语句的主要内容,如果未能解决你的问题,请参考以下文章

Shell:结构化语句

Linux Shell脚本编写——使用结构化命令

Shell脚本之forwhile循环语句和case分支语句

shell脚本结构化语句

代码片段:Shell脚本实现重复执行和多进程

shell语句结构