406
407

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

新しい curl コマンドの使い方 完全ガイド(2025年版)

Last updated at Posted at 2025-02-21

はじめに

curl とは対話シェルやシェルスクリプトから HTTP 通信を行うのによく使われるコマンドです。あらゆる環境(100 種類の OS)で動作し、macOS や Windows には標準でインストールされています。商用サポートもあり、互換性は非常に重視され、何年経っても同じ書き方で動きます。非常に長く使われており(1998 年生まれの 27 歳1)、そして古い情報もたくさんあります。この記事ではそういった古い情報を、より簡単で新しい curl コマンドの使い方にアップデートします。最初に結論を書いておくと、

もう -X POST -H "Content-Type: applicatoin/json" なんて書かなくていいですよ。
記事を読まない人のためのリンク

この記事を書くにあたって以下の記事を参考にしています。この記事が書かれたのは 2015 年、現在はそれから 10 年後です。

この記事は最新版の新機能の紹介ではなく、以前から使える機能を含む curl の新しい使い方ガイドです。

curl コマンド入門

まず curl コマンドを全く知らないという初心者に向けて、簡単に curl コマンドとこの記事の内容について説明します。curl コマンドは、多くの機能を持っておりすべてを把握するのは大変です。この記事では HTTP/HTTPS に関する機能のみを、次の3つに分けて解説しています。このことを意識して読むと curl コマンドの使い方を理解しやすくなるでしょう。

  1. GETメソッドと基本の話(ファイルのダウンロードやJSONデータの受信の話)
  2. HTML フォーム送信の話(<form> タグからの送信に相当、現在はあまり必要ない)
  3. RESTful API の話(JSON データの送信など、現在の主流の使い方)

RESTful API の使い方が主流となっている現在では、覚えるのが大変な「2. HTML フォーム送信の話」(-d , --data 系オプションに関する話)をスキップしても(ざっと読むだけも)構わないことを知っておくと少し楽になれます。

cURL (Client for URLs) と curl とは

curl とはコマンドの名前です。cURL(「Client for URLs」の語呂合わせ)は curl コマンドを作っているプロジェクトの名前です。また各言語用のライブラリの名前 (libcurl) としても使われています。

さてここで一つ、まったく関係のない curl があります。それはプログラミング言語の名前としての curl です。この場合は Curl 言語と書かれることが多いようです。

どちらも 1998 年頃に、運悪く同じ名前で公開されました。この2つを混同しないように注意してください。この件についての詳細は以下のページ説明されています。

発音 curl は「kurl」です - /kʌrl/ (/kɜrl/)

curl の日本での一般的な読み方は「カール」で良いでしょう。発音の「kurl」は以下のコミットメッセージからです。(コメント より /kʌrl/ (/kɜrl/) )

以前はプロジェクト名「cURL」を決めた理由について「see URL と発音できる」と書かれていたのですが、2021 年 12 月に、私達は決して see URL とは発音しないとして「see URL と読める」に変更したのが上記のコミットです。

個人的には「カール」と読んでいますが... FAQ に実際の発音の音声ファイルがあります。

We pronounce curl with an initial k sound. It rhymes with words like girl and earl. This is a short WAV file to help you: https://media.merriam-webster.com/soundc11/c/curl0001.wav

「くぅろぉ」?  (like girl and earl ⋯ girl はガールだからカールでいいよね? )

curl の高い互換性への考え方の紹介

以下のページで curl の互換性についての考え方が紹介されています。正確には ABI (Application Binary Interface) に関する話ですが、コマンドラインインターフェースについても同じと考えて良いでしょう。一言で言えば、互換性絶対破壊しないマンです。

curl は 2023 年 3 月に、バージョン 7.88.1 から 8.0.0 に変更されました(参照)。一般的にメジャーバージョンが更新されるときは、互換性のない変更が含まれることを意味しますが、curl の場合は大きくなりすぎたマイナーバージョン番号をリセットするだけのものです。つまり curl の 7 系 と 8 系の間に破壊的変更はないので安心して更新できます。

以下の文章は短かったので翻訳してみました。

API 互換性

libcurl は API の安定性を約束し、今日書かれたあなたのプログラムが将来も動き続けることを保証します。私達は互換性を壊すことはありません。

時間とともに、私達は API に機能、新しいオプション、新しい関数を追加しますが、私達は互換性がない方法で動作を変更したり、関数を削除したりしません。

最後に私達が互換性のない方法で API を変更したのは 2006 年の 7.16.0 であり、私達は同じことを行う予定はありません。

他の類似ツールとの比較

機能的な比較一覧が curl 公式サイトにあったので紹介します。ただし情報は少し古いまたは正確ではないかもしれないと思っています。

HTTP/HTTPS に限定して言えば、他のツールも機能的に見劣りしているわけではありません。wget/wget2 は HTML フォームの multipart/form-data の送信に直接対応してないのがウィークポイントですが、自分でマルチパートデータを組み立てればできないことはないと思います。HTTP PUT には非対応なのは結構ある感じですが、wget/wget2 に関しては随分と前から --method--header オプションに対応しているのでできる気がします。

もっとも個人的に curl が素晴らしいと思っている点は、どの環境でも動くことと互換性の高さ(バージョンアップは機能が増えるだけで壊れない)を何年もの間実現し続けていることなので、私はそれほど他のツールには興味を持っていません。標準でインストールされている可能性がある wget(GNU 版だけでなくサブセットの BusyBox 版もある) と fetch (FreeBSD) をダウンロード用に使うぐらいです。しかし NetBSD や OpenBSD には標準インストールのダウンロードツールはなく、いずれにしろ何かをインストールしなければならないのなら curl を選択することで環境依存のないどこでも動くシェルスクリプトが書けます。curlwgetfetch、全てに対応するコードを書く必要はありませんし、wgetfetch で機能が使えないからと言って全てで使える最低限の機能だけで頑張る必要もありません。オープンソースでユーザー数も多いので将来使えなくなるという心配もありません(元の開発者が続けられなくなっても誰でも後を引き継げる)。自作を含めユーザー数が少ないコマンドは長期持続性がなく、バグがあったり将来使えなくなるリスクが高いので継続した開発とユーザー数は重要です。そういう意味では、wget/wget2 や HTTPie は十分良い選択肢です。

今回使用した curl のバージョン (8.12.1)

2025 年 2 月 13 日にリリースされた curl 8.12.1 です。(記事執筆時点の最新版)

$ curl --version
curl 8.12.1 (x86_64-apple-darwin22.6.0) libcurl/8.12.1
OpenSSL/3.4.1 (SecureTransport) zlib/1.2.11 brotli/1.1.0
zstd/1.5.6 AppleIDN libssh2/1.11.1 nghttp2/1.64.0 librtmp/2.3
Release-Date: 2025-02-13
Protocols: dict file ftp ftps gopher gophers http https imap imaps
ipfs ipns ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp
smb smbs smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy
IDN IPv6 Kerberos Largefile libz MultiSSL NTLM SPNEGO SSL
threadsafe TLS-SRP UnixSockets zstd

ちなみに上記の Protocols:curl が対応しているプロトコルです。ビルドオプションによって変わり、Windows 標準インストール版はいくつかの機能が無効になっています。curl は HTTP 関連だけではなく、ネットワークに関する多くの通信に対応しています。例えば TELNET プロトコルにも対応しているので、curl コマンドは telnet コマンドがインストールされていない環境での最終手段です。

最新版バイナリのダウンロード

各種 OS 用バイナリのダウンロードはこちらから

その他に、本当に多種多様な OS 用のバイナリが提供されています。

各 OS のバージョン情報

参考として、curl のバージョンと 各 OS のバージョン情報です。この記事ではいくつかの新しい機能を説明していますが、現時点では標準で使えない環境があるようです。最新版をインストールしましょう。なお、curl の 7 系 と 8 系の間に破壊的な変更はなく、単に増えすぎたバージョン番号をリセットしただけとのことです。

詳細: https://curl.se/docs/releases.html より
この記事で解説している比較的新しいオプションを含むバージョンの抜粋

curl 追加オプション または OS (curl) のバージョン
Windows 10 1803 (7.55.1) 初搭載, Windows 11 初板 2021年10月 (7.55.1)
Ubuntu 18.04 (7.56.0)
7.61.0 [2018-07] --oauth2-bearer の HTTP 対応
AlmaLinux 8.10 (7.61.1)
7.63.0 [2018-12] --write-out "%{stderr} %{stdout}"
7.67.0 [2019-11] --no-progress-meter
Ubuntu 20.04 (7.68.0), AlmaLinux 9.5 (7.76.0), Ubuntu 22.04 (7.81.0)、Windows 10 / 11 2022年1月頃 (7.79.1)
7.82.0 [2022-03] --json
macOS 13.0 (7.84.0), macOS 13.1 (7.85.0), macOS 13.2 (7.86.0)
Windows 10 / 11 2022年7月頃 (7.83.1)
7.87.0 [2022-12] --url-query
macOS 13.3 (7.87.0), macOS 13.4 (7.88.1),
Windows 10 / 11 2023年4月頃 8.0.1, macOS 13.5-14.1 (8.1.2)
8.3.0 [2023-11] --variable, --expand-*, --write-out %output{}
macOS 14.2-14.4 (8.4.0), Windows 10 / 11 2023年11月頃 (8.4.0),
Ubuntu 24.04 (8.5.0), macOS 14.5 (8.6.0), macOS 14.6 - 15.2 (8.7.1)
Windows 10 / 11 2024年5月頃 (8.7.1), Windows 10 / 11 2024年10月頃 (8.9.1)
8.10.0 [2024-09-11] -vv, -vvv, -vvvv
Windows 10 / 11 2025年2月時点 (8.10.1)
8.12.0 [2025-02-05] --variable "name[0-99]@filename"
Ubuntu 25.04(予定)(8.12.1)

【参考】 jq のインストール

jqcurl にとって必ずしも必要なものではありませんが、RESTful API を呼び出そうっていうのなら、もはや必須の道具ですよね? jq の実装も本家あわせて私が知る限り 3 つあります。curl と同じようにどの環境でも動くツール兼言語です。

動作確認方法

まず最初にこの記事で使用している動作確認方法について説明します。この記事では curl コマンドがどのようなデータを送信しているかを確認するために、次の nc コマンドを使った簡易サーバーで実際に送信した通信内容を出力しています。

簡易サーバー
sh -c 'while command -p nc -l localhost 8080; do sleep 0.2; done' &

この記事で curl コマンドが出力しているようなログは、実際には nc コマンドが出力していることに注意してください。

$ sh -c 'while command -p nc -l localhost 8080; do sleep 0.2; done' &
[1] 95576      ← [1] はジョブ番号

$ curl -sS localhost:8080
GET / HTTP/1.1                 ┐
Host: localhost:8080           │
User-Agent: curl/8.12.1        ├ 実は nc コマンドの出力
Accept: */*                    │
                               ┘
^C     ← curl が止まるので CTRL+C で止める

$ kill %1    ← nc コマンドを止めたい場合はジョブ番号を指定して kill する
[1]+  Terminated    sh -c 'while command -p nc -l localhost 8080; do sleep 0.2; done'

ちなみに無限ループしているのは、curl コマンド送信を CTRL+C で止めたときに nc コマンドも終了してしまうからです。nc コマンド(環境によっては netcat)の引数は実装によって違うようなので動かなかったら調べてください。command -p はほとんどの人は書く必要はないのですが、私は Homebrew で別の nc コマンドをインストールしているので OS 標準版を呼び出すために使っています。Ubuntu と macOS はこれで動きました。Windows のやり方は調べていません。

もう一つの動作確認方法として、URL に file:/dev/null (Windows では file:/NUL:)を指定している箇所があります。これは file プロトコルでローカルの /dev/null ファイル、つまり空ファイルを読み込んで出力しており何も出力されません。たしか昔は URL を省略できたと思うのですが今はできないのでその代わりです。

$ curl -w 'Hello World\n' file:/dev/null
Hello World

これでどこかのサーバーにあまり負荷をかけることなくデータのみのシンプルなログが得られます。まあ、一部は http://example.com にアクセスしてるんですが。この機能 curl コマンド標準でできませんかね?

追記 上記の「たしか昔は URL を省略できた」という話は、おそらく Curl 8.6.0+ doesn't allow empty string in args で、空の URL が指定できなくなったというのが正しいようです。元々文書化されていなかった動作が修正された形です(文書化されていない動作とは言え互換性破壊されとるんやが……)。

トレース表示ツール curl-trace

この記事を書いた後に思いついたのでこの記事では使っていませんが、こういうのを作ってみました。

curl-trace
#!/bin/sh

curl --no-progress-meter -o /dev/null --trace - "$@" | (
  set -- awk
  type gawk >/dev/null 2>&1 && set -- gawk -n
  LC_ALL=C "$@" '
    /^[0-9a-f]+:/ {
      $0 = substr($0, 7, 47)
      for (i = 1; i <= NF; i++) {
        printf "%c", int("0x" (last = $i))
      }
    }
    /^== Info: .* sent off/ {
      if (last != "0a") print ""
      print "========================================"
    }
    /^<= Recv data/ { exit }
  '
)

引数は curl コマンドへの引数そのままです。

$ curl-trace example.com
GET / HTTP/1.1
Host: example.com
User-Agent: curl/8.12.1
Accept: */*

========================================
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
Cache-Control: max-age=2962
Date: Fri, 21 Feb 2025 11:07:37 GMT
Content-Length: 1256
Connection: keep-alive

こういうのでいいんだよ、こういうので

仕組みは --trace で通信データが 16 進数表記で出力されるので、それを元に戻しつつ必要な所だけ出力しています。この機能 curl コマンド標準でできませんかね?(2 回目)

-M (--manual), -h (--help) でドキュメント

この記事で解説している curl コマンドの機能やオプションはすべてではありません。本当に正しい情報やすべてを知るには公式のドキュメントを参照してください。通常は man curl で良いのですが、最新バージョンをインストールしたときなどで適切なドキュメントが参照できず困ることがあります。例えば Homebrew 版は諸事情(?)でインストールしても、インストールしたバージョンのマニュアルが参照できません。正しく設定すればよいのですが、それよりも簡単な方法が -M オプションを使用する方法です。この機能は 1998 年の 5.2 から実装されているようです。Windows 標準版ではこの機能は無効にされているようなので公式版をダウンロードしてください。この方法なら実行している curl のバージョンのドキュメントを簡単に参照できます。

$ curl -M | less

特定のオプションのドキュメントだけを見たい場合、-h (--help) オプションが便利です(8.10.0 以降)。

$ curl -h -o | less

これらのオプションについては開発者ブログのこちらの記事もどうぞ。

その他のリンクです。

オプションの数

すべてのオプションは curl --help all で出力できるので、すべてのオプションの数を数えるのは簡単です。

$ curl --help all
     --abstract-unix-socket <path>                 Connect via abstract Unix domain socket
     --alt-svc <filename>                          Enable alt-svc with this cache file
     --anyauth                                     Pick any authentication method
 -a, --append                                      Append to target file when uploading
   ︙
     
$ curl --help all | wc -l
267

設定ファイル (.curlrc, --config, --disable)

curl コマンドは起動時に読み込む設定ファイル(config ファイル)を持っています。config ファイルは curl コマンドのオプションに相当のものを記述した設定ファイルです。デフォルトではホームディレクトリの .curlrc が(あれば)読み込まれます(その他のデフォルトの config ファイルについてはドキュメント参照)。

config ファイルは --config オプションで指定できますが、指定するとデフォルトの .curlrc の読み込みが無効になるのではなく追加で読み込まれます。もしデフォルトの config ファイルの読み込みを無効にしたいのであれば curl コマンドの最初の引数として --disable オプションを指定します。シェルスクリプトの場合、config ファイルの読み込みを無効にしておけば、curl コマンドの動作がユーザーの設定に依存しなくなります。config ファイルの書式については --config オプションのドキュメントを参照してください。

ちなみになぜ --disable オプションが最初の引数に限定されているかというと、オプションを設定する設定ファイルは、このような変な仕様を作らないと上手くいかないからです(設定ファイルの中で --disable オプションが指定された場合とか。この問題は実際に実装してみると気づきます……)。

GETメソッドと基本の話

ファイルのダウンロードなど、基本的な GET の話はほとんど変わっていません。GET は「HTML フォーム送信の話」と「RESTful API の話」にも関係してきますが、ここでの話は HTML のリンクをクリックする操作に相当するものと考えると理解しやすくなります。ダウンロードはリンクをクリックするだけでも行われますよね?

URLはクォートしましょう

文字が長くなるのでこの記事では省略していますが、(シェルのメタ文字が含まれるのであれば)URL はクォートしたほうが良いです。

# &はシェルのメタ文字なのでそのまま書けない。?も場合によってはまずい。
curl localhost:8080?key1=value1&key2=value2

# クォートするのが安全
curl 'localhost:8080?key1=value1&key2=value2'

わかっている人なら構わないのですが、シェルの文法がよくわからないとか言っている人は素直にクォートしましょう。

あと、余談ですが Windows のバッチファイルでは %%% と書かないとダメとか。面倒なので確認していません。

よく使う -sSfL オプション

オプションはたくさんあります。オプション一覧 が欲しい方はドキュメントを参照するとよいでしょう。ここでは例のいつものよく使うオプションについて説明します。

オプション 意味
-s, --silent プログレスメーターや、エラーや警告を出力しない
-S, --show-error エラーを出力するが、警告は出力するようにはならない
-f, --fail HTTP レスポンスコードが 400、500 番台のときにエラーにする
-L, --location リダイレクトを自動的に追跡する

プログレスメーターだけを非表示にする (--no-progress-meter)

-s (--silent) オプションは、何も出力しないオプションで、プログレスメーターだけでなくエラーも警告も出力されなくなります。ただし -S オプションを付けるとエラーは出力されます。しかし警告は出力されません。プログレスメーターだけを消したい場合には、2019 年の 7.67.0で 新たに --no-progress-meter オプションが追加されています。つまり -sS--no-progress-meter に置き換えられるということです。長すぎるので、対話シェルではエイリアスを設定し、シェルスクリプトでは次のようなシェル関数を定義してデフォルトオプションを追加するとよいでしょう。

# curl コマンドへデフォルトのオプションを追加する
curl() {
  command curl --no-progress-meter "$@"
}

# 以下のように実行するだけで上記のオプションが追加される
curl example.com

--silent については開発者ブログのこちらの記事もどうぞ。

ちなみに ootw とは Option-Of-The-Week の略とのことです。一般的には「Outfit Of The Week」の略で、1 週間のお気に入りファッションコーディネートを紹介する時に使われる用語らしいです。

通信データを圧縮する (--compressed)

見逃されがちなのが、curl の通信は圧縮されていないということです。--compressed オプションを指定することで圧縮されます。(補足: 以下の出力は読みやすいように不要な行を削っています。)

$ curl -sS -v -o /dev/null example.com
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.12.1
> Accept: */*

< HTTP/1.1 200 OK
< Content-Type: text/html
< ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
< Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
< Cache-Control: max-age=1262
< Date: Thu, 20 Feb 2025 11:44:57 GMT
< Content-Length: 1256                ← 元のサイズ
< Connection: keep-alive
$ curl -sS -v -o /dev/null --compressed example.com
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.12.1
> Accept: */*
> Accept-Encoding: deflate, gzip      ← 圧縮を受け付ける

< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Type: text/html
< ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
< Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
< Vary: Accept-Encoding
< Content-Encoding: gzip              ← 圧縮されている
< Content-Length: 648                 ← サイズが減っている
< Cache-Control: max-age=1128
< Date: Thu, 20 Feb 2025 11:46:23 GMT
< Connection: keep-alive

-k (--insecure) オプションを使わない! 証明書エラーを無視しない!

 $\mathtt{\huge{危険な‐kオプションは使ってはいけません}}$

-k は kiken の略です。説明を読まない人のためにはこの一言だけで十分でしょう。正しい対処法は「サーバー側の設定を正しく行う」または「自分の環境の設定を正しく行う」です。時計がずれていたり OS が古かったりするのが原因なのでアップデートしましょう。

わかっている人にとってはあたり前のことなのでしょうが、なんの説明もなしに危険な -k オプションを便利だよと紹介する記事が多すぎます。読まない人は読まないですからね。だから読まない人のために書きました。どうしても使用する場合は代わりに ‐‐insecure オプションと書きます。「危険」という意味の分かりやすい別名です。対話シェルで時間がない時に使うことはあっても、シェルスクリプトの中に書くことはないはずです。ましてや本番環境では使用しません。

ちなみに、以下のようなエラーが出るときの話です。他にもあるかもしれません。

  • curl: (51) SSL: no alternative certificate subject name matches target host name
  • curl: (51) SSL: certificate subject name does not match target host name
  • curl: (58) Unable to set private key file: wrong pass phrase?
  • curl: (59) Could not load client certificate, OpenSSL error
  • curl: (60) SSL certificate problem: self signed certificate
  • curl: (60) SSL certificate problem: certificate has expired
  • curl: (60) SSL certificate problem: certificate is not yet valid
  • curl: (60) SSL certificate problem: unable to get local issuer certificate
  • curl: (60) SSL certificate problem: unable to verify the first certificate
  • curl: (60) SSL certificate problem: invalid CA certificate
  • curl: (60) SSL certificate problem: certificate has been revoked
  • curl: (77) Problem with the SSL CA cert (path? access rights?)
  • curl: (77) error setting certificate verify locations:
  • curl: (78) SSL: certificate revocation check failed

この記事を書いている最中、開発者ブログを読んでいたら以下の記事を見つけたので、やっぱ開発者も同じことを思っているんだなぁと思いました。

この記事によると、-k オプションを使用すると、curl はサーバーの以下の TLS 証明書の検証をスキップします。

  • 証明書が信頼された認証局(CA)によって署名されているか
  • 接続先のホスト名と証明書が一致しているか
  • 証明書の有効期限が切れていないか

また libcurl を使用する場合、以下の値を 0 (false) にしてはいけません

  • CURLOPT_SSL_VERIFYHOST
  • CURLOPT_SSL_VERIFYPEER

このオプションは以下の状況下でデバッグ目的で一時的に利用するためです。

  • サーバーの証明書を検証するための適切な CA 証明書をまだ持っていない
  • サーバーが誤った設定になっており、正規のチェックに失敗してしまう

なぜわかりにくい -k オプションが削除されずに残されているのか? それが curl の高い互換性の考え方だからです。-k オプションを非推奨にし将来削除して --insecure だけにすべきであるという提案は、意図的に壊す提案だとして却下されています。つまり残る手段は私達が -k オプションを使わないことしかないということです。

ファイルに出力する -o, -O オプション

-o (--output) オプションで指定したファイルに保存できます。-O (--remote-name) オプションだと、指定した URL のファイル名部分のみが使われます。

指定したファイル名で保存
$ curl -o index.html http://example.com
指定したURLのファイル名で保存
$ curl -O http://example.com/file.tar.gz

-o オプションを指定しない場合、標準出力に出力されますが、これを出力したくない場合にはファイル名に /dev/null を指定します。

出力を /dev/null に捨てる
$ curl -o /dev/null http://example.com

ちなみにリモートが指定する名前を使用したい場合には -J (--remote-header-name) オプションを指定します。この場合、既存のファイルは上書きされません(されたら危険ですね)。上書きを許可する --clobber オプションもあるようですが。あまり使わないほうが良いでしょう。

クエリー文字列を組み立てる (--url-query)

クエリー文字列を組み立てるときには、2022 年のバージョン 7.87.0 で追加された --url-query を使用します。これは昔の -G-d を併用した回避策を置き換え、従来はできなかった POST などと組み合せて使える方法です。キーはそのままですが値は URL エンコードされます。

クエリー文字列(URLの後ろの「?」以降)の組み立て
$ curl -sS --url-query key1=value1 --url-query key2=value2 localhost:8080
GET /?key1=value1&key2=value2 HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
POST でも動作する
$ curl -sS --url-query key=value -d data=value localhost:8080
POST /?key=value HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Content-Length: 10
Content-Type: application/x-www-form-urlencoded

data=value
値は URL エンコードされる
$ curl -sS --url-query key=値 localhost:8080
GET /?key=%e5%80%a4 HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

-i (--include) でレスポンスヘッダを出力

-i (--include) オプションを使用すると、レスポンスのボディの前にレスポンスヘッダを含めて出力します。レスポンスヘッダとレスポンスのボディは間には空行が挟まり区別できます。最近 --show-headers という意図が明確な別名が与えられました。

$ curl -i amazon.com
HTTP/1.1 301 Moved Permanently          ┐
Server: Server                          │
Date: Thu, 20 Feb 2025 11:14:11 GMT     │
Content-Type: text/html                 ├ レスポンス
Content-Length: 163                     │    ヘッダ
Connection: keep-alive                  │
Location: https://amazon.com/           ┘
                                             空行
<html>                                               <head><title>301 Moved Permanently</title></head>    │
<body>                                               <center><h1>301 Moved Permanently</h1></center>      ├ レスポンス
<hr><center>Server</center>                          │    ボディ
</body>                                              </html>                                              

この情報を使ってシェルスクリプトで分岐処理を行えます。もっともこんなことをやっているコードを見たことがありませんが。

HTTPステータスコードによる処理の分岐例
CR=$(printf '\r') # ヘッダの改行コード「CR LF」の CR を消すための準備
curl -sS -i example.com | {
  read -r http_version http_code message # ヘッダの1行目の取得
  while IFS= read -r line; do    # 空行まで読み飛ばすループ
    [ "${line%"$CR"}" ] || break # CRを削除した行が空行の場合にループを抜ける
  done
  case $http_code in
    2*) cat ;;     # 200番台なら標準出力に出力
    *) cat >&2 ;;  # それ以外なら標準エラー出力に出力
  esac
}

HTTP ステータスコード程度なら問題有りませんが、その他ヘッダは解析が面倒なので、-w の出力をレスポンスボディ前に行うやつが欲しい所です。なお、-i はレスポンスボディの一部のように出力されるため、-o で指定した出力先に出力されます。ちなみにレスポンスヘッダのみを見たい場合は、-D - -o /dev/null を使います-I はレスポンスヘッダを見るオプションではないので気をつけてください。

-i はレスポンスボディの一部のように出力 (include) される
# 出力を /dev/null に捨てるとレスポンスヘッダも出力されない
curl -sS -i -o /dev/null example.com

# レスポンスヘッダのみを見る正しい方法
curl -sS -D - -o /dev/null example.com

-w (--write-out) で追加の情報を出力

-w オプションを指定すると追加の情報を出力に加えます。-i オプションとは反対にレスポンスボディの後に出力されます。どのような情報を参照できるかは、%{json}%{header_json} を出力するのが簡単です。そのままでは見づらいので jq コマンドに通すと良いでしょう。補足ですが、jq . の後ろの . は今は不要です。

HTTP ヘッダ以外の情報を JSON 形式で出力
$ curl -sS -w '%{json}' -o /dev/null http://example.com | jq
    ︙ 長いので省略
HTTP ヘッダの情報を JSON 形式で出力
$ curl -sS -w '%{header_json}' -o /dev/null http://example.com | jq
    ︙ 長いので省略

知りたい項目名がわかったら書式を指定して出力すれば OK です。HTTP ヘッダの情報は %header{キー} で指定します。

$ curl -sS -w '%{method}: %{url}\n' -o /dev/null http://example.com
GET http://example.com

$ curl -sS -w '%header{content-length}\n' -o /dev/null http://example.com
1256

-w はデフォルトでは標準出力に出力しますが、バージョン 8.3.0 以降では出力先を標準エラー出力(%{stderr})に切り替えたり標準出力(%{stdout})に戻したりできます。またファイル(%output{ファイル名})に出力したり、追記( %output{>>ファイル名})もできます。

出力先の切り替え
$ curl -sS -w '%{stderr}stderr\n%{stdout}stdout\n' file:/dev/null >/dev/null
stderr

$ curl -sS -w '%{stderr}stderr\n%{stdout}stdout\n' file:/dev/null 2>/dev/null
stdout
ファイルに出力する
$ curl -sS -w '%output{/tmp/output.txt}Hello\n' file:/dev/null
$ cat /tmp/output.txt
Hello

$ curl -sS -w '%output{>>/tmp/output.txt}World\n' file:/dev/null
$ cat /tmp/output.txt
Hello
World

余談ですが、個人的にはファイルディスクリプタに出力できるようにして欲しくて、ほとんどの環境では /dev/fd/N が実装されているので代わりに使えるのですが、こういう事ができます。こんなコードを書こうという人はあまりいないかもしれませんが(提案しようかな?)。

#!/bin/sh

{
  http_code=$(
    {
      format='%output{/dev/fd/3}%{http_code}'
      curl -sS -w "$format" 'http://example.com' >&4 4>&-
    } 3>&1
  )
} 4>&1

# http_code が取れる!
echo "$http_code"

以下のように一時ファイルに出力すれば、上記のコードでやりたいことと同じことができますが、ファイルディスクリプタなら一時ファイルが不要です。

http_code=$(curl -sS -w '%{http_code}' -o /tmp/index.html example.com)
# http_code が取れる!
echo "$http_code"

変数と変数展開機能 (--variable, --expand-*)

2023 年 11 月にリリースされた 8.3.0 から使えるようになった非常に便利な機能が「変数」 です。これは --variable を使って curl コマンドだけで使える変数を定義できます。変数を展開したい場合は、--expand- を頭につけてオプションを指定します。つまり --url オプションはデフォルトでは変数は展開されませんが、展開させたい場合は --expand-url という形で指定します。少々面倒な使い方ですが互換性を保つためでしょう。

$ curl -sS --variable path=web --expand-url 'localhost:8080/{{path}}'
GET /web HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

この変数は頭に % を指定することで環境変数を取り込めます。環境変数が定義されていない場合はエラーになりますが、定義されていないときのデフォルト値も設定すればエラーになりません。

環境変数の取り込み
$ curl -sS --variable '%USER' --expand-url 'localhost:8080/{{USER}}'
GET /koichi HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

$ curl -sS --variable '%name=guest' --expand-url 'localhost:8080/{{name}}'
GET /guest HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: *

$ name=ken curl -sS --variable '%name=guest' --expand-url 'localhost:8080/{{name}}'
GET /ken HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

普通にシェル変数でいいじゃないか? と思っている頃だと思いますが、便利な機能の一つが起動時の設定ファイル(.curlrc など)で利用できることです。また {{v:url}} みたいな感じで関数を使用できます。関数は {{v:trim:url}} みたいにして複数組み合せられます。現在、定義されている関数は少ないですが、もっと拡張されていくでしょう。なお変数はもう一つ素晴らしい使い方があるのですが、それは後ほど。

設定ファイルで環境変数を取り込む

~/.curlrc
variable = %USER
expand-write-out = "Hello {{USER}}\n"
$ curl -sS -o /dev/null example.com
Hello koichi

URLエンコードするだけ {{v:url}}

おまけで jq コマンド版も紹介します。

$ curl --variable v='あいおえお' --expand-write-out '{{v:url}}\n' file:/dev/null
%E3%81%82%E3%81%84%E3%81%8A%E3%81%88%E3%81%8A

$ jq -rn --arg v あいうえお '@uri "\($v)"'
%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A

Base64エンコードするだけ {{v:b64}}

おまけで jq コマンド版も紹介します。

$ curl --variable v='あいおえお' --expand-write-out '{{v:b64}}\n' file:/dev/null
44GC44GE44GK44GI44GK

$ jq -rn --arg v あいうえお '@base64 "\($v)"'
44GC44GE44GG44GI44GK

JSONエスケープするだけ {{v:json}}

おまけで jq コマンド版も紹介しますが、少し出力が異なります。

$ curl --variable v=' \ " ' --expand-write-out '{{v:json}}\n' file:/dev/null
 \\ \"

$ jq -rn --arg v ' \ " ' '@json "\($v)"'
" \\ \" "

trimするだけ {{v:trim}}

$ curl --variable v='  hello  ' --expand-write-out '[{{v:trim}}]\n' file:/dev/null
[hello]

変数の内容をファイルから読み込む

変数の内容をファイルから読み込むこともできます。

$ cat file.txt
up
down
left
right

$ curl --variable v@file.txt --expand-write-out '[{{v:trim}}]\n' file:/dev/null
[up
down
left
right
]

読み込む位置をバイト単位で範囲を指定して読み込めます。

$ curl --variable v[6-10]@file.txt --expand-write-out '[{{v}}]\n' file:/dev/null
[down ]

$ curl --variable v[6-10]@file.txt --expand-write-out '[{{v:trim}}]\n' file:/dev/null
[down]

ファイルの中から特定の範囲のデータを取り出すというユースケースが謎でしたが、ファイルは固定長ファイルで空きはスペースで埋められていると考えれば、もう一つのユースケースが謎だった trim の使い道が見えてくるわけで・・・うーん? 行番号指定での読み込みとかで良くない?

追記 コメント より、ユースケースは分割アップデートではないかと。なるほど。

HEADメソッドの話

HEAD メソッドとは、ウェブサーバーに接続し「本文は返さなくて良い。ウェブページが更新されているかを知りたいだけなんだ。ヘッダ情報だけ返してくれ。」と問い合わせる時に使う HTTP メソッドです。本文を返さないためサーバーの負荷やネットワーク通信量を減らす素晴らしい機能ですが、おそらく curl コマンドの通常の利用者が使うことはあまりないでしょう。

-I(大文字アイ)はレスポンスヘッダを見る方法ではない

非常に多く見かける間違いなのですが、-i(小文字のアイ)オプションと -I(大文字のアイ)オプションは全く違う機能です。出力にレスポンスヘッダを含める -i (--include) に対して、-I (--head) オプションは HEAD メソッドを実行するオプションです。実行している HTTP メソッドが違います。GET メソッドのレスポンスヘッダを見たい時に HEAD メソッドを実行したらだめでしょう? まあほとんど GET と同じようなレスポンスヘッダを返すのですが、-I では POST などのレスポンスヘッダは見られません。ちゃんと説明するなら -I は「HEAD メソッドのレスポンスヘッダ」を出力するオプションです。

-I は HEAD メソッドを実行している
$ curl -I localhost:8080
HEAD / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

-I オプションを指定するとレスポンスボディではなくレスポンスヘッダを出力するという動作に切り替わったり、-X オプションで HTTP メソッドを上書きできるようなのでレスポンスヘッダを見られないこともなかったりと、微妙な仕様があり、それが勘違いしてしまう理由だと思いますが、レスポンスヘッダを見るなら -D オプションか -v オプションが適切です。

追記 -I (HEAD) と -D (GET) で違いがあるサイトないかなーと探していたら灯台下暗しというやつで、ずっと使っていた example.com が出力が違っていました。HEAD はレスポンスボディがいらないので、まあそうなるわなって感じですね。

-I (HEAD)と -D (GET) に違いがある例
$ curl -sS -I example.com
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
Cache-Control: max-age=1490
Date: Sat, 22 Feb 2025 11:38:41 GMT
Connection: keep-alive

$ curl -sS -D - -o /dev/null example.com
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
Cache-Control: max-age=1036
Date: Sat, 22 Feb 2025 11:38:47 GMT
Content-Length: 1256                 ← Content-Length が出力されている
Connection: keep-alive

-I オプションをレスポンスヘッダだけを出力するオプションだと思っていると「あれ? examle.com は Content-Length を返さないんだ?」と勘違いしてしまいます。

追記2 Cloudfrontのコンテンツ圧縮機能でハマった-I をレスポンスヘッダの確認機能だと勘違いしていたことによる失敗談を見つけました。こちらでは Content-Encoding の出力の有無に差があるようです。

デバッグ方法の話

-D (--dump-header) - レポンスヘッダの内容を確認する

-i と似た機能でこちらもレスポンスヘッダを出力しますが、レスポンスヘッダはレスポンスのボディの出力の一部としてではなく、指定したファイルに出力されます。ただし出力先に - を指定すると標準出力に出力されます。バージョン 8.10.0 から出力先に % を指定すると標準エラー出力に出力されるようになりました。

ヘッダを標準出力に出力する
$ curl -sSfL -D - -o /dev/null example.com
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
Cache-Control: max-age=1500
Date: Thu, 20 Feb 2025 11:13:47 GMT
Content-Length: 1256
Connection: keep-alive
ヘッダを標準エラー出力に出力する
$ curl -sSfL -D % -o /dev/null example.com
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
Cache-Control: max-age=1514
Date: Thu, 20 Feb 2025 11:17:45 GMT
Content-Length: 1256
Connection: keep-alive

なお -I はレスポンスヘッダを確認する方法ではない ので注意してください。

-v (--verbose, -vv, -vvv, -vvvv) - 通信内容の詳細を確認する

-v オプションを指定すると通信の詳細情報が標準エラー出力に出力されます。バージョン 8.10.0 から -vv-vvv-vvvv みたいに書くと詳細レベルが増加し、それまでは --trace-time--trace-ascii などを指定しなければ見られなかった情報が簡単に見られるようになりました。実際の出力は量が多いので省略します。ぜひ試してみてください。

$ curl -sSfL -v -o /dev/null example.com
* Host example.com:80 was resolved.
* IPv6: (none)
* IPv4: 96.7.128.175, 96.7.128.198, 23.192.228.80, 23.215.0.138, 23.192.228.84, 23.215.0.136
*   Trying 96.7.128.175:80...
* Connected to example.com (96.7.128.175) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/html
< ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
< Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT
< Cache-Control: max-age=2898
< Date: Thu, 20 Feb 2025 11:26:31 GMT
< Content-Length: 1256
< Connection: keep-alive
<
{ [1256 bytes data]
* Connection #0 to host example.com left intact
*

補足として -v -v のように分けて書いても詳細レベルは増加しません。これはコマンドラインインターフェースの互換性を保つための機能です(例えば設定ファイル .curlrcverbose を有効にしてる状態で対話シェルから -v を指定した場合)。詳細は開発者ブログを参照してください。

--trace, --trace-ascii

--trace を指定すると通信内容を 16 進数表記で出力し、--trace-ascii を指定すると ASCII で出力します。出力先はファイルまたは標準出力「-」や標準エラー出力「%」を指定できます。現在は代わりに -vvv などを使ったほうが楽でしょう。

通信内容を16進数表記で出力
$ curl -sSfL --trace - -o /dev/null example.com
== Info: Host example.com:80 was resolved.
== Info: IPv6: (none)
== Info: IPv4: 96.7.128.198, 23.215.0.136, 23.192.228.80, 23.215.0.138, 23.192.228.84, 96.7.128.175
== Info:   Trying 96.7.128.198:80...
== Info: Connected to example.com (96.7.128.198) port 80
== Info: using HTTP/1.x
=> Send header, 75 bytes (0x4b)
0000: 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a GET / HTTP/1.1..
0010: 48 6f 73 74 3a 20 65 78 61 6d 70 6c 65 2e 63 6f Host: example.co
0020: 6d 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 63 m..User-Agent: c
0030: 75 72 6c 2f 38 2e 31 32 2e 31 0d 0a 41 63 63 65 url/8.12.1..Acce
0040: 70 74 3a 20 2a 2f 2a 0d 0a 0d 0a                pt: */*....
== Info: Request completely sent off
<= Recv header, 17 bytes (0x11)
0000: 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK.
0010: 0a                                 
通信情報を ASCII で出力
$ curl -sSfL --trace-ascii - -o /dev/null example.com
== Info: Host example.com:80 was resolved.
== Info: IPv6: (none)
== Info: IPv4: 23.215.0.136, 23.192.228.80, 23.215.0.138, 23.192.228.84, 96.7.128.175, 96.7.128.198
== Info:   Trying 23.215.0.136:80...
== Info: Connected to example.com (23.215.0.136) port 80
== Info: using HTTP/1.x
=> Send header, 75 bytes (0x4b)
0000: GET / HTTP/1.1
0010: Host: example.com
0023: User-Agent: curl/8.12.1
003c: Accept: */*
0049:
== Info: Request completely sent off
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK

認証とセキュリティの話

どのくらいのセキュリティレベルが必要かは、時と場合次第です。いつでも真面目にやる必要はありません。ただし真面目なやり方を知っておく必要はあり、重要な場合には真面目なやり方をする必要があります。

手抜きしても構いませんが、それは手抜きが許されるときだけです。普段は「手抜きでいいじゃないか!」が許されたからといって、それを本番環境で使って良いことにはなりません。手抜きができるのは正しいやり方を知っている人だけであり、正しいやり方を知らないのであれば、それが精一杯ということであり、手を抜いていることにはなりません。

つまり、対話シェルで開発中に行う手抜きと、信頼性が高いシェルスクリプトの書き方は違うという話です。

コマンド引数の秘密情報は漏洩する

この話は公式本の「Everything curl」でも警告されています。

Unix/Linuxに共通する話として、コマンドの引数に書いたパスワードは同じコンピュータを使う他のユーザーから読み取れます。他のユーザーとは root ユーザーのことではありません。他の一般ユーザーです。したがってパスワードをコマンドの引数に書いてはいけません。もちろん HTTP ヘッダに指定するアクセストークンなども同じです。

コマンドの引数は他のユーザーに漏洩する
$ curl --user 'key:secret' localhost:8080
                   ↑
                他のユーザーに漏洩しています!

$ curl -H "Authorization: Bearer YourAccessToken" localhost:8080
                                 ↑
                         他のユーザーに漏洩しています!
他のユーザー (guest) から ps コマンド経由で見えてしまっている
$ whoami
guest

$ ps -a -o user,command  | grep curl
koichi   curl --user key:secret localhost:8080
guest    grep --color=auto curl

$ ps -a -o user,command  | grep curl
koichi   curl -H Authorization: Bearer YourAccessToken localhost:8080
guest    grep --color=auto curl

macOS ではパスワードが隠されているようですが、これは curl コマンドによる処理なので一瞬は見えるはずですし、アクセストークンの方は隠されていません。

「自分しかこのパソコンを使ってないからいいじゃないか!」というセリフは、この問題に気づいている人だけが言って良い言葉で、この問題を警告せずにコマンドの引数にパスワードやアクセストークンを指定しているサンプルコードが大量にあるのを見ると、気づいていない人が大半でしょう?

実際の脅威で言えば、学校などで1つのサーバーを複数人で利用している場合や、共有のレンタルサーバーで動かしているシェルスクリプトから、何かしらのコマンドの引数に書いた秘密情報は他のユーザーに漏洩しているということです。ぜひこの話を周りの人に広めてください。

.netrc や config で秘密情報を隠す

対処方法の1つは Everything curl にも書いてあるとおり .netrc ファイルや config ファイルを使うことです。.netrc ファイルはログイン名とパスワードを保存するファイルで、curl に限らず、wget やいくつかのネットワークツールなどで使われています。--netrc オプションを指定すればホームディレクトリ以下の .netrc ファイルを参照します。.netrc には複数の接続先情報を書けるのでホームディレクトリで十分だと思いますが、必要な場合は --netrc-file オプションで別のファイルも指定できます。

~/.netrc (パーミッションを600にして他ユーザーから見れなくすること)
# localhost に接続するための情報
machine localhost     # 接続先のホスト名
login key             # localhost に接続するときのログイン名
password secret       # localhost に接続するときのパスワード

# 補足: 同じファイルに複数の接続先情報を書ける
machine example.com
login guest
password guest
--netrc を指定すれば対応するホストのパスワードが送信される
$ curl --user 'key:secret' localhost:8080
GET / HTTP/1.1
Host: localhost:8080
Authorization: Basic a2V5OnNlY3JldA==
User-Agent: curl/8.12.1
Accept: */*

$ curl --netrc localhost:8080
GET / HTTP/1.1
Host: localhost:8080
Authorization: Basic a2V5OnNlY3JldA==        ← 一致している
User-Agent: curl/8.12.1
Accept: */*

HTTP ヘッダに書くアクセストークンなどでは .netrc は使えないので config ファイル(ファイル名は何でも良い)を使います。接続先ごとに接続情報は異なるはずなので ~/.curlrc ではなく --config オプションで指定することになるでしょう。こちらもパーミッションを 600 にして他ユーザーから見えないようにする必要があります。

myconfig
header = "Authorization: Bearer YourAccessToken"
--config で読み込む config を指定する
$ curl -H "Authorization: Bearer YourAccessToken" localhost:8080
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Authorization: Bearer YourAccessToken

$ curl --config myconfig localhost:8080
GET / HTTP/1.1
Host: localhost:8080
Authorization: Bearer YourAccessToken        ← 一致している
User-Agent: curl/8.12.1
Accept: */*

環境変数で秘密情報を隠す

秘密情報は環境変数を使っても隠すことができます。これが curl に追加された変数機能のもう一つの素晴らしい使い方です。もちろんシェルの変数機能を使ったらだめです。これは変数展開がコマンド実行の前に行われるからです。

ダメな例(シェルの変数は漏洩する)
$ user='key:secret'
$ curl --user "$user" localhost:8080

$ token=YourAccessToken
$ curl -H "Authorization: Bearer $token" localhost:8080

そうではなく、curl コマンドの環境変数の取り込みと変数展開機能を使います。

$ user='key:secret' curl --variable '%user' --expand-user '{{user}}' localhost:8080
GET / HTTP/1.1
Host: localhost:8080
Authorization: Basic a2V5OnNlY3JldA==
User-Agent: curl/8.12.1
Accept: */*

$ token='YourAccessToken' curl --variable '%token' \
    --expand-header 'Authorization: Bearer {{token}}' localhost:8080
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Authorization: Bearer YourAccessToken

コマンドが長いとクレームが来そうですが、そもそも「Authorization: Bearer ・・・」だって長いわけで、これを省略する config を使えばよいのです。秘密情報は環境変数で渡す方法であれば config ファイルの共通化は可能なはずです。また、シェルスクリプトであれば長くても問題ないでしょう。

ここでコマンド名の前の環境変数代入(token='YourAccessToken' curl・・・)は漏れないの?と思うかも知れませんが、この部分はシェルが解釈して実行している部分なので問題ありません。気になるようなら export コマンドで環境変数をエクスポートしても構いませんが、不要なコマンドにまで秘密情報を渡してしまうことになりかねないので注意が必要です。シェルスクリプトであれば一時的に環境変数として export した後にエクスポート属性を削除したほうが良いでしょう。なおコマンドの前で設定する環境変数は一時的なものでコマンドを終了した後には残りません。

token='YourAccessToken'

# コマンドの前で設定する環境変数は一時的なもの
#   ただし呼びだすコマンドが「シェル関数」の場合は
#   以下のシェルではシェル関数を抜けても環境変数のままなので注意
#   dash 0.5.9 まで、ksh93、NetBSD ksh、OpenBSD ksh 
token="$token" curl --variable '%token' \
    --expand-header 'Authorization: Bearer {{token}}' localhost:8080

# export を使う場合は、使用後にエクスポート属性を削除して環境変数ではなくす
export token
curl --variable '%token' \
    --expand-header 'Authorization: Bearer {{token}}' localhost:8080
export -n token  # bash でのみ使える
typeset +x token # bash、ksh93、zsh で使える

困ったことに POSIX で標準化されているシェルの機能の範囲では、変数の値を保持したままエクスポート属性のみを削除する簡単な方法がありません。bash などを使えば簡単ですが、どうしても dash などで実現したい人のためにエクスポート属性のみを削除する関数はこちらです。

エクスポート属性のみを削除するシェル関数
unexport() {
  while [ $# -gt 0 ]; do
    eval "set -- \"\$$1\" \"\$@\"; unset $1; $1=\$1; shift 2"
  done
}

export var1=123 var2=456
unexport var1 var2
echo "$var1 $var2" # => 123 456 (変数の中身はそのまま)

標準入力やファイルで秘密情報を隠す

-H などいくつかのオプションは標準入力からのデータ読み取りに対応しており、標準入力やファイルを使うことでも秘密情報を隠せます。ファイルを使う場合は他の人が読み取れないようにしてください。@-で標準入力、@ファイル名でファイルから読み取ります。

$ echo "Authorization: Bearer YourAccessToken" | curl -H @- localhost:8080
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Authorization: Bearer YourAccessToken

ここで echo コマンドの引数は他のユーザーから読み取られることはないのか? と思うかもしれませんが、echo コマンドは事実上すべてのシェルでビルトインコマンドなので ps コマンドなどのプロセス一覧に出現することはありません。ただし POSIX 的には echo コマンドをビルトインコマンドとして実装することは要求されていないので、可能性としては echo コマンドが外部コマンドである POSIX シェルの存在はありえます。ちなみに printf コマンドは多くのシェルでビルトインコマンドですが、OpenBSD sh/ksh と mksh では外部コマンドなので注意してください。

echo コマンドの代わりにヒアドキュメントを使う方法も安全です。これは完全にシェルの機能なので echo コマンドのような外部コマンドの可能性を心配する必要はありません。

$ curl -H @- localhost:8080 << 'HERE'
Authorization: Bearer YourAccessToken
HERE
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Authorization: Bearer YourAccessToken

bash、ksh93、zsh などであればヒアストリングから読み取る方もあります。

ヒアストリングを使う方法(すべての POSIX シェルで使えるわけではない)
$ curl -H @- localhost:8080 <<< "Authorization: Bearer YourAccessToken"
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Authorization: Bearer YourAccessToken

標準入力から読み込む場合の欠点は、標準入力を別の用途(例えば送信データなど)に使えなくなることです。ただし bash、ksh93、zsh などであればプロセス置換で回避可能です。

プロセス置換を使う方法(すべての POSIX シェルで使えるわけではない)
$ curl -H @<(echo "Authorization: Bearer YourAccessToken") localhost:8080
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.5.0
Accept: */*
Authorization: Bearer YourAccessToken

プロセス置換の代わりに mkfifo コマンドで作った名前付きパイプを使えばすべての POSIX シェルに対応できますが、名前付きパイプを安全作るにはどうすればいいかなどの話が必要で、ファイルを使う方が簡単なので割愛します。

アクセストークン(Bearerトークン)の指定 (--oauth2-bearer)

アクセストークン(Bearerトークン)は -H (--header)の代わりに --oauth2-bearer でも指定できます。こちらの方がより短く書けます。もちろんここまでの説明通り、--expand-oauth2-bearer '{{token}}' のような環境変数を使った書き方などもできます。

$ curl --oauth2-bearer YourAccessToken localhost:8080
GET / HTTP/1.1
Host: localhost:8080
Authorization: Bearer YourAccessToken
User-Agent: curl/8.12.1
Accept: */*

$ curl -H "Authorization: Bearer YourAccessToken" localhost:8080
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Authorization: Bearer YourAccessToken

--oauth2-bearer は HTTP だけでなく IMAP、LDAP、POP3、SMTP でも共通して使えます。このオプションは 2013 年の バージョン 7.33.0 で追加されました。古くから使えるオプションなのに使用例が少ない気がするのは何でだろう?と思ったら、HTTP で使えるようになったのは 2018 年のバージョン 7.61.0 からのようです。

HTML フォーム送信の話

curl コマンドは人がウェブページ、つまり HTML にアクセスするような使い方ができます。すでに説明した単純な GET はリンクをクリックするのと同等の操作です。HTML にはフォーム(<form> タグ)による送信機能があります。フォームの送信からは GET と POST が行えます。ここで説明するのはそのような HTML フォームの操作を curl コマンドから行う方法です。HTML フォームに関連する HTTP メソッド、つまり GET と POST は、RESTful API よりも数年早くに誕生しました。curl コマンドへの実装も HTML フォームに関連する機能が先行しています。近年の RESTful API の使い方は、HTML フォームの送信とあまり関係ないのですが、HTML フォーム送信の話は curl コマンドの使い方の基本でもあります。

GET メソッド (-G)

フォームの GET メソッドは HTML で次のように書きます。

<form method="GET" action="/get.cgi">
  <input type="text" name="name1" value="値1">
  <input type="text" name="name2" value="値2">
  <input type="submit">
</form> 

これと同じことを curl コマンドで行う場合には次のように書きます。

$ curl -G --data-urlencode name1=値1 --data-urlencode name2=値2 localhost:8080/get.cgi
GET /get.cgi?name1=%e5%80%a41&name2=%e5%80%a42 HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

-G (--get) オプションを指定しているのがポイントです。これがなければ POST になってしまいます。

次で説明する --data-urlencode を含む、-d (--data) 系オプションは 2000 年台の初期に POST を行うためのオプションとして追加された HTML フォーム(<form> タグの機能)に対応する機能です。HTML フォームは GET も送信でき、その場合は送信するとクエリー文字列に変換されます。この動作をシミュレートしたのが -G (--get) オプションです。<form> タグの場合は method 属性を省略したときのデフォルトが GET であるという違いはありますが、-G オプションの役目は -d 系オプションの指定によって POST になったものを GET に戻すことです。-X GET でも GET メソッドで送信できますが、-G (--get) オプションは HTML フォームと関連した機能という考え方が含まれています。詳細は以下の開発者ブログを参照してください。

POST メソッド (--data-urlencode, application/x-www-form-urlencoded)

フォームの POST メソッドは HTML で次のように書きます。

<form method="POST" action="/post.cgi">
  <input type="text" name="name1" value="値1">
  <input type="text" name="name2" value="値2">
  <input type="submit">
</form> 

これと同じことを curl コマンドで行う場合には次のように書きます。

$ curl --data-urlencode name1=値1 --data-urlencode name2=値2 localhost:8080/post.cgi
POST /post.cgi HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Content-Length: 33
Content-Type: application/x-www-form-urlencoded

name1=%E5%80%A41&name2=%E5%80%A42

「データを送信した場合は POST であるはずだ」という設計方針なのでしょう。Content-Typeapplication/x-www-form-urlencoded になっているところに注意してください。したがって --data-urlencod を使うのが一番簡単です。--data などを使う場合は自分で URL エンコードする必要があります。curl 組み込みに変数と変数展開機能を使っても URL エンコードできます。--data-urlencode の引数はただの文字列ではなく、ファイルの読み込みなど特殊な書式が使えます

--data-urlencode <data> 意味
content 文字列(=@ が含まれると違う意味になる)
=content 文字列(最初の = は無視される)
name=content 名前=文字列 形式
@filename 文字列(文字列はファイルから読み込む)
name@filename 名前=文字列 形式(文字列はファイルから読み込む)

便利な機能ですがシェルスクリプトなどでシェル変数をそのまま使用していると、文字列を送信するだけだと思っていたのに、ファイルを送信してしまう可能性があります。

文字列を送信するだけのはずがファイルを送信してしまう
value='@/etc/hosts'
curl --data-urlencode "$value" localhost:8080/post.cgi

# 代わりにこうする
curl --data-urlencode "=$value" localhost:8080/post.cgi

POST メソッド (-F, multipart/form-data)

フォームの POST メソッドで画像などをアップロードするときには HTML で次のように書きます。書き方が違うのは画像のような大きなデータは URL エンコードして渡すのは適していないからです。

<form method="POST" action="/post.cgi" enctype="multipart/form-data">
  <input type="text" name="name1" value="値1">
  <input type="text" name="name2" value="値2">
  <input type="file" name="image">
  <input type="submit">
</form> 

enctype="multipart/form-data" が追加されているのがポイントです。 これと同じことを curl コマンドで行う場合には次のように書きます。

$ curl -F name1=値1 -F name2=値2 -F image=@image.png localhost:8080/post.cgi
POST /post.cgi HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Content-Length: 413
Content-Type: multipart/form-data; boundary=------------------------dE72sJJFXYuwGekPOTi67v

--------------------------dE72sJJFXYuwGekPOTi67v
Content-Disposition: form-data; name="name1"

値1
--------------------------dE72sJJFXYuwGekPOTi67v
Content-Disposition: form-data; name="name2"

値2
--------------------------dE72sJJFXYuwGekPOTi67v
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png

(省略)
--------------------------dE72sJJFXYuwGekPOTi67v--

-F (--form) オプションを使用すると multipart/form-data で送信されます。ちなみに -F オプションと --data 系オプションを併用できない理由は、このように送信形式が異なるためです。filenameContent-Type は次のような書き方で指定できます。

-F 'image=@image.png;type=image/jpg;filename=img.jpg'

このように、-F オプションの引数はただの文字列ではなく、ファイルの読み込みなど特殊な書式が使えます。つまりシェル変数をそのまま使用すると、ファイルを送信してしまう可能性があります。代わりに --form-string を使えばこのような特殊な書き方が無効になるので適切なオプションを使い分ける必要があります。詳細はドキュメントを参照してください。

名前を送信するだけのはずがファイルを送信してしまう
name='@/etc/hosts'
curl -F name="$name" localhost:8080/post.cgi

# 代わりにこうする
curl --form-string name="$name" localhost:8080/post.cgi

--data 系オプション

--data-urlencode 以外のオプションは URL エンコードを行わないため、通常とは異なるデータの送信に使えます。ただし --data 系オプションはいずれも Content-Type をデフォルトで application/x-www-form-urlencoded に設定するため、自分で URL エンコードを行うか、必要な場合は -H (--header) オプションで Content-Type を別のもの変える必要があるでしょう。それぞれの --data 系オプションの違いは次のとおりです。詳細はドキュメントを参照してください。

オプション @ファイル 書式
-d, --data, --data-ascii 使える(改行が消える)
--data-binary 使える(改行は消えない)
--data-raw 使えない(引数の文字列をそのまま送信する時に使う)

Cookie を使う

注意: ほとんど検証していません。

curl コマンドで Cookie を使う時は、-b (--cookie) <data|filename> オプション(Cookie の送信)や -c (--cookie-jar) <filename> オプション(Cookie の保存)を使うようです。Cookie はファイルに保存することで維持されますが、curl コマンドは 1 回のコマンド実行で複数の URL にアクセスでき、Cookie を保存するファイル名を /dev/null にすることで、メモリ内だけで Cookie を維持できるようです。

$ curl -sSFl -c /dev/null localhost:8080/post.cgi localhost:8080/post.cgi

CGI 作るのも面倒だったので、どこぞのサンプルサイトで試してみましたが動いているようです。

RESTful API の話

おそらく現在の curl コマンドのメインの使い方でしょう。前提知識として「認証とセキュリティの話」は読んでおいてください。HTML フォーム送信の話は忘れても良いぐらいです。その方が簡単に使い方を理解できます。

JSON データを送信する (--json, -H)

2022 年のバージョン 7.82.0 で追加された --json オプションで、curl コマンドでの RESTful API の使い方は変わりました。基本として -H (--header) も -d (--data) も使いません。HTML フォーム送信の話は何だったんだ?というぐらい簡単です。基本はこれだけです。

$ curl --json '{"key": "value"}' localhost:8080/api
POST /api HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Content-Type: application/json
Accept: application/json
Content-Length: 16

{"key": "value"}

やることは --json オプションで JSON データを送信するだけです。以前は -H オプションで指定していた Content-Type: application/jsonAccept: application/json は自動的に設定されます。--json オプションはこの2つの設定を行い --data-binary オプションを指定することに相当します。したがって POST メソッドであり、内部的には --data-binary オプションと同じなので @ で始めることでファイルや標準入力からの読み込みも行えます。

# ファイルからの読み込み
curl --json @file.json localhost:8080/api

# 標準入力からの読み込み
curl --json @- localhost:8080/api < file.json

# ヒアドキュメントからの読み込み
curl --json @- localhost:8080/api << 'HERE'
{"key": "value"}
HERE

# パイプからの読み込み
echo '{"key": "value"}' | curl --json @- localhost:8080/api

もし 7.82.0 よりも古いバージョンしか使えない場合は、このように書くのと同じです。

curl のバージョンが古い場合の書き方
$ curl -H 'Content-Type: application/json' -H 'Accept: application/json' \
    --data-binary '{"key": "value"}' localhost:8080/api
POST /api HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Content-Type: application/json
Accept: application/json
Content-Length: 16

{"key": "value"}

もちろん RESTful API だからといって JSON データを送信するとは限らないので、異なる形式のデータを送信する場合は -H (--header) オプションを指定して Content-TypeAccept を変更し(JSON ではないので)--json ではなく --data 系オプションを使うことになるでしょう。

application/json は charset を持たない

Content-Type: application/jsoncharset が指定されていないことが気になる人がいるかも知れないので書いておくと、JSON データは RFC 8259 によって BOM なしの UTF-8 でなければならないと規定されており、IANA への登録 にも application/jsoncharset はありません。したがって charset を付けても何も効果がなかったり、付けてしまうと不正な Content-Type と解釈され正常に動作しない可能性も考えられます。

内部システムに関しては charset が必要だったり UTF-8 以外の文字コードを使う、または使える標準に準拠していないシステムがあるかもしれませんが、curl コマンドとしては charset は付けてはいけないものであり、どうしても必要な人だけが追加しなければいけないものです。なお、当然のことながらデータが UTF-8 に自動的に変換されるようなことはないので、curl コマンドに渡すデータは UTF-8 でなければなりません。もっとも最近は UTF-8 を基本的に使っているとは思いますが。

JSON データは jq や jo で組み立てよう

curl コマンドの引数の JSON データを手書きするのは面倒ですよね? 例えばキーのダブルクォートとか。それならば jq コマンドを使って書きましょう。もちろんその他のツールでも構いません。

JSON は jq で組み立てて curl に流し込めば良い
$ jq -n '{key: 123}' | curl --json @- localhost:8080/api
POST /api HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Content-Type: application/json
Accept: application/json
Content-Length: 17

{
  "key": 123
}

jq コマンドを使った JSON データの書き方の例をいくつか紹介します。

$ jq -n '
  {
    key: 123
  }'
{
  "key": 123
}

$ jq -nf /dev/stdin << HERE
  {
    key: 123
  }
HERE
{
  "key": 123
}

$ jq -n --arg key1 value1 --arg key2 value2 '$ARGS.named'
{
  "key1": "value1",
  "key2": "value2"
}

$ jq -n --arg key1 123 --argjson key2 456 '$ARGS.named'
{
  "key1": "123",
  "key2": 456
}

他にも jo などで JSON データの組み立ては可能で、もっと簡潔に書くことができます。

$ jo -p name=jo n=17 parser=false
{
    "name": "jo",
    "n": 17,
    "parser": false
}

$ jo -p name=jo n=17 parser=false | curl --json @- localhost:8080/api
POST /api HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Content-Type: application/json
Accept: application/json
Content-Length: 52

{
   "name": "jo",
   "n": 17,
   "parser": false
}

jo は Everything curl でも紹介されています。

curl コマンドの JSON データの扱いは面倒かもしれませんが、そもそも curl コマンドは JSON データ専用に作られているわけではありません。異なる機能を 1 つのコマンドにまとめるとそれはそれで便利なこともありますが、すべてを curl コマンドだけでやらなきゃいけないってこともないわけで、JSON データの組み立てはそれが得意な jq コマンドや jo コマンドに任せ、組み合わせて使うのが Unix 哲学の考え方です。

PUT でデータをアップロードする (-T)

PUT メソッドでファイルを送信する場合は -T (--upload-file) オプションを使います。

$ curl -sSfL -T file.txt localhost:8080/file
PUT /file HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Content-Length: 5

test

標準入力から読み取る場合は「-」を指定します。この場合 chunk で送信されるようです。

$ curl -sSfL -T - localhost:8080/file < file.txt
PUT /file HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Transfer-Encoding: chunked
Expect: 100-continue

5
test

0

PUT はただファイルをアップロードするだけなので、デフォルトでは Content-Type が指定されません。Content-Type の指定が必要な場合は -H (--header) オプションで追加します。JSON ファイルをアップロードする場合は --json オプションを使い、次で説明する -X オプションで PUT に変えたほうが簡単かもしれません。

テキストファイルまたは JSON ファイルのアップロード
$ curl -sSfL -H 'Content-Type: text/plain' -T file.txt  localhost:8080/file.txt
PUT /file.txt HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*
Content-Type: text/plain
Content-Length: 5

test

$ curl -sS -X PUT --json @file.json localhost:8080/file.json
PUT /file.json HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Content-Type: application/json
Accept: application/json
Content-Length: 11

{"a": 123}

DELETE や PATCH などを使う (-X)

その他の HTTP メソッドに変更したい場合は -X (--request) オプションを使います。

$ curl -X DELETE localhost:8080/file
DELETE /file HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.12.1
Accept: */*

どの HTTP メソッドに対応しているんだ? と悩む必要はありません。これは単に HTTP メソッドの名前を任意の文字列に変更する機能でしかないので、どんなものにでも変更できます。

  • データを送信しない場合は自動的に GET です
  • データを送信する場合は --json を使い自動的に POST です
  • ファイル全体をアップロードするときは -T を使い自動的に PUT です
  • -X オプションで明示的に指定する必要があるのは、これら以外のときだけです

さいごに

はぁーーー、やっと前からアップデートしたかった内容が書けました! HTTP/HTTPS 関連で他になにか便利な curl の使い方があったら教えてください。

  1. 記事公開時は改名前も含めて 1996 年生まれと書いていましたが、公式には 1998 年生まれという扱いのようなので変更しました。

406
407
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
406
407

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?