Linux程式設計:linux常用開發工具

2020-08-13 15:20:58

Vi編輯器

vi編輯器是Linux系統下最基本的編輯器,工作在字元模式下,由於不使用圖形介面,vi的工作效率非常高,且它在系統和服務管理中的功能是帶圖形介面的編輯器無法比擬的。vi編輯器共有三種工作模式,分別是:命令模式(command mode)、插入模式(insert mode)和底行模式(last line mode),這三種模式間可進行轉換,轉換方式如圖1所示。

 

圖1 vi編輯器模式轉換示意圖

在學習這三種模式之前,我們先來學習使用Vi編輯器開啓檔案的方法。

案例:將目錄etc下的passwd檔案複製到itheima使用者的家目錄中,將副本命名爲passwd,使用vi編輯器開啓家目錄下的passwd檔案。

[itheima@localhost ~]$ cp /etc/passwd passwd
[itheima@localhost ~]$ vi passwd

執行這兩條命令之後,就在Vi編輯器中打開了passwd檔案的副本。

下面 下麪,我們來分別介紹Vi編輯器的三種模式和每種模式對應的常用操作與命令。

1、命令模式

使用Vi編輯器開啓檔案後,預設進入命令模式。在該模式下,可通過鍵盤控制遊標的移動、文字內容的複製、貼上、刪除等。

(1)遊標移動

在命令模式中,遊標的移動可分爲6個常用的級別,分別爲:字元級、行級、單詞級、段落級、螢幕級和文件級。各個級別中的相關按鍵及其含義如表1所示。

表1 遊標移動操作

級別 操作符 說明
字元級 「左鍵」或字母「h」 使遊標向字元的左邊移動
「右鍵」或字母「l」 使遊標向字元的右邊移動  
行級 「上鍵」或字母「k」 使遊標移動到上一行
「下鍵」或字母「j」 使遊標移動到下一行  
符號「$」 使遊標移動到當前行尾  
數位「0」 使遊標移動到當前行首  
單詞級 字母「w」 使遊標移動到下一個單詞的首字母
字母「e」 使遊標移動到本單詞的尾字母  
字母「b」 使遊標移動到本單詞的首字母  
段落級 符號「}」 使遊標移至段落結尾
符號「{」 使遊標移至段落開頭  
螢幕級 字母「H」 使遊標移至螢幕首部
字母「L」 使遊標移至螢幕尾部  
文件級 字母「G」 使遊標移至文件尾行
n+G 使遊標移至文件的第n行  

(2)刪除

若需要對文件中的內容進行刪除操作,可以通過字母「x」、「dd」等來實現,相關按鍵及對應含義如表2所示。

表2 刪除操作

操作符 說明
字母「x」 刪除遊標所在的單個字元
字母「dd」 刪除遊標所在的當前行
n+dd 刪除包括遊標所在行的後邊n行內容
d+$ 刪除遊標位置到行尾的所有內容

(3)複製和貼上

對文件進行復制、貼上操作的相關按鍵及對應含義如表3所示。

表3 複製與貼上操作

操作符 說明
字母「yy」 複製遊標當前所在行
n+yy 複製包括遊標所在行後的n行內容
y+e 從遊標所在位置開始複製直到當前單詞結尾
y+$ 從遊標所在位置開始複製直到當前行結尾
y+{ 從當前段落開始的位置複製到遊標所在位置
p 將複製的內容貼上到遊標所在位置

在命令模式下,還有如下幾種常見的操作:

● 字母「u」:復原命令

● 符號「.」:重複執行上一次命令

● 字母「J」:合併兩行內容

● r+字元:快速替換遊標所在字元

熟練掌握以上按鍵,是提高使用使用vi編輯器編輯文件的效率,讀者應儘量掌握以上按鍵,並將其應用到實際操作中。

上文已經提到,vi編輯器有三種工作模式,下面 下麪分別介紹在vi編輯器中如何從命令模式切換到另外兩種模式。

(1)命令模式與插入模式間的切換

一般情況下,使用者可以使用按鍵「i」,直接進入編輯模式,此時內容與遊標的位置和命令模式相同。另外還有其餘多種按鍵,可以不同的形式切換到編輯模式,下面 下麪通過表4對其餘按鍵逐一進行講解。

表4 切換至編輯模式

操作符 說明
字母「a」 遊標向後移動一位進入編輯模式
字母「s」 刪除遊標所在字母進入編輯模式
字母「o」 在當前行之下新起一行進入編輯模式
字母「A」 遊標移動到當前行末尾進入編輯模式
字母「I」 遊標移動到當前行行首進入編輯模式
字母「S」 刪除遊標所在行進入編輯模式
字母「O」 在當前行之上新起一行進入編輯模式

另使用「ESC」鍵可從插入模式返回命令模式。

(2)命令模式與底行模式間的切換

在命令模式下使用輸入「:」或「/」按鍵,可進入底行模式。若想從底行模式返回到命令模式,可以使用「ESC」鍵。若底行不爲空,可以連按兩次「ESC」鍵,清空底行,並返回命令模式。

2、 插入模式

只有在插入模式下,才能 纔能對檔案內容進行修改操作,此模式下的操作與Windows操作系統中記事本的操作類似。插入模式與底行模式之間不能直接轉換。

3、 底行模式

底行模式可以對檔案進行儲存,也可進行查詢、退出編輯器等操作。下面 下麪將對底行模式中常用的一些操作進行講解。

(1):set nu。設定行號,僅對本次操作有效,當重新開啓文字時,若需要行號,要重新設定。

(2):set nonu。取消行號,僅對本次操作有效。

(3):n。使遊標移動到第n行。

(4):/xx。在檔案中查詢「xx」,若查詢結果不爲空,可以使用「n」查詢下一個,使用「N」查詢上一個。

(5)尾行模式下還可以進行內容替換,其操作符和功能如表5所示。

表5 內容替換

操作符 說明
:s/被替換內容/替換內容/ 替換遊標所在行的第一個目標
:s/被替換內容/替換內容/g 替換遊標所在行的全部目標
:%s/被替換內容/替換內容/g 替換整個文件中的全部目標
:%s/被替換內容/替換內容/gc 替換整個文件中的全部目標,且每替換一個內容都有相應的提示

(6)操作完畢後,如要儲存檔案或退出編輯器,可先使用「ESC」進入底行模式,再使用表6中的按鍵完成所需操作。

表6 儲存與退出

操作符 說明
:q 退出vi編輯器
:w 儲存編輯後的內容
:wq 儲存並退出vi編輯器
:q! 強行退出vi編輯器,不儲存對檔案的修改
:w! 對於沒有修改許可權的使用者強行儲存對檔案的修改,並且修改後檔案的所有者和所屬組都有相應的變化
:wq! 強行儲存檔案並退出vi編輯器

Vi編輯器的設定

講解vi編輯器的常用操作時曾提到,在底行模式中對vi編輯器進行的設定,只對本次操作有效,若重新使用vi編輯器開啓檔案,會發現在上一次操作中所做的設定全部被情況。那麼該如何長久有效的儲存vi編輯器的設定呢?

vi編輯器的設定資訊儲存在使用者家目錄的.vimrc檔案中,該檔案是一個隱藏檔案,使用「ls -al」命令可以看到。若想永久儲存vi編輯器的設定,需要在該檔案中進行定義。

首先使用vi編輯器開啓此檔案:

[itheima@localhost ~]$ vi .vimrc

之後在插入模式下,將要設定的資訊寫入該檔案,再儲存退出即可。

vi編輯器中較爲常用的設定如表7所示。

表7 儲存與退出

設定 說明
set number 設定行號
set autoindent 自動對齊
set smartindent 智慧對齊
set showmatch 括號匹配
set tabstop=4 使用tab鍵時爲4個空格
set mouse=a 滑鼠支援
set cindent 使用C語言格式對齊

GCC編譯器

GCC(GUN Compiler Collection,GUN編譯器套件)是由GUN開發的程式語言編譯器,其初衷是實現基於GUN操作系統的編譯。GCC編譯器由原來的只能處理C語言檔案,擴充套件爲支援Fortean、Pascal、Java、Objective-C等多種程式語言,現已被大多數類Unix操作系統採納爲標準的編譯器,GCC同樣適用於Windows操作系統。

1、 GCC編譯流程

GCC的編譯過程分爲四個步驟,分別是預處理(Perprocess)、編譯(Compilation)、彙編(Assembly)和鏈接(Linking)。此處將以名爲「hello.c」的C語言檔案爲例,對GCC的編譯流程進行分析講解。hello.c檔案中的程式碼具體如下:

#include <stdio.h>
int main()
{
  printf(「hello itheima!\n」);
  return 0;
}

(1)預處理

預處理階段主要處理原始碼中以「#」開頭的預編譯指令和一些註釋資訊,處理規則如下:

● 刪除程式碼中的「#define」,展開所有宏定義;

● 處理條件編譯指令,如#if、#ifdef、#undef等;

● 將由「#include」包含的檔案插入到預編譯指令對應的位置,若檔案中包含其它檔案,同樣進行替換;

● 刪除程式碼中的註釋;

● 新增行號和檔案標識;

● 保留#pragma編譯器指令。

預處理所用選項爲「-E」,對hello.c檔案進行預處理的命令如下:

[itheima@localhost ~]$ gcc **–E** hello.c –o hello.i

其中「-o」選項的功能是指定生成檔案的檔名,以下各步驟中選項「-o」的功能與此處相同。經過此步驟之後,會生成一個名爲hello.i的檔案,此時若檢視hello.i檔案中的內容,會發現「#include <stdio.h>」一行被標頭檔案stdio.h的內容替換。若原始檔中有宏定義、註釋、條件編譯指令等資訊,編譯器也會按照上文所述處理規則對其進行處理。

(2)編譯

在編譯階段,GCC會對經過預處理的檔案進行語法、詞法和語意分析,確定程式碼實際要做的工作,若檢查無誤,則生成相應的彙編程式碼檔案。編譯所用選項爲「-S」,操作方法如下:

[itheima@localhost ~]$ gcc **–S** hello.i –o hello.s

經過此步驟之後,會生成一個名爲hello.s的檔案。

(3)彙編

該過程將編譯後生成的彙編程式碼轉換爲機器可以執行的命令,即二進制指令,每一個彙編語句幾乎都會對應一條機器指令。彙編所用選項爲「-o」,操作方法如下:

[itheima@localhost ~]$ gcc **–c** hello.s –o hello.o

此時hello.o檔案中的內容爲機器碼。

(4)鏈接

鏈接的過程是組裝各個目標檔案的過程,在這個過程中會解決符號依賴和庫依賴關係,最終生成可執行檔案。操作方法如下:

[itheima@localhost ~]$ gcc hello.o –o hello

經過以上4個步驟,最終生成了可執行檔案hello。

GCC可以將單個檔案編譯成可執行檔案,也可以編譯鏈接多個檔案,生成可執行檔案,一般情況下我們不關心編譯過程,只關心編譯結果,此處只是通過編譯步驟中對應的命令講解了編譯的流程,下面 下麪將結合範例,就單檔案編譯和多檔案編譯分別做出講解。

2、 單檔案編譯

以上文給出的hello.c檔案爲例進行單檔案編譯,將該檔案編譯爲可執行檔案最簡單的方法,是在命令列鍵入如下命令:

[itheima@localhost ~]$ gcc hello.c

編譯的過程中,GCC編譯器會先將原始檔編譯爲目標檔案,再將目標檔案鏈接到可執行檔案,之後刪除目標檔案。編譯完成之後,當前目錄會生成一個預設名爲「a.out」的目標檔案。此時在命令列中鍵入可執行檔名,就會執行該程式並列印執行結果。檔案執行命令及執行結果如下:

[itheima@localhost ~]$ ./a.out
hello itheima!

使用gcc命令生成的所有可執行檔案的預設名稱都是「a.out」,若想指定可執行檔案的名字,可以使用「-o」選項,假設將編譯後生成的可執行檔案命名爲「hello」,則在命令列鍵入的命令如下:

[itheima@localhost ~]$ gcc hello.c –o hello

3、 多檔案編譯

當源程式較複雜時,可以將一個源程式分別寫在多個檔案中,如此便可以獨立編譯每個檔案。下面 下麪我們將一個實現整數相加功能的程式分別寫在三個檔案中,以此來介紹GCC中多檔案編譯的方法。

假設這三個檔案分別爲: _add.h、_add.c、fc_add.c,其中的程式碼分別如下:

_add.h         //標頭檔案,加法函數宣告
int _add(int a,int b);
_add.c         //加法函數定義
#include "_add.h"
int _add(int a,int b)
{
  int c = a + b;
  return c;
}
_main.c        //主函數檔案
#include <stdio.h>
#include "_add.h"
int main()
{
  int a = 10; 
  int b = 5;
  int c = _add(a,b);
  printf("c = %d\n",c);
  return 0;
}

則使用gcc編譯多個檔案的指令如下:

[itheima@localhost ~]$ gcc _main.c _add.c _add.h -o _main

其中_main爲生成的可執行檔案。執行檔案_main,其結果如下:

[itheima@localhost ~]$ ./_main 
c = 15

GDB偵錯

這裏介紹Linux系統中使用的一種非常強大的偵錯工具——gdb。gdb可以逐條執行程式、操控程式的執行,並且隨時可以檢視程式中所有的內部狀態,如各變數的值、傳給函數的參數、當前執行的語句位置等,藉此判斷程式碼中的邏輯錯誤。掌握了gdb的使用方法,Linux使用者將能使用更多靈活的方式去偵錯程式。

下面 下麪將結合幾個初學者易犯的錯誤範例,來講解如何使用gdb偵錯程式。

案例37:本案例的程式碼實現一個針對陣列的排序程式,其中包含:初始化陣列、陣列排序和陣列列印這三部分功能。

 1    #include <stdio.h>
 2    #include <stdlib.h>
 3    #include <time.h>
 4    #define N 5                                    //定義陣列長度
 5    void init_arr(int *arr, int len)            //生成亂數陣列
 6    {
 7        int i = 0; 
 8        for (i = 0; i < len; i++) {
 9            arr[i] = rand() % 20 + 1;
 10        }
 11    }
 12    void select_sort(int *arr, int len)        //選擇排序演算法
 13    {
 14        int i, j, k, tmp; 
 15        for (i = 0; i< len-1; i++) {
 16            k = j;
 17            for (j = i+1; j < len; j++) {
 18                if (arr[k] > arr[j]) 
 19                    k = j;
 20            }
 21            if (i != k) {
 22                tmp = arr[i];
 23                arr[i] = arr[k];
 24                arr[k] = tmp;
 25            }
 26        }
 27    }
 28    void print_arr(int *arr, int len)            //列印陣列
 29    {
 30        int i; 
 31        for (i = 0; i < len; i++)
 32            printf("arr[%d] = %d\n", i, arr[i]);
 33    }
 34    int main(void)
 35    {
 36        int arr[N];
 37        srand(time(NULL));                        //生成亂數種子
 38        init_arr(arr, N);                        //生成陣列
 39        print_arr(arr, N);                        //列印原始陣列
 40        select_sort(arr, N);                    //陣列排序
 41        printf("----------- after sort ------------\n");
 42        print_arr(arr, N);                        //列印排序後的陣列
 43        return 87;
 44    }

執行此段程式,結果如下:

arr[0] = 10
arr[1] = 19
arr[2] = 20
arr[3] = 20
arr[4] = 12
----------- after sort ------------
arr[0] = 10
arr[1] = 12
arr[2] = 19
arr[3] = 20
arr[4] = 20

程式順利執行,但按照預期,列印結果「after sort」所在行之後的部分,應爲一個有序序列,輸出結果顯然並非如此,這說明程式的邏輯出現了錯誤。此時我們可以啓用gdb偵錯工具,在程式碼中設定端點,逐步執行程式,再根據程式中變數值的變化,判斷錯誤原因。

在啓用gdb偵錯工具之前,首先需要在待偵錯的程式程式碼中加入偵錯資訊。實現此操作的方法如下:

[itheima@localhost ~]$ gcc gdbtest.c –o app –g

即在gcc編譯的基礎上,新增選項「-g」,此時將會生成一個帶有偵錯資訊的可執行檔案app。輸出直接編譯產生的檔案gdbtset和帶有偵錯資訊的可執行檔案app的詳細資訊,會發現檔案app要比gdbtest大,多出的內容將用於程式偵錯。

之後便可使用gdb偵錯此段程式,使用的命令如下:

(gdb) gdb app

執行該命令之後,系統會輸出gdb的版本號及其它相關資訊,此時的命令提示由「[itheima@localhost ~]$」變爲「(gdb)」。

與C語言等的偵錯步驟相同,在偵錯之前,需要先在程式碼中設定斷點,因此應先列出程式程式碼。列出程式程式碼的命令如下:

list 行號

該命令表示,從指定行開始,列出所有程式碼。使用此命令後,程式中的前10行程式碼將被顯示在螢幕上,此時可以使用「l」繼續列出程式碼。

根據之前程式輸出的結果,可以粗略判斷出有錯的程式碼應在排序函數中,因此可以在排序函數中設定斷點。設定斷點的命令如下:

b 行號

該命令表示在對應行設定一個斷點。

假設想要檢視程式碼中已經設定的斷點,可以使用「info」命令,該命令的格式如下:

info b

執行此命令後,對應程式碼中設定的斷點資訊將會顯示在螢幕上。此時程式中已設定斷點的資訊如下:

Num   Type      Disp Enb Address      What
1    breakpoint   keep y  0x000000000040064f in select_sort 
​                          at gdbtest.c:20

該資訊中主要包括:斷點編號Num、斷點狀態Enb、斷點地址Address以及斷點在程式中所處的位置。

在設定斷點時還可以指定條件,例如若想在i=5時設定斷點,可以使用以下命令:

b 22 if i = 5

該命令表示當「i=5」時,在程式碼第22行設定一個斷點,此時使用「info b」命令檢視斷點資訊,顯示結果如下:

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040064f in select_sort 
                                                   at gdbtest.c:20
2       breakpoint     keep y   0x0000000000400661 in select_sort 
                                                   at gdbtest.c:22
    stop only if i = 5

此時在第二個斷點的相關資訊之後,顯示「stop only if i = 5」,表示當程式執行到i=5時,斷點纔會生效。

在斷點的資訊中,有一項爲「Enb」,當此項顯示爲「y」時,表示斷點生效。此項可通過命令「disable」設定爲「n」,表示斷點無效,其使用格式如下:

disable Num

其中的參數「Num」表示斷點的編號。若要將斷點的「Enb」狀態重新修改爲「y」,可以使用命令「enable」。

若在偵錯的過程中,發現設定的某些斷點意義不大,可以將斷點刪除。刪除斷點的命令爲「delete」,其使用格式如下:

delete Num

斷點設定好之後,便可以再次執行程式,檢視偵錯資訊了。在gdb中執行程式的命令爲「run」,輸入此命令,程式將開始執行。

在遇到斷點時,程式會停止,此時可以使用命令「p」檢視當前狀態下程式碼中變數的值,該命令的使用方法如下:

p 變數名

若希望程式繼續向下執行,可以使用命令「s」,s即「step」,表示單步執行。使用命令s則會進入C函數內部,因C函數作爲標準函數庫,基本都不會出現錯誤,此時可以使用命令「n」跳過庫函數檢查。另外使用命令「finish」,也可以跳出當前函數,繼續往下執行。

使用命令p時,變數的值僅會輸出一次,若想在執行的過程中跟蹤某個變數的值,使用這種方法顯然比較麻煩。gdb中還提供了另外一個命令「display」,該命令的用法與p相同,但是程式每往下執行一句,需要跟蹤的變數的值就會被輸出一次。使用命令「undisplay」可以取消跟蹤。

分析程式,發現在select_sort函數中需要跟蹤的變數只有3個,即:i、j、k。使用display命令跟蹤這三個變數。發現在程式執行的過程中,變數k的值一直爲1,而正常情況下k應儲存外層回圈i的值,因此可以判斷k的賦值應該有問題。觀察程式碼,發現程式碼19行應爲「k=i」。

若想結束偵錯,可以使用「continue」結束當前斷點偵錯,再使用「quit」命令退出調試,回到命令視窗。

雖然gdb偵錯工具很強大,但其工作原理仍遵循「分析現象->假設錯誤原因->產生新現象->驗證假設」這一基本思想。透過現象深入分析錯誤原因,針對假設的原因設計驗證方法等,都需要嚴密的分析和思考,因此切不可過於依賴工具,而忽視了嚴謹思維的重要性。

Linux從入門到精通

配套資料(+企鵝:49.89.138.68)