jq の入力・真偽・シェルとの受け渡し

結論 jq に渡す . や .branch のような式をフィルタと呼ぶ。jq の入力は「JSON 値の並び」で、フィルタは値ごとに動く。連結した JSON は値の数だけ結果を出し、壊れた箇所の手前の値は、終了コード 5 で失敗しても stdout に出る。1 値だけを受けるなら -s で全体を読み、length == 1 と型を確かめる -s は全部を読めてから配列を作るので、1 行でも壊れた JSON Lines (1 行に 1 つの JSON を書く形式) では何も出さない。壊れた行だけを捨てるなら -R と fromjson? を使う 偽になるのは false と null だけで、0・""・[] は真、無い項目は null になる。// は false も右辺に置き換える。フラグの判定は真偽でなく == false や > 0 の比較で書く ツリーを辿る recurse(.children[]) は children の無いノードで止まるので、エラーを捨てる ? を付ける。祖先の条件を子孫に効かせるなら、後段の select (条件が真の値だけを残す関数) でなく recurse(f; cond) に書く シェルとの境目では値の形が変わる。@tsv は \ を二重にし、IFS=$'\t' read は空の列を詰め、--arg (シェルの値を jq の変数に渡すオプション) の値は 1 個 128 KiB までしか渡せない。1 件ごとに jq を起動すると、jq 1 回で全件を出す形より桁で遅い 以下は jq 1.8.2 / GNU bash 5.3.20 / Linux x86_64 で実測した。出力の rc= は echo "rc=$?" で出した終了コードで、stderr のエラー文が stdout より先に並ぶのは、パイプへの stdout がバッファされ、終了時に書かれるためである。 ...

2026-11-09 ·  2026-11-09 · 9 分 · 1810 文字

git worktree で共有されるものと分かれるもの

結論 linked worktree は、object・ブランチ (refs/heads/) とその reflog・.git/config を本体と共有する。HEAD とその reflog・index・作業ツリーは worktree ごとに持つ 本体か worktree かは、--git-dir と --git-common-dir を --path-format=absolute で絶対パスに揃えて比べる。揃えずに比べると、本体のサブディレクトリで worktree と判定される 1 つのブランチは 1 つの worktree でしか checkout できず、git worktree list --porcelain の先頭は本体になる。ブランチ名から worktree を引くと、本体のパスが返ることがある 新しい worktree に入るのは追跡中のファイルだけ。ignored の .env や node_modules は無く、パスに紐づく仕組み (node のモジュール解決、direnv の許可) も本体とは別の場所として扱う worktree で作ったコミットは、worktree とブランチを消すと reflog からも消える。gc の前なら git fsck --lost-found で拾える 以下は git 2.56.0 / GNU bash 5.3.20 / Linux x86_64 で実測した。出力の <root> は再現に使った一時ディレクトリで、コマンドのパスは <root> からの相対に縮めて書いた。 ...

2026-11-07 ·  2026-11-07 · 12 分 · 2333 文字

bash の trap で書く後始末と終了コード

結論 trap は渡した文字列を登録し、発火した時点でコードとして読み直す。単引用符なら変数は発火時の値になり、二重引用符なら張った時点の値が構文の一部として埋まる EXIT trap が発火するのはシェルの終了時で、張った関数の中ではない。その関数の local 変数はもう無いので、値は printf -v q '%q' で埋めるか、グローバル変数に置く EXIT trap は SIGINT・SIGTERM・SIGHUP で止められても走り、SIGKILL では走らない。trap cleanup EXIT INT と INT も並べると、Ctrl-C の後もスクリプトが先へ進む trap はシェルに 1 つの表で、関数ごとの範囲を持たない。ライブラリの trap - EXIT は呼び出し元の EXIT trap も消し、関数で張った RETURN trap は呼び出し元の return でも発火する set -e の下では、EXIT trap の中の失敗が終了コードを書き換え、trap の残りの行を飛ばす。set -u だけなら trap は止まるが、終了コードは残る 以下は GNU bash 5.3.20 / Linux x86_64 で実測した。 前提: trap は「後で読み直す文字列」をシェルの表に登録する trap '<文字列>' EXIT は、その場では何も実行しない。文字列をシェルの表に登録し、発火したときに新しいコマンドとして読み直す。 表はシェル (プロセス) に 1 つで、シグナル (EXIT・RETURN を含む) ごとに 1 本の文字列を持つ。同じシグナルに張り直すと、前の文字列は置き換わる 登録した文字列は trap -p <シグナル> で読める 張った時点と発火した時点では、変数の値も、呼び出し中の関数も、表の中身も変わりうる。以下の罠はどれもこのずれから起きる set -e 自体の効き方は bash の set -e が効かない場所と効きすぎる場所 に、 ERR trap がパイプの失敗で発火しない件は trap コマンドは pipe と併用すると動作しない に書いた。 ...

2026-11-05 ·  2026-11-05 · 9 分 · 1783 文字

bash のパイプが返す終了コードと SIGPIPE

結論 パイプの終了コードは、既定では末尾のコマンドのもの。set -o pipefail を立てると、最後に非 0 で終わったコマンドのものになる || と if はパイプ全体の終了コードを見る。cmd | grep -v X || true は cmd の失敗も 0 にし、if cmd | grep -q X は取得の失敗を「一致なし」と同じ偽にする 読み手が終わった後に書き手が書くと、書き手は SIGPIPE で 141 になり、pipefail がそれを全体の終了コードにする。| head、| grep -q、stdin を読まない読み手で起き、出力が小さくても書く順番しだいで起きる mapfile < <(cmd) はパイプではないので、cmd の失敗はどこにも残らない 判定に使う出力は先に変数へ取り、here-string で渡す。fallback 付きのコマンドは、成功したときだけ stdout に出す 以下は GNU bash 5.3.15 / GNU coreutils 9.11 / GNU grep 3.12 / Linux x86_64 で実測した。回数を書いたものはタイミングで決まるので、環境によって数字が変わる。 前提: パイプの終了コードはどのコマンドのものか ...

2026-10-27 ·  2026-10-27 · 12 分 · 2495 文字

スクリプトから呼ぶ fzf が返す値と終了コード

結論 --select-1 は候補が 1 件なら、--exit-0 は 0 件なら、一覧を出さずに終わる。「fzf で人が選ぶ」前提の導線でも、1 件に絞れた候補はそのまま確定する --query の既定はあいまい一致で、行全体を照合する。api は apps-migration に当たり、--exact でも main は domain-model に当たる。行と完全に一致させるのは ^main$ だけ fzf は --filter 以外では起動時に /dev/tty を開く。制御端末の無いプロセスでは、--select-1 で 1 件に絞れても failed to open /dev/tty で exit 2 になる 終了コードは 0 (選んだ) / 1 (一致なし) / 2 (エラー) / 130 (中断)。--expect= (キー無し) は 2。producer | fzf は、fzf が先に終わると producer が SIGPIPE の 141 で終わり、pipefail で全体が 141 になる {} {1} はシングルクォートで囲んだ 1 引数に展開される。自分で "{}" と囲むと、そのクォートが値に混ざる preview は行ごとに別の $SHELL -c で走り、呼び出し元の関数と export していない変数は見えない --multi の出力は選んだ順。--tac は入力の並びを反転するだけで、カーソルは常に並びの先頭に乗る 以下は fzf 0.74.3 / bash 5.3.15 / linux/amd64 で実測した。対話の操作は script コマンドが作る疑似端末 (pty) の中で fzf を動かし、キーを送って再現した。 ...

2026-10-16 ·  2026-10-16 · 8 分 · 1549 文字