課后練習:C語言實現Linux命令——od
————————CONTENTS————————
題目詳情與分析
題目:復習c文件處理內容,編寫myod.c,用myod XXX實現Linux下od -tc -tx1 XXX的功能。
在Linux下使用od -tc -tx1 XXX
的效果為:
下面對od命令進行簡單的分析:
1、功能
od命令用於將指定文件內容以八進制、十進制、十六進制、浮點格式或ASCII編碼字符方式顯示,通常用於顯示或查看文件中不能直接顯示在終端的字符。od命令系統默認的顯示方式是八進制,名稱源於Octal Dump。
常見的文件為文本文件和二進制文件。od命令主要用來查看保存在二進制文件中的值,按照指定格式解釋文件中的數據並輸出,不管是IEEE754格式的浮點數還是ASCII碼,od命令都能按照需求輸出它們的值。
2、格式
od [<選項><參數>] [<文件名>]
3、常用的命令選項
-t
- a:具名字符;
- c:ASCII字符或者反斜杠;
- d[SIZE]:十進制,正負數都包含,SIZE字節組成一個十進制整數;
- f[SIZE]:浮點,SIZE字節組成一個浮點數;
- o[SIZE]:八進制,SIZE字節組成一個八進制數;
- u[SIZE]:無符號十進制,只包含正數,SIZE字節組成一個無符號十進制整數;
- x[SIZE]:十六進制,SIZE字節為單位以十六進制輸出,即輸出時一列包含SIZE字節。
例如:od -tx testfile
表示以十六進制輸出,默認以四字節為一組(一列)顯示;od -tx1 testfile
表示以十六進制輸出,每列輸出一字節。
設計思路
題目的要求是,實現od -tc -tx1 XXX
命令,-tc表示輸出ASCII字符,-tx1表示以十六進制輸出,每組輸出一字節。
觀察到od命令每行輸出16個字節,所以需要在讀取文件的過程中加一個循環,每讀取16個字節后輸出並換行。-tc用格式化輸出%c
,-tx用格式化輸出%x
。
這樣一來,基本的框架就有了。偽代碼大概是這個樣子的:
int main()
{
從命令行參數讀入文件;
if(參數為“-tc -tx1”){
while(未到達文件末尾){
將文件以字節為單位讀入一個定長數組(16字節);
分別以%c和%x格式依次輸出數組中的內容;
}
}
}
根據這個流程設計程序,再調整一下格式,就能得到基本的輸出了。關鍵代碼如下:
while(fgets(ch,17,file)!=NULL){
for(i=0;i<16;i++)
{
if(ch[i]=='\0')
break;
printf("%x ",ch[i]);
}
printf("\n");
for(i=0;i<16;i++)
{
if(ch[i]=='\0')
break;
putchar(' ');
putchar(' ');
printf("%c", ch[i]);
putchar(' ');
}
printf("\n");
}
遇到的問題及解決
『問題一』:以上程序具有很大的局限性,只能得到od -tc -tx1 XXX
這個命令的輸出。如何同時實現“-tc”、“-tx1”、“-td1”、“-to1”等選項的功能呢?
『解決』:
這需要對我們輸入的命令行參數進行判斷,根據判斷結果執行不同的操作。這樣看來,應該讓不同的功能由不同的函數實現,每次判斷並調用即可。也有助於將來程序功能的拓展。
需要注意的是,命令行參數argv[0]為文件的地址信息,使用
int main(int argc,char *argv[])
{
for(int i = 0; i < strlen(argv[0]); i++){
printf("%c", argv[0][i]);
}
return 0;
}
可以看到輸出結果:
所以,輸入的參數是從argv[1]開始的。
由此,可以拓展“-tc”、“-tx1”、“-td1”、“-to1”等功能,只是最后的格式化輸出有所區別。
- “-tc”:printf("%c", ch[i]);
- “-tx1”:printf("%x ",ch[i]);
- “-td1”:printf("%d ",ch[i]);
- “-to1”:printf("%o ",ch[i]);
『問題二』:為了實現功能,以上分別將–tx1、–td1、–to1與–tc等寫成了不同的函數。比如,如果檢測到命令行輸入了–tc,就先在控制台打印全部ASCII字符。但注意到Linux命令是一行ASCII字符,一行進制相間輸出的,所以如果與此同時檢測到傳入了–tx1參數,就要移動光標至(0,1),再調用輸出進制的函數,將其全部輸出。如何做到呢?
『解決』:
查閱了相關資料,了解到可以利用 windows.h
定義的 SetConsoleCursorPosition()
來實現對光標的控制。具體方法為:
- ①定義一個COORD類型的結構體;
- ②設置結構體中x和y的值,即光標的位置;
- ③調用SetConsoleCursorPosition()函數,完成設置。
//設置光標的位置
void gotoxy(int x,int y)
{
COORD c;
c.X=x-1;
c.Y=y-1;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),c);
}
C語言實現控制台中光標隨意移動和C語言編程——控制台程序光標控制兩篇博客中有詳盡的介紹,可以實現一個類似於終端的命令行程序。
『問題三』:當輸入的第一個參數為-tc
時,if(argv[1] == "-tc"))
始終判斷為假。
『解決』:第一次寫的時候,if(argv[1] == "-tc"))
始終判斷為假,打印了argv[1]
的值也的確是"-tc"
。
這是因為,字符串比較需要用專門的庫函數strcmp(str1,str2)
,如果兩個字符串完全相同,則返回值為0。以此來進行判斷。
既然遇到這個問題,在這里就順便復習一下strcmp()函數的特性和用法。string.h庫對函數的說明如下:
函數原型:
int strcmp(const char *s1, const char *s2);
描述:比較s1和s2指向的兩個字符串。如果完全匹配,則兩字符串相同,否則比較首次出現不匹配的字符對。通過字符編碼值比較字符。如果兩個字符串相同,函數返回0;如果第一個字符串小於第二個字符串,函數返回小於0的值;如果第一個字符串大於第二個字符串,函數返回大於0的值。
『問題四』:以上輸出與linux下od的輸出還有一些細微的差別,比如缺少每行最前面的“0000020”等計數標識;上一行的ASCII字符與下一行的進制沒有對齊等等,如何調整格式,嚴格仿照linux下的形式輸出呢?
『解決』:
- 如何輸出“0000020”?
- 通過觀察發現,每行開頭這串數字為八進制,數值為在本行之前的字符數。所以,只需在prinf()函數中格式化輸出printf("%07o",參數)即可。
- 如何使同一個字符的ASCII字符與對應的進制上下對齊?
- printf()的修飾符中,數字表示最小字段寬度。如果該字段不能容納待打印的數字或字符串,系統會用更寬的字段。所以,例如printf("4d%",參數)即可打印寬度為4的十進制數。
- 發現程序無法顯示“\n”的ASCII字符,但linux的od命令可以,怎么修改?
- 對讀取的字符進行判斷,如遇到“\n”,手動輸出。要注意使用轉義字符,即printf("\n")。
待實現的設想與思考
『設想一』:
由於虛擬機出了點問題,還在嘗試修復,所以這個程序起初是在Windows下寫的。當試圖將此程序從Windows下移植到Linux下時,出現了錯誤提示:
查詢資料了解到,windows.h包含windows下的所有API函數、常量、結構體的聲明等等,Ubuntu下沒有windows內核dll的支持,無法移植。那么Ubuntu有沒有什么功能類似的函數呢?
『思考』:
在Linux下curses函數庫和關於curse.h終端圖形庫的學習兩篇博客中學習到,curses.h函數庫的int move(int new_y, int new_x);
可以實現光標位置的移動,功能類似於windows.h。
除此之外,curses函數庫還可以實現清除屏幕、移動和更新窗口、彩色顯示等等功能,目前正在參考婁老師分享的《UNIX.Linux下curses庫開發指南》進行學習。
學習反思與感悟
在用C語言模擬od命令時,起初想法很簡單,僅僅完成od -tc -tx1 XXX的功能即可。實現之后,又將目光放在了功能拓展上,因為畢竟od的選項不同,得到的結果也不一樣。除了-tx1,其余如-td1、-to1等選項的功能如何一並實現呢?所以我想到了將不同的功能封裝成不同的函數,判斷並調用即可。
但實現過程中又遇到了很多問題:注意到Linux命令的執行結果是ASCII字符與進制相間輸出的,如果檢測到輸入的參數為-tc和-tx1,系統先調用-tc對應的函數將ASCII字符全部打印,那么再打印十六進制時,就需要將光標移動到開頭,再調用-tx1對應的函數。
為了解決這個問題,我找到了windows.h和conio.h庫函數,實現了我的設想;但Linux下不支持該庫函數,所以又在Ubuntu安裝了curses函數庫。在安裝curses函數庫的過程中,也出現了各種錯誤提示,例如:Temporary failure resolving 'us.archive.ubuntu.com'
,即暫時不能解析域名“us.archive.ubuntu.com”;E: 無法獲得鎖 /var/lib/apt/lists/lock - open (11 資源臨時不可用)
之類的問題,不過最終都順利解決了。目前還在修改調試程序,爭取在Linux下運行成功。
本來挺簡單的問題,卻在實現的過程中頻遇坎坷......貌似我的確把問題復雜化了(無奈.jpg)。不管怎樣,雖然探索的過程很艱辛,但收獲不小。感謝婁老師的建議!接下來會繼續完善程序的~
附1:myod.c「1.0版本」(Windows下)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <conio.h>
void tc(FILE *file);
void tx(FILE *file);
void to(FILE *file);
void td(FILE *file);
int main(int argc,char *argv[])
{
if(strcmp(argv[1], "-tc")==0){
FILE *file=fopen(argv[3],"r");
tc(file);
}
if(strcmp(argv[2], "-tx1")==0){
FILE *file=fopen(argv[3],"r");
tx(file);
}
else if(strcmp(argv[2], "-to1")==0){
FILE *file=fopen(argv[3],"r");
to(file);
}
else if(strcmp(argv[2], "-td1")==0){
FILE *file=fopen(argv[3],"r");
td(file);
}
return 0;
}
void tc(FILE *file)
{
char ch[18];
int i=0,j=0;
while(fgets(ch,17,file)!=NULL){
printf("%07o",16*j);
j++;
for(i=0;i<16;i++)
{
if(ch[i]=='\n')
{ i++;
putchar(' ');
printf("\\n");
}
if(ch[i]=='\0')
break;
putchar(' ');
putchar(' ');
printf("%c", ch[i]);
putchar(' ');
}
printf("\n\n");
}
printf("%07o",16*(j-1)+i);
fclose(file);
}
void tx(FILE *file)
{
//Initialize the coordinates
COORD coord = {0, 1};
//Set the position
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
char ch[18];
int i;
while(fgets(ch,17,file)!=NULL){
printf(" ");
for(i=0;i<16;i++)
{
if(ch[i]=='\n')
{ i++;
printf("%3x ",'\n');
}
if(ch[i]=='\0')
break;
printf("%3x ",ch[i]);
}
printf("\n\n");
}
fclose(file);
}
void to(FILE *file)
{
//Initialize the coordinates
COORD coord = {0, 1};
//Set the position
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
char ch[18];
int i;
while(fgets(ch,17,file)!=NULL){
printf(" ");
for(i=0;i<16;i++)
{
if(ch[i]=='\n')
{ i++;
printf("%03o ",'\n');}
if(ch[i]=='\0')
break;
printf("%03o ",ch[i]);
}
printf("\n\n");
}
fclose(file);
}
void td(FILE *file)
{
//Initialize the coordinates
COORD coord = {0, 1};
//Set the position
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
char ch[18];
int i;
while(fgets(ch,17,file)!=NULL){
printf(" ");
for(i=0;i<16;i++)
{
if(ch[i]=='\n')
{ i++;
printf("%3d ",'\n');
}
if(ch[i]=='\0')
break;
printf("%3d ",ch[i]);
}
printf("\n\n");
}
fclose(file);
}
運行結果:
附2:參考資料
- Linux命令(2)——od命令
- Linux下curses函數庫
- C語言實現控制台中光標隨意移動
- C語言編程——控制台程序光標控制
- E: 無法獲得鎖 /var/lib/apt/lists/lock - open (11: 資源暫時不可用)
- 關於curse.h終端圖形庫的學習
- Ubuntu apt-get install 問題: Could not resolve 'cn.archive.ubuntu.com'
- 文件操作 及文件指針移動 rewind ftell