magnoliakmemo

雑にメモを残すためのブログです。Amazonのアソシエイトとして、運営のMagnolia.Kは適格販売により収入を得ています。

Cisco 150AX MIB 実機確認で判明した要注意項目

Cisco 150AX MIB 実機確認で判明した要注意項目

確認条件

  • SNMP: v2c / snmpwalk -Cc -On
  • 構成: CBW151AXM-Q × 1、CBW150AX-Q × 2
  • ファームウェア: 10.7.2.0
  • 確認日: 2026-08-16
  • 定義との比較元: Cisco公式 cisco-mibs リポジトリ、commit e623726(2026-08-10)

正常に取得できた項目は記載せず、定義と一致しない、値の意味が不明、または実用上利用できない項目だけをまとめる。

AP情報

項目 OID 実測結果 問題と扱い
AP動作モード (bsnAPMonitorOnlyMode) 1.3.6.1.4.1.14179.2.2.1.1.5.<AP> 全3台が 7 公式MIBの列挙は 06のみ。7は未定義なので表示名へ変換せず unknown(7) と扱う。
AP機種コード (bsnAPType) 1.3.6.1.4.1.14179.2.2.1.1.22.<AP> CBW150AX-Qが 106、CBW151AXM-Qが 118 公式MIBでは 106=ap9105AXI118は未定義。150AXの機種判定には使わず、文字列の bsnAPModel(列16)を使う。
Primary Controller名 (bsnAPPrimaryMwarName) 1.3.6.1.4.1.14179.2.2.1.1.10.<AP> 1台だけ値があり末尾に制御文字 0x02、他2台は空 定義上は優先WLC名であり、CBWのPrimary/Subordinate AP区分ではない。文字列も不正なので役割判定に使わない。
Static IP設定 ...1.1.25.<AP> / ...1.1.28.<AP> bsnAPIsStaticIP=0(DHCP)なのに、bsnAPStaticIPAddressが全台で現在IPと同値 静的IP「設定値」という定義と実装が整合しない。現在IPは列19を使い、列28は使わない。
IOS Version (bsnAPIOSVersion) 1.3.6.1.4.1.14179.2.2.1.1.31.<AP> 全台が 10.7.2.0 MIBはIOS AP以外では空文字を返す定義だが、CBWでもSoftware Versionと同じ値を返す。IOSバージョンとして使わない。
Ethernet slot数 (cLApMaxNumberOfEthernetSlots) 1.3.6.1.4.1.9.9.513.1.1.1.1.9.<AP> Ethernet MACを持つ全3台が 0 「最大Ethernet slot数」という定義に対して実装値が実機構成を表していない。
Max Client Limit状態 (cLApMaxClientLimitSet) 1.3.6.1.4.1.9.9.513.1.1.1.1.64.<AP> 全台が 0 型は TruthValue で、有効値はtrue=1、false=20は型定義違反。
接続AP数 (cLApConnectCount) 1.3.6.1.4.1.9.9.513.1.1.1.1.66.<AP> 3 APが参加中でも全台 0 定義上はControllerへ接続中のAP数だが一致しない。MIB上もdeprecatedなので使わない。
未定義AP列 1.3.6.1.4.1.9.9.513.1.1.1.1.99.102 文字列、Gauge32INTEGERの値を返す 最新公式MIBでは列98の次が列103で、99〜102は未定義。値の意味を推測して利用しない。

クライアント情報

項目 OID 実測結果 問題と扱い
接続先APモード (bsnMobileStationApMode) 1.3.6.1.4.1.14179.2.1.4.1.28.<client> 接続中の全12クライアントが 7 公式MIBの列挙はlocal=0、monitor=1、remote=2、rogue detector=3のみ。7は未定義。

Radio・RF情報

項目 OID 実測結果 問題と扱い
未定義Radio列 1.3.6.1.4.1.14179.2.2.2.1.25.<AP>.<slot> 全6 Radioが文字列 N/A 公式MIBには列25の定義がなく、列23の次は列28。チャネル幅などの値として解釈しない。
実際のチャネル幅 該当する有効なper-AP OIDなし Radioテーブルから取得不能 1.3.6.1.4.1.9.9.615.1.1.1.1.10.<radio-type> はDCAの全体設定(実測は2.4GHz=20 MHz、5GHz=80 MHz)であり、各APが現在使う幅ではない。
干渉電力 (bsnAPIfInterferencePower) 1.3.6.1.4.1.14179.2.2.14.1.2.<AP>.<slot>.<channel> 2.4GHzの39行中22行が -128 公式MIBは単位、有効範囲、-128の意味を定義していない。実測上は未検出・欠測の番兵値と判断し、数値集計から除外する。
5GHz干渉 1.3.6.1.4.1.14179.2.2.14 slot 0(2.4GHz)のch 1〜13だけ。up状態のslot 1は0行 5GHzの干渉電力・干渉率はこの機種/FWでは取得不能。
5GHzノイズ 1.3.6.1.4.1.14179.2.2.15 slot 0のch 1〜13だけ。up状態のslot 1は0行 2.4GHzのノイズ値は妥当だが、5GHzノイズは取得不能。

温度情報

項目 OID 実測結果 問題と扱い
装置内部温度 (bsnSensorTemperature) 1.3.6.1.4.1.14179.2.3.1.13.0 5000 公式MIBは単位を摂氏と定義しており、文字どおりなら5000℃という異常値。実装が50.00℃を100倍で返している可能性はあるが、そのスケールはMIBに定義されていないため、そのまま温度として使わない。
温度Alarm下限/上限 1.3.6.1.4.1.14179.2.3.1.14.0 / .15.0 両方 0 commercial環境(定義上0〜40℃)で上下限が同じ0℃なのは監視閾値として不整合。Alarm判定に使わない。
AP別温度 (cLApEnvTemperatureDegree) 1.3.6.1.4.1.9.9.513.1.1.11.1.1.<AP> 全3台が空文字 MIBは摂氏・華氏の温度文字列を返す定義だが値がない。対応する状態も全台 unknown(0) で、AP別温度は取得不能。
標準Sensor MIB 1.3.6.1.2.1.99 / 1.3.6.1.4.1.9.9.91 いずれも No Such Object ENTITY-SENSOR-MIBCISCO-ENTITY-SENSOR-MIBは未実装で、代替温度値も取得不能。

国コード・時刻

項目 OID 実測結果 問題と扱い
Global Country (bsnGlobalDot11CountryIndex) 1.3.6.1.4.1.14179.2.3.1.5.0 none(23) AP別設定は全台 J4で日本向けチャネルを使用中。グローバル国コードとして実態と一致しない。
AP Country (cLApCountryCode / Allowed) 1.3.6.1.4.1.9.9.513.1.1.3.1.1 / .2 全台 J4 説明は「3 letter country code」だが2文字のRegulatory Domainらしい値を返す。国コードとして解釈しない。
Timezone差 1.3.6.1.4.1.14179.2.3.1.27.0 / .30.0 時刻文字列はJSTと一致する一方、時差はhours=0、minutes=0 MIB定義ではlocal timeとUTCとの差。日本では+9のはずなので不一致。
Controller epoch (bsnSystemClockTime) 1.3.6.1.4.1.14179.2.3.1.49.0 実際のUnix時刻よりちょうど9時間先 定義はUTC基準のUnix秒だが、JSTの壁時計をUTCとして数値化している。epochとして使う場合は不正。

イメージ・RRM設定

項目 OID 実測結果 問題と扱い
Image swap (cldlAPSwapImage) 1.3.6.1.4.1.9.9.513.4.1.1.1.1.4.<AP> 全台 0 TruthValueの有効値は1/2であり、0は型定義違反。
Download image (cldlAPDownloadImage) 1.3.6.1.4.1.9.9.513.4.1.1.1.1.5.<AP> 全台 0 公式列挙はprimary=1、backup=2、abort=3のみ。0は未定義。
Optimized Roaming (clrRrmParametersOptRoamEnable) 1.3.6.1.4.1.9.9.615.1.1.1.1.22.<radio-type> 2.4GHz、5GHzとも 0 TruthValueの有効値は1/2であり、0は型定義違反。

Mesh情報

項目 OID 実測結果 問題と扱い
Neighbor種別 (clMeshNeighborType) 1.3.6.1.4.1.9.9.616.1.3.1.1.2.<AP>.<neighbor> 全3リンクが Hex-STRING: 00 MIBはparent/neighbor/excluded/child等のBITS。00は全ビット未設定で、親子関係を判定できない。
Neighbor更新時刻 (clMeshNeighborUpdate) 1.3.6.1.4.1.9.9.616.1.3.1.1.5.<AP>.<neighbor> 約206日というTimeTicks。取得時のsysUpTimeは約5日 MIBはsysUpTime基準のTimeStampと定義しているため、sysUpTimeを超える値は不正。実測値はUnix秒をTimeTicksへ誤格納したものに近い。
Mesh Node統計 1.3.6.1.4.1.9.9.617.1.1.1 3 AP × 10カウンタがすべて 0 Neighbor統計に実トラフィックがある同時点でも全ゼロ。150AXでは未実装のスタブとみなし監視対象から外す。
Mesh Queue統計 1.3.6.1.4.1.9.9.617.1.1.2 3 AP × 5 queueの平均、最大、drop、更新時刻、queue容量がすべて 0 queue容量と更新時刻までゼロのため実データではない。監視対象から外す。

なお、clMeshNeighborLinkSnr82024 dB は低いリンクを含むものの、MIB定義に合う実測値であり異常項目には含めていない。

参照MIB

hashi、というフレームワークを作ってみた

前回から一度作り直してみた


チュートリアル

Scala 3 製の、型安全な Rails 風 HTTP ルーティングライブラリ hashi を、ゼロから順に 積み上げて学ぶチュートリアルです。核は ルートのパス・型・ハンドラ引数がコンパイル時に 検証される こと。細部の仕様は routing.md を参照してください。

import tech.magnolia.hashi.*
import tech.magnolia.hashi.Result.*

1. 最小のサーバ

@main def app(): Unit =
  val routes = Seq(
    get("/hello") { _ => Ok("world") }
  )
  val server = Server.start(8080, routes)   // Jetty 12 + 仮想スレッド
  server.join()                             // 終了待ち(.stop() で停止)

get(...) は文字列補間子 route"GET /hello"完全に等価(同じマクロ)。動詞関数 get / post / put / patch / delete / head / options が使えます。

2. 型付きパスパラメータ(ここが肝)

get("/users/:id[Int]"): (req, id: Int) =>      // id は Int として渡る
  Ok(s"user $id")
  • :id[Int][Int]型注釈 として書くと、ハンドラの第 2 引数以降に その型 で 渡ります。型が合わなければ コンパイルエラー
  • 変換できないリクエスト(/users/abc)は「マッチしない」→ 404(例外は飛ばない)。
  • 組み込み型: String(既定)/ Int / Long / Boolean / UUID
  • ハンドラの第 1 引数は常に Request

パスの記法いろいろ:

get("/u/:name")                  { (_, name: String)               => Ok(name) }        // 既定 String
get("/files/*path")              { (_, p: String)                  => Ok(p) }           // splat(残り全部)
get("/users(/:id[Int])")         { (_, id: Option[Int])            => Ok(id.toString) } // 省略可能 → Option
get("/posts/:id[Int](.:format)") { (_, id: Int, f: Option[String]) => Ok(s"$id.$f") }   // 拡張子
get("""/items/:id{\d{4,}}""")    { (_, id: String)                 => Ok(id) }          // 正規表現制約

ユーザー定義型は完全修飾名 + given Conv[T]:

given Conv[java.time.LocalDate] = s => scala.util.Try(java.time.LocalDate.parse(s)).toOption
get("/events/:on[java.time.LocalDate]") { (_, d: java.time.LocalDate) => Ok(d.toString) }

3. レスポンス(Result

Ok("text")                       // 200
Created("made")                  // 201
NoContent                        // 204
Redirect("/login")               // 302(permanent = true で 301)
BadRequest("bad")                // 400
NotFound("nope")                 // 404
Json(user, status = 201)         // JSON(§5)
Stream(contentType = "text/csv") { out => out.write(...) }  // 逐次配信

装飾(どの Result にも付与可):

Ok("hi").withHeader("X-Foo", "bar").withCookie(Cookie("sid", "abc", httpOnly = true))

4. クエリパラメータ(型付き・例外なし)

get("/search"): req =>
  val page = req.queryParamOr[Int]("page", 1)   // ?page=2 → 2、無ければ 1
  val tags = req.queryParams[String]("tag")     // ?tag=a&tag=b → Seq("a","b")
  Ok(s"$page ${tags.mkString(",")}")

queryParam[T]Option)/ queryParams[T]Seq)/ queryParamOr[T]。フォームボディも 対称 API(formParam 等)。どちらも未トラスト入力として 例外を投げず 「不在」に倒します。

5. JSON ボディ(型安全に受け取る)

import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker

final case class CreateUser(name: String, age: Int = 0)
given JsonValueCodec[CreateUser] = JsonCodecMaker.make

post("/users").body[CreateUser]: (req, u) =>   // u は CreateUser
  Json(u.name, status = 201)
  • .body[T] でボディを ハンドラ引数として型付きで受け取る(パスパラメータの後ろに追加)。
  • given JsonValueCodec[T] が無ければコンパイルエラー。不正 JSON は自動で 400
  • 出力は Json(value)(要 codec)。手動派は req.bodyAs[T] / req.bodyAsOption[T]

6. ルートの構成・逆引き

scope("/api/v1")(                          // プレフィックス付与
  get("/users/:id[Int]") { (_, id: Int) => Ok(id.toString) }
)

resources[Int]("posts")(                   // RESTful 一括生成
  index = Some(_ => Ok("all")),
  show  = Some((_, id) => Ok(s"post $id"))
)

val r = get("/users/:id[Int]") { (_, id: Int) => Ok("") }
r.url(Tuple1(42))                          // "/users/42" ← 型安全な逆引き

val named = NamedRoutes("user" -> r)
named.url("user", Tuple1(42))              // 名前で逆引き(実行時チェック)

7. ミドルウェア

val mw = Middleware.chain(
  Middlewares.logging(),                   // "GET /x -> 200 1.2 ms"
  Middlewares.requestId(),                 // X-Request-Id 付与
  Middlewares.cors(origin = "*"),
  Middlewares.basicAuth() { (u, p) => u == "ann" && p == "s3cret" }  // 401 で短絡
)
Server.start(8080, routes, middleware = mw)

ミドルウェアは素の関数 (Request, Request => Result) => Resultnext を呼ぶ前後で 加工/短絡します。認証成功時はユーザを X-Auth-User ヘッダで下流へ渡します。

8. エラーハンドリング

val errors = ErrorHandler(
  onException   = { case _: NoSuchElementException => NotFound("gone") },
  onNotFound    = req => Json(...),                 // 404 を JSON で
  onServerError = (_, _) => InternalServerError()   // 既定で例外メッセージは漏らさない
)
Server.start(8080, routes, errorHandler = errors)

例外 → Result マッピングと、404 / 405 / 500 のレンダリングを差し替え。エラーレスポンスも ミドルウェアを通って返ります。

given Signer = Signer(sys.env("APP_SECRET"))          // HMAC-SHA256
given SessionConfig = SessionConfig()                  // Secure + HttpOnly + SameSite=Lax

post("/login").body[Creds]: (_, c) =>
  Redirect("/").withSession(Map("user" -> c.name))     // 署名付きセッション
get("/me") { req => Ok(req.session.getOrElse("user", "guest")) }      // 検証済みで読む
post("/logout"){ _ => Redirect("/").clearSession }

署名は 改ざん検知(暗号化ではない)。低レベルに withSignedCookie / signedCookie も。

10. 静的ファイル配信

import java.nio.file.Paths
val pub = Paths.get("public")
get("/assets/*path") { (req, p: String) => StaticFiles.serve(pub, p, req) }
get("/")             { req => StaticFiles.serveFile(pub.resolve("index.html"), req) }

Content-Type 推定・ETag/304・Range/206・パストラバーサル防御 込み。

11. テスト(サーバ不要)

import tech.magnolia.hashi.testkit.*

val routes = Seq(get("/users/:id[Int]") { (_, id: Int) => Ok(s"user $id") })

dispatch(routes, TestRequest.get("/users/3")).bodyText         // "user 3"(先勝ち・404/405 もサーバと同じ)
dispatch(routes, TestRequest.post("/x").json(CreateUser("a"))) // JSON ボディ
r.runIfMatches(TestRequest.get("/users/42"))                   // 1 ルートをパスマッチ込みで
r.call(TestRequest.get("/_"), 42)                              // パラメータを直接渡してハンドラだけ

詳しくは testing.md

12. フロント連携(型を共有)

// Scala の case class → TypeScript 型
TsGen.module[(Task, TaskInput)]      // export interface Task { ... }

// 型付き fetch クライアント生成
import tech.magnolia.hashi.tsclient.TsClient, TsClient.op
TsClient.render(Seq(
  op("getTask", "GET", "/tasks/{id}").path[Long]("id").returns[Task]
))                                   // export async function getTask(id: number): Promise<Task>

// OpenAPI 3
import tech.magnolia.hashi.openapi.*, OpenApi.*
OpenApi.document(Info("API", "1.0"), Seq(get("/tasks").ok[Seq[Task]]))

13. 全部入りの実例

examples/crud/ が H2 + JSON CRUD + 静的フロント + 型生成のフルサンプルです。

sbt "runMain tech.magnolia.hashi.examples.crud.runTaskApp"   # http://localhost:8080/
sbt "runMain tech.magnolia.hashi.examples.crud.genClient"    # TS クライアント生成
sbt "runMain tech.magnolia.hashi.examples.crud.genOpenApi"   # OpenAPI 生成

学習の順番(おすすめ)

  1. §1–§3(サーバ・ルート・型付きパラメータ)で核を掴む。
  2. §5 JSON ボディ で型安全の旨味を体感。
  3. §11 テストdispatch を回しながら §4・§6–§10 を試す。
  4. 仕上げに §13 の CRUD 実例 を読む → routing.md で細部を確認。

Hashi — Scala 3 のマクロで「型安全なルーティング」を素直に書けるWebフレームワーク

ここ2週間くらい完全にClaude Codeだけで作っているウェブアプリケーションフレームワーク

Jettyの安定性と、Scala3の型機能をフルに使った型安全なルート定義ができるところが特徴

まだGitHubにはアップしていないけど、こんな構想です


TL;DR

  • Hashi は Scala 3 + Jetty 12 で書かれた軽量 Web フレームワークです
  • パスを p"/users/{id:Int}" と書くと、id が Int だとコンパイル時に確定し、ハンドラの引数型もそこから推論されます
  • パスパラメータは 個数無制限・型は String / Int / Long / Double / Boolean
  • request を引数なしで参照できる DSL、関数合成によるミドルウェア、型クラスによるレスポンス直列化
  • JSON(uPickle)、WebSocket、HTTP/2、静的配信、CORS、W3C 分散トレーシング、テスト用クライアントを同梱

まず触ってみる

  import tech.magnolia.hashi.*

  @main def hello(): Unit =
    hashi.serve(port = 8080):
      get(p"/"):
        Ok("Hello, Hashi!")

      get(p"/users/{id:Int}"): (id: Int) =>
        Ok(s"User $id")

/users/42 は User 42 を返し、/users/abc は 自動的に 400 を返します(Int への変換に失敗するため、ハンドラには到達しません)。型変換の責務がルーティング層にあるのがポイントです。

何が「型安全」なのか

Hashi の核心は p"..." という文字列インターポレータ・マクロです。

get(p"/users/{id:Int}/posts/{slug}"): (id: Int, slug: String) => Ok(s"user=$id post=$slug")

ここで何が起きているか:

  1. p"..." がコンパイル時にパス文字列を解析し、{id:Int} の :Int から型を取り出して RouteBuilder[Int : String : EmptyTuple] という型レベルのタプルを生成する
  2. get はその型に応じてオーバーロード解決され、ハンドラの引数が (Int, String) だとコンパイラが推論する
  3. パスの型注釈とハンドラの引数がズレていればコンパイルエラー

つまり「型の証明はエッジ(マクロ+オーバーロード解決)で済ませる」設計です。ランタイムのルータ自体は正規表現マッチの素直なディ スパッチに保たれ、複雑さが DSL 層に隔離されています。request をハンドラ内で引数なしに呼べるのも Scala 3 のコンテキスト関数型(Context ?=>)のおかげです。

get(p"/info"): Ok(s"${request.method} ${request.path}")

パスパラメータは個数無制限

多くの「型付きルーティング」を持つフレームワークは、内部で型をタプルに畳み込む都合上アリティ上限があります(Scala 2 系では 22 が壁になりがち)。Hashi は Scala 3 のネイティブタプルを使うため上限がありません。多引数ラムダ構文もそのまま使えます。

get(p"/orders/{oid:Long}/items/{iid:Int}/rev/{rev:Int}/{active:Boolean}"): (oid: Long, iid: Int, rev: Int, active: Boolean) => Ok(s"order=$oid item=$iid rev=$rev active=$active")

/ 演算子でパスを組み立てるスタイルも同じく任意アリティです。

get("/users" / p[Int] / "posts" / p[String] / p[Long]): (uid: Int, slug: String, rev: Long) => Ok(s"$uid/$slug/$rev")

JSON は型クラスで透過的に

hashi-upickle を入れると、ケースクラスをそのまま返せます。レスポンスの直列化は Responder[T] 型クラスに委譲されているので、String も JSON も SSE も同じ経路で扱われます。

case class User(id: Int, name: String) derives ReadWriter

post(p"/users"): request.bodyAs[User] match case Right(u) => Ok(u) // → {"id":...,"name":"..."} case Left(e) => BadRequest(e)

ミドルウェアは「関数の合成」

ミドルウェアは ハンドラ => ハンドラ という関数で、登録順に入れ子で合成されます(リクエストは外→内、レスポンスは内→外)。

hashi.serve(port = 8080): use: next => if request.header("X-Api-Key").contains("secret") then next else Forbidden("no key")

use: next =>
  next.withHeader("X-Powered-By", "hashi")

get(p"/secret"):
  Ok("ok")

ひととおり揃っている

hashi.serve(port = 8080): serveFiles("/static", java.nio.file.Path.of("public")) // 静的配信

use(Tracing())                                           // W3C TraceContext
get(p"/trace"):
  Ok(trace.map(_.traceId).getOrElse("-"))

ws(p"/echo/{room}"): (room: String) => sess =>           // WebSocket
  sess.onMessage(m => sess.send(s"[$room] $m"))

notFound:
  NotFound("見つかりません")
onError: e =>
  InternalServerError(s"oops: ${e.getMessage}")

XSS 対策として html"..." インターポレータ(自動エスケープ)や、サーバーを起動せずルートを叩ける TestClient も同梱しています。

test("パスパラメータ"): given HashiServer = new HashiServer() get(p"/greet/{name}"): (name: String) => Ok(s"Hello, $name!") assertEquals(TestClient().get("/greet/world").body, "Hello, world!")

設計の割り切り(正直なところ)

  • ルーティングは登録順の線形探索・先勝ち。トライ木や優先度はなく、ルート順序の設計は利用者の責任です
  • ランタイムの Route は型を Any に消します。型安全はコンパイル時に閉じる、という明確な境界設計です
  • ネームスペース/プレフィックスは未実装

まとめ

Hashi は「Scala 3 のマクロとコンテキスト関数で、型安全なルーティングを素直な見た目のまま書く」ことに振り切ったフレームワークで す。型の旨味はコンパイル時に取り切り、ランタイムは退屈に保つ — その割り切りが心地よさになっています。

zshの補完についてのメモ

zshでは、コマンドラインでの入力に対する便利な補完機能が用意されている。

その挙動を確認した結果のメモ

コマンド名補完

環境変数$PATH上に存在するコマンドをインクリメンタルサーチで候補を表示する。

コマンド名を途中まで入力し、TABキーを一回押下すると候補のコマンド名が表示される。

% vi[TAB]
vi              viewdiagnostic  vim             vimtutor        vis
view            vifs            vimdiff         vipw            visudo

複数回TABキーを押下すると、候補が次々と入力エリアに表示される。

例えば、上記の状態で2回TABキーを押下すると、下記の表示となる。

% view
vi              viewdiagnostic  vim             vimtutor        vis
view            vifs            vimdiff         vipw            visudo

そのままRETURNキーを押下すると実行される。

パス名補完

コマンド名が確定し、SPACEキーを押下してTABキーを押下すると、カレントディレクトリ配下のディレクトリ名や、ファイル名が候補として表示される。

% vim[SPACE][RETURN]
Applications/     Desktop/
...

例えば、上記の状態で2回TABキーを押下すると、下記の表示となる

% vim Desktop/
Applications/     Desktop/
...

この状態で更に「/」キーを押下してTABキーを押下すると、Desktop/ディレクトリ配下のディレクトリ名や、ファイル名が候補として表示される。

zshの補完機能 compinit

zshにはcompinitというプラグインが付属しており、サブコマンドや、オプションを補完してくれます。

.zshrcに以下の行を追加と有効になります。

autoload -Uz compinit && compinit

サブコマンドが大量に存在するgitで試してみます

git sまで入力し、TABキーを押下すると、「S」から始まるサブコマンドが表示されます。

% git s[TAB]
send-email      -- send collection of patches as emails
send-pack       -- push objects over git protocol to another repository
shell           -- restricted login shell for GIT-only SSH access
shortlog        -- summarize git log output
show            -- show various types of objects
...

この補完は、コマンドごとにzsh側で用意している定義ファイルが有り、その内容を元に表示されています(つまり、バージョンが合わない場合、正確でない可能性がある)。

macOS 26では、下記のディレクトリに配置されています。

/usr/share/zsh/5.9/functions

補完の定義ファイルは、$fpathを元に検索されます。

追加の補完の定義ファイル

zshが提供していない補完の定義は、以下のリポジトリで見つかるかもしれない。

github.com

より強力な補完機能

github.com

zshが提供する補完機能に加え、ヒストリから補完してくれるプラグイン

シンタックスハイライト

github.com

$PATH上にコマンドが存在すると、色を変えてくれるので、コマンドのタイプミスが無いことに気付ける

また、ファイルパスを入力した際は、存在するパスの場合はアンダーラインが引かれるため、ファイルパスのタイプミスに気付ける

補完機能と、組み合わせると、非常に少ないタイプ数でコマンドを入力し、誤りがあってもすぐ気付くことができる、便利

Ghosttyの使い方メモ

Ghosttyの使い方メモ

インストール

ダウンロードサイトからインストーラをダウンロードするか、パッケージマネージャーでインストールする。

ghostty.org

brew install --cask ghostty

zshの補完ファイルと、manファイルは、homebrewがインストールする

Ghosttyのconfigファイル用のvimプラグインのインストール

Ghosttyのconfigファイル用のvim, nvim用プラグインが用意されているので、個別にインストールする

cp -r /Applications/Ghostty.app/Contents/Resources/vim/vimfiles/. $HOME/.vim
cp -r /Applications/Ghostty.app/Contents/Resources/nvim/site/. $HOME/.config/nvim

ただし、ftdetect/ghostty.vimmacOS用Ghosttyの設定ファイルを自動検知しないため、以下のように書き換える(Ver.1.2.3)

au BufRead,BufNewFile */ghostty/config,*/ghostty/themes/* setf ghostty
↓
au BufRead,BufNewFile */ghostty/config,*/ghostty/themes/*,*/*.ghostty/config setf ghostty

環境変数$EDITORvimか、nvimを設定することで、ghostty +edit-configで指定したエディタが起動し、configが読み込まれる

:makeで構文がチェックできる

ヘルプの確認

ghostty --help

manで詳細が分かる。

man ghostty
man 5 ghostty

キーバインドの確認

まずはキーバインドについて、どんな設定項目が有り、どんな設定になっているか確認する。

ghostty +list-keybinds --default

基本的な設定

Ghosttyは、ゼロコンフィグを目指している(設定なしで使える)ので、カスタマイズをする必要性は低い

環境変数$EDITORが設定された状態で、以下のコマンドを叩くとそのエディタが起動する

ghostty +edit-config

フォントと、サイズは完全に好みで(標準搭載されているJet Brains Monoは良いけど、リガチャでけっこう形が変わるので、0xProtoを使っている)

font-family = "0xProto"
font-size = 18
font-thicken = true
theme = "Tomorrow Night Bright"
window-width = 120
window-height = 40

macOS Tahoe 26から、Terminalに、SF Mono Terminalというフォントが追加になっている

macOS Tahoe 26から、Terminalに、SF Mono Terminalというフォントが追加になっている

  • Terminalに内蔵された専用フォント
  • 従来のSF Monoとは別のフォント
  • Powerlineのグリフが増えている
  • OpenTypeではなく、TrueType
  • RegularとItalicは有るが、Boldはない(ウェイトの数も少ない)
  • SF Mono、バージョンはダウンロードできるバージョンの方が少し新しい(内蔵フォントは15、ダウンロード版は18)
  • SF Mono Terminalのバージョンは21

その他

  • iTerm2はアプリビルトインのPowerlineのグリフを持つが、デザインが少し独特(LNが太め)
  • GhosttyはアプリビルトインのPowerlineのグリフに加え、Nerd Fontのグリフも持つ
  • Ghosttyはフォールバック先のフォントを独自に指定できる
  • 一方で、iTerm2はNon-ASCII用に別のフォントを指定できる
  • Terminalはフォールバック先は指定できないが、SF Mono Terminalのフォールバック先はシステムフォントのSF Proなので、デザインの統一感はある