用 C# 实现文件信息统计(wc)命令行程序
软件的需求分析
程序处理用户需求的模式为:
- wc.exe [parameter][filename]
在[parameter]中,用户通过输入参数与程序交互,需实现的功能如下:
1、基本功能
- 支持 -c 统计文件字符数
- 支持 -w 统计文件单词数
- 支持 -l 统计文件总行数
2、拓展功能
- 支持 -a 返回高级选项(代码行 空行 注释行)
- 支持 -s 递归处理符合条件的文件
3、高级功能
- 支持 -x 程序以图形界面与用户交互
[filename] 是待处理文件名。
基本功能的实现
我们先来讨论基本功能的实现,在基本功能中,用户通过输入命令行的方式与程序实现交互。如 wc.exe -c -w -l D:\hello.txt 命令是实现统计 D 盘下 hello.txt 文件的字数、单词数以及总行数;而 wc.exe -l D:\hello.txt 命令是实现统计 D 盘下 hello.txt 文件的总行数;可以发现,用户每次输入的参数个数可能会是不同的,所以我们首先要做的就是从用户的输入命令中分理出命令参数和文件名。
可以用如下代码实现这一功能,在这段代码中,我们将用户输入的字符串截取成字符串数组,数组的最后一位为文件名,其他位的均为命令参数。
1 string message = ""; // 存储用户命令 2 while (message != "exit") 3 { 4 Console.Write("wc.exe "); 5 message = Console.ReadLine(); // 得到输入命令 6 string[] arrMessSplit = message.Split(' '); // 分割命令 7 int iMessLength = arrMessSplit.Length; 8 string[] sParameter = new string[iMessLength-1]; 9 // 获取命令参数数组 10 for (int i=0; i< iMessLength-1; i++) 11 { 12 sParameter[i] = arrMessSplit[i]; 13 } 14 // 获取文件名 15 string sFilename = arrMessSplit[iMessLength - 1]; 16 }
接下来,我们新建一个类 WC 来对命令进行处理,类的基本框架如下:
1 public class WC 2 { 3 private string sFilename; // 文件名 4 private string[] sParameter; // 参数数组 5 private int iCharcount; // 字符数 6 private int iWordcount; // 单词数 7 private int iLinecount; // 总行数 8 9 // 参数控制信息 10 public void Operator(string[] sParameter, string sFilename) 11 { 12 } 13 // 统计基本信息:字符数 单词数 行数 14 private void BaseCount(string filename) 15 { 16 } 17 // 打印信息 18 private void Display() 19 { 20 } 21 }
在Operator()方法中,捕捉 "-c"、"-w"、"-l" 命令,通过参数素组的设置调用不同的类方法进行处理;Display()方法用来打印输出信息; BaseCount() 方法用以统计指定文件的字符数、单词数以及总行数。
我们首先填充 Operator() 方法,此方法在参数数组中包含 "-c" 或 "-w" 或 "-l" 时调用 BaseCount() 方法实现文件基本信息的统计,调用 Display() 方法打印结果。代码如下:
1 // 控制信息 2 public void Operator(string[] sParameter, string sFilename) 3 { 4 this.sParameter = sParameter; 5 this.sFilename = sFilename; 6 7 foreach (string s in sParameter) 8 { 9 // 基本功能 10 else if (s == "-c" || s == "-w" || s == "-l") 11 { 12 break; 13 } 14 else 15 { 16 Console.WriteLine("参数 {0} 不存在", s); 17 break; 18 } 19 } 20 }
在 BaseCount() 中,通过传入的文件名对文件进行读取。并进行字符数、单词数、总行数的判断。判断规则如下
- 对文件进行逐字符的读取,每读取一个字符,则字符数加1
- 在读取到 '\n' 字符时,判定文件总行数加1
- 在读取到单词分割符时,判定文件单词数加1。单词分割符可以是空格,制表符和各类标点符号
1 // 统计基本信息:字符数 单词数 行数 2 private void BaseCount(string filename) 3 { 4 try 5 { 6 // 打开文件 7 FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); 8 StreamReader sr = new StreamReader(file); 9 int nChar; 10 int charcount = 0; 11 int wordcount = 0; 12 int linecount = 0; 13 //定义一个字符数组 14 char[] symbol = { ' ', '\t', ',', '.', '?', '!', ':', ';', '\'', '\"', '\n', '{', '}', '(', ')', '+' ,'-', 15 '*', '='}; 16 while ((nChar = sr.Read()) != -1) 17 { 18 charcount++; // 统计字符数 19 20 foreach (char c in symbol) 21 { 22 if(nChar == (int)c) 23 { 24 wordcount++; // 统计单词数 25 } 26 } 27 if (nChar == '\n') 28 { 29 linecount++; // 统计行数 30 } 31 } 32 iCharcount = charcount; 33 iWordcount = wordcount + 1; 34 iLinecount = linecount + 1; 35 sr.Close(); 36 } 37 catch (IOException ex) 38 { 39 Console.WriteLine(ex.Message); 40 return; 41 } 42 }
接下来,我们只需要将统计信息打印出来,就完成了基本功能的实现。
1 // 打印信息 2 private void Display() 3 { 4 foreach (string s in sParameter) 5 { 6 if (s == "-c") 7 { 8 Console.WriteLine("字 符 数:{0}", iCharcount); 9 } 10 else if (s == "-w") 11 { 12 Console.WriteLine("单 词 数:{0}", iWordcount); 13 } 14 else if (s == "-l") 15 { 16 Console.WriteLine("总 行 数:{0}", iLinecount); 17 } 18 } 19 Console.WriteLine(); 20 }
扩展功能的实现
在扩展功能中,用户通过输入命令行的方式与程序实现交互。并且在基本功能的基础上新增了2个命令参数;wc.exe -s -c -w D:\*.txt 实现统计 D 盘下所有文件名是txt文件的字符数与单词数的统计。wc.exe -a -c -w D:\hello.txt 实现 D 盘 hello.txt 文件 空行数、代码行数、注释行数以及字符数和单词数的统计。
在这里,我们需要扩展WC类的结构
1 public class WC 2 { 3 private string sFilename; // 文件名 4 private string[] sParameter; // 参数数组 5 private int iCharcount; // 字符数 6 private int iWordcount; // 单词数 7 private int iLinecount; // 行 数 8 private int iNullLinecount; // 空行数 9 private int iCodeLinecount; // 代码行数 10 private int iNoteLinecount; // 注释行数 11 12 // 初始化 13 public WC() 14 { 15 this.iCharcount = 0; 16 this.iWordcount = 0; 17 this.iLinecount = 0; 18 this.iNullLinecount = 0; 19 this.iCodeLinecount = 0; 20 this.iNoteLinecount = 0; 21 } 22 23 // 控制信息 24 public void Operator(string[] sParameter, string sFilename) 25 { 26 this.sParameter = sParameter; 27 this.sFilename = sFilename; 28 29 foreach (string s in sParameter) 30 { 31 // 遍历文件 32 if (s == "-s") 33 { 34 } 35 // 高级选项 36 else if (s == "-a") 37 { 38 } 39 // 基本功能 40 else if (s == "-c" || s == "-w" || s == "-l") 41 { 42 ... 43 } 44 else 45 { 46 ... 47 } 48 } 49 } 50 51 // 统计基本信息:字符数 单词数 行数 52 private void BaseCount(string filename) 53 { 54 ... 55 } 56 57 // 统计高级信息:空行数 代码行数 注释行数 58 private void SuperCount(string filename) 59 { 60 } 61 // 打印信息 62 private void Display() 63 { 64 ... 65 } 66 }
修改 Operator() 方法,让其能捕捉到 "-a" 命令,实现高级选项(代码行、空行、注释行的统计),代码如下:
1 // 高级选项 2 else if (s == "-a") 3 { 4 Console.WriteLine("文件名:{0}", sFilename); 5 SuperCount(sFilename); 6 BaseCount(sFilename); 7 Display(); 8 break; 9 }
在 SuperCount() 中,通过传入的文件名对文件进行读取。进行代码行、空行、注释行的统计。判断规则如下:
- 空行:本行全为空行或是格式控制字符,若包含代码,则只有不超过一个可显示字符,如 "{";
- 注释行:本行开头除去多余空格或格式控制字符,以 ”//" 开头,或单字符+ "\\" 开头的行;
- 代码行:除去空行和注释行的其他行。
代码如下
1 // 统计高级信息:空行数 代码行数 注释行数 2 private void SuperCount(string filename) 3 { 4 try 5 { 6 // 打开文件 7 FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); 8 StreamReader sr = new StreamReader(file); 9 String line; 10 int nulllinecount = 0; 11 int codelinecount = 0; 12 int notelinecount = 0; 13 while ((line = sr.ReadLine()) != null) 14 { 15 // 除去每行开头多余空格和格式控制字符 16 line = line.Trim(' '); 17 line = line.Trim('\t'); 18 // 空行 19 if (line == "" || line.Length <= 1) 20 { 21 nulllinecount++; 22 } 23 // 注释行 24 else if(line.Substring(0, 2) == "//" || line.Substring(1, 2) == "//") 25 { 26 notelinecount++; 27 } 28 // 代码行 29 else 30 { 31 codelinecount++; 32 } 33 } 34 iNullLinecount = nulllinecount; 35 iCodeLinecount = codelinecount; 36 iNoteLinecount = notelinecount; 37 sr.Close(); 38 } 39 catch (IOException ex) 40 { 41 Console.WriteLine(ex.Message); 42 return; 43 } 44 }
再次修改 Operator() 方法,让其能捕捉到 "-s" 命令,递归处理符合条件的文件,代码如下:
1 // 遍历文件 2 if (s == "-s") 3 { 4 try 5 { 6 string[] arrPaths = sFilename.Split('\\'); 7 int pathsLength = arrPaths.Length; 8 string path = ""; 9 10 // 获取输入路径 11 for (int i = 0; i < pathsLength - 1; i++) 12 { 13 arrPaths[i] = arrPaths[i] + '\\'; 14 15 path += arrPaths[i]; 16 } 17 18 // 获取通配符 19 string filename = arrPaths[pathsLength - 1]; 20 21 // 获取符合条件的文件名 22 string[] files = Directory.GetFiles(path, filename); 23 24 foreach (string file in files) 25 { 26 Console.WriteLine("文件名:{0}", file); 27 SuperCount(file); 28 BaseCount(file); 29 Display(); 30 } 31 break; 32 } 33 catch (IOException ex) 34 { 35 Console.WriteLine(ex.Message); 36 return; 37 } 38 }
高级功能的实现
在高级功能中,用户单独输入 "-x" 参数,调用图形界面实现单个文件的选中,并输出文件信息。首先,我们修改主函数,让其可以接受单个 "-x" 命令。
1 static void Main(string[] args) 2 { 3 string message = ""; 4 5 while (message != "exit") 6 { 7 Console.Write("wc.exe "); 8 // 得到输入命令 9 message = Console.ReadLine(); 10 message = message.Trim(' '); 11 message = message.Trim('\t'); 12 if (message != "-x") 13 { 14 // 分割命令 15 string[] arrMessSplit = message.Split(' '); 16 int iMessLength = arrMessSplit.Length; 17 string[] sParameter = new string[iMessLength - 1]; 18 // 获取命令参数 19 for (int i = 0; i < iMessLength - 1; i++) 20 { 21 sParameter[i] = arrMessSplit[i]; 22 } 23 // 获取文件名 24 string sFilename = arrMessSplit[iMessLength - 1]; 25 // 新建处理类 26 WC newwc = new WC(); 27 newwc.Operator(sParameter, sFilename); 28 } 29 else 30 { 31 string[] sParameter = new string[1]; 32 sParameter[0] = message; 33 WC newwc = new WC(); 34 newwc.Operator(sParameter, ""); 35 } 36 } 37 }
接下来,在 Operator() 函数中增加对于 “-x" 参数的捕捉,实现调用 OpenFileDialog() 类调用文件选择对话框。
1 if(s == "-x") 2 { 3 string resultFile = ""; 4 OpenFileDialog fd = new OpenFileDialog(); 5 fd.InitialDirectory = "D:\\Patch"; 6 fd.Filter = "All files (*.*)|*.*|txt files (*.txt)|*.txt"; 7 fd.FilterIndex = 2; 8 fd.RestoreDirectory = true; 9 if (fd.ShowDialog() == DialogResult.OK) 10 { 11 resultFile = fd.FileName; 12 Console.WriteLine("文件名:{0}", resultFile); 13 SuperCount(resultFile); 14 BaseCount(resultFile); 15 DisplayAll(); 16 } 17 break; 18 }
注意:在主函数入口前加入 [STAThread] ,否则对话框不会显示。
1 class Program 2 { 3 [STAThread] 4 static void Main(string[] args) 5 { 6 ... 7 } 8 }
最后,我们新增 DisplayAll() 函数打印全部信息
1 private void DisplayAll() 2 { 3 foreach (string s in sParameter) 4 { 5 Console.WriteLine("字 符 数:{0}", iCharcount); 6 Console.WriteLine("单 词 数:{0}", iWordcount); 7 Console.WriteLine("总 行 数:{0}", iLinecount); 8 Console.WriteLine("空 行 数:{0}", iNullLinecount); 9 Console.WriteLine("代码行数:{0}", iCodeLinecount); 10 Console.WriteLine("注释行数:{0}", iNoteLinecount); 11 } 12 Console.WriteLine(); 13 }
至此,我们基本实现了 wc.exe 的基本功能、扩展功能和功能。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步