C言語でdiffコマンドを書きたい

■要求定義
– ファイルとファイルの内容の違いを表示する
– diff 旧ファイル名 新ファイル名

■diffのサンプル
e.g.
before.txt

体重 66Kg
abcdef
123456

after.txt

体重 64Kg
abcdef
123456

$ diff before.txt after.txt
1c1
< 体重 66Kg --- > 体重 64Kg

■検討
– 差分の計算には(1)編集距離、(2)Longest Common Subsequence:最長共通部分列、(3)Shortest Edit Script
– Wuアルゴリズムでは、2つの要素列を、エディットグラフに見立てて、点(0,0)から点(M,N)までの最短経路を求める問題に還元する
– Wuアルゴリズムは難易度が高いか?

同じか否かを検出

int main(){

	int i, n, c1, c2;
	FILE *fp1, *fp2;

	fp1 = fopen("before.txt", "r");
	fp2 = fopen("after.txt", "r");

	if(fp1==NULL || fp2==NULL){
		printf("file open error\n");
		return 1;
	}

	int ln = 1;
	int cl = 0;

	while ((c1=fgetc(fp1))!=EOF && (c2=fgetc(fp2))!=EOF){
		ln += (c1 == '\n') ? 1: 0;
		cl += (c1 == '\n') ? 0: ++cl;

		if(c1 != c2){
			printf("difference found [%c][%c] at line %d col %d\n", c1, c2, ln, cl);
			fclose(fp1);
			fclose(fp2);
			return 2;
		}

	}
	printf("no difference\n");

	fclose(fp1);
	fclose(fp2);

	return 0;
}

$ ./dev
difference found [6][4] at line 1 col 1022
$ ./dev
no difference
天才がいるな。

■動的計画法(JavaScript)

function distance(A, B){
	const M = A.length;
	const N = B.length;
	// DPのテーブル(大きさ(M + 1)*(N + 1)の2次元配列)
	// D[i][j] = (0, 0)から(i, j)までの最小コスト(編集距離)
	const D = new Array(M + 1);
	for(let i = 0; i <= M; i++){
		D[i] = new Array(N + 1);
		// (0,0)から(i,0)までの距離はi
		D[i][0] = i;
	}
	for(let j = 1; j <= N; j++){
		// (0,0)から(0, j)までの距離はj
		D[0][j] = j;
	}
	// D[i][j]を順番に求める
	for(let i = 1; i <= M; i++){
		for(let j = 1; j <= N; j++){
			// 縦横については +1, 斜めの辺については + 0
			D[i][j] = A[i-1] === B[j - 1]
			 ? Math.min(D[i-1][j] +1, D[i][j-1]+1, D[i-1][j-1])
			 : Math.min(D[i-1][j] +1, D[i][j-1]+1); 
		}
	}
	return D[M][N];
}

gonpなどwuアルゴリズムにはライブラリがあるようです。

C言語でcatコマンドを書きたい

■要求定義
– ファイルの内容を表示する

■検討
– ファイルの中身を1行ずつ表示したい
– ファイルの1行を何バイトにするか?

#include <stdio.h>

int main(){

	FILE *fp;
	char str[1024];

	fp = fopen("hello.c","r");

	if(fp == NULL){
		printf("failed.\n");
		return -1;
	}

	while((fgets(str,256,fp)) !=NULL){
		printf("%s", str);
	}
	fclose(fp);

	return 0;
}

$ ./dev
#include

int main(){

FILE *fp;
char str[1024];

fp = fopen(“hello.c”,”r”);

if(fp == NULL){
printf(“failed.\n”);
return -1;
}

while((fgets(str,256,fp)) !=NULL){
printf(“%s”, str);
}
fclose(fp);

return 0;
}

PHPのfgets()だと、lengthが指定されない場合、1024バイト上限だから、fgets(str,256,fp)もfgets(str,1024,fp)の方が良いのかな。
しかし、C言語勉強していると、他の言語との互換性を強く感じられるな。

C言語でfindコマンドを書きたい

■要求定義
– ファイルの検索をする

■検討
– ファイルがあった時
-> ファイル名をそのまま返す
$ ls
dev hello.c
$ find hello.c
hello.c

– ファイルが無かった時
-> エラーメッセージを表示する
$ find test.dat
find: ‘test.dat’: No such file or directory

– fopenとstat(非標準)を使う方法がある

fopenで存在確認

int main(){

	FILE *fp = "hoge.c";
	if(fopen(fp, "r") == NULL) {
		printf("'%s': No such file or directory\n", fp);
	} else {
		printf("%s\n", fp);
	}

	return 0;
}

$ ./dev
‘hogehoge.c’: No such file or directory

メッセージをlinuxに合わせると雰囲気を出せる。

#include <stdio.h>
#include <sys/stat.h> // ファイル・ディレクトリに関する情報取得


int main(){

	struct stat st; 
	FILE *fp = "hogehoge.c";
	if(stat(fp, &st) != 0) {
		printf("'%s': No such file or directory\n", fp);
	} else {
		printf("%s\n", fp);
	}

	return 0;
}

$ ./dev
‘hogehoge.c’: No such file or directory

成功すると 0 を、エラー発生時には -1を返す。
非標準なので、statよりもfopenか。

返り値が0 | 1 ってのが、2進数を扱っている実感ができて良い。

C言語でpwdコマンドを書きたい

■要求定義
– 現在いる位置を表示する

■考察
-getcwd()のカレントディレクトリ取得を使う

#include <stdio.h>


int main(){

	char pathname[256];
	getcwd(pathname, 256);
	printf("%s\n", pathname);

	return 0;
}

$ ./dev
/home/vagrant/dev

これは何も考える事ないです。

C言語でrmコマンドを書きたい

■要求定義
– ファイルやフォルダを削除する
– リクルーシブの削除も対応したい

■考えた事
– remove()と_unlink()があるらしい
– remove関数は、厳密には「その名前でのファイルへのアクセスを、再びファイルを生成しない限り、不可能にする」という処理を行なっているらしい

int main(){

	// char *dir = "./newfolder";
	char *dir = "./hoge.txt";
	if(remove(dir) == 0){
		printf("%sの削除が完了しました。\n", dir);
	} else {
		printf("%sの削除に失敗しました。\n", dir);
	}
	return 0;
}

$ ./dev
./hoge.txtの削除が完了しました。

*dirをファイルがあるフォルダに変更するとremoveでは削除できない
$ ./dev
./newfolderの削除に失敗しました。

->ディレクトリ内のファイルを削除してからディレクトリを削除するというステップが必要

■フォルダの中のファイルリスト一覧表示

#include <stdio.h>
#include <dirent.h>

int main(){

	DIR *dir;
	struct dirent *dp;
	char dirpath[] = "./newfolder";

	dir = opendir(dirpath);
	if(dir == NULL) { return 1; }

	dp = readdir(dir);
	while(dp != NULL){
		printf("%s\n", dp->d_name);
		dp = readdir(dir);
	}

	if(dir != NULL) {
		closedir(dir);
	}
	return 0;
}

$ ./dev
.
foo.c
..
hoge.c

これをremove()と組み合わせれば再帰的に削除できるか。

#include <stdio.h>
#include <string.h>
#include <dirent.h>

int main(){

	DIR *dir;
	char newdir[255] = "./newfolder/";

	char *target = "./newfolder";
	if(remove(target) == 0){
		printf("%sの削除が完了しました。\n", target);
	} else {

		struct dirent *dp;
		dir = opendir(target);
		if(dir == NULL) { return 1; }

		dp = readdir(dir);
		while(dp != NULL){
			// printf("%s\n", dp->d_name);
			strcat(newdir,dp->d_name);
			printf("%s\n", newdir);
			remove(newdir);
			dp = readdir(dir);
		}
		if(dir != NULL) {
			closedir(dir);
		}
		remove(target);
		printf("%sの削除が完了!\n", target);
	}

	
	return 0;
}

$ ./dev
./newfolder/.
./newfolder/.foo.c
./newfolder/.foo.c..
./newfolder/.foo.c..hoge.c
./newfolderの削除が完了!

strcatで文字列連結するのがうまくいってない。何故だろう。

C言語でrmdirコマンドを書きたい

■要求定義
– 空のフォルダを削除する

#include <stdio.h>
#include <sys/stat.h>

int main(){

	char *dir = "./newfolder";
	rmdir(dir);
		
	return 0;
}

$ ./dev

フォルダの中にファイルが入っているときは、rmdirでは削除されない。

linuxコマンドとcの標準ライブラリ関数がネーミングが同じ場合は面白みがないな。

C言語でtouchコマンドを書きたい

■要求定義
– ファイルを作成する

■検討
– touchはファイルの中身は書かないから、fopen, fcloseだけで良いか?

#include <stdio.h>

int main(){

	FILE *fp;
	fp = fopen("test.txt", "w");
	fclose(fp);
		
	return 0;
}

$ ./dev

■追加
– コマンドラインで入力した値のファイルを作成する

#include <stdio.h>

int main(){

	char ss[256];
    gets(ss);

	FILE *fp;
	fp = fopen(ss, "w");
	fclose(fp);
		
	return 0;
}

$ ./dev
hoge.txt

できました。

fopenの拡張子はテキストファイル(.dat, .txt, .c, .h)、バイナリファイル(.exe, .jpg, .gif, .mpeg)どちらも可

なるほど、C言語はバイナリの概念がかなり出てくるな。。

C言語でclearコマンドを書きたい

■要求定義
– 消さずに画面を綺麗にする

■検討
– windowsでsystem(“cls”); で消せると書いてある。嫌な予感がするが、bionicでも動く?

#include 
#include 

int main(){

	for(int idx=0; idx<10; idx++){
    	printf("%d\n", idx);
  	}

	system("clear");
		
	return 0;
}

0
1
2
3
4
5
6
7
8
9
sh: 1: cls: not found

やっぱり。DOS/Windowsコマンドらしく、linuxには無いとのこと。

int main(){

	for(int idx=0; idx<10; idx++){
    	printf("%d\n", idx);
  	}

	system("clear");
		
	return 0;
}

これなら動くけど意味ないね。

エスケープシーケンシーとはターミナルを制御する制御文字
printf("\033[2J") //画面クリア
などがある。

system("\033[2J");


う〜ん、clearコマンドとはちょっと違うな。

C言語でcpコマンドを書きたい

■要求定義
– ファイルやフォルダをコピーする
– cp (移動元ファイル名/フォルダ名) (移動先ファイル名/フォルダ名) 

#include <stdio.h>
#include <stdlib.h> //汎用ライブラリ
#include <string.h>

int main(){

	FILE *fpSrc, *fpDes, *fp;
	char ss[256], ch[50];

	fpSrc = fopen("test.txt", "r");
	fpDes = fopen("copy.txt", "w");

	while(fgets(&ss, 256, fpSrc) != NULL){
		printf("%s", ss);
		fwrite(ss, 1, 8, fpDes);
	}

	fclose(fpSrc);
	fclose(fpDes);

	printf("コピーしました。\n");
		
	return 0;
}

$ ./dev
asdf
1234コピーしました。

■考察
あれ、コピー先のデータはバイナリデータで入ってるな。
調べてみると、fwriteはバイナリデータの書き込みとのこと。
fwriteではなく、fputs()に変更します。

int main(){

	FILE *fpSrc, *fpDes;
	char ss[256];

	fpSrc = fopen("test.txt", "r");
	fpDes = fopen("copy.txt", "w");

	while(fgets(&ss, 256, fpSrc) != NULL){
		printf("%s", ss);
		fputs(ss, fpDes);
	}

	fclose(fpSrc);
	fclose(fpDes);

	printf("コピーしました。\n");
		
	return 0;
}

コピーできました。
バイナリとは2進数データのこと。