# skull-shell

C++20 と xmake で作った、小さな POSIX 風シェルです。

## SkullOS について

この shell は、自作 Linux ディストリビューション **SkullOS** の標準 shell です。SkullOS 側は別 repository として管理します。

## ビルド

```sh
xmake
```

## 実行

```sh
xmake run skull
```

## プロジェクト構成

```text
src/
  builtins/   cd, pwd, exit などの組み込みコマンド
  core/       シェルのメインループと共通データ構造
  executor/   fork、exec、パイプ、リダイレクト、プロセス待機
  parser/     コマンドラインの字句解析とパース
  ui/         プロンプト、起動バナー、補完、履歴操作
```

## 現在の機能

- 起動時の skull バナー
- 履歴付きの独自対話プロンプト
- `~/.skull_history` への履歴保存
- `Up` / `Down` による通常の履歴移動
- `Ctrl-R` による複数単語履歴検索
- `Right` / `Tab` / `Ctrl-F` で採用できるインライン autosuggestion
- `Tab` によるコマンド名・パス補完
- `fork` + `execvp` による外部コマンド実行
- 組み込みコマンド: `help`, `alias`, `unalias`, `dlload`, `dlcall`, `dllist`, `dlunload`, `jobs`, `fg`, `bg`, `prompt`, `cd`, `pwd`, `exit`
- 起動設定ファイル: `~/.skullrc`, `~/.skullrc.d/*`
- シングルクォートとダブルクォート
- バックスラッシュエスケープ
- 環境変数展開: `$HOME`, `${HOME}`, `$?`
- 環境変数設定: `export`, `NAME=value`, `NAME=value command`
- glob 展開: `*.cpp`
- パイプ: `|`
- 条件実行: `&&`, `||`
- 入力リダイレクト: `<`
- 出力リダイレクト: `>`, `>>`
- 末尾 `&` によるバックグラウンド実行
- 基本的なジョブ制御: `jobs`, `fg`, `bg`
- `prompt` または `$SKULL_PROMPT` によるプロンプトカスタマイズ

## 例

```sh
pwd
cd /tmp
echo "hello skull" | wc -c
printf "one\ntwo\n" > lines.txt
cat < lines.txt
sleep 10 &
jobs
fg %1
alias ll='ls -la'
ll
unalias ll
prompt 'skull:{cwd}:{status}> '
exit
```

alias は各コマンドの先頭単語に対して展開されます。パイプ内の各コマンドにも適用されます。alias は現在のシェルセッション内で有効です。

## デフォルト alias

Skull Shell はユーザーの起動設定ファイルを読む前に、次の alias を追加します。

```sh
alias cls='clear'
alias ll='ls -la'
```

`~/.skullrc` または `~/.skullrc.d/*.skull` で上書きできます。

## 起動設定ファイル

Skull Shell はまず `~/.skullrc` を読み込み、その後 `~/.skullrc.d/` にある通常ファイルのうち、拡張子が `.skull` のものだけを名前順で読み込みます。空行と `#` で始まる行は無視されます。

```sh
# ~/.skullrc
alias ll='ls -la'
alias gs='git status'
```

```sh
# ~/.skullrc.d/10-navigation.skull
alias ..='cd ..'
```

起動設定ファイルでは、現在 `alias`, `unalias`, `cd`, `pwd`, `prompt`, `export` などの組み込みコマンドを利用できます。起動時に外部コマンドは実行しないようにしています。

## 動的モジュール

Skull Shell は `dlopen` で共有ライブラリを読み込み、`dlsym` で C ABI の関数を呼び出せます。

```sh
dlload ./libhello.so hello
dllist
dlcall hello skull_echo hello from plugin
dlunload hello
```

呼び出せる関数は次の ABI にしてください。

```cpp
extern "C" int skull_echo(int argc, char** argv) {
    // argv[0] は関数名、argv[1...] は dlcall から渡された引数です。
    return 0;
}
```

Linux で plugin をビルドする例:

```sh
g++ -std=c++20 -fPIC -shared plugin.cpp -o libhello.so
```

## 履歴・補完

`Up` / `Down` で通常の履歴移動ができます。`Ctrl-R` を押すと複数行の履歴検索が開き、1つ以上の単語を入力すると、それらをすべて含む履歴だけに絞り込まれます。マッチした単語は色付きでハイライトされ、選択中の候補も強調表示されます。`Ctrl-R` をもう一度押すと候補を巡回します。検索結果はページングされ、表示中の最後の候補を越えると次ページへ移動します。検索リストを開いている間の `Enter` は選択中の履歴をプロンプトに入れるだけで、もう一度 `Enter` を押すと実行します。

過去のコマンドの先頭を入力すると、薄い色でインライン autosuggestion が表示されます。行末で `Right`, `Tab`, `Ctrl-F` を押すと採用できます。

autosuggestion がない場合、`Tab` は行頭のコマンド名、またはそれ以外の位置のファイルパスを補完します。

## プロンプト

テンプレートでプロンプトを変更できます。

```sh
prompt 'skull:{cwd}:{status}> '
export SKULL_PROMPT='[{status}] {cwd}$ '
prompt '{user}@{hostname}:{cwd}{symbol} ||{if:ssh?SSH:local} {time} {if:success?ok:bad}'
prompt '{green}{user}{reset}@{cyan}{hostname}{reset}:{yellow}{cwd}{reset}{symbol} '
```

`||` で左プロンプトと右プロンプトを分けられます。右プロンプトは端末幅に余裕があるときだけ、薄い色で右寄せ表示されます。

利用できるフィールド:

| フィールド | 意味 |
| --- | --- |
| `{user}` | `USER` または `LOGNAME` から取得した現在のユーザー |
| `{hostname}` | `gethostname` から取得したホスト名 |
| `{cwd}` | 現在のディレクトリ名 |
| `{path}` | 現在のディレクトリのフルパス |
| `{path:~}` | `$HOME` を `~` に省略した現在のディレクトリパス |
| `{status}` | 直前コマンドの終了ステータス |
| `{date}` | ローカル日付。形式は `YYYY-MM-DD` |
| `{time}` | ローカル時刻。形式は `HH:MM:SS` |
| `{uid}` | 数値のユーザー ID |
| `{symbol}` | root なら `#`、それ以外なら `$` |

色とスタイルのフィールド:

| フィールド | 意味 |
| --- | --- |
| `{reset}` | 色とスタイルをリセット |
| `{bold}` | 太字 |
| `{dim}` | 薄い表示 |
| `{black}` / `{red}` / `{green}` / `{yellow}` | 通常の前景色 |
| `{blue}` / `{magenta}` / `{cyan}` / `{white}` | 通常の前景色 |
| `{bright_black}` / `{bright_red}` / `{bright_green}` / `{bright_yellow}` | 明るい前景色 |
| `{bright_blue}` / `{bright_magenta}` / `{bright_cyan}` / `{bright_white}` | 明るい前景色 |

条件付きプロンプト式:

| 式 | 意味 |
| --- | --- |
| `{if:ssh?yes:no}` | SSH セッションなら `yes`、それ以外なら `no` |
| `{if:root?yes:no}` | uid 0 なら `yes`、それ以外なら `no` |
| `{if:success?yes:no}` | 直前ステータスが 0 なら `yes`、それ以外なら `no` |
| `{if:env:NAME?yes:no}` | 環境変数 `NAME` が設定されていて空でなければ `yes`、それ以外なら `no` |
| `{if:name?yes:no}` | `name` という独自条件が終了ステータス 0 なら `yes` |

独自条件は `condition` で登録できます。コマンドは `/bin/sh -c` 経由で実行され、終了ステータス 0 が true です。

```sh
condition git='test -d .git'
condition wsl='grep -qi microsoft /proc/version'
prompt '{cwd} {if:git?git:}{symbol} ||{if:wsl?WSL:linux}'
condition git=
```

## ジョブ制御

`&` でコマンドをバックグラウンド実行し、`jobs` で確認し、`fg` でフォアグラウンドに戻せます。

```sh
sleep 30 &
jobs
fg %1
```

## 環境変数と glob

コマンド実行前に環境変数が展開されます。`$?` は直前コマンドの終了ステータスに展開されます。`*.cpp` のような glob は、ファイルにマッチした場合に展開されます。マッチしなかった glob はそのまま残ります。

環境変数は `export`、代入だけのコマンド、一時的なコマンド環境で設定できます。

```sh
export HOGE=huga
export FOO = bar
ONLY=value
TEMP=one env
TEMP = two env
```

`NAME=value command` と `NAME = value command` は、そのコマンドだけに環境変数を渡します。コマンドなしの代入は、シェル自身の環境を更新します。

## ライセンス

Skull Shell は Apache License 2.0 で公開します。
