当前位置:   article > 正文

课后练习:C语言实现Linux命令——od

od c语言

课后练习:C语言实现Linux命令——od

————————CONTENTS————————


题目详情与分析

题目:复习c文件处理内容,编写myod.c,用myod XXX实现Linux下od -tc -tx1 XXX的功能。

在Linux下使用od -tc -tx1 XXX的效果为:

1071476-20170929181802856-190030670.png

下面对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

这样一来,基本的框架就有了。伪代码大概是这个样子的:

  1. int main()
  2. {
  3. 从命令行参数读入文件;
  4. if(参数为“-tc -tx1”){
  5. while(未到达文件末尾){
  6. 将文件以字节为单位读入一个定长数组(16字节);
  7. 分别以%c和%x格式依次输出数组中的内容;
  8. }
  9. }
  10. }

根据这个流程设计程序,再调整一下格式,就能得到基本的输出了。关键代码如下:

  1. while(fgets(ch,17,file)!=NULL){
  2. for(i=0;i<16;i++)
  3. {
  4. if(ch[i]=='\0')
  5. break;
  6. printf("%x ",ch[i]);
  7. }
  8. printf("\n");
  9. for(i=0;i<16;i++)
  10. {
  11. if(ch[i]=='\0')
  12. break;
  13. putchar(' ');
  14. putchar(' ');
  15. printf("%c", ch[i]);
  16. putchar(' ');
  17. }
  18. printf("\n");
  19. }

返回目录


遇到的问题及解决

『问题一』:以上程序具有很大的局限性,只能得到od -tc -tx1 XXX这个命令的输出。如何同时实现“-tc”、“-tx1”、“-td1”、“-to1”等选项的功能呢?

『解决』:

这需要对我们输入的命令行参数进行判断,根据判断结果执行不同的操作。这样看来,应该让不同的功能由不同的函数实现,每次判断并调用即可。也有助于将来程序功能的拓展。

需要注意的是,命令行参数argv[0]为文件的地址信息,使用

  1. int main(int argc,char *argv[])
  2. {
  3. for(int i = 0; i < strlen(argv[0]); i++){
  4. printf("%c", argv[0][i]);
  5. }
  6. return 0;
  7. }

可以看到输出结果:

1071476-20170929181658465-1494630080.png

所以,输入的参数是从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()函数,完成设置。
  1. //设置光标的位置
  2. void gotoxy(int x,int y)
  3. {
  4. COORD c;
  5. c.X=x-1;
  6. c.Y=y-1;
  7. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),c);
  8. }

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下时,出现了错误提示:

1071476-20170929181818872-2063439486.png

查询资料了解到,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下)

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <windows.h>
  5. #include <conio.h>
  6. void tc(FILE *file);
  7. void tx(FILE *file);
  8. void to(FILE *file);
  9. void td(FILE *file);
  10. int main(int argc,char *argv[])
  11. {
  12. if(strcmp(argv[1], "-tc")==0){
  13. FILE *file=fopen(argv[3],"r");
  14. tc(file);
  15. }
  16. if(strcmp(argv[2], "-tx1")==0){
  17. FILE *file=fopen(argv[3],"r");
  18. tx(file);
  19. }
  20. else if(strcmp(argv[2], "-to1")==0){
  21. FILE *file=fopen(argv[3],"r");
  22. to(file);
  23. }
  24. else if(strcmp(argv[2], "-td1")==0){
  25. FILE *file=fopen(argv[3],"r");
  26. td(file);
  27. }
  28. return 0;
  29. }
  30. void tc(FILE *file)
  31. {
  32. char ch[18];
  33. int i=0,j=0;
  34. while(fgets(ch,17,file)!=NULL){
  35. printf("%07o",16*j);
  36. j++;
  37. for(i=0;i<16;i++)
  38. {
  39. if(ch[i]=='\n')
  40. { i++;
  41. putchar(' ');
  42. printf("\\n");
  43. }
  44. if(ch[i]=='\0')
  45. break;
  46. putchar(' ');
  47. putchar(' ');
  48. printf("%c", ch[i]);
  49. putchar(' ');
  50. }
  51. printf("\n\n");
  52. }
  53. printf("%07o",16*(j-1)+i);
  54. fclose(file);
  55. }
  56. void tx(FILE *file)
  57. {
  58. //Initialize the coordinates
  59. COORD coord = {0, 1};
  60. //Set the position
  61. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
  62. char ch[18];
  63. int i;
  64. while(fgets(ch,17,file)!=NULL){
  65. printf(" ");
  66. for(i=0;i<16;i++)
  67. {
  68. if(ch[i]=='\n')
  69. { i++;
  70. printf("%3x ",'\n');
  71. }
  72. if(ch[i]=='\0')
  73. break;
  74. printf("%3x ",ch[i]);
  75. }
  76. printf("\n\n");
  77. }
  78. fclose(file);
  79. }
  80. void to(FILE *file)
  81. {
  82. //Initialize the coordinates
  83. COORD coord = {0, 1};
  84. //Set the position
  85. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
  86. char ch[18];
  87. int i;
  88. while(fgets(ch,17,file)!=NULL){
  89. printf(" ");
  90. for(i=0;i<16;i++)
  91. {
  92. if(ch[i]=='\n')
  93. { i++;
  94. printf("%03o ",'\n');}
  95. if(ch[i]=='\0')
  96. break;
  97. printf("%03o ",ch[i]);
  98. }
  99. printf("\n\n");
  100. }
  101. fclose(file);
  102. }
  103. void td(FILE *file)
  104. {
  105. //Initialize the coordinates
  106. COORD coord = {0, 1};
  107. //Set the position
  108. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
  109. char ch[18];
  110. int i;
  111. while(fgets(ch,17,file)!=NULL){
  112. printf(" ");
  113. for(i=0;i<16;i++)
  114. {
  115. if(ch[i]=='\n')
  116. { i++;
  117. printf("%3d ",'\n');
  118. }
  119. if(ch[i]=='\0')
  120. break;
  121. printf("%3d ",ch[i]);
  122. }
  123. printf("\n\n");
  124. }
  125. fclose(file);
  126. }

运行结果:

1071476-20170929181843637-1396191070.png

返回目录


附2:参考资料

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

转载于:https://www.cnblogs.com/Vivian517/p/7612255.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/705515
推荐阅读
相关标签
  

闽ICP备14008679号