Shell/bash语法, since 2020-10-26

Shell是什么?

(2020.10.31 Sat)
Shell是运行在terminal中的文本互动程序,其分析文本输入,然后把文本转换成相应的计算机动作。说到底,Shell是一个运行着的程序,该程序接收到你两次单击'Enter'之间的输入,对输入的文本进行分析。

Shell命令分为三类:

  • Shell内建函数(built-in function)
  • 可执行文件(executable file)
  • 别名(alias)

Shell内建函数是保存在Shell内部的脚本。可执行文件是保存在Shell之外的脚本。Shell必须在系统中找到对应命令名的可执行文件,才能正确执行。可以用绝对路径来告诉Shell可执行文件所在的位置。所谓路径,是指一个文件在存储空间的位置。如果用户只给出命令,没有给出准确的位置,Shell必须自行搜索一些特殊的位置,也就是默认路径。

Alias是给某个命令起的一个简称,以后在Shell中就可以通过这个简称来调用对应的命令,比如

$alias freak="free -h"

Shell会记住这个别名,在Shell中输入命令freak,都将等价于输入free -h。

在Shell中,可以通过type命令来了解命令的类型。如果一个命令是可执行文件,则打印出文件路径。

$type date
$type pwd

bash
Shell是文本解释器程序的统称,而bash是其中的一种,提供了丰富的功能。可通过下面指令查看Shell类型

$echo $Shell

bash语法

变量

赋值符号,比如'=',左右两侧不能有空格。如果文本中包含空格,用单引号或双引号包裹文本。也可以把命令输出的文本直接赋予一个变量用``符号囊括。引用变量时,需要在变量前加入符号$。用{}把变量独立于其他字符。注,同样的指令在MAC的bash中可能不同。

$a=world
$b='abc def'
$c=`date`
$another=$a
$echo $c
$echo "Hello ${a}and this is a test"
数学运算

在bash中,数字和运算符都被当做普通文本,无法像C语言一样边界的进行数学运算。可通过$(())来进行数学运算。

$a=$((2+4+6))
$echo $a
$12
$b=$((100+$a))
$112
返回代码

一般来说,C语言的脚本返回的正常输出代码是0,如果有错误,则是大于0的数字。在bash脚本中用echo $?指令在运行可执行文件后,可获得返回代码。

$gcc foo.c #编译
$./a.out #运行代码
$echo $? #打印返回值

$rm some_file.txt
$echo $?

Linux中可以一行执行多个程序,用;隔开。

$touch demo.file; ls;
$pwd; ls;

执行多个程序时,可以要求后一个程序的运行参考第一个程序的返回代码。当只有前一个程序返回成功代码0时,后一个才运行,使用&&。当前一个失败时后一个才运行,使用||.

$rm demo.file && echo "rm succeed"
$rm demo.file || echo "rm failed"
bash脚本

多行bash命令写入一个文件就形成了bash脚本。执行bash脚本时,Shell将逐行执行脚本中的命令。例如,在一个文本编辑器,如nano中输入下面代码,保存为hw.bash文件,

#!/bin/bash
echo hello
echo world

其中的第一行代码#!/bin/bash,说明了该脚本使用的Shell,也就是路径为/bin/bash的bash程序。
在Shell中的同一个路径下,输入如下指令即可执行

$ ./hw.bash

Shell将打印两行文字,即文件中输出的文字。
注意bash的输出符号>和>>。这两个符号都用于写入文件,如果没有该文件,则创建。如果有文件,>会完全覆盖该文件,>>会在文件后面写入,类似于python的append。比如下面bash脚本info.bash

#!/bin/bash
echo "This mac info: " > device.log
lscpu >> device.log
uname >> device.log
free -h >> device.log

第一行创建了一个device.log文件,而且如果该文件存在,则先清空再写入。后面的指令在文件中已有的内容后面添加相应的信息。

脚本参数
bash脚本运行时,可携带参数。参数在bash脚本中以变量的形式使用,比如脚本a.bash中,

#!/bin/bash
echo $0
echo $1
echo $2

bash脚本中以$0, $1, $2...的方式获得bash脚本运行时的参数。用下面方式运行脚本

$ ./a.bash hello world

$0 是命令的第一部分,也就是./a.bash,$1代表了传入的第一个参数也就是hello,$2代表了传入的第二个参数也就是world,以此类推。上面的程序打印:

./a.bash
hello 
world

变更参数,同一段脚本有不同的行为,大大提高了bash脚本的灵活性,比如前面的info.bash脚本,脚本修改如下

#!/bin/bash
echo "Mac info: " > $1
uname >> $1
free -h >> $1
pwd >> $1

运行可修改为

$./info.bash info.log

产生相同的运行结果。
脚本返回代码
按照惯例,脚本正常退出时返回代码0。在脚本结尾可用exit命令设置返回代码。正常结束时,加入exit 0指令并不必要,因为默认返回也是0.在脚本中出现exit命令,则直接在exit处停止,并返回exit指令给出的返回代码。

#!/bin/bash
echo hello
exit 360
echo world

运行脚本之后,用$?查询脚本的返回代码。

$./info.bash
$echo $?

函数

函数用function xxx () {..}来表述,调用时给出函数名即可。跨脚本调用函数需要使用source指令,类似于python的import。
一个有函数的脚本a.bash

#!/bin/bash
# define a function
function first_fun () {
    lscpu >> log
    free -h >> log
    uname -e >> log
}
# revoke it
first_fun

之后在Shell中运行该bash文件即可。

函数中的变量传递和bash文件的变量传递相似,使用$x这样的指令。如脚本b.bash。

#!/bin/bash
function sec_fun () {
    lscpu >> $1
    free >> $1
}
sec_fun logfile1.log
sec_fun logfile2.log

调用了两次函数,分别对两个log文件进行写入。

跨脚本调用时,需要标注source xxx.bash。比如b.bash中有函数sec_fun,在c.bash调用sec_fun时需要加入source指令。c.bash脚本如下。

#!/bin/bash
source b.bash
sec_fun log_file_3.log

在Shell中也可以调用文件中的函数

$source b.bash #类似于import b (in python)
$sec_fun logfile3.log

逻辑判断

用test指令进行逻辑判断,表达式为真,返回0,否则返回1。形式为test expression。常用逻辑关系见下表

逻辑关系 表达
大于> -gt (greater than)
小于< -lt (less than)
等于= -eq
不等于 -ne
大于等于 -ge
小于等于 -le
文件是否是否存在 -e
普通文件是否存在 -f
目录文件是否存在 -d
软链接文件是否存在 -L
文件是否可读、写、执行 -r -w -x
与、或、非 -a -o !expression
文本是否排名靠前/后(字典顺序) > <
$test 3 -gt 2; echo $?
$test -e a.out; echo $?
$test -r file.txt; echo $
# 做与或非的逻辑判断表达形式
$!expression
$expression1 -a expression2
$expression2 -o expression2

关于文本的判断,注意文本判断时表达符号和变量(或常亮)要分开,即之间有空格。

$test abc = bx; echo $?
$test abc != abc; echo $?
$test apple > tea; echo $?

比较文本时的>代表了按文本顺序一个文本在另一个文本之前。

选择结构

格式

if [condition]
then 
    expression
else
    if [condition1]
    then
        expression
else
    expression
fi

比如,判断当前用户是否为root的d.bash

#!/bin/bash
var='whoami'
if [$var = 'root']
then
    echo "you are root"
else
    echo "you are not root"
fi

e.bash用于判断一个文件是否存在。

#!/bin/bash
filename=$1
if [ -e $filename]
then 
    echo "$filename exists"
else
    echo "$filename no exists"
fi
echo 'end'

运行它,即可返回结果。

$./e.bash a.out

Case语句
除了if else语句,还可使用case语句,类似C的case when语句。case的每个分支结尾需要用两个分号,;;。格式如下

case expression in
x)
    expression1
;;
y)
    expression2
;;
*)
    expression3
;;
esac

注意到其中出现通配符*,代表着匹配任意文本,?代表任意一个字符,[]代表范围内一个字符。

#!/bin/bash
var='whoami'
case $var in
root)
    echo 'you are root'
;;
abc)
    echo 'you are abc'
;;
*)
    echo 'you are somebody'
;;
esac

循环结构

即while和for语法。while的格式如下

while [ condition]
do 
    expression
done

for的格式

for expression in something
do 
    expression
done

注意seq命令经常用于for循环。

#!/bin/bash
now=`date + '%Y%m%d%H%M'`
dealine=`date --date='1 hour' +'%Y%m%d%H%M'`
while [ $now -lt $dealine]
do
    date
    echo 'not yet'
    sleep 10
    now=`date +'%Y%m%d%H%M'`
done
echo 'now deadline reached'
#!/bin/bash
for user in lucy lily jackie
do
    echo '$user'
done
#!/bin/bash
total=0
for number in `seq 1 10 100`
do 
    total=$(($total+$number))
done

需要结束循环时,用break指令退出。

bash与C

二者都是面向过程的编程语言。bash变量只能是文本类型,C的变量可以很复杂。bash本质上是个Shell,一个命令解释器程序,而不是编程语言,bash编程知识命令解释器程序提供的一种互动方法,bash脚本只能与bash进程互动,不能直接调动CPU的功能,运行速度可能比不上可执行文件。

当然,bash脚本可以不用编译就由bash进程理解并执行,开发bash脚本比开发C脚本要快的多。

reference

1 Vamei,周昕梓著,树莓派开始,玩转Linux,中国工信出版社,电子工业出版社,2018

你可能感兴趣的:(Shell/bash语法, since 2020-10-26)