Geminiをシェルでも使えるようにしよう

Gemini(Bard)が一般公開されておよそ二年の月日が流れました。
皆さんは、「コマンド忘れた！　あれだよ、あれ！」となった経験はありますか？私は多々あります。
そんな時はAIに聞くなりググるなりあきらめるなりしていると思います。
その時にウィンドウいちいち切り替えるのって、地味に手間ですよね？
シェルからすぐ聞けたらすごい便利だと思いませんか？
そこで作ったのがこちら。

# コード

```bash:gemini
#!/bin/bash

API_KEY='YOUR_API_KEY' # Insert your API key

function showUsage () { # Show usage
  echo "\
Usage:
  ./gemini [options] <prompt>...
Description:
  Use Gemini with the Gemini API
  Insert the API key into API_KEY variable before use
Options:
  -h, --help    Display this usage and exit\
"
  exit 0
}
function error () { # Display an error message if exit status isn't 0
  exitStatus=${?}
  echo "Error! (Exit status: ${exitStatus})" 1>&2
  exit ${exitStatus}
}
function postGemini () { # Send POST request
  curl -sS "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${API_KEY:?}" \
    -H 'Content-Type: application/json' \
    -X POST \
    -d "{ 
      \"contents\": [
        { 
          \"parts\":[
            {
              \"text\": \"${*:?}\"
            }
          ] 
        }
      ]
    }" | \
  jq '.candidates[].content.parts[].text' || \
  error
}

function main () {
  if [ "${1}" = "-h" ] || [ "${1}" = "--help" ]; then # Show help when -h or --help is inputed
    showUsage
  fi
  response=$(postGemini "${*}")
  printf "%b" "${response:1:-1}"
}

# Start
main "${*}"

exit ${?}
```

bash対応。一部のシェルでは動かない可能性があります。
これはGoogleのGemini APIを利用して作ったものです。
APIキーを取得する必要があるので、以下のページから取得してください。

https://ai.google.dev/gemini-api/docs?hl=ja

無料版と有料版がありますが、よほどのことがない限り無料版で事足ります。
[詳しくはここを参照](https://ai.google.dev/gemini-api/docs/pricing?hl=ja)

# 解説のようなもの

まずベースにしたのは先ほどのリンクにある `` curl `` のコマンドです。

```bash:shell
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=YOUR_API_KEY" \
  -H 'Content-Type: application/json' \
  -X POST \
  -d '{
    "contents": [
      {
        "parts": [
          {
            "text": "Explain how AI works in a few words"
          }
        ]
      }
    ]
  }'
```

これに `` jq -r `` で整形すればもうそれで終わりじゃね？
と思っていた時期がありましたが現実はそう甘くありませんでした。
Gemini君は律儀にも最後に `` \n `` を入れてくるので `` jq -r `` でやると空白の行が生まれてくるのです。
そこで `` printf `` を使った整形に切り替えました。
bashは優秀なので最後に改行文字がない場合も改行してくれます。
Geminiが間違って最後に改行文字を送らなかった場合にもきれいに表示させることができるのです。神。

# 実行例

ここからは実行例とその時に注意することです。

### ①　基本

```terminal:Terminal
$ ./gemini 明日の東京の天気を教えて
明日の東京の天気は晴れ時々曇りの予報です。最高気温は25℃、最低気温は13℃の予報です。

tenki.jp: [https://tenki.jp/forecast/3/16/4410/13101/](https://tenki.jp/forecast/3/16/4410/13101/)

お出かけの際は、紫外線対策や寒暖差対策をされると良いでしょう。
```

マークダウン形式で返されるので少々見づらいですが、許容範囲内でしょう。

### ②　スペースが入った文章

スペースが入っていても返してくれます。
でもエスケープ処理はやりましょう。
```terminal:Terminal
$ ./gemini Where is Morocco\?
Morocco is located in **North Africa**, bordering the North Atlantic Ocean and the Mediterranean Sea. It is just south of Spain, separated by the Strait of Gibraltar.
```

:::note warn

`` " `` を使いたい場合はコードの関係上、 `` \\\" `` と入力しないといけないので注意してください。

<details><summary><b>\"で入力した場合</b></summary>

```terminal:Terminal
$ ./gemini 次の文章を英語に翻訳して: \"中東には石油が豊富にあります。\"
jq: error (at <stdin>:7): Cannot iterate over null (null)
Error! (Exit status: 5)
```

エラーが返されてしまいました。これは内部で

```json
{ 
  "contents": [
    { 
      "parts":[
        {
          "text": "次の文章を英語に翻訳して: "中東には石油が豊富にあります。""
        }
      ] 
    }
  ]
}
```

このようになり、エスケープ処理を行っていない状態となったためだと推測されます

</details>

:::

### ③　コマンド検索

最初に想定していた用途もしっかりこなしています。

~~~terminal:Terminal
$ ./gemini debianでネットワークデバイスを作るコマンドってなんだっけ？　一つ挙げて
Debianでネットワークデバイスを作成するコマンドとして、`ip` コマンドがよく使われます。

例えば、`ip link add` コマンドを使って仮想的なネットワークデバイスを作成できます。

```bash
sudo ip link add <デバイス名> type dummy
```

`<デバイス名>` の部分には、作成したいデバイスの名前（例：`dummy0`）を指定します。
~~~

これでコマンドを思い出せますね！

# おまけ

ゲストモードで何の会話もしてないのに今までの話題を言うよう指示すると…

![スクリーンショット 2025-03-26 131946.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4044565/be886a5d-c3a8-4d4d-adef-a79c3242b05d.png)

嘘っぱちを言ってきます。恐ろしい子。

# 参考

 - [Gemini API  |  Google AI for Developers](https://ai.google.dev/gemini-api/docs?hl=ja#rest)

 - [Gemini Developer API の料金  |  Gemini API  |  Google AI for Developers](https://ai.google.dev/gemini-api/docs/pricing?hl=ja)

 - [DeepL翻訳：高精度な翻訳ツール](https://www.deepl.com/ja/translator)

 - [jqコマンドでjsonデータを整形・絞り込み #JSON - Qiita](https://qiita.com/Nakau/items/272bfd00b7a83d162e3a)

 - [jqを活用してAPIレスポンス等から欲しい情報だけを抽出する【中級編】 | DevelopersIO](https://dev.classmethod.jp/articles/road-to-jq-master-apprentice/)

 - [よく使うcurlコマンドのオプション #Linux - Qiita](https://qiita.com/ryuichi1208/items/e4e1b27ff7d54a66dcd9)

 - [8.4.25　printfコマンド（書式の引数を書式に従って変換し，標準出力に出力する） : JP1 Version 11 JP1/Advanced Shell ](https://itpfdoc.hitachi.co.jp/manuals/3021/30213b3220/0383.HTM)

 - [【シェル芸人への道】Bashの変数展開と真摯に向き合う #ShellScript - Qiita](https://qiita.com/t_nakayama0714/items/80b4c94de43643f4be51)

 - [いい加減覚えよう。 \`command > /dev/null 2>&1\`の意味 #Linux - Qiita](https://qiita.com/ritukiii/items/b3d91e97b71ecd41d4ea)