[机派X] Day 8 - 我是 Bash 我调皮,令人匪夷所思的 Bash 语法

引言

昨天介绍了套件管理软件以及图形化使用者界面的安装,也是指令介绍的最後一篇文章。学习了这麽多指令後,如果能灵活运用,使用 Linux 来做一些基础的软件开发我想应该都还撑的住!

今天是机派X系列文章的第八天。
本篇将介绍 Bash 的变数操作、条件判断与流程控制。学习过这些东西後,就能用 Bash 将数条指令组合起来依序执行,变成有用的小程序。

本篇大纲:

  • 引言
  • Bash 变数的设置与取用
  • Bash 读取使用者的输入
  • Bash 条件判断介绍
  • Bash 回圈介绍
  • 总结
  • 关於本文章系列

也许你会疑惑,为什麽要学习这些,这些很明显都是在学程序啊,但是我的专案又不用 Bash 开发!为何要学这些?
原因很简单:如果以 Linux 做为开发平台,学习 Bash 有助於你写一些「小工具」,让你在主要程序开发的过程中更方便。
我会在下一篇文章中用 Bash 写一些实用的小程序,希望能解除大家的疑惑。

由於下一篇文章才会带领大家开新档案、撰写脚本,所以建议将本篇与下一篇搭配着看。

Bash 变数的设置与取用

首先,来介绍变数这个东西。

可以想像变数(Variable)就是一个盒子,用於储存资料。撰写程序时,常常会需要很多不同的「盒子」来储存不同资料。每个变数(或说盒子)都有一个名称,该名称由写程序的人定义,以便撰写程序时调用,透过呼叫变数名称就可以取得或变更其中的资料内容。

在 Bash 中设定变数可以使用 export 来完成,若要取用变数则要透过 $ 。

以下是 export 的语法,并请注意 = 左右两边不可有空格!

export NAME[=DATA]

例如:

export my_name='Kevin Huang'

在变数名称前方加上 $ 就可以取得该变数的值:

echo "My name is $my_name !"

Note :
此时,单引号与双引号的使用就会出现差异。如果使用单引号,具有特殊意义的字元(例如:$ 符号)不会被解析,因此会「如实呈现」显示「My name is $my_name !」。如果使用双引号,具有特殊意义的字元(例如:$ 符号)才会被解析,因此会显示「My name is Kevin Huang !」。

接下来,你可以尝试以下范例:

export name='Kevin'
export name='$name Huang'
echo "$name"
export name='Kevin'
export name="$name Huang"
echo "$name"

Note :
如果你有学过其他程序语言,你可以能会好奇:怎麽没有介绍变数的资料型态(Data Type)呢?
Answer:因为 Bash 只有一种资料型态,就是字串(String),所以其实没有必要特别介绍,毕竟对於没有学过其他程序语言的新手来说,资料型态的概念比较陌生,Bash 这种特殊的资料型态不说也罢,说了之後搞不好概念更乱了。

Bash 读取使用者的输入

可以使用 read 来读取使用者输入的资料,VARIABLE 则是储存使用者输入资料的变数名,-r 引数则是不解析输入资料中的跳脱字元,-p 引数则是给使用者的提示文字。
每次执行 read 都是读取一行,行中若有空白则会依照空白切割,并将切割出来的字串依序储存在变数中。

read [-r] [-p PROMPT] VARIABLE......

举例来说:

read -p 'Please input your name and address: ' name address

使用者输入:

Bella Taipei, Taiwan

可以使用 echo 印出使用者输入的文字:

echo "Hello $name. $address is a nice place."

Bash 条件判断介绍

有时候会需要针对资料内容做条件判断,依照判断的结果执行不同的指令。
举例来说:请使用者输入年龄,若输入的年龄大於等於 18 岁就 ...... ,否则就 ...... 。

以下是如果 TEST 条件判断为真(True),就会执行 Block 中的指令。

if [[ TEST ]]
then
    # Block
fi

以下是如果 TEST 条件判断为真(True),就会执行 Block 1 中的指令;
如果条件判断为假(False),则会执行 Block 2 中的指令。

if [[ TEST ]]
then
    # Block 1
else
    # Block 2
fi

以下是如果 TEST1 条件判断为真(True),就会执行 Block 1 中的指令,其余测试则不再判断;如果条件判断为假(False),才会接续 TEST2 的判断。
如果 TEST2 条件判断为真(True),就会执行 Block 2 中的指令;如果条件判断为假(False),就会直接执行 Block 3 的指令。

if [[ TEST1 ]]
then
    # Block 1
elif [[ TEST2 ]]
then
    # Block 2
else
    # Block 3
fi

Note :

  1. [[ 与 ]] 的左右两边都要有空格。
  2. 除了 [[ ]] 之外,也可以使用 [ ] ,请参考 这篇文章 的说明。
  3. 区块的缩排(Indent)并不是语法的一部分,因此可有可无,但是为了让 coding style 看起来舒服一点,建议要缩排。

TEST 是要执行的测试(test),依照测试结果会返回真(True)或假(False)。

测试 测试说明
!TEST 反转 TEST 的测试结果。
-z STRING 判断 STRING 是否为空字串。
-n STRING 判断 STRING 是否不为空字串。
STRING1 = STRING2 判断 STRING1 是否与 STRING2 相同。
STRING1 != STRING2 判断 STRING1 是否与 STRING2 不同。
STRING1 > STRING2 判断 STRING1 的 ASCII 序列是否大於 STRING2。
STRING1 < STRING2 判断 STRING1 的 ASCII 序列是否小於 STRING2。
INTEGER1 -eq INTEGER2 判断 INTEGER1 是否等於 INTEGER2。
INTEGER1 -ne INTEGER2 判断 INTEGER1 是否不等於 INTEGER2。
INTEGER1 -gt INTEGER2 判断 INTEGER1 是否大於 INTEGER2。
INTEGER1 -lt INTEGER2 判断 INTEGER1 是否小於 INTEGER2。
INTEGER1 -ge INTEGER2 判断 INTEGER1 是否大於或等於 INTEGER2。
INTEGER1 -le INTEGER2 判断 INTEGER1 是否小於或等於 INTEGER2。
TEST1 && TEST2 将 TEST1 与 TEST2 的测试结果做 AND 逻辑运算。
TEST1 || TEST2 将 TEST1 与 TEST2 的测试结果做 OR 逻辑运算。
-f PATH 判断 PATH 是否存在,而且是一个档案。
-d PATH 判断 PATH 是否存在,而且是一个目录。
-s PATH 判断 PATH 是否存在,而且档案内容不为空。
-r PATH 判断 PATH 是否存在,而且当前使用者对该档案具有读取权限。
-w PATH 判断 PATH 是否存在,而且当前使用者对该档案具有写入权限。
-x PATH 判断 PATH 是否存在,而且当前使用者对该档案具有执行权限。

举几个例子来看看吧:

read -p 'How old are you? ' age

if [[ $age -ge 18 ]]
then
    echo 'Let us do something interesting ......'
fi
read -p 'How old are you? ' age

if [[ $age -ge 18 ]]
then
    echo 'Let us do something interesting ......'
else
    echo 'Go to the basement and sweep the floor!'
fi
read -p 'How many books on the shelf? ' numBook

if [[ $numBook -lt 0 ]]
then
    echo '??????????????????????????????????'
elif [[ $numBook -eq 0 ]]
then
    echo 'You can buy a book from www.books.com.tw .'
elif [[ $numBook -gt 10 ]]
then
    echo 'You are a man or woman who likes to read!'
else
    echo 'You can buy more books from www.books.com.tw .'
fi

Bash 回圈介绍

有时候也会需要重复执行某些指令,因此会需要用到回圈(loop)。
回圈有很多种写法,以下将一一举例介绍。

for i in {0..10}
do
    echo "There is/are $i rabbit(s)."
done
for i in {0..15..3}
do
    echo "There is/are $i rabbit(s)."
done
for i in {1..9}
do
    for j in {1..9}
    do
        printf "    %1d * %1d = %2d" $i $j $(($i * $j))
    done

    echo ''
done
for i in {1..10}
do
    for ((j=$i; j<=10; ++j))
    do
        echo -n '*'
    done

    echo ''
done
export TRUTH=(49 20 4C 4F 56 45 20 43 53 49 45)

for letter in ${TRUTH[@]}
do
    printf "\x$letter"
done

echo ''
export name=''

while [[ -z $name ]]
do
    read -p 'Your name? ' name
done

echo "Hello $name."
export number=2

while :
do
    echo "There are $number dogs."

    if [[ $number -eq 10 ]]
    then
        break
    fi

    export number=$(( $number + 1 ))
done

总结

因为今天的文章内容有点乾,所以只好补张图化解尴尬气氛。
我找不到这文的梗在哪里,所以帮你种了一个
图源传送门

气氛现在从尴尬变成非常尴尬。
情况从糟糕变成难以理解
图源传送门

关於本文章系列

如果对於文章内容有建议、纠错或图源标示不正确的问题,欢迎参考 [机派X] Day 1 尝试与文章作者联络。
想看更多本系列的文章,请连结至 [机派X] Day 1 查看大纲。


<<:  想办法找找市场区隔吧!

>>:  Day 6 - 产生内文加密所需的 IV 值

Day12【Web】网路攻击:DoS 与 DDoS

Dos 攻击 全称为 Denial-of-Service Attack 即「阻断服务攻击」, 亦被称...

[JS] You Don't Know JavaScript [this & Object Prototypes] - Object [上]

前言 我们在前面几章中介绍了this的绑定,说明了this最常被搞混的观点也介绍了如何透过call-...

端点安全防护 - 防毒软件与软件防火墙

适用人员: 技术人员。 适用法规: 资通安全责任等级分级办法 技术面分类提要 网路架构 端点安全防护...

控制反转与依赖注入(一)

控制反转与依赖注入一 ...

Day28 修复老照片

修复老照片 教学原文参考:修复老照片 这篇文章会介绍在 GIMP 使用「仿制工具」和「着色滤镜」,将...