Windows+C#でseq/head/cat/tac/tail/rev風コマンドを作る

8-1-2.コマンドラインからC#

WindowsにVisualStudioをインストールせずに、C#プログラミングをしてみましょう。
Linuxのseq,head,cat,tac,tail,rev風のコマンドを作ってみます。

まずは、C#のコンパイラを探してみましょう。

Windowsにデフォルトで入ってるC#コンパイラを探す

コマンドプロンプトを起動して、dir /b /s csc.exeを実行します。

たくさん見つかりましたが、今回は一番上のこのパスを通しておきます。
C:\Windows\Microsoft.NET\Framework\v2.0.50727

システムのプロパティ→環境変数

→システムの環境変数→Pathに、さきほどのパスを追加します。

パスが通ったかコマンドプロンプトで、cscを実行して確認してみます。

C#の簡単なプログラムを作って実行してみましょう。

using System;

class Test{
	static void Main(string[] args){
		Console.WriteLine("Hello "+args[0]);
	}
}

csc ファイル名
でコンパイルできます。

C#でseq風コマンドを作る

seqコマンドの作成に入る前に、seqコマンドの動かし方、3パターンをおさらいしてみます。

引数が1つの場合は、1から指定した数までの1ずつ増加する数列を作ります。

takk@deb83:~$ seq 10
1
2
3
4
5
6
7
8
9
10
takk@deb83:~$

引数が2つの場合は、第一引数が数列の最初の数で、第二引数が最後の数となります。1ずつ増加します。

takk@deb83:~$ seq 5 10
5
6
7
8
9
10
takk@deb83:~$

最後に引数が3つの場合。第一引数が最初の数、第二引数が増加数、第三が最後の数です。

takk@deb83:~$ seq 10 -3 -15
10
7
4
1
-2
-5
-8
-11
-14
takk@deb83:~$

このようにseqコマンドは指定する引数の数によって、引数の位置が異なります。プログラムで判定するには、引数の数をチェックします。

     5	int inc = 1;
     6	int first = 1;
     7	int i,last;
     8	if(args.Length > 2)
     9		inc = int.Parse(args[1]);
    10	if(args.Length > 1)
    11		first  = int.Parse(args[0]);
    12	last = int.Parse(args[args.Length-1]);
    13

引数チェックをして、first、inc、lastを設定したら、for文で回して表示するだけです。
ただし、incがマイナスの場合は、数列も大きな数から小さな数になっていきますので、増加減少の判定が必要になります。

    14	if(inc > 0)
    15		for(i=first;i<=last;i+=inc)
    16			print(i);
    17	else if(inc < 0)
    18		for(i=first;i>=last;i+=inc)
    19			print(i);

ちなみに、coreutilsのseqコマンドでは、増分を0にすると無限ループとなります。

takk@deb83:~/tmp$ seq 1 0 5
1
1
1
1
1
1
1
1
1
1
1^C

C#のseqコマンドプログラムを全体を通して見てみましょう。

     1	using System;
     2
     3	class Test{
     4		static void Main(string[] args){
     5			int inc = 1;
     6			int first = 1;
     7			int i,last;
     8			if(args.Length > 2)
     9				inc = int.Parse(args[1]);
    10			if(args.Length > 1)
    11				first  = int.Parse(args[0]);
    12			last = int.Parse(args[args.Length-1]);
    13
    14			if(inc > 0)
    15				for(i=first;i<=last;i+=inc)
    16					print(i);
    17			else if(inc < 0)
    18				for(i=first;i>=last;i+=inc)
    19					print(i);
    20		}
    21		static void print(int num){
    22			System.Console.WriteLine(""+num);
    23		}
    24	}

ではWindowsで使ってみます。

C#でhead風コマンドを作る

seq風コマンドの次は、head風コマンドです。

headのファイルを読み込んで頭10行表示する基本動作から作ります。

     1	using System;
     2	using System.IO;
     3
     4	class Head{
     5		static void Main(string[] args){
     6			int i,num=10;
     7
     8			TextReader tr;
     9
    10			tr=new StreamReader(args[0]);
    11			string line;
    12			for(i=1;(line=tr.ReadLine())!=null && (i<=num);i++){
    13				Console.WriteLine(line);
    14			}
    15			tr.Dispose();
    16		}
    17	}

引数で指定したファイルをTextReaderで読み込んで頭10行表示するプログラムです。
一応動きます。

D:\tmp> head head.cs
using System;
using System.IO;

class Head{
	static void Main(string[] args){
		int i,num=10;

		TextReader tr;

		tr=new StreamReader(args[0]);
D:\tmp> head head.cs

ファイルを複数使えるように改造してみます
headは複数ファイル指定時は、各ファイルのタイトルを表示して、head処理をするように修正してみます。

     1	using System;
     2	using System.IO;
     3
     4	class Head{
     5		static void Main(string[] args){
     6			int i,num=10;
     7			int fileind=0,filenum=args.Length;
     8
     9			TextReader tr;
    10			do{
    11				tr=new StreamReader(args[fileind]);
    12				Console.WriteLine("==> "+args[fileind]+" <==");
    13				string line;
    14				for(i=1;(line=tr.ReadLine())!=null && (i<=num);i++){
    15					Console.WriteLine(line);
    16				}
    17				tr.Dispose();
    18			}while(++fileind<args.Length);
    19		}
    20	}

実行すると、

D:\tmp> head head.cs seq.cs
==> head.cs <==
using System;
using System.IO;

class Head{
	static void Main(string[] args){
		int i,num=10;
		int fileind=0,filenum=args.Length;

		TextReader tr;
		do{
==> seq.cs <==
using System;

class Seq{
	static void Main(string[] args){
		int inc = 1;
		int first = 1;
		int i,last;
		if(args.Length > 2)
			inc = int.Parse(args[1]);
		if(args.Length > 1)
D:\tmp>

各ファイルのhead時にタイトルが表示されました。

では標準入力と、-(ハイフン)による行数指定もやってみましょう。

-(ハイフン)のチェックは面倒なので、引数を数値に変換したときに、マイナスの数値になったら行数指定のオプションと解釈するように作ります。
エラーがcatchされるのは、オプションがない場合です。

     1	using System;
     2	using System.IO;
     3
     4	class Head{
     5		static void Main(string[] args){
     6			int i,tmp,num=10;
     7			int fileind=0,filenum=args.Length;
     8
     9			try{
    10				if((tmp=int.Parse(args[0])) < 0){
    11					num=tmp*-1;
    12					fileind=1;
    13					filenum--;
    14				}
    15			}catch{
    16			}
    17
    18			TextReader tr;
    19			do{
    20				if(filenum==0){
    21					tr=Console.In;
    22				}else{
    23					tr=new StreamReader(args[fileind]);
    24					if(filenum>1)
    25						Console.WriteLine("==> "+args[fileind]+" <==");
    26				}
    27				string line;
    28				for(i=1;(line=tr.ReadLine())!=null && (i<=num);i++){
    29					Console.WriteLine(line);
    30				}
    31				tr.Dispose();
    32			}while(++fileind<args.Length);
    33		}
    34	}

実行結果です。

C#でcat風コマンドを作る

headをちょっと改造するだけでcatが作れます。前回作ったhead風コマンドが左。それを元に今回修正したcat風コマンドが右です。

10行目でcatでよく使うオプション-nの判定をしています。C#における文字列の比較は、変数==”文字列”、とするだけで比較できます。
28行目のiの初期化を6行目にもっていっています。catで複数ファイルを指定したとき、-nではまとめてナンバリングされるため、毎回i=1をしていては都合が悪いのです。かつ、catではファイルを全部表示するため、行数で終了判定する必要がありません。
右側の28行目では、フォーマットを指定して文字列を表示しています。{0,6}という書き方はまったく慣れません。なぜこんな設計にしたのでしょうか。printfの書式じゃだめなんですかねえ。そういえばJAVAも最初の頃はprintfありませんでしたね。C#でもできたら嬉しい。

cat風のソースです。

     1	using System;
     2	using System.IO;
     3
     4	class Cat{
     5		static void Main(string[] args){
     6			int i=1,nflag=0;
     7			int fileind=0,filenum=args.Length;
     8
     9			try{
    10				if(args[0]=="-n"){
    11					nflag=1;
    12					fileind=1;
    13					filenum--;
    14				}
    15			}catch{
    16			}
    17
    18			TextReader tr;
    19			do{
    20				if(filenum==0){
    21					tr=Console.In;
    22				}else{
    23					tr=new StreamReader(args[fileind]);
    24				}
    25				string line;
    26				for(;(line=tr.ReadLine())!=null;i++){
    27					if(nflag==1)
    28						Console.Write(String.Format("{0,6}	", i));
    29					Console.WriteLine(line);
    30				}
    31				tr.Dispose();
    32			}while(++fileind<args.Length);
    33		}
    34	}

実行結果です。

C#でtac風コマンドを作る

tac風コマンドを作ってみます。catがheadをベースにして簡単に作れたように、tacもまたcatから作れます。逆に表示するだけですからね。

catとの差分を見て行きましょう。catが上、—-を挟んで下がtacのプログラムです。

< class Cat{
---
> class Tac{

クラス名がCatからTacになっています。
オブジェクト思考でプログラミングをしているわけではないので、特に名前はなんでも良いのです。

< 		int i=1,nflag=0;
---
> 		int num=1,nflag=0;

変数iがnumに置き換わりました。-nオプションが使えるtacを作ろうと思いますので、nflagはcatで使っていたものをそのまま残します。

< 			string line;
< 			for(;(line=tr.ReadLine())!=null;i++){
< 					Console.Write(String.Format("{0,6}	", i));
< 				Console.WriteLine(line);
---
> 			string[] lines = tr.ReadToEnd().Split('\n');
> 			int i=lines.Length-1;
> 			while(i-->0){
> 					Console.Write(String.Format("{0,6}	", num++));
> 				Console.WriteLine(lines[i]);

ではtac風コマンドのプログラム全体です。

     1	using System;
     2	using System.IO;
     3
     4	class Tac{
     5		static void Main(string[] args){
     6			int num=1,nflag=0;
     7			int fileind=0,filenum=args.Length;
     8
     9			try{
    10				if(args[0]=="-n"){
    11					nflag=1;
    12					fileind=1;
    13					filenum--;
    14				}
    15			}catch{
    16			}
    17
    18			TextReader tr;
    19			do{
    20				if(filenum==0){
    21					tr=Console.In;
    22				}else{
    23					tr=new StreamReader(args[fileind]);
    24				}
    25				string[] lines = tr.ReadToEnd().Split('\n');
    26				int i=lines.Length-1;
    27				while(i-->0){
    28					if(nflag==1)
    29						Console.Write(String.Format("{0,6}	", num++));
    30					Console.WriteLine(lines[i]);
    31				}
    32				tr.Dispose();
    33			}while(++fileind<args.Length);
    34		}
    35	}

実行結果です。

C#でtail風コマンドを作る

tail風コマンドです。tailもまたtacから作れます。
差分を見ていきましょう。

< class Tac{
---
> class Tail{

tailに変えておきます。

< 		int num=1,nflag=0;
---
> 		int num=1,tmp;

ナンバリングは必要ないのでnflagは取っ払って、代わりに文字列数値変換時に使うtmpを定義します。

< 			if(args[0]=="-n"){
< 				nflag=1;
---
> 			if((tmp=int.Parse(args[0])) < 0){
> 				num=tmp*-1;

headの時と同じく、マイナス値を利用します。

> 				if(filenum>1)
> 					Console.WriteLine("==> "+args[fileind]+" <==");

ここもheadと同じく、複数ファイルの時はヘッダを付けます。

< 			int i=lines.Length-1;
< 			while(i-->0){
< 				if(nflag==1)
< 					Console.Write(String.Format("{0,6}	", num++));
< 				Console.WriteLine(lines[i]);
---
> 			if(lines.Length < num)
> 				num = lines.Length;
> 			for(int i=num;i>0;i--){
> 				Console.WriteLine(lines[lines.Length-i-1]);

-(ハイフン)で指定する数字がファイルの行数より少ない場合のチェックも入れておきます。

tail風コマンドのソースを全部見てみます。

     1	using System;
     2	using System.IO;
     3
     4	class Tail{
     5		static void Main(string[] args){
     6			int num=1,tmp;
     7			int fileind=0,filenum=args.Length;
     8
     9			try{
    10				if((tmp=int.Parse(args[0])) < 0){
    11					num=tmp*-1;
    12					fileind=1;
    13					filenum--;
    14				}
    15			}catch{
    16			}
    17
    18			TextReader tr;
    19			do{
    20				if(filenum==0){
    21					tr=Console.In;
    22				}else{
    23					tr=new StreamReader(args[fileind]);
    24					if(filenum>1)
    25						Console.WriteLine("==> "+args[fileind]+" <==");
    26				}
    27				string[] lines = tr.ReadToEnd().Split('\n');
    28				if(lines.Length < num)
    29					num = lines.Length;
    30				for(int i=num;i>0;i--){
    31					Console.WriteLine(lines[lines.Length-i-1]);
    32				}
    33				tr.Dispose();
    34			}while(++fileind<args.Length);
    35		}
    36	}

実行結果です。

C#でrev風コマンドを作る

head風コマンドからrev風コマンド作ります。

< class Head{
---
> class Rev{

ヘッドがリバースです。

< 		int i,tmp,num=10;

いらないものは削除。

< 		try{
< 			if((tmp=int.Parse(args[0])) < 0){
< 				num=tmp*-1;
< 				fileind=1;
< 				filenum--;
< 			}
< 		}catch{
< 		}
<

削除。引数なしです。

< 				if(filenum>1)
< 					Console.WriteLine("==> "+args[fileind]+" <==");

削除ばかりです。

< 			for(i=1;(line=tr.ReadLine())!=null && (i<=num);i++){
< 				Console.WriteLine(line);
---
> 			for(;(line=tr.ReadLine())!=null;){
> 				char[] a = line.ToCharArray();
> 				Array.Reverse(a);
> 				Console.WriteLine(a);

これが反対にするロジックです。Reverseは、引数で指定した配列を、破壊的置換してくれるので戻り値が不要です。

全ソース。

     1	using System;
     2	using System.IO;
     3
     4	class Rev{
     5		static void Main(string[] args){
     6			int fileind=0,filenum=args.Length;
     7
     8			TextReader tr;
     9			do{
    10				if(filenum==0){
    11					tr=Console.In;
    12				}else{
    13					tr=new StreamReader(args[fileind]);
    14				}
    15				string line;
    16				for(;(line=tr.ReadLine())!=null;){
    17					char[] a = line.ToCharArray();
    18					Array.Reverse(a);
    19					Console.WriteLine(a);
    20				}
    21				tr.Dispose();
    22			}while(++fileind<args.Length);
    23		}
    24	}

結果です。

コメント

  1. […] コマンドラインからC#をビルドする方法、「Windows+C#でseq風コマンドを作る」を見てください。 […]

タイトルとURLをコピーしました