はじめに

ターミナルでよく使う処理は、ブラウザを開くより curljq でそのまま呼び出す方が手早く済みます。

この記事では、外部のデータや API を使って、手元のシェルを少し便利にする実例をまとめました。zshrc にそのまま入れやすいものを中心にしています。

この記事で扱う内容は次のとおりです。

  • 祝日付きの3か月カレンダーを表示する
  • 気象庁データから天気を表示する
  • 地震情報を一覧で見る
  • LLM API をシェルから呼び出す

後半では、API ではないものの一緒に置いておくと便利だったエイリアスも載せています。

祝日付きカレンダーを表示する

まずは、cal コマンドを少し便利にする例です。

cal コマンドはlinuxやmacOSのターミナルでカレンダーを表示できるシェルです。ターミナルで高速表示できるカレンダーは地味に便利で重宝しています。

項目コマンド
今月のカレンダーを表示cal
指定した月のカレンダーを表示cal 3 2024
年間カレンダーを一覧で表示cal 2024
先月、当月、翌月のカレンダーを表示cal -3

とくに先月・当月・翌月をまとめて表示する cal -3 が便利なのですが、標準では祝日が出ません。そこで、内閣府が公開している祝日 CSV を合わせて表示するようにしました。

cal -3; curl -s https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv | iconv -f SHIFT-JIS -t UTF-8 | grep -E "`date -v-1m '+%Y/%-m/'`|`date '+%Y/%-m/'`|`date -v+1m '+%Y/%-m/'`"

calコマンドの実行イメージ:

内閣府が公開している祝日 CSV を curl で取得し、iconvSHIFT-JIS から UTF-8 に変換しています。そのうえで cal -3 の表示に合わせて、先月・当月・翌月の行だけを grep で拾っています。

.zshrccal3 として登録しておくと、普段使いしやすいです。

alias cal3="cal -3; curl -s https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv | iconv -f SHIFT-JIS -t UTF-8 | grep -E \"\$(date -v-1m '+%Y/%-m/')|\$(date '+%Y/%-m/')|\$(date -v+1m '+%Y/%-m/')\""
Note

ここでは API というより CSV 配信データを使っていますが、「公開データをシェルから引いて整形する」という意味では同じ発想です。

天気情報を表示する

次は気象庁のデータを使って、ターミナルに天気、気温、風、波などを表示する例です。

curl https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json | jq

参考:

取得した JSONjq で整形し、wx として使えるようにしました。

alias wx="curl -s 'https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json' \
  | jq -r '
    .[0].timeSeries[0] as \$ts0 |
    .[0].timeSeries[1] as \$ts1 |
    .[0].timeSeries[2] as \$ts2 |
    \"地域: \(\$ts0.areas[0].area.name)\n天気: \(\$ts0.areas[0].weathers[0])\n風: \(\$ts0.areas[0].winds[0])\n波: \(\$ts0.areas[0].waves[0] // \"-\")\n降水確率: \(\$ts1.areas[0].pops[0] // \"-\")/\(\$ts1.areas[0].pops[1] // \"-\")/\(\$ts1.areas[0].pops[2] // \"-\")/\(\$ts1.areas[0].pops[3] // \"-\")\n最高気温: \(\$ts2.areas[0].temps[0] // \"-\")℃\n最低気温: \(\$ts2.areas[0].temps[2] // \"-\")℃\"
  '"

wxコマンドの実行イメージ:

https://www.jma.go.jp/bosai/forecast/#area_type=offices&area_code=130000

Note

この例では地域コードを固定しています。位置情報から自動で地域を決めるところまでは入れず、まずは「自分がよく見る地域をすぐ出せる」形にとどめています。

地震情報を一覧で見る

地震などの災害時は、Web ページ自体は重くても JSON だけなら取得しやすいことがあります。そこで、気象庁の一覧データから直近1週間の地震情報を見やすく整形する関数も作りました。

# 地震情報(直近1週間分)
quake() {
  curl -s "https://www.jma.go.jp/bosai/quake/data/list.json" \
  | jq -r '
    ["日時","震源地","M","最大震度"],
    (
      .[]
      | select((.anm // "") != "")
      | (.at | sub("\\+09:00$"; "") | strptime("%Y-%m-%dT%H:%M:%S")) as $ts
      | select(($ts | mktime) >= (now - 7*24*60*60))
      | [
          ($ts | strftime("%Y-%m-%d %H:%M")),
          .anm,
          (.mag // "-"),
          (.maxi // "-")
        ]
    )
    | @tsv
  ' \
  | column -t -s $'\t'
}

quakeコマンドの実行イメージ:

LLM API をシェルから呼び出す

次の例は OpenAI API のような LLM API を、シェルから直接呼び出したいときの最小構成です。記事公開時点では ChatGPT への問い合わせ例として使っていましたが、ここで言いたいのは「標準入力を受け取って API に投げ、結果をそのまま返す流れ」です。

API キーはスクリプトへ直書きせず、環境変数から読む方が安全です。

次のように、ターミナルからChatGPTに直接質問するシェルスクリプトを作ってみました。

#!/bin/bash

API_KEY="${OPENAI_API_KEY:?OPENAI_API_KEY is not set}"
ENDPOINT="https://api.openai.com/v1/chat/completions"
MODEL="gpt-4o"

INPUT=$(cat)

ESCAPED_INPUT=$(printf '%s' "$INPUT" | jq -Rs .)

# JSONリクエストボディを組み立て
JSON=$(cat <<EOF
{
  "model": "$MODEL",
  "messages": [
    {
      "role": "system",
      "content": "あなたは日本語で答えるAIアシスタントです。"
    },
    {
      "role": "user",
      "content": $ESCAPED_INPUT
    }
  ],
  "temperature": 0.7
}
EOF
)

# APIリクエスト送信
RESPONSE=$(curl -s "$ENDPOINT" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d "$JSON")

if echo "$RESPONSE" | jq -e '.error' > /dev/null; then
  echo "エラー: $(echo "$RESPONSE" | jq -r '.error.message')" >&2
  exit 1
fi

echo "$RESPONSE" | jq -r '.choices[0].message.content'

使い方は次のとおりです:

# 例1
echo "ChatGPTとは何ですか?" | ./chatgpt.sh
# 例2
cat q.txt | ./chatgpt.sh
Caution

モデル名や料金体系は変わることがあるので、実際に使うときは公式ドキュメントや料金ページを確認してください。

API以外でも便利だったシェルの例

ここからは API ではありませんが、.zshrc に入れておくと便利だったものです。

GhostScript で PDF を圧縮する

macOS で PDF をコマンドラインから圧縮したいときに使っている例です。自炊した PDF やスキャンした資料を軽くしたいとき に便利でした。

GhostScript というソフトウェアを使ってPDFを圧縮します。macOSの場合は brew を使ってインストールできます。

パッケージバージョンインストールコマンド
gs10.01.2brew install ghostscript

次のように圧縮したいPDF in.pdf を次のコマンドで圧縮して out.pdf として書き出すことができます。

gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf

試してみたところ、138MBあったPDFが76MBへ削減できました!およそ50%の圧縮です! PDFSETTINGS オプションを変更することで圧縮率を高める方法もありますが、電子書籍だと文字が読めない状態になってしまいましたのでここでは紹介しません。

ワンライナー化してエイリアスにしておくと再利用しやすいです。

# PDF圧縮
alias pdfmin='f(){ input="$1"; output="${input%.*}_min.pdf"; gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile="$output" "$input" && echo "$output"; }; f'
Caution

モノによっては大して圧縮されなかったり、圧縮自体がエラーになってうまく書き出せなかったりしますのでご注意ください。圧縮エラーになるファイルが結構ありました。

パスワードジェネレータ

パスワードを自動作成したい場合、自分で考えるのは大変ですし、セキュリティ的にも脆弱です。そこでWebサービスを使おうかと考えるのですが、そのサイト自体を信用できずに利用をためらってしまうなんてことありませんか?そんなあなたに朗報、自前のシェルでパスワード生成できるようにしてみました!

# パスワードジェネレータ(API用)
alias genhex="openssl rand -hex 32"
# パスワードジェネレータ(普段使い用)
alias genpass="LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32; echo"

生成される文字の違い

  • genhex → 16進数(0–9, a–f)だけで構成される。64桁のランダムな文字列が出力される。
  • genpass → 英大小文字+数字の62種類からランダムに選ばれる。扱いやすいけど十分に複雑。

補足:なぜこれで強いパスワードになるのか?

openssl rand は暗号学的に安全な乱数生成器(CSPRNG)を内部で利用しており、暗号用途に十分なエントロピーを持つ乱数を生成してくれます。APIキーやトークンの生成に最適です。

一方、/dev/urandom はカーネルが管理している乱数デバイスです。マウスの動きやディスクアクセスなど、OS内部の「予測できない揺らぎ」からエントロピーを集めて乱数を作ります。

Note

エントロピーとは、もともと物理学の用語で「乱雑さ」や「無秩序さ」を表します。情報理論では「情報の予測不可能性」を意味します。

  • サイコロを1回振ると6通り → エントロピーは小さい(2.58ビット)。
  • サイコロを100回振ると 6^100 通り → エントロピーは膨大(約258ビット)。

つまり「どれだけ先が読めないか」を数字で表したものがエントロピーです。
エントロピー=「予測できなさ」の量 と考えると分かりやすいです。

関連記事