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

■要求定義
– ファイルやフォルダのオーナーやグループを変更する
– chown [オプション] ユーザー[:グループ] ファイル

■chownサンプル
$ ls -l sample.txt
–w–wx-wT 1 vagrant vagrant 89 May 17 11:50 sample.txt
// ここではユーザー所有権「vagrant」、グループ所有権「vagrant」
// [vagrant]から[root]に変更する
$ sudo chown root sample.txt
$ ls -l sample.txt
–w–wx-wT 1 root vagrant 89 May 17 11:50 sample.txt

基本的にファイルを作成したユーザがユーザ所有権者になる
ユーザ所有権を変更すると、所有権者以外はファイルの変更ができなくなる

所有者を自分にしておき、パーミッションを700にすると、他のユーザは見ることができない
ユーザグループに属しておけば、ユーザグループの権限が作業できる

■検討
#include
int chown(const char *path, uid_t owner, gid_t group);

– whoamiで自分のユーザ名、id ${ユーザ名}でuid, gidを調べられる
$ whoami
vagrant
$ id vagrant
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant)

– ユーザ名一覧は「/etc/passwd」ファイルにある
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
// 省略
ubuntu:x:1001:1001:Ubuntu:/home/ubuntu:/bin/bash
mysql:x:111:115:MySQL Server,,,:/nonexistent:/bin/false

■ユーザ追加の手順
$ sudo su –
$ useradd -m hpscript
$ passwd ${ユーザ名} // パスワード設定
$ usermod -G sudo hpscript // sudoユーザに追加
$ cat /etc/group | grep hpscript
sudo:x:27:ubuntu,hpscript
hpscript:x:1002:

■ユーザ変更の手順
$ su hpscript

■グループ追加の手順
$ groupadd test-group

■グループにユーザ追加の手順
$ sudo gpasswd -a hpscript test-group
Adding user hpscript to group test-group
$ id hpscript
uid=1002(hpscript) gid=1002(hpscript) groups=1002(hpscript),1003(test-group)

$ sudo touch hpscript.txt
$ ls -l
-rw-r–r– 1 root root 0 May 18 03:51 hpscript.txt
あれ、sudoでtouchをするとルートユーザで作成したことになりますな。

#include <stdio.h>
#include <unistd.h>

int main(){

	char *fp;
	fp = "./hpscript.txt";
	int uid = 1001;
	int gid = 1001;

	if(chown(fp, uid, gid) == 0){
		printf("success\n");
	} else {
		printf("try again\n");
	};

	return 0;
}

$ ./dev
try again

あれ、なんでやろう??

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

■要求定義
– ファイルやフォルダのアクセス権限を変更する

e.g.
$ ls -l
-rw-rw-r– 1 vagrant vagrant 89 May 17 11:50 sample.txt
$ chmod 777 sample.txt
$ ls -l
-rwxrwxrwx 1 vagrant vagrant 89 May 17 11:50 sample.txt

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

int main(void){

	FILE *fp = "./sample.txt";

	chmod(fp, 666);

	return 0;
}

これでは上手く行かない。

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

int main(int argc, char**argv){

	char ans;
	int return_code = 0;

	if(argc == 2){
		printf('%sに実行許可を与えます。宜しいでしょうか(y/n)==>', *(argv+1));
		scanf('%c', &ans);

		if(ans == 'y' || ans == 'Y'){
			if(chmod(*(argv+1),
				S_IRUSR | S_IWUSR | S_IXUSR | /* S_IRUSR 00400, S_IWUSR 00200, S_IXUSR 00100 rwx*/
				S_IRGRP | S_IXGRP | /*IRGRP 00040 r-x, IXGRP 00010 */
				S_IROTH | S_IXOTH) == 0) { /*IROTH 00004, S_IXOTH 00001 */
				printf('実行許可を与えました。\n');
			} else {
				printf("実行許可を与えられませんでした\n");
				perror("");
				return 1;
			}
		} else {
			printf("キャンセルします。\n");
		}
	} else {
		printf("実行時引数の数が不正です。\n");
		return 2;
	}

	return 0;
}

■mode一覧
S_ISUID 04000:実行時のセット・ユーザー・ID(set user ID)。
S_ISGID 02000:実行時のセット・グループ・ID(set group ID)。
S_ISVTX 01000:スティッキー(sticky)ビット。
S_IRUSR 00400:所有者(owner)による読み取り(read)。
S_IWUSR 00200:所有者による書き込み(write)。
S_IXUSR 00100:所有者による実行(execute)・検索(search)。
S_IRGRP 00040:グループ(group)による読み取り。
S_IWGRP 00020:グループによる書き込み。
S_IXGRP 00010:グループによる実行・検索。
S_IROTH 00004:他人(others)による読み取り。
S_IWOTH 00002:他人による書き込み。
S_IXOTH 00001:他人による実行・検索。

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

■要求定義
– 指定した文字列がテキスト内に存在した場合、その行を抽出する

e.g.
sample.txt

Aries
Taurus
Gemini
Cancer
Leo
Virgo
Libra
Scorpio
Sagittarius
Capricorn
Aquarius
Pisces

$ grep -n Leo sample.txt // オプションの-nは行数
5:Leo
$ grep -c Leo sample.txt // オプションの-cは回数
1
$ grep Cancer ./*
./sample.txt:Cancer

■検討事項
– 正規表現で、検索文字と完全一致する文字が含まれていれば、ファイル名を返す(return 0)として、含まれていなければreturn 1とするで良いか。

strchrで先頭が一致するアドレスをポインタで返す

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

int main(void){

	char str[] = "100-0005";
	char *adr1, *adr2;

	adr1 = strchr(str, (int)'-'); //文字列 str の先頭から'-'を探し、最初に見つかった位置をポインタで返却
	printf("result1: %s\n", adr1);

	adr2 = strchr(str, (int)'x'); 
	printf("result2: %s\n", adr2);

	return 0;
}

$ ./dev
result1: -0005
result2: (null)

文字列の一致にはstrstrを使用する。

int main(void){

	char str[] = "〒100-0005 東京都千代田区丸の内一丁目";
	char *adr1, *adr2;

	adr1 = strstr(str, "千代田区"); //文字列strの中から文字列を探しそのアドレスを返す
	printf("result1: %s\n", adr1);

	adr2 = strstr(str, "港区"); 
	printf("result2: %s\n", adr2);

	return 0;
}

$ ./dev
result1: 千代田区丸の内一丁目
result2: (null)

### 正規表現を使った方法
– ヘッダーファイル「regex.h」をインクルード
– 正規表現を使った検索を行うには、正規表現のオブジェクトを格納するregex_t型の構造体と、正規表現にマッチしたインデックスを格納するregmatch_t型の構造体の配列が必要
– regcomp():正規表現コンパイル, regexec():正規表現による検索実行, regfree():regex_t型のオブジェクトのメモリ解放 を使用する

int regcomp(regex_t *preg, const char *regex, int cflags)
int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)

#include 
#include 

int main(void){

	char str[] = "〒100-0005 東京都千代田区丸の内一丁目";
	regex_t preg; // 正規表現のオブジェクト
	size_t num = 5;
	regmatch_t pmatch[num]; // 正規表現にマッチしたインデックスを格納

	const char pattern[] = "〒([0-9]{3})-([0-9]{4})(.+)";

	// 正規表現のコンパイル
	if (regcomp(&preg, pattern, REG_EXTENDED|REG_NEWLINE) != 0){
		printf("failed\n");
		return -1;
	}

	// 入力文字列の出力
	printf("input char is: %s\n", str);

	// 正規表現による検索
	if(regexec(&preg, str, num, pmatch, 0) != 0){
		printf("does not matched\n");
	} else {
		for (int i = 0; i < num; i++){
			if(pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0){
				printf("matched index is %d~%d, str: ", (int)pmatch[i].rm_so, (int)pmatch[i].rm_eo);
				for (int j = pmatch[i].rm_so ; j < pmatch[i].rm_eo; j++){
					putchar(str[j]);
				}
			}
			printf("\n");
		}
	}

	// オブジェクトのメモリ開放
	regfree(&preg);

	return 0;
}

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言語はバイナリの概念がかなり出てくるな。。