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箇所で現れます:
- 「変数展開」処理 …
$fooやx=1のようなケース →var.c/expand.cあたり。 - 「コマンド実行」処理 …
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.c→lookupvar()→vartabadd 1 2のような コマンド実行 →exec.c→find_command()→cmdtable
なので $add と add() が独立して動くわけです。
## 🧠 効率的な調査ルート(おすすめ)
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 namnod や struct 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.cetc.) を diff で追うと、構文要素がどの段階で固定化されているかがわかります。
## ✅ まとめ
| ツール | 役割 | 出力ファイル | 内容 |
| ------------ | ----------- | ------------ | -------------------- |
mksyntax |
字句解析用の構文表生成 | syntax.c/h |
文字クラス表 |
mknodes |
ASTノード定義生成 | nodes.c/h |
ノード構造体 |
mkinit |
初期化関数リスト生成 | init.c |
モジュール初期化ルーチン |
mksignames |
シグナル名テーブル生成 | signames.c |
SIGINT → "INT" 対応表 |
チャッピーは現代のBSD magazineの役目を果してくれているな。最近はどこを見ても 生成AIとpythonの話しか無いからねぇ。たまに嘘を付くライターでもヒントを提示 してくれるから、嘘を発見すると言うおまけが有ると思えばいいんだな。
README
遂に、年寄趣味の世界に突入か? いやいや、豊かな言語世界に誘う入門書ですよ。 何と言っても、五七五のたった17文字で、心情世界を表現しなければいけない。 しかも、通常はその中に季節(を表わす季語)を盛り込めってんだから、相当 頭を使うぞ。
凡人は、発想が大体似てくるんで、凡庸な句しか出てこない。それを是正して キラリと光る秀作にしましょって案内だ。
夏休みってお題が与えられた時、どう脳を働かせるか? 連想するんだ。 宿題、かき氷、ラジオ体操、旅行ぐらいを連想。それぞれの連想を更に発展 していく。宿題を発展させると、日記、工作、自由研究みたいにね。 後は、それらを取捨選択してくらしい。
夏休みくらげに刺され大変だ
鶏小屋の前で絵日記夏休み (いつきさん推薦)
行列のできるラーメン店の前で、スマホして時間を潰すなんて勿体ないぞ。 ラーメンをお題(季語かどうかは知らんけど)にして、色々と連想してみれ。 ウゥーン。どうもピンとこないので、中秋の名月に鞍替え。
毎日が丸くなれよと月見酒
月と雲どっちが勝つか賭けてみろ (馬に駈けるよりは風流、かな?)
あの月を取ってくれろと泣く子かな (一茶翁)