[Lv4]minishell(1/3)

https://github.com/syamashi/minishell

課題準備から。
0. 0) githubリポジトリ作りましょう

共同作業に欠かせません。
・リポジトリ作成、招待、作業までの流れ。
https://qiita.com/future_kame/items/9fa256aea09faa28b357
・作業branchを切って、masterにmargeするまでの操作

# 自分のローカルにあるmasterを最新の状態にします
git pull origin master

# 今いるブランチを確認します。
> git branch
* master

# 自分のブランチを作ります。
> git branch syamashi

# ブランチに移動します。
> git checkout syamashi

# (2回目以降) 自分のブランチを最新のmasterにします
> git pull origin master

# --- syamashiブランチで自分の開発をします --- 
# masterを汚さないので好きなだけpushして大丈夫

git add *
git commit -m "#104 fix"
git push origin syamashi

# 修正が完了したら、いよいよmasterにmargeします。
# githubのGUIから、pullrequestを送るのがわかりやすいです。
# Pull requestsタブから、自分の更新が表示されているので、
# pull requestを作成して、margeすればいいです。

# 直接masterに更新する荒業もできます。
git checkout origin master

# syamashiブランチのコードに更新して、pushする
git pull origin syamashi
git add *
git commit -m "syamashi marge"
git push origin master

0. 1 githubのissuesタブを活用しましょう。
bugを見つけたら、とにかくissueあげるといいです。
# XXX なるissue番号が表示されるので該当issueをfixしたら

git commit -m "#20 fix"

コメントにissue番号を記載すると、自動でissueと修正commitが連携されます。

さて、早速実装に入ります。
minishellのライフサイクルは、
・標準入力を受け取る
・入力を解析する
・実行結果を返す
を繰り返し、
・"exit"入力、もしくは入力が空のときにctrl+Dで終了
です。
------ 実装 ------

 #include  <unistd.h>

int main(int ac, char **av, char **envp) {
 execve("/bin/bash", av, envp);
}





------実装1:入力------
1.1) まず標準入力を受け取ります。
libftフォルダを設置すること、srcフォルダの中にmain.cの記述を想定します。

# include <stdlib.h>
# include <stdio.h>
# include <stdint.h>
# include <errno.h>
# include <stdbool.h>
# include <fcntl.h>
# include <signal.h>
# include <unistd.h>
# include <sys/wait.h>
# include <sys/types.h>
# include <sys/stat.h>

# include "../libft/libft.h"

# define STDERR 2
# define MINISHELL "MINISHELL$ "

void minishell(char **envp);
void sig_int_input();
void sig_quit_input();

// envpは環境変数
int main(int argc, char **argv, char **envp)
{
   if (argc == 1)
       minishell(envp);
}

void minishell(char **envp)
{
  char *line;
  int  ret;
  
  ret = 0;
  while(1)
  {
      char *line;
      
      ft_putstr_fd(MINISHELL, STDERR);
	   // ctrl+C, ctrl+\ の指示
	   if (signal(SIGINT, sig_int_input) == SIG_ERR)
	   {
	       ft_putstr_fd(strerror(errno), STDERR);
		   exit (1);
	   }
	   if (signal(SIGQUIT, sig_quit_input) == SIG_ERR)
	   {
	       ft_putstr_fd(strerror(errno), STDERR);
		   exit (1);
  	   }
      // 文字を受け取る
      if ((get_next_line(0, &line) == 0))
		{
	        ft_putstr_fd("exit\n", STDERR);
			exit (ret);
		}
      ft_putstr_fd(line, STDERR);
      ft_putstr_fd("\n", STDERR);
      free(line);
  }
}


void	sig_int_input()
{
	ft_putstr_fd("\b\b  \b\n", STDERR);
	ft_putstr_fd(MINISHELL, STDERR);
}

void	sig_quit_input()
{
	ft_putstr_fd("\b\b  \b\b", STDERR);

・int mainの第三引数を指定すると、環境変数が受け取れます。
Q. 環境変数?
あらかじめ設定されている便利変数です。コマンドの実行に必要なUSER名やPATHなどが格納されています。minishellをexitすれば復元されるので、恐れず変更して大丈夫です。
bash上で確認できます。

> export | grep USER
declare -x USER="syamashi"

> echo $USER
syamashi

・get_next_lineの中のread(buf, 0)で入力待機状態になります。
Q. get_next_lineの戻り値は?
A.
Enterで文末に"\n"が付与。return (1)されます。
ctrl+DでEOFが付与。return (0)されます。

Q. STDERR?
strace(Linux専用コマンドかも)で動作確認をすると、ループ開始の表示文字はfd2=エラー出力とされています。
straceはbashの動作logを出力するコマンド。課題を進める多くの助けや気づきになります。困ったら頻繁に確認します。

strace -o trace.txt -f bash -c 'echo aaa'

・signalを設定します。
https://linuxjm.osdn.jp/html/LDP_man-pages/man7/signal.7.html

signalは、ctrlとキーによる中断処理を設定できます。mlx_hookのキー設定と同じ。中断処理はコードのどこにいても、最優先で処理されます。

ctrl+Cは SIG_INT に操作関数を指定。
ctrl+\は SIG_QUIT を設定します。
ctrl+Dは…実はシグナル処理しないです。EOFが送られるだけかも。

このあたりはいずれ遭遇するかもしれません。

SIGINT	 2	Term	キーボードからの割り込み (Interrupt)
SIGQUIT	 3	Core	キーボードによる中止 (Quit)
SIGKILL	 9	Term	Kill シグナル
SIGSEGV	11	Core	不正なメモリー参照
SIGPIPE	13	Term	パイプ破壊:
 読み手の無いパイプへの書き出し

SIG_DEL	そのシグナルに対するデフォルトの操作を行う.
SIG_IGN	シグナルを無視する.

Q. /b?
A. 流行ってます。
Ctrl+Cを押すと、^Cが画面上に表示されるのを、無理やり表示させないようにしたものです。
\bはカーソルを戻す。' 'はスペースで上書き表示。
read(0)でなく、termcapで入力受付を実装すれば、この気遣いが無用になるかもしれません。

ここまでで、入力と終了ができるハリボテbashができました。
-> 次:minishell(2/3) : コマンド実行の実装

この記事が気に入ったらサポートをしてみませんか?