Shell编程之流程控制

本文主要对Shell中的流程控制语句进行简单总结,另外本文所使用的Linux环境为CentOS Linux release 8.2.2004,所使用的Shell为bash 5.1.0(1)-release

一、if

语法:

# 写法一,if和then写在一行
# 分隔符;在任何命令结尾都可以使用,命令单独成行时可省略
if test-commands1; then
    commands1;
[elif test-commands2; then
    commands2;]
……
[else 
    other-commands;]
fi
# 写法二
if test-commands1
then
    commands1
[elif test-commands2
then
    commands2]
……
[else
    other-commands]
fi
# 单行写法
if test-commands1; then commands1; [elif test-commands2; then commands2;][else other-commands;] fi

执行流程说明:首先执行test-commands1,如果其返回状态为0,则执行commands1。如果test-commands1返回状态非0,并且有elif子句,则依次执行每个elif的test-commands,如果其退出状态为0,则执行其对应的命令。如果有else子句,并且最后的ifelif子句的最后一个命令退出状态为非0,则执行other-commands。整个命令的返回状态是最后一个被执行的命令的退出状态,如果没有一个条件为真,则返回0。

示例如下:

#!/bin/bash

read -p "输入家庭年收入 > " income
# 根据输入的家庭年收入,输出对应的级别
if ((income>50000000)); then
    echo "巨富"
elif ((income>=10000000)); then
    echo "豪富"
elif ((income>=5000000)); then
    echo "中富"
elif ((income>=1000000)); then
    echo "小富"
elif ((income>=300000)); then
    echo "高产"
elif ((income>=150000)); then
    echo "中产"
elif ((income>=80000)); then
    echo "小康"
elif ((income>=30000)); then
    echo "穷"
elif ((income>=10000)); then
    echo "贫困"
else
    echo "赤贫"
fi

输入30000000,执行结果:
1
输入120000,执行结果:
2

二、case

语法:

# 写法一
case word in
    [ [(] pattern [| pattern])
        command-list 
        ;;]
    ……
esac
# 写法二
case word in
    [ [(] pattern [| pattern])
        command-list ;;]
    ……
esac
# 写法三
case word in
    [ [(] pattern [| pattern]) command-list ;;]
    ……
esac
# 单行写法
case word in [ [(] pattern [| pattern]) command-list ;;]… esac

执行流程说明:case会选择性地执行与word所匹配的第一个模式(pattren)对应的command-list。|用来分隔多个模式,)用来结束模式列表。模式列表和其对应的command-list合起来称为子句,case子句的数量不限,每个子句都必须由;;;&;;&结束。最先匹配的模式决定了要执行的command-list。通常习惯使用*作为最后一个模式来定义默认情况,因为该模式总是匹配的。这里的word在匹配之前要经历波浪号扩展、参数扩展、命令替换、算术扩展以及引用去除(quote removal)。每个模式也都要经历波浪号扩展、参数扩展、命令替换以及算术扩展。如果没有任何模式与之匹配,则整个case返回状态为0,否则返回状态为最后一个被执行的command-list的退出状态。

如果启用了shell的nocasematch选项,则在匹配时不考虑字母的大小写。
;;;&;;&结束子句的区别:如果用;;来结束子句,则在匹配了第一个模式后就不会再匹配其他模式。如果用;&代替;;,如果下面还有其他子句,则会继续执行下一个子句对应的command-list。如果用;;&&代替;;,如果下面还有其他子句,则会测试下一个子句的模式(如果有的话),并在匹配成功时执行相对应的command-list。
模式中*匹配任何字符串,?匹配任意单个字符,[…]匹配方括号中任一字符,由连字符-分隔的一对字符是范围表达式,使用当前语言区域(locale)设置的排序序列和字符集中任何在这两个字符之间(包括这两个字符)的字符都会被匹配。如果在[之后的第一个字符是!~,则匹配没有出现在方括号中的任一字符,如果要匹配-,可以把它放在方括号第一个或最后一个位置,如果要匹配],可以把它放在方括号第一个位置。

使用case根据输入的一个整数输出对应星期的英文单词的示例如下:

#!/bin/bash

read -p "输入1-7的整数 > " num
# 根据输入的整数(范围1到7),输出对应星期的英文单词
case ${num} in
    (1)
        echo "Monday"
        ;;
    (2)
        echo "Tuesday"
        ;;
    (3)
        echo "Wednesday"
        ;;
    (4)
        echo "Thursday"
        ;;
    (5)
        echo "Friday"
        ;;
    (6)
        echo "Saturday"
        ;;
    (7)
        echo "Sunday"
        ;;
    (*)
        echo "Invalid Data"
        ;;
esac

执行结果:
3
使用case根据输入的菜单选项或菜名进行点菜的示例如下:

#!/bin/bash

# 模拟一份简单菜单
echo "#####菜单#####
  1.青椒肉丝
  2.麻婆豆腐
  3.辣子鸡丁
  4.鱼香肉丝
  5.水煮肉片
##############"
# 循环输入菜单选项或菜名进行点菜,输入0或点菜结束退出循环以结束点菜
while read -p "请点菜 > " choice
do
    case ${choice} in
        1 | "青椒肉丝")
            echo "青椒肉丝一份"
            ;;
        2 | "麻婆豆腐")
            echo "麻婆豆腐一份"
            ;;
        3 | "辣子鸡丁")
            echo "辣子鸡丁一份"
            ;;
        4 | "鱼香肉丝")
            echo "鱼香肉丝一份"
            ;;
        5 | "水煮肉片")
            echo "水煮肉片一份"
            ;;
        0 | "点菜结束")
            echo "点菜结束,稍后上菜"
            break
            ;;
        *)
            echo "请选择菜单中的菜品"
            ;;
    esac
done

执行结果:
4
使用case根据输入的一个字符输出字符对应的类型的示例如下:

#!/bin/bash

read -n 1 -p "输入一个字符 > " char
# 根据输入的字符输出字符的类型(是英文字母、数字还是其他字符)
case ${char} in
    [a-zA-Z])
        echo -e "\n英文字母" ;;
    [0-9])
        echo -e "\n数字" ;;
    *)
        echo -e "\n其他字符" ;;
esac

输入K,执行结果:
5
输入6,执行结果:
6
case子句以;;结束,在匹配了第一个模式之后不再继续匹配下面的模式;case子句以;&结束,在匹配了第一个模式之后会继续执行之后的子句对应的命令,不管是否匹配这些子句中的模式;case子句以;;&结束,在匹配了第一个模式之后会继续匹配下面的模式,只执行匹配的模式对应的命令。示例如下:

#!/bin/bash

read -p "输入一个字符串 > " str

echo -e "\ncase子句以;;结束"
case ${str} in
    h?o) echo "以h开头o结尾的字符串,中间只能为任意一个字符" ;;
    h*o) echo "以h开头o结尾的字符串" ;;
    [e-m][c-e]*) echo "以字符e到m的开头,字符c到e为第二个字符的字符串" ;;
    *) echo "任意字符串" ;;
esac

echo -e "\ncase子句以;&结束"
case ${str} in
    h?o) echo "以h开头o结尾的字符串,中间只能为任意一个字符" ;&
    h*o) echo "以h开头o结尾的字符串" ;&
    [e-m][c-e]*) echo "以字符e到m的开头,字符c到e为第二个字符的字符串" ;&
    *) echo "任意字符串" ;&
esac

echo -e "\ncase子句以;;&结束"
case ${str} in
    h?o) echo "以h开头o结尾的字符串,中间只能为任意一个字符" ;;&
    h*o) echo "以h开头o结尾的字符串" ;;&
    [e-m][c-e]*) echo "以字符e到m的开头,字符c到e为第二个字符的字符串" ;;&
    *) echo "任意字符串" ;;&
esac

输入hao,执行结果:
7
输入haeo,执行结果:
8

三、while

语法:

while test-commands
do
    commands
done
#单行写法
while test-commands; do commands; done

执行流程说明:只要test-commands的退出状态为0,就执行commands。其返回状态是commands中最后一个被执行的命令的退出状态,如果commands没有被执行则返回0。

计算1加到100的和的示例如下:

#!/bin/bash

i=1
sum=0
while ((i<=100))
do
    ((sum+=i,i++))
done
echo "1加到100的和为${sum}"

执行结果:
9

四、until

语法:

until test-commands
do
    commands
done
# 单行写法
until test-commands; do commands; done

执行流程说明:只要test-commands的退出状态为非0,就执行commands。其返回状态是commands中最后一个被执行的命令的退出状态,如果commands没有被执行则返回0。

计算1加到100的和的示例如下示例如下:

#!/bin/bash

i=1
sum=0
until ((i>100))
do
    ((sum+=i,i++))
done
echo "1加到100的和为${sum}"

执行结果:
10

五、for

语法:

# 第一种形式
for name [in [words …] ]
do
    commands
done    
# 第一种形式单行写法
for name [ [in [words …] ] ; ] do commands; done
# 第二种形式
for (( expr1; expr2; expr3 ))
do
    commands
done
# 第二种形式单行写法
for (( expr1; expr2; expr3 )); do commands; done

执行流程说明:

  • 第一种形式:将words扩展成一个列表,将结果列表中的每个元素都赋值给name并执行一次commands。如果没有in words,则依次对每个位置参数都执行一次commands,就像指定了in $@一样。其返回状态是commands中最后一个被执行的命令的退出状态。如果对单词的扩展没得到任何元素,则不执行任何命令,返回状态为0。
  • 第二种形式:首先对算术表达式expr1进行求值,然后反复对算术表达式expr2进行求值,直到它的求值结果为0。每当expr2求值结果为非0值时,则执行一次commands并对算术表达式expr3进行求值。如果省略了任何表达式,则其表现就像表达式求值结果为1一样。其返回值是commands中最后一个被执行的命令的退出状态,如果任何一个表达式都是无效的,则返回false。

第一种形式的for循环可以直接指定要循环的列表,多个值之间以空格分隔。示例如下:

#!/bin/bash

echo "循环打印1,2,3,4,5,6"
for i in 1 2 3 4 5 6
do
    echo ${i}
done
# 变量i在for循环外依然有效,其值为最后一次循环赋的值
echo "循环中i的最后一个值为${i}"
# 变量i也允许修改
i=1
echo "现在i的值为${i}"

echo "循环打印Bourne Shell,BourneAgain Shell,C Shell,Korn Shell"
# 列表中的值包含空格,可以用双引号引起来(推荐)
for var1 in "Bourne Shell" "BourneAgain Shell" "C Shell" "Korn Shell"
do
    echo ${var1}
done
echo "循环中var1的最后一个值为${var1}"
var1="Bourne Shell"
echo "现在var1的值为${var1}"

echo "循环打印a'b,c'd,e\"f,g\"h"
# 如果列表中的值包含'或",可以通过反斜杠\转义或引号引起来
for var2 in a\'b "c'd" e\"f 'g"h'
do
    echo ${var2}
done
echo "循环中var2的最后一个值为${var2}"
var2="a'b"
echo "现在var2的值为${var2}"

执行结果:
11
第一种形式的for循环可以通过{start..end}的方式指定要循环的列表,{start..end}指定列表取值范围。示例如下:

#!/bin/bash

sum=0
for i in {1..100}
do
    ((sum+=i))
done
echo "1加到100的和为${sum}"

执行结果:
12
第一种形式的for循环可以使用变量来指定要循环的列表。示例如下:

#!/bin/bash

echo "以空格分隔每个单词"
list="Bourne Shell BourneAgain Shell C Shell Korn Shell"
for var1 in ${list}
do
    echo ${var1}
done

echo "以,分隔每个单词"
list="Bourne Shell,BourneAgain Shell,C Shell,Korn Shell"
# Shell会把$IFS中的每个字符作为分隔符,在单词拆分(word splitting)时按照这些字符进行拆分
# 如果IFS未设置,它的值默认为空格符、制表符、换行符
# 将IFS的默认值保存在变量IFS_OLD中
IFS_OLD=${IFS}
# 将IFS的值修改为,,只以,作为分隔符
IFS=,
for var2 in ${list}
do
    echo ${var2}
done
# 将IFS恢复为IFS的默认值
IFS=${IFS_OLD}

执行结果:
13
第一种形式的for循环可以指定数组中的所有下标或元素为要循环的列表来遍历数组。示例如下:

#!/bin/bash

declare -A user=(["username"]="zs123" ["password"]="a123456" ["age"]=25 ["email"]="zs123@xxx.com")

echo "直接遍历数组元素的值"
for v in ${user[@]}
do
    echo ${v}
done

echo "通过下标遍历数组"
for i in ${!user[@]}
do
    echo "${i} : ${user[${i}]}"
done

执行结果:
14
第一种形式的for循环可以通过命令替换指定命令执行结果为要循环的列表。示例如下:

#!/bin/bash

# 将输入重定向读取的内容重定向输出到文件中
cat > test.txt << EOF
Expand words, and execute commands
once for each member
in the resultant list, with name
bound to the current member.
EOF

echo "以IFS的默认值分隔每个单词"
for word in $(cat test.txt)
do
    echo ${word}
done

echo "只以换行符分隔每个单词"
# Shell会把$IFS中的每个字符作为分隔符,在单词拆分(word splitting)时按照这些字符进行拆分
# 如果IFS未设置,它的值默认为空格符、制表符、换行符
# 将IFS的默认值保存在变量IFS_OLD中
IFS_OLD=${IFS}
# 将IFS的值修改为换行符,只以换行符作为分隔符
IFS=$'\n'
for line in $(cat test.txt)
do
    echo ${line}
done
# 将IFS恢复为IFS的默认值
IFS=${IFS_OLD}

执行结果:
15
第一种形式的for循环可以通过文件名扩展所匹配的文件列表为要循环的列表。

文件名扩展:单词拆分后,除非设置了set命令-f选项,否则Bash会在每个单词中扫描字符*?[。如果找到其中一个字符,并且没有被引用(quote),则该单词会被视为一个模式(pattern),并被一个与该模式匹配的文件名列表按字母顺序排序的方式所替换。如果未找到匹配的文件名并且禁用了shell的nullglob选项,则不处理该单词。如果启用了shell的nullglob选项并且未找到匹配的文件名,则删除该单词。

示例如下:

#!/bin/bash

echo "循环打印/root目录下的所有文件并判断它是目录还是普通文件"
# /root/*匹配/root目录下的所有文件
for file1 in /root/*
do
    if [[ -d ${file1} ]]
    then
        echo "${file1}是一个目录"
    elif [[ -f ${file1} ]]
    then
        echo "${file1}是一个普通文件"
    fi  
done

echo "循环打印所有以b或c开头的后缀名为.sh的文件"
# [bc]*.sh匹配所有以b或c开头的后缀名为.sh的文件
for file2 in [bc]*.sh
do
    echo ${file2}
done

echo "循环打印所有只有单个字符的后缀名为.sh的文件"
# ?.sh匹配所有只有单个字符的后缀名为.sh的文件
for file3 in ?.sh
do
    echo ${file3}
done

执行结果:
16
第一种形式的for循环如果没有指定in words,则默认$@为要循环的列表。示例如下:

#!/bin/bash

function func1() {
    for parameter1
    do
        echo ${parameter1}
    done
}
function func2() {
    for parameter2 in $@
    do
        echo ${parameter2}
    done
}
echo "省略列表时循环打印的结果"
func1 1 3 5 7 9
echo "指定\$@时循环打印的结果"
func2 2 4 6 8 10

执行结果:
17
第二种形式的for循环计算1加到100的和的示例如下:

#!/bin/bash

sum=0
for (( i=1; i<=100; i++ ))
do
    ((sum+=i))
done
echo "1加到100的和为${sum}"

执行结果:
18
第二种形式的for循环中的三个表达式中的任意一个都可以省略,其表现就像表达式求值结果为1一样。这里以三个表达式都省略为例:

#!/bin/bash

sum=0
i=1
# 省略了第二个表达式,循环就为无限循环,可以在循环体内使用break命令强制结束循环
for (( ; ; ))
do
    if ((i>100))
    then
        break
    fi
    ((sum+=i,i++))
done
echo "1加到100的和为${sum}"

执行结果:
19

六、select

select结构使生成菜单变得很简单。语法:

select name [in words …]
do
    commands
done
# 单行写法
select name [in words …]; do commands; done

执行流程说明:words将被扩展并生成一个项目列表,扩展的单词集合将被打印到标准错误输出流上,每个单词前面都会加上一个序数。如果省略in words,就打印位置参数,就像指定了in $@一样。之后将显示PS3提示符并从标准输入读取一行输入。如果输入行包含一个与所显示的其中一个单词对应的数字,则name的值就被设置为该单词。如果输入为空,则重新显示单词列表(项目列表)和提示符。如果输入了EOF,则select将会结束。输入任何其他值都会导致name被置为空值。读取的输入行保存在变量REPLY中。每次选择之后都会执行commands,直到一个break命令被执行为止,此时select执行结束。

使用select生成一个简单菜单并进行选择的示例如下:

#!/bin/bash

echo "#####菜单#####"
# 输入的时候按下Ctrl+D组合键可以退出select
select name in "青椒肉丝" "麻婆豆腐" "辣子鸡丁" "鱼香肉丝" "水煮肉片"
do
    echo ${name}
done

执行结果:
20
使用select生成菜单并结合case根据菜单选项进行点菜的示例如下:

#!/bin/bash

echo "#####菜单#####"
# 使用select生成菜单选项并结合case根据菜单选项进行点菜
# 最后一个菜单选项为退出选项,通过break命令退出select以结束点菜
select name in "青椒肉丝" "麻婆豆腐" "辣子鸡丁" "鱼香肉丝" "水煮肉片" "点菜结束"
do
    case ${name} in
        "青椒肉丝")
            echo "${name}一份"
            ;;
        "麻婆豆腐")
            echo "${name}一份"
            ;;
        "辣子鸡丁")
            echo "${name}一份"
            ;;
        "鱼香肉丝")
            echo "${name}一份"
            ;;
        "水煮肉片")
            echo "${name}一份"
            ;;
        "点菜结束")
            echo "${name},稍后上菜"
            break
            ;;
        *)
            echo "请选择菜单中的菜品"
            ;;
    esac
done

执行结果:
21

七、break

break [n]

使用内置命令break可以从forwhileuntilselect循环中退出。如果给定n,则退出n层循环,n必须大于或等于1。除非n小于1,否则返回状态为0。

ifthenelifelseficaseesacuntilwhileforselectindodone都是Shell保留字。而breakcontinue是Shell内置命令。

使用break退出单层循环的示例如下:

#!/bin/bash

for (( i=1; ; i++ ))
do
    if ((i>9))
    then
        # 退出外层循环
        break
    fi
    for (( j=1; ; j++ ))
    do
        if ((j>i))
        then
            # 退出内层循环
            break
        fi
        echo -n "${i}*${j}=$((i*j)) "
    done
    echo ""
done

执行结果:
22
使用break退出多层循环的示例如下:

#!/bin/bash

for (( i=1; ; i++ ))
do
    for (( j=1; ; j++ ))
    do
        if ((i>9))
        then
            # 退出2层循环
            break 2
        fi
        if ((j>i))
        then
            # 退出内层循环
            break
        fi
        echo -n "${i}*${j}=$((i*j)) "
    done
    echo ""
done

执行结果:
23

八、continue

continue [n]

使用内置命令continue可以跳过forwhileuntilselect的本次循环,继续执行下一次循环。如果给定n,则跳过n层循环中的本次循环(n层循环的每一层都会跳过本次循环),继续执行下一次循环。n必须大于或等于1。除非n小于1,否则返回状态为0。

使用continue跳过单层循环的本次循环的示例如下:

#!/bin/bash

for (( i=1; i<=9; i++ ))
do
    if ((i%2==0))
    then
        # 跳过外层循环的本次循环
        continue
    fi
    for (( j=1; j<=i; j++ ))
    do
        if ((j%3==0))
        then
            # 跳过内层循环的本次循环
            continue
        fi
        echo -n "${i}*${j}=$((i*j)) "
    done
    echo ""
done

执行结果:
24
使用continue跳过多层循环的本次循环的示例如下:

#!/bin/bash

for (( i=1; i<=9; i++ ))
do
    for (( j=1; j<=i; j++ ))
    do
        if ((j%3==0))
        then
            # 跳过2层循环的本次循环
            continue 2
        fi
        echo -n "${i}*${j}=$((i*j)) "
    done
    echo ""
done
echo ""

执行结果:
25

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RtxTitanV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值