shell 和 Makefile

本文介绍的是 shell 和 Makefile 的常用命令

本文介绍的是 shell 和 Makefile 的常用命令

Shell 和 Makefile

shell介绍

shell是操作系统的终端命令行

  • shell就是壳的意思,在计算机中经常提到shell是用户操作接口的意思,可以说它是人机交互的一种方式。。
  • 因为计算机程序本身很复杂,里面的实现和外面的调用必须分开。接口本身就是对内部复杂的实现的一种封装,外部只需要通过接口就可以很容易的实现效果,但是却不用理会内部实现的复杂性和原理。
  • 我们可以使用shell和操作系统、uboot等软件系统进行交互。具体来说就是我们通过shell给软件系统输入命令然后回车执行,执行完成后又会回到shell命令行可以再次输入命令执行。
  • 编写shell脚本时使用的语言就是shell语言,又叫脚本语言。

常用shell语言

  1. 在linux下常用的脚本语言其实就是bash、sh;
  2. perl、python这样的高级shell脚本语言,常用在网络管理配置等领域,系统运维人员一般要学习这些。
  3. 脚本语言一般在嵌入式中应用,主要是用来做配置。(一个复杂的嵌入式程序都是可配置的,配置过程就是用脚本语言来实现的)自然不会使用过于复杂的脚本语言特性,因此只需要针对性的学习即可。
  4. linux下最常用的脚本就是bash,我们学习也是以bash为主。

shell脚本的运行机制:解释运行

  • C/C++这种编写过程是:编写出源代码(源代码是不能直接运行的)然后编译链接形成可执行二进制程序,然后才能运行;而脚本程序不同,脚本程序编写好后源代码即可直接运行(没有编译链接过程)
  • shell程序是解释运行的,所谓解释运行就是说当我们执行一个shell程序时,shell解析器会逐行的解释shell程序代码,然后一行一行的去运行。(顺序结构)
  • CPU实际只认识二进制代码,根本不认识源代码。脚本程序源代码其实也不是二进制代码,CPU也不认识,也不能直接执行。只不过脚本程序的编译链接过程不是以脚本程序源代码为单位进行的,而是在脚本运行过程中逐行的解释执行时才去完成脚本程序源代码转成二进制的过程(不一定是编译链接,因为这行脚本程序可能早就编译连接好了,这里我们只是调用它)的。

动手写第一个shell

编辑器、编译器、运行方法(脚本的3种执行方法)

  1. shell程序是文本格式的,只要是文本编辑器都可以。但是因为我们的shell是要在linux系统下运行的,所以换行符必须是\n,而windows下的换行符是\r\n,因此windows中的编辑器写的shell不能在linux下运行。所以我们整个课程都是在linux下使用vi编辑器(实际上是vim)进行编写调试的。
  2. shell程序运行的运行的方法
    • ./xx.sh 要求shell程序必须具有可执行权限。chmod a+x xx.sh 来添加可执行权限
    • source xx.sh source是linux的一个命令,这个命令就是用来执行脚本程序的。这样运行不需要脚本具有可执行权限
    • bash xx.sh bash是一个脚本程序解释器,本质上是一个可执行程序。这样执行相当于我们执行了bash程序,然后把xx.sh作为argv[1]传给他运行

hello world程序和解释

  • shell程序的第一行一般都是: #!/bin/sh
    • 这行话以 #! 开始,后面加上一个 pathname,这行话的意思就是指定shell程序执行时被哪个解释器解释执行。所以我们这里写上 /bin/sh 意思就是这个shell将来被当前机器中/bin目录下的sh可执行程序执行。
    • 可以将第一行写为:#!/bin/bash 来指定使用bash执行该脚本。
  • 脚本中的注释使用 ## 开头的行是注释行。如果有多行需要注释,每行前面都要加 #。( #就相当于是C语言中的 //

shell编程

shell中的变量定义和引用

  • 变量定义和初始化。
    • shell是弱类型语言(语言中的变量如果有明确的类型则属于强类型语言;变量没有明确类型就是弱类型语言),和C语言不同。在shell编程中定义变量不需要制定类型,也没有类型这个概念。
    • 变量定义时可以初始化,使用 = 进行初始化赋值。在shell中赋值的 = 两边是不能有空格的。
    • 变量赋值,变量定义后可以再次赋值,新的赋值会覆盖老的赋值
  • 变量引用。
    • shell中引用一个变量必须使用 $ 符号,$ 符号就是变量解引用符号。
    • 注意:$ 符号后面跟一个字符串,这个字符串就会被当作变量去解析。如果这个字符串本身没有定义,执行时并不会报错,而是把这个变量解析为空。也就是说在shell中没有被定义的变量其实就相当于是一个定义并赋值为空的变量。
    • 注意:变量引用的时候可以$var,也可以${var}。这两种的区别是在某些情况下只能用${var}而不能简单的$var,比如 ${var}you 如果写成 $varyou 就是一个 varyou 变量

shell中无引用、单引号和双引号的区别

  • 单引号:所见即所得,即输出时会将单引号内的所有内容都原样输出,或者描述为单引号里面看到的什么就输出什么,称为强引用

  • 双引号:输出双引号的所有内容;如果内容中有命令(要反引)、变量、特殊转义,会先把变量、命令、转义字符解析出结果,然后在输出最终内容,这称为弱引用

  • 反引号:一般用于命令,执行的时候命令会被执行,相当于 $(),赋值和输出都要用反引号引起来。

  • 双引号中

    • $ 加变量名可以取变量的值: "${var}me"
    • \$ 表示 $ 的字面值,输出 $ 符号
    • \` 表示 ` 的字面值
    • \" 表示 " 的字面值
    • \\ 表示\ 的字面值
    • 除以上情况之外,在其它字符前面的 \ 无特殊含义,只表示字面值。
    # 双引号和反引号
    [root@localhost ~]# time="`date`"
    [root@localhost ~]# echo $time
    2019年 08月 01日 星期四 08:21:15 CST
      
    # 单引号和反引号
    [root@localhost ~]# time='`date`'
    [root@localhost ~]# echo $time
    `date`
    

shell中调用linux命令

  1. 直接执行
  2. 反引号括起来执行。有时候我们在shell中调用linux命令是为了得到这个命令的返回值(结果值),这时候就适合用一对反引号来调用执行命令。PATH=`pwd`

shell中的选择分支结构

  1. 典型if语言格式

    if [表达式]; then
    	xxx
    	yyy
    	zzz
    else
    	xxx
    	ddd
    	uuu
    fi
    
  2. if的典型应用

    • -f 判断文件是否存在。注意[ -f ] 里面前后都有空格,不能省略。
    • -d 判断目录是否存在
    • "str1" = "str2" 判断字符串是否相等
    • -eq 判断数字是否相等,-ge, -le, -lt, -gt
    • -z 判断字符串是否存在且为空
      • 如果变量本身没定义也是不成立(也就是说 -z 认为没定义不等于为空)
    • -o 逻辑或

shell中的循环结构

  1. for 循环

    • 语法格式

      for 变量 in 串行
      do
         执行命令
      done
      
    • 用for循环在家目录下创建aaa1-aaa10,然后在aaa1-aaa10创建bbb1-bbb10的目录

      #!/bin/bash
      for k in $( seq 1 10 ); do
         mkdir /home/kuangl/aaa${k}
         cd /home/kuangl/aaa${k}
         for l in $( seq 1 10 ); do
         	  mkdir bbb${l}
         	  cd /home/kuangl/aaa${k}
         done
         cd ..
      done
      
    • 列出var目录下各子目录占用磁盘空间的大小

      #!/bin/bash
      DIR="/var"
      cd $DIR
      for k in $(ls $DIR); do
        [ -d $k ] && du -sh $k
      done
      
  2. while 循环

    • 语法格式

      1 while 条件测试
      2 do
      3   执行命令
      4 done
      
    • 读取文件的内容

      1 #!/bin/bash
      2 while read kuangl
      3 do
      4   echo ${kuangl}
      5 done < /home/kuangl/scripts/testfile
      
      • 使用read有标准输入读取数据,放入变量kuangl中,如果读到的数据非空,就进入循环
      • 把该行数据显示出来
      • /home/kuangl/scripts/testfile 的内容转向输入将给read读取
    • 计算 1 + 2 + …… + 10

      1 #!/bin/bash
      2 declare -i i=1  # 声明i和sum为整数型
      3 declare -i sum=0 
           
      4 whileecho的创建和追加输入文件 ((i<=10)) #i<=10,执行循环
      5 do
      6   let sum+=i
      7   let i++
      8 done  #遇到done,回到行6去执行条件测试
           
      9 echo $sum
      
  3. until循环

    • 语法格式

      1 until 条件测试
      2 do
      3  执行命令
      4 done
      
    • 计算 1 + 2 + …… + 10

      1 #!/bin/bash
      2 declare -i i=10
      3 declare -i sum=0
           
      4 until ((i>10))
      5 do
      6   let sum+=i
      7   let ++i
      8 done
           
      9 echo $sum
      

echo的创建和追加输入文件

  • 在shell中可以直接使用echo指令新建一个文件,并且将一些内容传入这个文件中。创建文件并输入内容的关键就是 >
  • 还可以使用echo指令配合追加符号 >> 向一个已经存在的文件末尾追加输入内容。

case语句

  • 语法格式

    case 变量 in 
    值1 )
        执行动作1
        ;;
    值2 )
        执行动作2
        ;;
    值3 )
        执行动作3
        ;;
    ....
    * )
        如果变量的值都不是以上的值,则执行此程序
        ;;
    esac
    
  • 输入选择

    echo 'Input a number:'
    read Num
    case $Num in
        1)  echo 'You select 1'
        ;; #常规的break是在每一项后面加 ;;
        2)  echo 'You select 2'
        ;;
        3)  echo 'You select 3'
        ;;
        4|5)  echo 'You select 4 or 5'
        ;;
        *)  echo 'default'
        ;;
    esac
    

调用shell程序的传参

shell程序本身也可以在调用时传参给他,包括:

  • $# 表示调用该shell时传参的个数。($#计数时只考虑真正的参数个数)
  • $0、$1、$2····· 则依次表示传参的各个参数。
  • 对于命令:./a.out aa bb cc
    • C 语言:
      • argc = 4
      • argv[0] = ./a.out
      • argv[1]是第一个有效参数····
    • Shell 脚本 source a.sh aa bb cc
      • $# = 3
      • $0 = a.sh
      • $1 = aa
  • shell中的 $#, $1 等内置变量的值不是不可变的,而是可以被改变,被 shift 指令改变。shift指令有点像左移运算符,把我们给shell程序的传参左移了一个移出去了,原来的 $2 变成了新的$1,原来的 $# 少了1个。

Makefile基础回顾

Makefile的作用和意义

工程项目中c文件太多管理不方便,因此用Makefile来做项目管理,方便编译链接过程。uboot和linux kernel本质上都是C语言的项目,都由很多个文件组成,因此都需要通过Makefile来管理。所以要分析uboot必须对Makefile有所了解。

目标、依赖、命令

  • 目标就是我们要去 make xxx 的那个 xxx,就是我们最终要生成的东西。
  • 依赖是用来生成目录的原材料
  • 命令就是加工方法,所以 make xxx 的过程其实就是使用命令将依赖加工成目标的过程。

通配符%和Makefile自动推导(规则)

  • % 是Makefile中的通配符,代表一个或几个字母。也就是说 %.o 就代表所有以 .o 为结尾的文件。
  • 所谓自动推导其实就是Makefile的规则。当Makefile需要某一个目标时,他会把这个目标去套规则说明,一旦套上了某个规则说明,则Makefile会试图寻找这个规则中的依赖,如果能找到则会执行这个规则用依赖生成目标。

Makefile中定义和使用变量

Makefile中定义和使用变量,和shell脚本中非常相似。都没有变量类型,直接定义使用,引用变量时用 $var

伪目标(.PHONY)

  • 伪目标意思是这个目标本身不代表一个文件,执行这个目标不是为了得到某个文件或东西,而是单纯为了执行这个目标下面的命令。
  • 伪目标一般都没有依赖,因为执行伪目标就是为了执行目标下面的命令。既然一定要执行命令了那就不必加依赖,因为不加依赖意思就是无条件执行。
  • 伪目标可以直接写,不影响使用;但是有时候为了明确声明这个目标是伪目标会在伪目标的前面用 .PHONY 来明确声明它是伪目标。

Makefile的文件名

Makefile的文件名合法的一般有2个:Makefile或者makefile

Makfile中引用其他Makefile(include指令)

有时候Makefile总体比较复杂,因此分成好几个Makefile来写。然后在主Makefile中引用其他的,用include指令来引用。引用的效果也是原地展开,和C语言中的头文件包含非常相似。

命令前面的@用来静默执行

  • 在makefile的命令行中前面的@表示静默执行。
  • Makefile中默认情况下在执行一行命令前会先把这行命令给打印出来,然后再执行这行命令。

Makefile中几种变量赋值运算符

赋值运算符 说明
= =赋值的变量,在被解析时他的值取决于最后一次赋值时的值,所以你看变量引用的值时不能只往前面看,还要往后面看
:= :=来赋值的,则是就地直接解析,只用往前看即可。
?= 如果变量前面并没有赋值(没有被定义)过则执行这条赋值,如果前面已经赋值过了则本行被忽略。(实验可以看出:所谓的没有赋值过其实就是这个变量没有被定义过
+= 相当于给字符串stcat接续内容,续接的内容和原来的内容之间会自动加一个空格隔开

Makefile的环境变量

  • makefile中用export导出的就是环境变量。一般情况下要求环境变量名用大写,普通变量名用小写
  • 环境变量普通变量不同,可以这样理解:环境变量类似于整个工程中所有Makefile之间可以共享的全局变量,而普通变量只是当前本Makefile中使用的局部变量。所以要注意:定义了一个环境变量会影响到工程中别的Makefile文件,因此要小心。
  • Makefile中可能有一些环境变量可能是makefile本身自己定义的内部的环境变量或者是当前的执行环境提供的环境变量(譬如我们在make执行时给makefile传参:make CC=arm-linux-gcc,其实就是给当前Makefile传了一个环境变量CC,值是 arm-linux-gcc。我们在make时给makefile传的环境变量值优先级最高的,可以覆盖makefile中的赋值)。这就好像C语言中编译器预定义的宏__LINE__ __FUNCTION__等一样。

Makefile中使用通配符

通配符 说明
* 若干个任意字符
? 1个任意字符
[] 将[]中的字符依次去和外面的结合匹配
% 表示任意多个字符,和*很相似,但是%一般只用于规则描述中,又叫做规则通配符

Makefile的自动变量

在有些情况下文件集合中文件非常多,描述的时候很麻烦,所以我们Makefile就用一些特殊的符号来替代符合某种条件的文件集,这就形成了自动变量。

自动变量 说明
$@ 规则的目标文件名
$< 规则的依赖文件名
$^ 依赖的文件集合