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(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
這裏介紹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偵錯工具很強大,但其工作原理仍遵循「分析現象->假設錯誤原因->產生新現象->驗證假設」這一基本思想。透過現象深入分析錯誤原因,針對假設的原因設計驗證方法等,都需要嚴密的分析和思考,因此切不可過於依賴工具,而忽視了嚴謹思維的重要性。
配套資料(+企鵝:49.89.138.68)