DASH村

Table of Contents

Lisp-1 or Lisp-2

shellとまともに向き合い始めて、あの問題がどうなってるか興味が湧いてきた。 そう、Lisp/schemeをやってきた身としては、避けて通れない問題。Lisp-1/Lisp-2。

同名のシンボルで変数と関数を区別できるかってやつね。

ad$ cat test.sh
add='This is var'

add(){
    echo 'Arg are' $1 'and' $2
    res=$(($1 + $2))
}

main() {
    echo 'Call add'
    add $1 $2
}

addと言う変数と関数を定義してみた。

ad$ . ./test.sh
ad$ type add
add is a function
ad$ echo $add
This is var
ad$ add 3 4
Arg are 3 and 4

そしてそれをkshに読み込み。typeで、お前は何だと確認。答は関数だった。じゃ、 変数として参照したら? 変数の素振りを見せたぞ。ちゃんと計算するんかい? どうやら大丈夫だった。Lisp-2系の挙動を示した。

ad$ typeset -ft add
ad$ type add
add is a traced function
ad$ main 3 4
Call add
+ echo Arg are 3 and 4
Arg are 3 and 4
+ res=7
ad$ typeset -f +t add
ad$ type add
add is a function

ちょっと余力で、関数にトレース属性を付けてみる。関数内がくまなくトレースされる 所がLispのそれ(入口と出口のみ)と、ちょっと違う。指定した関数だけ、トレース 結果(行頭に+が付く)を表示してくれた。解除は、+t 。ちょっと + で解除って 直感に反するけど、shell全体がそういう体系になってるんで慣れろ。 ますますもってLispだなあ。

ad$ alias p='echo'
ad$ p $add
This is var
ad$ type p
p is an alias for echo

rubyのputとpみたいな事もOK。

久しぶりにチャッピー(若い人はChatGPTをこう呼ぶらしい)に聞いてみた。

A: とても良い質問です。 結論から言うと、/はい、これは POSIX シェルや ksh などの伝統的な Bourne 系シェルの仕様に基づく挙動であり、関数名と変数名が独立した名前空間を持つため、同名で共存できます。/ つまり、shell は Lisp-2 的な言語 です。


### 🔹 背景:Lisp-1 / Lisp-2 問題とは

分類 関数と変数の名前空間
---------- -------------------- ------------------------------------------
Lisp-1 共有している(関数と変数は同一名前空間) Scheme など。変数 add があれば (add 3 4) に影響する。
Lisp-2 分離している(関数と変数は別の名前空間) Common Lisp、Emacs Lisp、そして多くの shell。

シェルも「関数名の空間」と「変数名の空間」が別々になっているため、同じ名前を使っても衝突しません。


### 🔹 実際の挙動(例:ksh, bash, dash)

あなたの例のように:

add='This is var'

add() {
    echo "Arg are $1 and $2"
    res=$(($1 + $2))
}
  • $add → 変数を参照(文字列 ="This is var"=)
  • add 3 4 → 関数を実行

どちらも問題なく動作します。

type add では関数定義が優先的に表示されますが、変数も同時に存在します。


### 🔹 POSIX の仕様上どうなっているか

POSIX sh の仕様では、

  • 関数名はコマンド名の名前空間に属する
  • 変数は変数の名前空間に属する と定義されており、これらは独立しています。

参照: https://pubs.opengroup.org/onlinepubs/9699919799/

関数を定義する name() 構文で作られる「関数名」は、コマンド呼び出しの際に探索される「コマンド検索パス」の一種として扱われるので、 add をコマンドとして実行すると「関数名の表」をまず見る → 見つかれば実行。 一方 $add のように $ 展開を使う場合は、変数名のテーブルを見る。 この二つはまったく別の辞書(ハッシュテーブル)に保持されています。


### 🔹 注意点

  • 同名の/組み込みコマンド/や/外部コマンド/と関数名が衝突する場合は挙動が異なります。 (=type= や command による解決順に依存)
  • unset は関数と変数を区別できます:
unset add       # 変数だけ削除
unset -f add    # 関数だけ削除

### 🔹 まとめ

項目 内容
-------- ----------------------------------------
同名の関数と変数 共存可能
理由 関数と変数が別の名前空間を持つ(Lisp-2 的設計)
仕様 POSIX で許可されている。ksh, bash, zsh, dash でも同様
確認コマンド type, declare, set, unset -f

ついでにもうひとつ質問

Q: shellでの整数の範囲って規定が有りますか? それとも実装依存ですか?

A: とても良い質問です。 結論から言うと:

POSIXシェルでは整数の範囲は規定されておらず、実装依存です。


## 🔹 POSIX仕様上の扱い

POSIX(=IEEE Std 1003.1=)の =Arithmetic Expansion=(算術展開 =$(( … ))=)に関する定義を確認すると:

The arithmetic evaluation shall be performed using signed long arithmetic. The range and precision of arithmetic operations shall be implementation-defined.

つまり:

  • /型は「符号付き long 整数」/として扱うことが/推奨(not required)/されているが、
  • 実際のビット幅や範囲は「実装定義(implementation-defined)」 です。

参考: 🔗 POSIX spec – Arithmetic Expansion


## 🔹 各実装の実際の範囲

シェル 内部実装 整数範囲 備考
--------- -------------------- --------------------------------- ----------------------------------
bash =long=(C言語の符号付きlong) 32bit環境では ±2³¹−1、64bit環境では ±2⁶³−1 $(( ... )) はCのlong算術を使用
dash 同上 環境依存(long) オーバーフロー時は符号反転(wrap)
ksh93 独自整数型(通常long) 環境依存(多くは64bit) printf "%T\n" $(( ... )) などで型確認可
zsh Cのlong ±2⁶³−1(64bit環境) setopt C_BASES などで表記変更可能

## 🔹 動作確認の例

64bit Linux上のbashでは:

$ echo $((9223372036854775807))   # 2^63-1
9223372036854775807

$ echo $((9223372036854775808))   # +1 でオーバーフロー
-9223372036854775808

long のオーバーフローとしてラップアラウンドします。


## 🔹 小数・浮動小数はどうなる?

POSIXシェル(および bash, dash, ksh, zsh などの標準モード)では 整数演算のみ が定義されており、 浮動小数点演算(=3.14 + 2.71= など)は/未定義動作(エラーまたは0)/ になります。

例:

$ echo $((3.14 + 2))
bash: 3.14: syntax error: invalid arithmetic operator

ただし:

  • ksh93 は拡張で 浮動小数点演算 をサポートします。
$ ((x = 3.14 + 2))
$ echo $x
5.14

## 🔹 まとめ

項目 内容
-------------- -----------------------------------------------
整数範囲 POSIX上は/実装定義/(多くはCの long に依存)
オーバーフロー wrap-around(符号付きlongとしての動作)
浮動小数 標準POSIXシェルでは非対応(ksh93のみ対応)
典型的範囲(64bit環境) −9223372036854775808+9223372036854775807

勿論、多倍長演算なんてサポートされていない。rubyは偉いぞ。

DASH村

Lubuntuの/bin/shは正体が/bin/dashだった。ならばOpenBSDにも入れておこうと 思った。portsになってるんで、一発導入が可能なんだけど、それじゃつまらん。 昭和の時代に戻って、由緒正しく入れてみる。まあ、INSTALLを見てそのまま実行 するだけだけど。

CFLAGS=-g ./configure; gmake; gmake install

manが変な所に鎮座しちゃったので、それだけは配置しなおしたよ。

ob$ ldd /usr/local/bin/dash
/usr/local/bin/dash:
        Start    End      Type  Open Ref GrpRef Name
        1b001000 3b004000 exe   1    0   0      /usr/local/bin/dash
        00b00000 20b11000 rlib  0    1   0      /usr/lib/libc.so.100.3
        00de8000 00de8000 ld.so 0    1   0      /usr/libexec/ld.so

portsのレシピでは、こうなっていた。

CONFIGURE_ARGS =        --enable-fnmatch --enable-glob --with-libedit
CONFIGURE_ENV +=        LIBS="-ledit -lcurses"

.if ${FLAVOR:Mstatic}
# --enable-static will fail because it also brings --Wl,--fatal-warnings
CONFIGURE_ENV +=        LDFLAGS="${STATIC}"
.endif

編集機能付けときました。外部ライブラリィーを全部取り込むバージョンも有りますぜ。 kshは外部依存無しだったよ。これがOpenBSD流。

sakae@lu:~$ ldd /usr/bin/dash
        linux-vdso.so.1 (0x00007fffe9ffe000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000074b258a00000)
        /lib64/ld-linux-x86-64.so.2 (0x000074b258c9b000)

Linuxのそれも、staticにはしてないですね。

with gdb

記念にgdbしてみる。

ob$ gdb -q dash
Reading symbols from dash...
(gdb) b set TAB TAB
setcmd          setjmp.h        setpgid@plt     setstackmark
setinputfile    setjmp@plt      setpwd          setvar
setinputstring  setjobctl       setrlimit       setvareq
setinteractive  setparam        setrlimit@plt   setvarint
setjmp          setpgid         setsignal
(gdb) b setvar
Breakpoint 1 at 0x1b219: file var.c, line 197.

まだソースを1行たりとも見ていないのだけど、gdbのbreakコマンドで補完して、 それらしい関数を出現させた。山勘で止る所を指示。そして実行。

(gdb) r
Starting program: /tmp/dash-0.5.12/src/dash

Breakpoint 1, setvar (name=0x1ad13eb6 "PWD",
    val=0x60f24d80 "/tmp/dash-0.5.12/src", flags=1) at var.c:197
197             q = endofname(name);
(gdb) bt
#0  setvar (name=0x1ad13eb6 "PWD", val=0x60f24d80 "/tmp/dash-0.5.12/src",
    flags=1) at var.c:197
#1  0x1ad1b0c5 in setpwd (val=0xcf7be899 "/tmp/dash-0.5.12/src", setold=0)
    at cd.c:331
#2  0x1ad2e1dc in init () at init.c:208
#3  0x1ad237c2 in main (argc=1, argv=0xcf7be774) at main.c:146
(gdb) c
Continuing.
$ hoge="hello"
$ exit
[Inferior 1 (process 35378) exited normally]
(gdb)

PWDは変数っぽいけど、自前の変数指定では、駄目だなあ。

on Lubuntu

リナ側にもgdb用のdashを作ってみた。ソースの取り寄せ方法は下記に習う。

mkdir work; cd work; apt source dash

インストールしない、srcの中に鎮座してる奴ね。 少し規模を探ってみる。

sakae@lu:src$ nm dash  | grep ' T ' | wc
    168     504    4789
sakae@lu:src$ nm dash  | grep ' D ' | wc
     20      60     570
sakae@lu:src$ nm dash  | grep ' B ' | wc
     63     189    1819

普通に規模と言えばソースの本数とか総行数なんだろうけど、バイナリアンは、こういう 分析もする訳ですよ。

チャッピーに聞いてみる

さて、この先どうする? 専用の執事が居るじゃん!!

Q: OpenBSD上にdashとkshのデバッグ環境を作成して、関数と変数の名前空間が どの様に実現されているか調査したいです。 Lisp-2的な扱いとの事なんで、シンボル(名前)に対して、値スロットと関数スロットが 用意されるのかと最初に想像しました。

でもそれじゃ、余りにLisp的過ぎます。きっと変数/関数用にハッシュテーブルでも 用意するのが、妥当だろうと考えました。何はともあれ、REPLになってるはずだろう から、どんな構造か手始めに調べます。下記はdashを動作させておき、そこにgdbを アタッチして、REPLの一旦を覗いてみたものです。

(gdb) bt
#0  _thread_sys_read () at /tmp/-:2
#1  0x0197a276 in _libc_read_cancel (fd=0, buf=0x39435bd8 <basebuf>,
    nbytes=1024) at /usr/src/lib/libc/sys/w_read.c:27
#2  0x19448491 in preadfd () at input.c:198
#3  0x194482aa in preadbuffer () at input.c:247
#4  0x194479c3 in __pgetc () at input.c:138
#5  0x194477f0 in pgetc () at input.c:159
#6  0x1945199e in pgetc_eatbnl () at parser.c:839
#7  0x19451f8a in xxreadtoken () at parser.c:796
#8  0x19451b07 in readtoken () at parser.c:699
#9  0x1944fafc in list (nlflag=1) at parser.c:170
#10 0x1944f9b7 in parsecmd (interact=1) at parser.c:156
#11 0x1944c057 in cmdloop (top=1) at main.c:218
#12 0x1944bef5 in main (argc=1, argv=0xcf7fb204) at main.c:176

この先、どう効率良く(なるべくソース内をさ迷よわなく)調査したら良いでしょうか?

A: 非常に鋭い問題意識ですね。 しかも gdb のスタックトレースまできっちり取られていて、方向性も正しいです。

ここから dash / ksh における「変数と関数の名前空間」実装を効率的に調査する ための手順を整理します。 dash のコード構造を前提に話しますが、ksh も同じ方針で追えます(ただし ksh はより複雑で、変数型がオブジェクト寄りに抽象化されています)。


## 🧭 調査の全体戦略

シェルの実装は大雑把に言えば次の3段階で動いています:

字句解析 → 構文解析 → 抽象構文木(AST)生成 → 実行(eval / execute)

このうち 名前空間の処理 は主に以下の2箇所で現れます:

  1. 「変数展開」処理$foox=1 のようなケース → var.c / expand.c あたり。
  2. 「コマンド実行」処理foo 1 2 のように呼び出すとき → exec.c / eval.c あたり。

これらがそれぞれ 別のテーブルを参照している ことを突き止めれば、「Lisp-2的分離」の根拠が見えてきます。


## 🧩 dash の場合

### 1️⃣ ソース構造の把握

src/bin/dash/ に主要ファイルがあります(OpenBSD でもほぼ同じ構成):

ファイル 役割
------------ -----------------------
parser.c 字句・構文解析(トークンからAST生成)
eval.c ASTを評価(関数呼び出し、組み込み実行など)
exec.c コマンド検索・実行
var.c 変数テーブル管理(set/unset/get)
input.c REPL入力
memalloc.c 簡易アロケータ
nodes.c ASTノード定義
options.c シェルオプション管理

### 2️⃣ 関数名の登録箇所を探す

関数定義(=name() { … }=)が入力されたとき、どこで関数表に登録されるかを辿ります。

#### 経路

parser.c → eval.c → evaltree() → evalfunctiondef()

関数定義ノード (NDEFUN) の評価を行う箇所を確認:

/* eval.c */
case NDEFUN:
    defun(node->narg.text, node->narg.np);
    break;

defun() が本体。

#### defun() の中身(dash/src/eval.c)

void defun(char *name, union node *func)
{
    struct cmdentry entry;
    entry.cmdtype = CMDFUNCTION;
    entry.u.func = func;
    hashcmd(name, &entry);
}

ここで出ました:/=hashcmd()=/。 これが関数名(コマンド名)を ハッシュテーブルに登録 する部分です。


### 3️⃣ hashcmd() の正体を掴む

exec.c に定義されています。関数名と外部コマンドを管理する「コマンドテーブル」です。

struct tblentry {
    struct tblentry *next;
    char *cmdname;
    struct cmdentry cmdtype;
    ...
};

STATIC struct tblentry *cmdtable[CMDTABLESIZE];

つまり「関数名」はここに格納されます。 hashcmd() は単に cmdtable[] に突っ込む関数です。 このテーブルには関数・外部コマンド・組み込みコマンドが区別されて入ります。

関数名は「コマンドテーブル」に登録される。


### 4️⃣ 変数は別経路

一方、変数代入・展開は var.c で処理されています。

  • 変数は struct var 構造体で表現
  • varhash() により vartab[] テーブルに登録
struct var {
    struct var *next;
    int flags;
    char *name;
    char *text;
};

STATIC struct var *vartab[VTABSZ];

つまり 関数テーブル (cmdtable)変数テーブル (vartab) が完全に独立している。 これが「Lisp-2的な名前空間分離」の直接的根拠です。


### 5️⃣ 実行時にどちらが参照されるか確認する

  • $add のような 展開expand.clookupvar()vartab
  • add 1 2 のような コマンド実行exec.cfind_command()cmdtable

なので $addadd() が独立して動くわけです。


## 🧠 効率的な調査ルート(おすすめ)

gdb で REPL にアタッチした状態で、以下の関数にブレークポイントを貼ると、 「どちらの名前空間が参照されているか」をダイナミックに観察できます。

(gdb) b hashcmd          # 関数定義時
(gdb) b find_command     # コマンド実行時
(gdb) b setvar           # 変数代入時
(gdb) b lookupvar        # 変数展開時

REPLで以下を順に打ちます:

add='This is var'
add() { echo hi; }
echo $add
add

すると:

  • setvar$add 変数が登録される(vartab参照)
  • hashcmd → 関数 add が登録される(cmdtable参照)
  • lookupvar → 変数展開(vartab参照)
  • find_command → 関数呼び出し(cmdtable参照)

これで dash の「2空間構造」が完全に見えるはずです。


## 🧩 ksh の場合

ksh はもう少しオブジェクト指向的で、 struct namnodstruct Namval で「変数・関数・配列」を統一的に扱います。 ただし、関数名と変数名は 異なる名前空間のツリーに登録 されます。

該当箇所:

  • src/cmd/ksh93/sh/namval.c
  • =src/cmd/ksh93/sh/xec.c=(関数登録)
  • nv_search() / nv_open() などがエントリーポイント

ksh93は dash より抽象的ですが、思想は同じく Lisp-2構造 です。


## ✅ まとめ

要素 dashの実装 ファイル 確認関数
------ -------------- -------- -----------------------------
変数の管理 vartab[] var.c setvar(), lookupvar()
関数の管理 cmdtable[] exec.c hashcmd(), find_command()
両者の分離 完全に独立
名前空間構造 Lisp-2的(別ハッシュ)

Q: dashをコンパイルした時、下記の様な副産物が生成されていました。 名前からして(mkXXX)、dashで使用する関数とかを生成している様に想像 しますが、何をやっているか説明して下さい。 本来ならMakefileを参照すれば済む話とは思いますが、GNUのMakefileは 複雑すぎて、人間が読む物では無いです。

te$ ls -l | grep rwx
drwxr-xr-x  3 sakae  sakae     512 Oct 13 06:39 bltin/
-rwxr-xr-x  1 sakae  sakae  343564 Oct 13 06:40 dash*
-rwxr-xr-x  1 sakae  sakae   25796 Oct 13 06:39 mkinit*
-rwxr-xr-x  1 sakae  sakae   19892 Oct 13 06:39 mknodes*
-rwxr-xr-x  1 sakae  sakae    9828 Oct 13 06:39 mksignames*
-rwxr-xr-x  1 sakae  sakae   18268 Oct 13 06:39 mksyntax*

A: 素晴らしい観察眼です。 その通り、=mkinit=, mknodes, mksignames, mksyntax はすべて /dash 自身のビルド時に使われるコード生成ツール/(いわば /self-hosted preprocessor utilities/)です。 これらは dash の実行ファイルにはリンクされませんが、/dash の構文解析や初期化処理に必要なCソースを自動生成する/ための中間ツール群です。


## 🧩 概要一覧

ツール名 生成するファイル 主な目的
-------------- ---------------------- -----------------------
mksyntax syntax.c, syntax.h 文字クラス・構文表の生成(字句解析器用)
mknodes nodes.c, nodes.h 抽象構文木(AST)ノード構造体定義を自動生成
mkinit init.c モジュール初期化関数テーブルを生成
mksignames signames.c シグナル番号とシグナル名の対応テーブル生成

これらはすべて dash の src/ ディレクトリに含まれており、 make 実行時に最初にビルドされ、/同じソースツリー内の .def ファイルやヘッダ定義を入力にしてCファイルを出力/します。 順に詳しく説明します。


## 🧱 1. mksyntax — 構文解析用の文字クラス表生成器

目的: dash の字句解析器(=parser.c= / =syntax.c=)が参照する「文字クラス表」を生成します。 これは「この文字は空白?」「変数名の一部として有効?」「引用符?」などを効率的に判断するための静的テーブルです。

入力: syntax.c のテンプレート定義(例: syn.c.in=)や、定数マクロ定義 /出力:/ =syntax.c, syntax.h

生成されるものの例:

const char syntax[256] = {
    /* 0-31: control chars */
    SYN_EOF, SYN_EOF, ..., SYN_EOF,
    /* 32 ' ' */
    SYN_SP,
    /* 33 '!' */
    SYN_META,
    ...
};

機能的にはis_namechar(), is_space(), is_meta() のような判定を if文ではなく配列参照で一発で行うための表を作る。


## 🌳 2. mknodes — ASTノード構造体自動生成器

目的: 構文解析後の「抽象構文木(AST)」ノード構造体群を定義する nodes.c, nodes.h を自動生成します。

入力: nodetypes というテキスト定義ファイル。 各ノードの種類・フィールド・型などが記述されています。

出力: nodes.c, nodes.h

例(入力ファイル一部)

name=command
    next: NODEPTR
    words: NODEPTR
    redirects: NODEPTR

生成結果(出力)

struct ncmd {
    int type;
    struct ncmd *next;
    struct narg *words;
    struct nfile *redirects;
};

→ dash の parser.c でASTを生成するとき、これらのノード構造体が使われます。

ポイント: dashのノード定義は可変的(ノード種ごとにフィールドが違う)なので、 人手で書くより mknodes に自動生成させる方が安全。


## 🔧 3. mkinit — モジュール初期化関数テーブル生成器

目的: dash の各ソースファイルに存在する INIT, RESET, UNREGISTER マクロをスキャンし、 それらを統合して起動時・終了時に呼ばれる初期化ルーチン init.c を生成します。

入力: ソース中のマクロ宣言(例:=INIT { … }=)

出力: init.c

生成されるもの(例)

void init(void) {
    init_input();
    init_var();
    init_eval();
    ...
}

→ シェル起動時にこれが呼ばれ、全モジュールを初期化します。

備考: dash は小型ながらモジュール構造を持っており、 モジュール間の初期化順序を mkinit が決める仕組みになっています。


## ⚡ 4. mksignames — シグナル名テーブル生成器

目的: シグナル番号(=SIGINT=, SIGTERM, …)と文字列("INT", "TERM")の対応テーブルを生成します。 kill, trap などで必要になります。

入力: /usr/include/sys/signal.h をパースする (プラットフォーム依存部分を正しく取得するために、ビルド環境のヘッダを直接読む)

出力: signames.c

出力例:

const char *const sys_signame[NSIG] = {
    "HUP", "INT", "QUIT", "ILL", ...
};

→ dash の trap コマンドやエラーメッセージで使用。


## ⚙️ 生成順序のイメージ

簡略化した依存関係を図にすると:

mksyntax ─┬─> syntax.c, syntax.h
mknodes  ─┬─> nodes.c, nodes.h
mkinit   ─┬─> init.c
mksignames─┬─> signames.c
             │
             ▼
            dash (本体ビルド)

すべて make 実行時に「まずこれらツールをビルド → 実行してCファイルを出力 → dash本体をコンパイル」という流れになります。 つまり dash は自分の部品を部分的に「自己生成」してからリンクされる 仕組みです。


## 🧠 参考: 効率的な読み方のコツ

  • mknodes.c, mkinit.c, … はそれぞれ単独で完結しており短い(数百行)ので、 dash本体より先に読むと「この生成物がどの部分を支えているか」が分かりやすい。
  • make V=1 で実際の生成コマンドを見ると理解が進みます。 例:
./mknodes nodetypes > nodes.c
./mkinit *.c > init.c
./mksyntax > syntax.c
  • 生成結果 (syntax.c, nodes.c etc.) を diff で追うと、構文要素がどの段階で固定化されているかがわかります。

## ✅ まとめ

ツール 役割 出力ファイル 内容
------------ ----------- ------------ --------------------
mksyntax 字句解析用の構文表生成 syntax.c/h 文字クラス表
mknodes ASTノード定義生成 nodes.c/h ノード構造体
mkinit 初期化関数リスト生成 init.c モジュール初期化ルーチン
mksignames シグナル名テーブル生成 signames.c SIGINT → "INT" 対応表

チャッピーは現代のBSD magazineの役目を果してくれているな。最近はどこを見ても 生成AIとpythonの話しか無いからねぇ。たまに嘘を付くライターでもヒントを提示 してくれるから、嘘を発見すると言うおまけが有ると思えばいいんだな。

README

NHK俳句 夏井いつきの「凡人俳句」からの脱出

遂に、年寄趣味の世界に突入か? いやいや、豊かな言語世界に誘う入門書ですよ。 何と言っても、五七五のたった17文字で、心情世界を表現しなければいけない。 しかも、通常はその中に季節(を表わす季語)を盛り込めってんだから、相当 頭を使うぞ。

凡人は、発想が大体似てくるんで、凡庸な句しか出てこない。それを是正して キラリと光る秀作にしましょって案内だ。

夏休みってお題が与えられた時、どう脳を働かせるか? 連想するんだ。 宿題、かき氷、ラジオ体操、旅行ぐらいを連想。それぞれの連想を更に発展 していく。宿題を発展させると、日記、工作、自由研究みたいにね。 後は、それらを取捨選択してくらしい。

夏休みくらげに刺され大変だ

鶏小屋の前で絵日記夏休み (いつきさん推薦)

行列のできるラーメン店の前で、スマホして時間を潰すなんて勿体ないぞ。 ラーメンをお題(季語かどうかは知らんけど)にして、色々と連想してみれ。 ウゥーン。どうもピンとこないので、中秋の名月に鞍替え。

毎日が丸くなれよと月見酒

月と雲どっちが勝つか賭けてみろ (馬に駈けるよりは風流、かな?)

あの月を取ってくれろと泣く子かな (一茶翁)


This year's Index

Home