跳至主要內容

Linux shell


# 为什么要学习Shell呢?

不管是做运维还是做android系统开发,都要学习shell

这里我们主要是针对android系统开发,AOSP-android开源项目。

我们学习Shell有两个目的,一个是能看懂编译控制脚本,另外一个是能够修改或者说可以编写编译控制脚本。

什么是Shell

如果你接触计算机多年,可能有人跟你说过黑乎乎的命令行窗口就是shell,Windows有命令行窗口,而Linux各个发行版本也有命令行窗口。

Snip20181124_1.png
Snip20181124_1.png

我们先不管这个。我们平时都是通过UI交互来控制操作系统。

从一个整体的角度看操作系统,应该如下:

Snip20181124_2.png
Snip20181124_2.png

而shell命令行窗口就是前面我们提到的小黑窗了。对于一般的用户来说,比较常用的是可视化操作,比如说复制一个文件,而shell命令行窗口里输入命令也可以实现复制文件。

shell能做的,可视化操作不一定有,而可视操作有的,shell一定有。

shell就是黑乎乎的命令行窗口,对不对呢?应该说还不够准确,这只是其中一个入口。除了使用命令行窗口输入命令,编程语言使用系统的API也可是可以调用shell命令的,我们也可以通过编写shell脚本去执行。

shell应该是一个命令解析器,解析来自脚本的命令或者第三方应用的指令给操作系统。

Shell脚本

接下来我们就开始学习一下shell脚本,环境限制哈,今天周六休息写这篇文章,在家里没有服务器,所以不能结合实际的AOSP实际例子分析了,但是理解了这些知识,也是可以看懂shell脚本的。

shell解析器

我现在有一个树莓派的机器,里面也是基于Linux的一个系统,怎么查看解析器呢?

Snip20181124_3.png
Snip20181124_3.png

而默认的解析器则是/bin/sh

Snip20181124_4.png
Snip20181124_4.png

一般来说,我们在写shell脚本的时候,需要指定解析器,否则就使用默认的解析器。

shell开始

shell脚本以

#!/bin/bash

开头,看过AOSP里shell脚本的同学可能有印象,它并没有以这个开头,如果不指定的话,默认就是/bin/bash作为shell的解析器。

hello world!

Snip20181124_5.png
Snip20181124_5.png

wq保存,解析一下: Snip20181124_6.png

除了用sh以外,我们还可以这样子执行脚本:

Snip20181124_7.png
Snip20181124_7.png

但是调用./helloworld.sh的时候要注意,先修改权限,否则没法执行这个脚本的。

eg2:

我们再来一个例子,把上面的动作转在命令去完成:

1、在~/shell_code/目录下面创建一个名字为helloworld2.sh的文件

2、往这个文件里写入内容:输出helloworld

这其实对着我们AOSP里的自动控制流程了,一些重复的操作就可以抽象出来,用脚本代替。

Snip20181124_8.png
Snip20181124_8.png

执行一看看结果如何?

Snip20181124_9.png
Snip20181124_9.png

shell变量

在shell脚本中,变量分成两类,一类是系统定义的变量,一类是用户自己定义的变量。

常用的系统变量有哪些呢?

比如说前面提到的:$SHELL,还有$HOM, $USER ,$PATH ,$PWD….

我们可以输出来看看它们的值是什么:

Snip20181124_10.png
Snip20181124_10.png

执行结果: Snip20181124_11.png

这个了解一下就好,以后写脚本的时候可以使用到,写多几次就记住了。

知道这些,如果以后要复制文件呀,或者添加环境变量之类的就简单得多了,对吧。

自己定义变量怎么玩呢?

跟我们的java不一样,直接变量=值就可以了。

不需要生命类型

eg:

a=10
echo $a

注意哦,赋值没有空格。

Snip20181124_12.png
Snip20181124_12.png

字符串也一样,shell脚本是若类型语言,不类型,跟javaScript一样。

静态变量,也就是常量,只读的哦,不可以unset

readonly b=20
echo $b
#看结果会不会报错
unset $b
Snip20181124_13.png
Snip20181124_13.png

定义变量有什么规则呢?

  1. 名字,明明规则不多说了,都是有经验的开发人员;
  2. 赋值等号两边没有空格;
  3. 赋值的内容默认为字符串类型,需要进行一个转换才能进行运算;
  4. 参数/字符串有空格,需要用双引号以表示成一体的。

如何把局部变量提升成全局变量,让其他地方可以访问呢?

比如说我们在shell脚本里定义一个变量VAR=10,怎么样可以直接在命令行中访问呢?

var.sh

#!/bin/bash

echo $VAR
Snip20181125_14.png
Snip20181125_14.png

shell中特殊的变量

$n,其中n表示数字,从0开始,一般不超过10

$n表示什么呢?我们输出一下吧:

Snip20181125_15.png
Snip20181125_15.png

执行结果:

Snip20181125_16.png
Snip20181125_16.png

我们java的main函数也是可以传参数的,这里面的话大家理解一下就好,当我们执行一下shell的时候,可以传进来参数。

如果是$1那么是第二个参数,我再修改一个代码:

Snip20181125_17.png
Snip20181125_17.png

相信大家理解了吧:

Snip20181125_18.png
Snip20181125_18.png

$#,这也是一个特殊变量,它表示的是参数的个数。在java中可以用不定参数来表示,然后以数据的形式进行获取。

在shell中,我们获取到了参数的个数以后,可以进行遍历,下面我们就举例说明一下吧:

这里面我们还没有学到循环语句,我们就只读取参数的个数即可

#!/bin/bash

echo "parameter count is --- > $#"
Snip20181125_19.png
Snip20181125_19.png

shell中的运算符 对于运算符大家应该很熟悉了吧

Snip20181125_20.png
Snip20181125_20.png

上图来自己菜鸟

看这篇文章的都是有经验的程序员了,这些东西应该非常简单的

要注意的点:expr后面要有空格,运算符两边要有空格,会看就好

这里跟赋值不一样,需要双等号的两边需要有空格

基本格式是这样子的$((运算表达式)) 或者写成$[运算表达式]

条件语句

语法:

[空格 条件 空格]

条件两边要有空格

条件里只要有内容就为true,除非为空为false,接着就是判条件

比如说:[ abc ] 返回true,[]返回false

常用的条件判断

= 比较字符串

-lt 小于(less than)

-gt 大于 (greater than)

-ne 不等于 (not equal)

-eq 等于 (equal)

-le 小于等于 (less equal)

-ge 大于等于 (greater equal)

记住几个单词怎么切换都行对吧!

接直来除了这些以外,我们还有可能判断到文件是否可以读写之类的

-r 判断文件是否可读,true表示可读,false表示不可读

-w 判断写

-x 判断是否可执行

-e 判断文件是否存在

-f 判断是不是文件

-d 判断是不是一个目录

后面我们结合条件语句来写一些例子

shell中的流程语句

流程控制语句在各种编程言中都有的呢,在shell中也有

if的判断

case语句

for循环

while循环

if判断语句 先上代码吧,我们判断两个数是否相等,用到前面的

Snip20181125_25.png
Snip20181125_25.png

执行结果怎么样的呢?当然是输出a=b啦

Snip20181125_26.png
Snip20181125_26.png

if语句有两种写法:

if [ 条件 ];then
程序逻辑
fi

第二种写法,就是then换下来:

if [ 条件语句 ]
then
程序语句
fi

要注意的地方是if后面有空格,条件语句两边有空格

多写几次吧,写错了以后就记住了。

再来一个elif,在java里是else if,在shell里是elif

比如说我们判断输入的内容:

Snip20181125_28.png
Snip20181125_28.png

执行结果如下:

Snip20181125_29.png
Snip20181125_29.png

case语句

case语句的话,这里我们不直接给出格式了,直接上代码

因为代码更容易看懂

#!/bin/bash

case $1 in
1)
        echo "female"
;;
2)
        echo "male"
;;
*)      echo "unknow"
;;
esac
Snip20181125_30.png
Snip20181125_30.png

一看代码就知道了吧,按模板去套就可以了。

## for循环语句

这个格式是怎么样子的呢?

for((初始值;条件;变量控制语句))

do

循环体

done

比如我们输出一个三解形

Snip20181125_31.png
Snip20181125_31.png

除了这种,还有

for loop in 1 2 3 4
do
       echo $loop
done
Snip20181125_32.png
Snip20181125_32.png

while循环

while循环的语法

while [ 条件 ]
do
    语句
done
#!/bin/bash

total=0
i=1
while [ $i -le 100 ]
do
        total=$[$total+$i]
        i=$[$i+1]
done
echo "total is $total"

执行结果

Snip20181125_33.png
Snip20181125_33.png

读取控制台的输入read

我们AOSP里,要选择编译的版本,这个时候,需要从控制台窗口中读取号码,然后才去进行编译。

怎么样读取控制台的输入呢?

read

选项有:

-t:等待时间,单位为秒

-p:指定读取值时的提示符:

eg:

#!/bin/bash

echo "please input the version you want to compile:\n"
echo "1.user-version\n2.engineer-version"

read -p "your selected version is:" version

if [ $version -ne 1 -a $version -ne 2 ]
then
        echo "version not right"
else
        case $version in
                1)
                        echo "you selected version is user-version"
                ;;
                2)
                        echo "you selected version is engineer-version"
                ;;
                esac
fi

echo "your input is $version"
Snip20181125_34.png
Snip20181125_34.png

函数

函数的话我们主要分为两块,一部分是系统函数,另外一部分则是我们自己写的函数。

常用的系统函数

basename 全路径名称,获取到文件的名字

dirname 全路径名称,获取除文件名以外的路径地址

这两个在自动化编译的时候,关于路径的处理用得上

自定义函数

怎么样写自己的函数呢?之前在分析编译脚本的时候,就有lunch这个函数了,用于加载要编译的版本。

定义函数:

[function] 函数名[()]

{

函数体

}

调用:

函数名

举例子

#!/bin/bash

function sum()
{
total=0
total=$[$1+$2]
echo "total is --> $total"
}

read -p "please input first number:" number1
read -p "please input second number:" number2

sum $number1 $number2

执行结果:

Snip20181125_35.png
Snip20181125_35.png

学会以上这些内容,结合例子,多写代码,看懂是没问题的,要写出来,还得需要一些时间。