課后練習:C語言實現Linux命令——od


課后練習: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 ,--format=TYPE:指定輸出格式,格式包括a、c、d、f、o、u和x,各含義如下:

  • 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:參考資料

——————TO BE CONTINUED...——————


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM