シェルを作ろう(1)コマンドを実行する。
さて、今回は予告通り入力を受け取って、コマンドを実行するところまで試してみたいと思います。
使用する関数、システムコール
- printf
- fgets
- strcpy
- strtok
- calloc/malloc
- execve
入力を受け取る(fgets)
さて、Cで入力(文字列)を受け取りたい時どんな関数を使おうと思うでしょうか?
多くの人はscanfを思い浮かべるかと思います、この関数も使い方によってはとても便利なのですが、
シェルのコマンドでは基本的にプロンプトが表示された行に入力された文字列を一行丸ごと読みとりたいので、scanfだと少し不便です。
(ここではscanfが本質ではないですが、scanfを使うと空白文字が挟まるごとに文字列を格納するための空間が新しく必要になりますよね?)
というわけで今回は一行ごとに入力を受け取るfgetsという関数を使いたいと思います。
以下プログラム例
#include <stdio.h> #define SIZE 128 int main(void){ char buf[SIZE] = ""; fgets(buf,SIZE,stdin); printf("%s",buf); return 0; }
実行例
$gcc -Wall -std=c99 test_fgets.c -o test_fgets $./test_fgets hello world! hello world! $
上記のようにfgets関数を使うと改行が現れるまで、空白' ‘やタブ’\t',改行文字'\n'も合わせて一つの空間に読み込んでくれます。 (改行文字も一緒に読み込んでくれるので,printfで出力する時に\nがなくても改行してくれます。)
プログラムの実行について
さて、次はstrtokの説明です…と言いたいところですが、その前にstrtokを使う理由を説明したいと思います。 例えばlsコマンドなどを使う時,
$ls -la
実はこのようにコマンドを実行する時、入力したコマンドやオプションは空白文字ごとに分割され、main関数の第二引数に渡されて (ちなみにmain関数の第一オプションにはコマンドとオプションの合計の個数 (コマンドライン引数の個数)が渡されます。) それをプログラムが解釈して実行されています。
例えば上の例ようにコマンドを入力すると
int main(int argc,char *argv[])
main関数の引数を設定すると
argcには2
argv[0]には"ls"という文字列(が確保されている場所へのポインタ)、
argv[1]には"-la"という文字列(が確保されている場所へのポインタ)
argv[2]にはNULLが格納されます。
このように、コマンドを実行するために受け取った文字列を空白文字区切りで配列に格納するために便利なのがstrtokという関数です。
文字列を分割する。(strtok)
strtokのプログラム例
#include <stdio.h> #include <stdlib.h> #include <string.h> define SIZE 128 int main(void){ char commands[128] = ""; char *tmp = (char *)NULL; printf("$"); fgets(commands,SIZE,stdin); tmp = strtok(commands," \t\n"); printf("%s\n",tmp); while((tmp = strtok(NULL," \t\n"))!= NULL){ printf("%s\n",tmp); } return 0; }
実行例
$gcc -Wall -std=c99 test_strtok.c -o test_strtok $./strtok this is a pen this is a pen $
このように strtokを使うと、一つの文字列を指定した文字で簡単に分割できます。
さて次はいよいよコマンドの実行です。
コマンドを実行する(execve)
さて、コマンドを実行するためにはexecveを使用するのですが、このシステムコールには3つの引数があります。
まず第一引数は実行するコマンドのパス名
これで指定したコマンドがあればそれを実行し(厳密には単に実行するわけではない)、なければ-1を返して通過します。
第二引数にはargvのような順でコマンドライン引数に相当する文字列集合を渡します。
第3引数にはmain関数第3引数(一般にenvpと表記される。)に渡される環境を渡します。
これはコマンドライン引数に現れないパスなどの値が格納されています。
さてそれでは以下にプログラム名を示します。
実際に試してみましょう!
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUFSIZE 128 #define ARGNUM 32 int main(int argc, char *argv[], char *envp[]){ char buf[BUFSIZE]; char *p; char *arg[ARGNUM]; int i = 0; fgets(buf,BUFSIZE,stdin); p = strtok(buf," \t\n"); arg[i] = calloc(strlen(p),sizeof(char)); strcpy(arg[i],p); //printf("%s\n",arg[i++]); while((p = strtok(NULL," \n\t")) != NULL){ arg[i] = calloc(strlen(p),sizeof(char)); strcpy(arg[i],p); //printf("%s\n",arg[i++]); } arg[i] = NULL; execve(arg[0],arg,envp); printf("failed execve\n"); return 0; }
(本当はfreeをして解放するべきですがうっかり時間がないので割愛)
実行例
$ ./test_execve /bin/ls -a . test_execve test_fgets test_strtok .. test_execve.c test_fgets.c test_strtok.c $./test_execve hello failded execve $
注 この際、まだパス展開の機能がないのでプログラムはフルパスで指定してやる必要があります。
うまく実行できましたね! 短い説明ですが、とりあえず今回はこれで終わりとさせていただきます。
シェルを作ろう(0)導入編
先日、某理学部情報科学科システムプログラミング実験の課題の一つとしてシェル(コマンドラインシェル)を作りました。 (制作期間2週間、OCamlのインタプリタの作成と並行だったのでえぐかった…) 授業の課題要件を超えて色々と実装したので、せっかくなので記事にします!
環境
シェルといったらCLI,CLIといったらLinux!
ということで、今回は以下の様なLinux環境上向けにシェルを作りました。
- OS: Ubuntu14.04
- コンパイラ: gcc4.8(マイナーバージョンは忘れたのであとで修正します。)
もしかしたらMacやCygwinでも動くかもしれませんが自身がないです。 というのも今回はLinuxのシステムコールの嵐!とまでは行かないまでも、多くのシステムコールを使っているので 互換性は保証されません。
ソースコード
とりあえず、作成したソースコードは以下にあるので、気になった方はぜひご覧ください。
https://github.com/progrunner17/my_shell
次回予告
シェルってなんだっけ?ってなった人こんな画面みたことないですか?
この様にターミナル上で入力を受け取ってコマンドを実行したり色々してくれるプログラムです。*1
という訳で次回は以下のことをやりたいと思います!
- プロンプトの表示*2
- 入力の処理
- コマンドの実行
その次はおそらくforkを使ったプロセスの複製について書きます。
(SECCAMPの応募では時間がなくて全然書けなかったのでぶっちゃけこれを先に書きたい…)
とりあえず導入編はこれで終わり、次回以降実際にシェルの作成の説明をしていこうと思います!
今回こそしっかり最後まで書き上げることを目標にします笑
注釈
*1:GUIシェルなどを考えるとこの定義とは全く異なります。
気になった人は以下リンクなどでより詳しい定義を確認してください。
シェル - Wikipedia
VHDLの基本的な論理演算と型(VHDL入門その1)
VHDLの基本的な論理演算
構文 | 意味 |
---|---|
A<=B | AにBを代入 |
A and B | AとBの論理積 |
A or B | AとBの論理和 |
A xor B | AとBの排他的論理和 |
not A | Aの否定 |
A nand B | not ( A and B ) |
A nor B | not ( A or B) |
A xnor B | not (A xor B) |
VHDLとデータ型
VHDLでは様々なデータ型が用意されているが、標準で用意されているものとライブラリ(パッケージ)を読み込む必要があるものがあるので、以下にその一覧をまとめる。
なお、VHDLでは文字列以外で大文字小文字の区別がない。
標準で用意されている型
データ型 | 内容 |
---|---|
integer | 32ビット符号付き整数 |
real | 浮動小数点 |
bit | ’0’/‘1’ |
bit_vector | bitのベクトル型 |
boolean | true/false |
character | ASCII文字 |
time | 時間の物理タイプ*1 |
severity level | メッセージタイプ*2 |
natural(positive) | integerのサブセット*3 |
string | 文字列 |
IEEE.STD_LOGIC_1164で定義される型
データ型 | 内容 |
---|---|
std_logic | ’0’や’1’、不定値’X’など |
std_logic_vector | std_logicのベクトル型 |
EEE.STD_LOGIC_ARITH
データ型 | 内容 |
---|---|
signed | 符号付き整数 |
unsigned | 符号なし整数 |
IEEE.STD_LOGIC_ARITHは複数あるらしい? 参考サイト *4
STD.TEXTIOパッケージで定義される型
データ型 | 内容 |
---|---|
line | |
text | |
side | |
width |
STDはいまのところ使う予定がないので保留…
GHDLでパッケージstd_logic_arith, std_logic_signed, std_logic_unsigned, std_logic_textio. を使うときは—ieee=synopsysのオプションをつける必要がある
VHDLを試してみた。
この記事はIS17er Advent Calendar 2016 - Adventar23日目の記事として書かれました。
VHDLとは?
VHDL(Very High Speed Integrated Circuit Hardware Description Language)は米国防総省で作られたハードウェア記述言語の一種で、現在はIEEEによって標準化されている。
Adaを参考に作られた言語なので知っていると少し学習しやすいかもしれない...らしい。
(GHDLドキュメント
http://ghdl.readthedocs.io/en/latest/Introduction.htmlより)
IS2017erにとってはハードウェア構成法のレポートで推奨されている言語なので、とりあえず動かしてみたときの記録を書く。
開発環境(GHDL/GTKWave)
GHDLというオープンソースのVHDLシミュレータと、GHDL推奨の波形表示ソフトGTKWaveを用いる。
導入は下記サイトを参考にした。
lv4.hateblo.jp
VHDLの基本構成
VHDLは基本的に、ライブラリ宣言部、エンティティ宣言部、アーキテクチャ宣言部の3つの部分から構成される。
全加算器を例に見てみよう。
adder.vhdl
-- ライブラリ宣言 library IEEE ; use IEEE.STD_LOGIC_1164.ALL ; --エンティティ宣言部 entity adder is port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit); end adder; --アーキテクチャ宣言部 architecture rtl of adder is begin s <= i0 xor i1 xor ci; co <= (i0 and i1) or (i0 and ci) or (i1 and ci); end rtl;
ちなみに、このコードはほぼGHDLのドキュメントhttp://ghdl.readthedocs.io/en/latest/Starting_with_GHDL.htmlのコピー。
ghdlで日本語のコメントが弾かれたので、実際はコメントを消去して保存した。
ライブラリ宣言
C言語でいうincliude文のようなもの。
libraryで使用するライブラリを指定し、useでそのライブラリ中のパッケージ(アイテム)を指定する。
このサンプルコードでは説明のため宣言したが、実際にはライブラリは使用していない。
(#include<stdlib.h>したのにprintfしか使ってない感覚。)
エンティティ宣言
C言語での関数プロトタイプ宣言のようなもの。
エンティティ名、変数名、型、モード(in,out等)を指定する。
アーキテクチャ宣言
C言語での実際の関数記述部分に近い。
内部信号(ローカル変数)を使う際はisとbegeinの間に宣言する。
テストベンチ
上のコードでは単に全加算器を定義しただけなので、実際に動かすためにテストコード(テストベンチと呼ぶことが多いっぽい)
adder_tb.vhdl
-- A testbench has no ports. entity adder_tb is end adder_tb; architecture behav of adder_tb is -- Declaration of the component that will be instantiated. component adder port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit); end component; -- Specifies which entity is bound with the component. for adder_0: adder use entity work.adder; signal i0, i1, ci, s, co : bit; begin -- Component instantiation. adder_0: adder port map (i0 => i0, i1 => i1, ci => ci, s => s, co => co); -- This process does the real job. process type pattern_type is record -- The inputs of the adder. i0, i1, ci : bit; -- The expected outputs of the adder. s, co : bit; end record; -- The patterns to apply. type pattern_array is array (natural range <>) of pattern_type; constant patterns : pattern_array := (('0', '0', '0', '0', '0'), ('0', '0', '1', '1', '0'), ('0', '1', '0', '1', '0'), ('0', '1', '1', '0', '1'), ('1', '0', '0', '1', '0'), ('1', '0', '1', '0', '1'), ('1', '1', '0', '0', '1'), ('1', '1', '1', '1', '1')); begin -- Check each pattern. for i in patterns'range loop -- Set the inputs. i0 <= patterns(i).i0; i1 <= patterns(i).i1; ci <= patterns(i).ci; -- Wait for the results. wait for 1 ns; -- Check the outputs. assert s = patterns(i).s report "bad sum value" severity error; assert co = patterns(i).co report "bad carry out value" severity error; end loop; assert false report "end of test" severity note; -- Wait forever; this will finish the simulation. wait; end process; end behav;
adder.vhdl同様 http://ghdl.readthedocs.io/en/latest/Starting_with_GHDL.htmlから引用。
シミュレーションしてみる
GCCでCのコードをコンパイルするのと同様に、GHDLでVHDLのコードを analysis、elaborateして実行ファイルを生成する。
$ ghdl -a adder.vhdl $ ghdl -a adder_tb.vhdl $ ghdl -e adder_tb
オプション-r で実行する際に、--vcd=で波形データを出力できる
$ ghdl -r adder_tb --vcd=adder_tb.vcd
生成されたファイルはgtkwaveで参照できる
$ gtkwave adder_tb.vcd
gtkwaveのウィンドウが表示されたら、 左下のciとかioとかをメインの黒い部分にドラッグアンドドロップして左上の一番左の虫眼鏡のアイコンで縮尺を合わせると以下の様に表示された。
時間がないのでとりあえずここで終了
VHDLの基本的な論理演算と型(VHDL入門その1) - 情報系学生ランナーの備忘録
参考サイト
初めてでも使えるVHDL文法ガイド ―― 文法ガイド編|Tech Village (テックビレッジ) / CQ出版株式会社
初めてでも使えるVHDL文法ガイド ―― 記述スタイル編|Tech Village (テックビレッジ) / CQ出版株式会社
sublimetextでcを書く(情報科学基礎実験を乗り越えるために)
この記事はIS17er Advent Calendar 2016 - Adventarの8日目の記事として書かれました。
情報科学基礎実験もとうとうアセンブリに突入しニーズが低いとは思いますが、きっと誰かの役に立つと信じてsublimetextでc言語の開発環境?を整えた話を書きます。(僕自身はそろそろvimに乗り換えるつもりなのですが...笑)
Sublimetextとは?
"恋に落ちるエディタ"というキャッチフレーズとともに近年人気のエディタらしい (AtomとかBracketsとか競合に押されている感じもあるが...)
授業ではEmacsが推奨されているがsublimetextの方が気楽に使えると思う。
sublimetextの良さや導入方法については時間がないので下記サイトに丸投げ。
オススメのパッケージ
僕がsublimetextでCやアセンブリを書く際に使っていパッケージを紹介します。
- ConvertToUTF8
- BracketHighliter
- TrailingSpaces
- C Improved
- Clang-Complete
- x86 and x86_64 Assembly
ConvertToUTF8
様々な文字コードをUTF8に変換して使うためのパッケージ ありがたみを感じることはないけど多分ないと文字化けなどで困る
BracketHighliter
カーソルの外側の括弧がわかりやすいようにハイライトをつけるパッケージ
↑行番号の隣と{}の下にハイライトが付いている
TrailingSpace
行末の空白をハイライトしてくれるパッケージ
C Improved
カラースキームが少し良くなる
C Improved - Packages - Package Control
Clang-Complete
⌘
+s
でセーブすると文法ミスを指摘してくれる他、
Cの各種関数の自動補完とかもしてくれる。
コンパイルせずにエラーがわかるし、エラー箇所を直接ハイライトしてくれるから結構助けられてる。
x86 and x86_64 Assembly
アセンブリをハイライトしてくれる
x86 and x86_64 Assembly - Packages - Package Control
C言語用ビルドシステム
sublimetextでは⌘
+b
でソースコードをsublimetextのコンソール上でコンパイル及び実行してくれる...
はずが、デフォルトではC言語用の設定がなかったので自分で書いた。
上のようにTools>Build System> New Build Systemで新しい設定ファイルを作成。
今回はC言語用なので C.sublime-buildという名前で保存
{ "selector":"sourse.c", "cmd":["gcc","-Wall","$file","-o","${file/c/out/}"], "variants":[ { "name":"実行", "cmd":"${file/c/out/}" }, { "name":"debag", "cmd":["gcc","-Wall","-g","$file","-o","${file/c/out/}"] }, { "name":"アセンブリ出力", "cmd":["gcc","-Wall","-S","$file","-o","${file/c/s/}"] }, { "name":"コンパイルのみ", "cmd":["gcc","-Wall","-c","$file","-o","${file/c/o/}"] } ] }
以上のように(JSON形式で)設定を書くと⌘
+b
でソースコードをコンパイルしてくれる
(例えばhello.cを書いている時は$ gcc -Wall hello.c -o hello.out
を実行してくれる)
実行したりアセンブリを出力したかったりする時は⌘
+shift
+b
から他のコマンドを選択して実行できるようにした。
それぞれの設定の意味は察してください笑
(詳しい説明は下記のサイトとかを見るといいかもしれない)
Build Systems — Sublime Text Unofficial Documentation
なんだかグダグダになってしまったのでそのうち真面目に書き直すつもりですが、解決方法、アドバイス等あれば教えていただけるとありがたいです。
最後まで読んでくれた方々ありがとうございました!!
ログインシェルの変更(Mac,zsh)
macの標準のシェルはbashだが、zshの方が何かと便利らしいのでとりあえず導入。
zshの導入
zshも標準でインストールされているものがあるが、バージョンが古かったので先日導入したbrewを使ってzshをインストール
brew install zsh
確かこれでインストールしたはず。(zshのインストール自体は少し前にしたので記憶が曖昧)
$ which -a zsh /bin/zsh /usr/local/bin/zsh
後者がbrewでインストールした方だろうということでディレクトリを覗いてみると、zshとzsh-5.2と2つのコマンドがあったのでバージョンを調べてみた。
$ /usr/local/bin/zsh --version zsh 5.2 (x86_64-apple-darwin15.6.0) $ /usr/local/bin/zsh-5.2 --version zsh 5.2 (x86_64-apple-darwin15.6.0)
知らなかったけど、どうやら両方エイリアスで、本体はbrewでインストールしたソフトはusr/local/Cellar/以下にあるらしい。 ビールの保管場所がセラーってなんか良い...なんて思いつつ次に行くことにします。
ログインシェルの変更
ログインシェルに変更できるシェルは/etc/shellsに記載されているシェルらしいので確認
$ cat /etc/shells # List of acceptable shells for chpass(1). # Ftpd will not allow users to connect who are not using # one of these shells. /bin/bash /bin/csh /bin/ksh /bin/sh /bin/tcsh /bin/zsh
今回はbrewでインストールしたzshを使いたいので末尾に/usr/local/bin/zsh
を追加
最後にログインシェルを変更するためにchsh -s /usr/local/bin/zsh
を実行
(多分chshはchange shell の略)
ターミナルを再起動しログインシェルが変更されているか確認
$ echo $SHELL /usr/local/bin/zsh
どうやら正しく変更できたらしい。
参考サイト
パッケージマネージャーの導入
Macの入手から数日...
中間試験やら走っていたりやら、Macに触りたくても触れない生活が続いた末にようやく少し遊ぶ余裕ができたので、少し環境構築を進めることができたので投稿。
Windowsと異なりせっかく標準でターミナル環境が整っていることだしソフトウェアの管理もパッケージマネージャーでやることにした。
今までyumとかpacmanとかは少しかじったことはあったけど、Macに関しては全く前提知識がなく、MacportsとかFinkとか複数選択肢があることに若干驚きつつ、学科でなんとなく聞いたり、日本語の情報が多そうなのでHomebrew/caskを導入することにした。
Xcodeのインストール
brewのインストールに関わらずMacでの開発環境の構築にはXcodeをインストールすべきということで導入。
なんとなくでインストールしたので記録もほとんどないが、4GB以上あってダウンロードに結構時間がかかった気がする。
Homebrewのインストール
googleで検索かけたらトップに日本語公式サイトが出てきたのでページを開く。
指示通りスクリプトをターミナルに貼り付けて実行
参考サイト
MacにHomebrewを導入する方法&使い方まとめ | vdeep
の記述通り
$ brew doctor
を実行
Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry and just ignore them. Thanks!
Warning: You have Xcode 8 installed without the CLT;
this causes certain builds to fail on OS X El Capitan (10.11).
Please install the CLT via:
sudo xcode-select --install
と帰ってきた。
どうやらXcodeのCLT(Command Line Tools)がインストールされていないようなので
$ sudo xcode-select --install
でインストール
その後下記を実行
$ brew doctor
Your system is ready to brew.
とりあえずうまくインストールできたらしい。
使い慣れない環境だとこんな小さなことにも一喜一憂してしまう。
学科のアドベントカレンダーに投稿できそうなネタはしばらく書けそうもないです。
まぁそこらへんはプロにお任せするとして少しずつブログの投稿を稼いで行きます。