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