1. 程式人生 > >《2.uboot和系統移植-第2部分-2.2.補基礎之shell和Makefile》

《2.uboot和系統移植-第2部分-2.2.補基礎之shell和Makefile》

《2.uboot和系統移植-第2部分-2.2.補基礎之shell和Makefile》

第一部分、章節目錄
2.2.1.shell介紹
2.2.2.動手寫第一個shell
2.2.3.shell程式設計學習1
2.2.4.shell程式設計學習2
2.2.5.shell中的迴圈結構
2.2.6.shell中其他值得關注的知識點
2.2.7.Makefile基礎回顧
2.2.8.Mafile補充學習1
2.2.9.Makefile補充學習2

第二部分、章節介紹
2.2.1.shell介紹
本節簡單介紹並引入shell,講了常用的shell語言和shell的解釋執行特性
2.2.2.動手寫第一個shell
本節帶大家動手寫第一個shell,並且對shell程式的格式做註解
2.2.3.shell程式設計學習1
本節主要學習了shell中變數的定義、賦值與引用,另外特別講了shell中字串與單引號雙引號等的含義和區別
2.2.4.shell程式設計學習2
本節首先講了shell中引用linux命令的方法,然後重點講了shell中的if語句的各種常見用法。
2.2.5.shell中的迴圈結構
本節介紹shell的兩種迴圈結構,while迴圈和for迴圈。我們在uboot的配置檔案中會用到while迴圈
2.2.6.shell中其他值得關注的知識點
本節介紹shell中剩下的幾個對我們有用的語法,如switch case結構、shell執行程式時的傳參等
2.2.7.Makefile基礎回顧
本節課回顧裸機中使用到的一些Makefile特性,主要目的是回顧和總結。
2.2.8.Mafile補充學習1
本節學習一些新的Makefile特性,其中關鍵是幾種賦值運算子的區別。
2.2.9.Makefile補充學習2
本節學習Makefile的自動變數和萬用字元規則,這些是理解常見Makefile的關鍵知識點。

第三部分、隨堂記錄
2.2.1.shell介紹
2.2.1.1、shell是作業系統的終端命令列
(1)shell可以理解為軟體系統提供給使用者操作的命令列介面,可以說它是人機互動的一種方式。
(2)我們可以使用shell和作業系統、uboot等軟體系統進行互動。具體來說就是我們通過shell給軟體系統輸入命令然後回車執行,執行完成後又會回到shell命令列可以再次輸入命令執行。
(3)上述的操作方式一般情況下工作很好,但是有缺陷。譬如我們要在linux下建立一個檔案a.c,可以touch a.c 但是如果我現在是用在linux下建立100個檔案,分別為a1.c a2.c…a100.c 如果這時候還是手工去命令列下執行命令建立也可以,但是很累。最好的做法就是把建立過程寫成一個shell指令碼程式,然後去執行這個shell指令碼程式,執行這個程式的效果和手工在命令列輸入那些命令效果一樣的。(回憶在arm裸機中安裝交叉編譯工具鏈時,建立arm-linux-xxx的符號連結時)

2.2.1.2、shell是一類程式語言
(1)編寫shell指令碼時使用的語言就是shell語言,又叫指令碼語言。
(2)shell指令碼其實是一類語言而不是一個語言。

2.2.1.3、常用shell語言:sh、bash、csh、ksh、perl、python等
(1)在linux下常用的指令碼語言其實就是bash、sh;
(2)perl、python這樣的高階shell指令碼語言,常用在網路管理配置等領域,系統運維人員一般要學習這些。
(3)指令碼語言一般在嵌入式中應用,主要是用來做配置。(一個複雜的嵌入式程式都是可配置的,配置過程就是用指令碼語言來實現的)自然不會使用過於複雜的指令碼語言特性,因此只需要針對性的學習即可。
(4)linux下最常用的指令碼就是bash,我們學習也是以bash為主。

2.2.1.4、shell指令碼的執行機制:解釋執行
(1)C語言(C++)這種編寫過程是:編寫出原始碼(原始碼是不能直接執行的)然後編譯連結形成可執行二進位制程式,然後才能執行;而指令碼程式不同,指令碼程式編寫好後原始碼即可直接執行(沒有編譯連結過程)
(2)shell程式是解釋執行的,所謂解釋執行就是說當我們執行一個shell程式時,shell解析器會逐行的解釋shell程式程式碼,然後一行一行的去執行。(順序結構)
(3)CPU實際只認識二進位制程式碼,根本不認識原始碼。指令碼程式原始碼其實也不是二進位制程式碼,CPU也不認識,也不能直接執行。只不過指令碼程式的編譯連結過程不是以指令碼程式原始碼為單位進行的,而是在指令碼執行過程中逐行的解釋執行時才去完成指令碼程式原始碼轉成二進位制的過程(不一定是編譯連結,因為這行指令碼程式可能早就編譯連線好了,這裡我們只是呼叫它)的。

2.2.2.動手寫第一個shell
2.2.2.1、編輯器、編譯器、執行方法(指令碼的3種執行方法)
(1)shell程式是文字格式的,只要是文字編輯器都可以。但是因為我們的shell是要在linux系統下執行的,所以換行符必須是’\n’,而windows下的換行符是"\r\n",因此windows中的編輯器寫的shell不能在linux下執行。所以我們整個課程都是在linux下使用vi編輯器(實際上是vim)進行編寫除錯的。
(2)編譯器 不涉及,因為shell是解釋性語言,直接編輯完就可以執行。
(3)shell程式執行的執行有多種方法,這裡介紹三種方法:
第一種:./xx.sh,和執行二進位制可執行程式方法一樣。這樣執行shell要求shell程式必須具有可執行許可權。chmod a+x xx.sh來新增可執行許可權。
第二種:source xx.sh,source是linux的一個命令,這個命令就是用來執行指令碼程式的。這樣執行不需要指令碼具有可執行許可權。
第三種:bash xx.sh,bash是一個指令碼程式直譯器,本質上是一個可執行程式。這樣執行相當於我們執行了bash程式,然後把xx.sh作為argv[1]傳給他執行。

2.2.2.2、hello world程式和解釋
(1)shell程式的第一行一般都是: #!/bin/sh 這行話以#!開始,後面加上一個pathname,這行話的意思就是指定shell程式執行時被哪個直譯器解釋執行。所以我們這裡寫上/bin/sh意思就是這個shell將來被當前機器中/bin目錄下的sh可執行程式執行。
可以將第一行寫為:#!/bin/bash來指定使用bash執行該指令碼。
注意:在ubuntu上面預設使用的直譯器sh其實不是bash,而是dash。dash是ubuntu中預設使用的指令碼直譯器。
(2)指令碼中的註釋使用#,#開頭的行是註釋行。如果有多行需要註釋,每行前面都要加#。(#就相當於是C語言中的//)
(3)shell程式的正文,由很多行shell語句構成。

2.2.2.3、shell並不神祕
(1)shell就是把以前命令列中鍵入執行的命令寫成了程式。shell其實就是為了避免反覆的在命令列下手工輸入而發明的一種把手工輸入步驟記錄下來,然後通過執行shell指令碼程式就能再次複述原來記錄的手工輸入過程的一種技術。
(2)shell編輯完可以直接執行(不需編譯)

2.2.3.shell程式設計學習1
2.2.3.1、shell中使用linux命令
(1)練習1:當前目錄下建立檔案a.txt
(2)練習2:當前目錄下建立資料夾dir,dir下建立檔案b.txt
總結:以上2個練習的目的是讓大家基本學會寫指令碼,明白指令碼程式設計其實就是把以前在命令列下輸入的命令挪到指令碼程式中去然後一次執行。

2.2.3.2、shell中的變數定義和引用
(1)變數定義和初始化。shell是弱型別語言(語言中的變數如果有明確的型別則屬於強型別語言;變數沒有明確型別就是弱型別語言),和C語言不同。在shell程式設計中定義變數不需要制定型別,也沒有型別這個概念。
(2)變數定義時可以初始化,使用=進行初始化賦值。在shell中賦值的=兩邊是不能有空格的。
注意:shell對語法非常在意,非常嚴格。很多地方空格都是必須沒有或者必須有,而且不能隨意有沒有空格。
(3)變數賦值,變數定義後可以再次賦值,新的賦值會覆蓋老的賦值。shell中並不刻意區分變數的定義和賦值,反正每個變數就是一個符號,這個符號的值就是最後一個給他賦值時的值。
(4)變數引用。shell中引用一個變數必須使用 符號, 符號就是變數解引用符號。
注意: s h e l l 符號後面跟一個字串,這個字串就會被當作變數去解析。如果這個字串本身沒有定義,執行時並不會報錯,而是把這個變數解析為空。也就是說在shell中沒有被定義的變數其實就相當於是一個定義並賦值為空的變數。 注意:變數引用的時候可以 var,也可以 v a r {var}。這兩種的區別是在某些情況下只能用 {var}而不能簡單的$var

2.2.3.3、shell中無引用、單引號和雙引號的區別
(1)shell中使用字串可以不加雙引號,直接使用。而且有空格時也可以,但是缺陷是不能輸出"或者其他轉義字元。
(2)shell中也可以使用單引號來表示字串,也是直接使用的,不能輸出轉義字元。

(3)單引號中:完全字面替換(不可包含單引號本身)
(4)雙引號中:
$加變數名可以取變數的值
反引號仍表示命令替換
$表示 的字面值 輸出 符號
`表示`的字面值
"表示"的字面值
\表示\的字面值
除以上情況之外,在其它字元前面的\無特殊含義,只表示字面值。

2.2.4.shell程式設計學習2
2.2.4.1、shell中呼叫linux命令
(1)直接執行
(2)反引號括起來執行。有時候我們在shell中呼叫linux命令是為了得到這個命令的返回值(結果值),這時候就適合用一對反引號(鍵盤上ESC按鍵下面的那個按鍵,和~在一個按鍵上)來呼叫執行命令。

2.2.4.2、shell中的選擇分支結構
(1)shell的if語言用法很多,在此只介紹常用的,其他感興趣可以自己去學
(2)典型if語言格式
if [表示式]; then
xxx
yyy
zzz
else
xxx
ddd
uuu
fi
(3)if的典型應用
判斷檔案是否存在。(-f),注意[]裡面前後都有空格,不能省略。
判斷目錄是否存在 (-d)
判斷字串是否相等(“str1” = “str2”),注意用一個等號而不是兩個
判斷數字是否相等(-eq)、大於(-gt)、小於(-lt)、大於等於(-ge)、小於等於(-le) 回憶一下在ARM裸機中講述ARM彙編條件執行時,曾經用過這些條件判斷的縮寫。(eq就是equal,gt就是greater than,lt就是less than,ge就是greater or equal,le就是less or equal)
判斷字串是否為空(-z)注意-z判斷時如果變數本身沒定義也是不成立(也就是說-z認為沒定義不等於為空)

(4)if判斷式中使用“-o”表示邏輯或
相當於C語言中在if後面的條件式中用邏輯與、邏輯或來連線2個式子,最終的if中是否成立取決於2個式子的邏輯運算結果。

(5)邏輯與&&和邏輯或||與簡寫的if表示式相結合

2.2.5.shell中的迴圈結構
2.2.5.1、for迴圈
(1)要求:能看懂、能改即可。不要求能夠完全不參考寫出來。因為畢竟嵌入式並不需要完全重新手寫shell,系統管理員(伺服器運維人員,應用層系統級管理開發的才需要完全掌握shell)

2.2.5.2、while迴圈
(1)和C語言的迴圈在邏輯上無差別
(2)要注意很多格式要求,譬如:while後面的[]兩邊都有空格,[]後面有分號分號(如果do放在一行的話),i++的寫法中有兩層括號。

2.2.5.3、echo的建立和追加輸入檔案
(1)在shell中可以直接使用echo指令新建一個檔案,並且將一些內容傳入這個檔案中。建立檔案並輸入內容的關鍵就是>。
(2)還可以使用echo指令配合追加符號>> 向一個已經存在的檔案末尾追加輸入內容。

2.2.6.shell中其他值得關注的知識點
2.2.6.1、case語句
(1)shell中的case語句和C語言中的switch case語句作用一樣,格式有差異
(2)shell中的case語句天生沒有break,也不需要break,和C語言中的switch case不同。shell中的case預設就是匹配上哪個執行哪個,不會說執行完了還去執行後面的其他case(就好像shell中的case語言預設都帶了break)。

2.2.6.2、呼叫shell程式的傳參
(1)C語言中可以通過main函式的argc和argv給程式傳參(詳情參考《4.8.3.argc、argv與main函式的傳參》)
(2)shell程式本身也可以在呼叫時傳參給他。在shell程式內部使用傳參也是使用的一些特定符號來表示的,包括:
KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲表示呼叫該shell時傳參的個…#計數時只考慮真正的引數個數)
$0、$1、$2·····則依次表示傳參的各個引數。

C語言:./a.out aa bb cc argc = 4, argv[0] = ./a.out, argv[1]是第一個有效引數····

shell:source a.sh aa bb cc $# = 3, $0是執行這個shell程式的解析程式的名字,$1是第一個有效引數的值,$2是第2個有效引數的值·····

注意:shell中的很多語法特性和C語言中是相同的,也有很多是不同的。所以大家學的越多越容易混淆(本質原因還是用的不熟悉,用的少),解決方案是:做筆記、作總結、多寫程式碼經常用

2.2.6.3、while迴圈和case語言和傳參結合
(1)shell中的break關鍵字和C語言中意義相同(都是跳出)但是用法不同。因為shell中case語句預設不用break的,因此在shell中break只用於迴圈跳出。所以當while中內嵌case語句時,case中的break是跳出外層的while迴圈的,不是用來跳出case語句的。
(2)shell中的$# $1等內建變數的值不是不可變的,而是可以被改變,被shift指令改變。shift指令有點像左移運算子,把我們給shell程式的傳參左移了一個移出去了,原來的$2變成了新的 1 1,原來的 #少了1個。

2.2.7.Makefile基礎回顧
2.2.7.1、Makefile的作用和意義
(1)工程專案中c檔案太多管理不方便,因此用Makefile來做專案管理,方便編譯連結過程。
(2)uboot和linux kernel本質上都是C語言的專案,都由很多個檔案組成,因此都需要通過Makefile來管理。所以要分析uboot必須對Makefile有所瞭解。
2.2.7.2、目標、依賴、命令
(1)目標就是我們要去make xxx的那個xxx,就是我們最終要生成的東西。
(2)依賴是用來生成目錄的原材料
(3)命令就是加工方法,所以make xxx的過程其實就是使用命令將依賴加工成目標的過程。
2.2.7.3、萬用字元%和Makefile自動推導(規則)
(1)%是Makefile中的萬用字元,代表一個或幾個字母。也就是說%.o就代表所有以.o為結尾的檔案。
(2)所謂自動推導其實就是Makefile的規則。當Makefile需要某一個目標時,他會把這個目標去套規則說明,一旦套上了某個規則說明,則Makefile會試圖尋找這個規則中的依賴,如果能找到則會執行這個規則用依賴生成目標。

2.2.7.4、Makefile中定義和使用變數
(1)Makefile中定義和使用變數,和shell指令碼中非常相似。相似是說:都沒有變數型別,直接定義使用,引用變數時用$var

2.2.7.5、偽目標(.PHONY)
(1)偽目標意思是這個目標本身不代表一個檔案,執行這個目標不是為了得到某個檔案或東西,而是單純為了執行這個目標下面的命令。
(2)偽目標一般都沒有依賴,因為執行偽目標就是為了執行目標下面的命令。既然一定要執行命令了那就不必加依賴,因為不加依賴意思就是無條件執行。
(3)偽目標可以直接寫,不影響使用;但是有時候為了明確宣告這個目標是偽目標會在偽目標的前面用.PHONY來明確宣告它是偽目標。

2.2.7.6、Makefile的檔名
(1)Makefile的檔名合法的一般有2個:Makefile或者makefile

2.2.7.7、Makfile中引用其他Makefile(include指令)
(1)有時候Makefile總體比較複雜,因此分成好幾個Makefile來寫。然後在主Makefile中引用其他的,用include指令來引用。引用的效果也是原地展開,和C語言中的標頭檔案包含非常相似。

2.2.8.Mafile補充學習1
2.2.8.1、Makefile中的註釋用#
(1)Makefile中註釋使用#,和shell一樣。

2.2.8.2、命令前面的@用來靜默執行
(1)在makefile的命令列中前面的@表示靜默執行。
(2)Makefile中預設情況下在執行一行命令前會先把這行命令給打印出來,然後再執行這行命令。
(3)如果你不想看到命令本身,只想看到命令執行就靜默執行即可。

2.2.8.3、Makefile中幾種變數賦值運算子
(1)= 最簡單的賦值
(2):= 一般也是賦值
以上這兩個大部分情況下效果是一樣的,但是有時候不一樣。
用=賦值的變數,在被解析時他的值取決於最後一次賦值時的值,所以你看變數引用的值時不能只往前面看,還要往後面看。
用:=來賦值的,則是就地直接解析,只用往前看即可。

(3)?= 如果變數前面並沒有賦值過則執行這條賦值,如果前面已經賦值過了則本行被忽略。(實驗可以看出:所謂的沒有賦值過其實就是這個變數沒有被定義過)
(4)+= 用來給一個已經賦值的變數接續賦值,意思就是把這次的值加到原來的值的後面,有點類似於strcat。(在shell makefile等檔案中,可以認為所有變數都是字串,+=就相當於給字串stcat接續內容)(注意一個細節,+=續接的內容和原來的內容之間會自動加一個空格隔開)

注意:Makefile中並不要求賦值運算子兩邊一定要有空格或者無空格,這一點比shell的格式要求要鬆一些。

2.2.8.4、Makefile的環境變數
(1)makefile中用export匯出的就是環境變數。一般情況下要求環境變數名用大寫,普通變數名用小寫。
(2)環境變數和普通變數不同,可以這樣理解:環境變數類似於整個工程中所有Makefile之間可以共享的全域性變數,而普通變數只是當前本Makefile中使用的區域性變數。所以要注意:定義了一個環境變數會影響到工程中別的Makefile檔案,因此要小心。
(3)Makefile中可能有一些環境變數可能是makefile本身自己定義的內部的環境變數或者是當前的執行環境提供的環境變數(譬如我們在make執行時給makefile傳參。make CC=arm-linux-gcc,其實就是給當前Makefile傳了一個環境變數CC,值是arm-linux-gcc。我們在make時給makefile傳的環境變數值優先順序最高的,可以覆蓋makefile中的賦值)。這就好像C語言中編譯器預定義的巨集__LINE__ __FUNCTION__等一樣。

2.2.9.Makefile補充學習2
2.2.9.1、Makefile中使用萬用字元
(1)* 若干個任意字元
(2)? 1個任意字元
(3)[] 將[]中的字元依次去和外面的結合匹配

還有個%,也是萬用字元,表示任意多個字元,和*很相似,但是%一般只用於規則描述中,又叫做規則萬用字元。

關於萬用字元,Makefile還有一些wildcard等比較複雜的萬用字元用法,具體參考《跟我一起學Makefile》即可。

2.2.9.2、Makefile的自動變數
(1)為什麼使用自動變數。在有些情況下檔案集合中檔案非常多,描述的時候很麻煩,所以我們Makefile就用一些特殊的符號來替代符合某種條件的檔案集,這就形成了自動變數。
(2)自動變數的含義:預定義的特殊意義的符號。就類似於C語言編譯器中預製的那些巨集__FILE__一樣。
(3)常見自動變數:
[email protected] 規則的目標檔名
$< 規則的依賴檔名
$^ 依賴的檔案集合