C言語で簡易シェルを作る
- 作成日: 2023-08-07
- 更新日: 2023-12-24
- カテゴリ: C言語
C言語で簡易シェルを作る
毎日暑いですがみなさま体調の方はよろしいでしょうか。
この暑さで無理をするとあっという間に天国へ行ってしまいますので気を付けてください。
私も熱湯部屋でプログラミングをしてあの世に行かないように気を付けています。
シャレじゃなくほんとにあの世に行ってしまいますからね。
さて今回は暑いので簡易シェルを作ります。
シェルって端末ですね。Bashとかコマンドプロンプトとか。
と言ってもエミュレーターみたいなもので実際には何の仕事もしません。
ただフォルダを作れて親フォルダと子フォルダを移動できるだけです。
関連記事
C言語でcharをintに変換する方法
C言語でenumをtypedefして使う【列挙型】
C言語でforeachマクロを実装する方法
C言語でnull判定する方法【NULL, 比較】
C言語でできることを解説!C言語歴16年の開発者が語る
C言語でオブジェクト指向する【単一継承の方法】
C言語でグローバルに関数を使う方法
C言語でシャローコピーとディープコピーを実装する
実行風景
以下は簡易シェルを実行している所です。
ソースコード全文
ソースコード全文は以下になります。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
enum {
NCHILDS = 8,
NFNAME = 256,
};
typedef struct Node {
char name[NFNAME];
struct Node *parent;
struct Node *childs[NCHILDS];
size_t childs_len;
} Node;
typedef struct {
Node *root;
Node *ref_current; // do not delete
} Shell;
void
node_del(Node *self) {
for (size_t i = 0; i < self->childs_len; i++) {
node_del(self->childs[i]);
}
free(self);
}
Node *
node_new(void) {
return calloc(1, sizeof(Node));
}
void
shell_destroy(Shell *self) {
node_del(self->root);
}
void
shell_init(Shell *self) {
*self = (Shell){0};
self->root = node_new();
self->ref_current = self->root;
strcpy(self->root->name, "/");
}
void
shell_draw(Shell *self) {
system("clear"); // Windows: shelltem("cls");
if (self->ref_current == NULL) {
return;
}
Node *node = self->ref_current;
printf("[ %s ]\n", node->name);
for (size_t i = 0; i < node->childs_len; i++) {
Node *child = node->childs[i];
if (i == node->childs_len - 1) {
printf("┗ ");
} else {
printf("┣ ");
}
printf("[ %s ]\n", child->name);
}
}
bool
shell_update(Shell *self) {
printf("cd:n, mkdir:name, quit > ");
fflush(stdout);
char cmd[128];
if (!fgets(cmd, sizeof cmd, stdin)) {
return false;
}
cmd[strlen(cmd) - 1] = '\0';
if (!strncmp(cmd, "cd", 2)) {
char *p = strchr(cmd, ':');
if (p) {
p++;
Node *node = self->ref_current;
if (!strcmp(p, "..")) {
if (node->parent) {
self->ref_current = node->parent;
}
} else {
int n = atoi(p);
if (n >= node->childs_len) {
fprintf(stderr, "childs overflow\n");
return true;
}
Node *child = node->childs[n];
if (strlen(child->name)) {
self->ref_current = child;
}
}
}
} else if (!strncmp(cmd, "mkdir", 5)) {
char *p = strchr(cmd, ':');
if (p) {
p++;
Node *node = self->ref_current;
if (node->childs_len >= NCHILDS) {
fprintf(stderr, "childs overflow\n");
return true;
}
Node* child = node_new();
strcpy(child->name, p);
child->parent = node;
node->childs[node->childs_len++] = child;
}
} else if (!strncmp(cmd, "quit", 4)) {
return false;
}
return true;
}
int main(void) {
Shell shell;
shell_init(&shell);
for (;;) {
shell_draw(&shell);
if (!shell_update(&shell)) {
break;
}
}
shell_destroy(&shell);
return 0;
}
定数
定数は以下になります。
enum {
NCHILDS = 8,
NFNAME = 256,
};
NCHILDS
はノードが持つ子ノードの数です。
NFNAME
はノードのname
(char
配列)の要素数です。
構造体
構造体は以下になります。
typedef struct Node {
char name[NFNAME];
struct Node *parent;
struct Node *childs[NCHILDS];
size_t childs_len;
} Node;
typedef struct {
Node *root;
Node *ref_current; // do not delete
} Shell;
Node
はフォルダなどを表すオブジェクトの構造体です。
Node
のname
はノードの名前(フォルダ名)、parent
は親ノードへのリンク、childs
は子ノードの配列、childs_len
は子ノードの現在の長さです。
Shell
はシェルを表す構造体です。
root
は動的にメモリ確保されたルートノード(根ノード)、ref_current
は現在表示するノードへの参照(free不要なポインタ)です。
ノード間を移動するにはcd
コマンドを使いますが、cd
を実行するとref_current
が更新されます。
ノードを作るにはmkdir
コマンドを使いますが、ノードを作るとchilds
に動的にメモリ確保されたノードのアドレスが保存されます。
main関数
main
関数は以下になります。
int main(void) {
Shell shell;
shell_init(&shell);
for (;;) {
shell_draw(&shell);
if (!shell_update(&shell)) {
break;
}
}
shell_destroy(&shell);
return 0;
}
まずshell_init()
でシェルを初期化します。
それから無限ループに入ってshell_draw()
で描画、shell_update()
で更新を行います。
shell_update()
がfalse
を返して来たら無限ループから抜けてshell_destroy()
でメモリを解放します。
shell_init関数
void
shell_init(Shell *self) {
*self = (Shell){0};
self->root = node_new();
self->ref_current = self->root;
strcpy(self->root->name, "/");
}
shell_init()
ではself->root
にルートノードを確保します。
node_new()
でノードのメモリを確保しています。
self->ref_current
にself->root
を設定して現在のノードをルートにします。
self->root->name
に文字列の「/」を設定します。
shell_destroy関数
void
shell_destroy(Shell *self) {
node_del(self->root);
}
shell_destroy()
ではself->root
をnode_del()
で削除します。
node_new, node_del関数
Node *
node_new(void) {
return calloc(1, sizeof(Node));
}
node_new()
でNode
のメモリをcalloc()
で確保します。
calloc()
はメモリ確保後に確保したメモリを0クリアします。
void
node_del(Node *self) {
for (size_t i = 0; i < self->childs_len; i++) {
node_del(self->childs[i]);
}
free(self);
}
node_del()
はchilds
の要素をnode_del()
して最後にself
をfree()
します。
shell_draw関数
void
shell_draw(Shell *self) {
system("clear"); // Windows: shelltem("cls");
if (self->ref_current == NULL) {
return;
}
Node *node = self->ref_current;
printf("[ %s ]\n", node->name);
for (size_t i = 0; i < node->childs_len; i++) {
Node *child = node->childs[i];
if (i == node->childs_len - 1) {
printf("┗ ");
} else {
printf("┣ ");
}
printf("[ %s ]\n", child->name);
}
}
shell_draw()
関数ではシェルを描画します。
まずsystem("clear")
で端末の描画をクリアします。
これはWindowsの場合はsystem("cls")
になります。
system
関数は引数のコマンドを実行します。
self->ref_current
のname
を出力。
それからnode->childs
の要素を取り出して罫線とchild->name
を出力します。
出力すると以下のようになります。
[ / ]
┣ [ hige ]
┣ [ hoge ]
┗ [ moge ]
shell_update関数
shell_update
関数はシェルを更新します。
bool
shell_update(Shell *self) {
printf("cd:n, mkdir:name, quit > ");
fflush(stdout);
char cmd[128];
if (!fgets(cmd, sizeof cmd, stdin)) {
return false;
}
cmd[strlen(cmd) - 1] = '\0';
if (!strncmp(cmd, "cd", 2)) {
char *p = strchr(cmd, ':');
if (p) {
p++;
Node *node = self->ref_current;
if (!strcmp(p, "..")) {
if (node->parent) {
self->ref_current = node->parent;
}
} else {
int n = atoi(p);
if (n >= node->childs_len) {
fprintf(stderr, "childs overflow\n");
return true;
}
Node *child = node->childs[n];
if (strlen(child->name)) {
self->ref_current = child;
}
}
}
} else if (!strncmp(cmd, "mkdir", 5)) {
char *p = strchr(cmd, ':');
if (p) {
p++;
Node *node = self->ref_current;
if (node->childs_len >= NCHILDS) {
fprintf(stderr, "childs overflow\n");
return true;
}
Node* child = node_new();
strcpy(child->name, p);
child->parent = node;
node->childs[node->childs_len++] = child;
}
} else if (!strncmp(cmd, "quit", 4)) {
return false;
}
return true;
}
まずプロンプトを出して一行入力をユーザーにさせます。
printf("cd:n, mkdir:name, quit > ");
fflush(stdout);
char cmd[128];
if (!fgets(cmd, sizeof cmd, stdin)) {
return false;
}
cmd[strlen(cmd) - 1] = '\0';
cd:0
と入力すると現在のノードの0番目の子要素に移動。
cd:1
では1番目の要素に移動します。
cd:..
では現在のノードの親ノードに移動します。
mkdir:hige
と入力すると現在のノードの子ノードとしてhige
という名前のノードを作ります。
if (!strncmp(cmd, "cd", 2)) {
char *p = strchr(cmd, ':');
if (p) {
p++;
Node *node = self->ref_current;
if (!strcmp(p, "..")) {
if (node->parent) {
self->ref_current = node->parent;
}
} else {
int n = atoi(p);
if (n >= node->childs_len) {
fprintf(stderr, "childs overflow\n");
return true;
}
Node *child = node->childs[n];
if (strlen(child->name)) {
self->ref_current = child;
}
}
}
}
上記ではcd
コマンドの処理を書いています。
まず入力されたcmd
をstrncmp()
で先頭に2文字だけ"cd"
と比較します。
それからstrchr()
で:
を探索しそのポインタを取得します。
ポインタがあればp++
して:
を読み飛ばします。
それからp
が..
ならnode
(self->ref_current
)のparent
をref_current
に保存します。
それ以外、つまりp
が数字ならatoi()
で数値に変換します。
そしてnode->childs
の数字の要素のノードを取り出し、そのノードをref_current
に保存します。
else if (!strncmp(cmd, "mkdir", 5)) {
char *p = strchr(cmd, ':');
if (p) {
p++;
Node *node = self->ref_current;
if (node->childs_len >= NCHILDS) {
fprintf(stderr, "childs overflow\n");
return true;
}
Node* child = node_new();
strcpy(child->name, p);
child->parent = node;
node->childs[node->childs_len++] = child;
}
}
上記ではmkdir
コマンドの処理を書いています。
node_new()
でノード(child
)を作り、そのノードのchild->name
にp
をコピーします。
つまりmkdir:hige
ならhige
がchild->name
にコピーされます。
child->parent
に親ノードとしてnode
(ref_current
)を入れておきます。
そしてnode->childs
にchild
を保存します。
else if (!strncmp(cmd, "quit", 4)) {
return false;
}
上記ではquit
コマンドの処理です。
shell_update()
は返り値がbool
でfalse
を返すと無限ループが終了します。
true
の場合は無限ループが継続します。
おわりに
今回はC言語で簡易シェルを作りました。
なにか参考になれば幸いです。
🦝 < シェルシェルシェル
🦝 < クーラー付きシェル