Gawk で通信する
AWK はテキストを加工するコマンドとして知られていますが、Gawk ならネットワーク通信もできます。
この記事の内容は次のとおりです。
- Gawk の通信は簡単
- HTTP なら簡単だが、現在は HTTPS が主流なので工夫が必要
筆者の環境は
cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.4 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
Gawk のネットワーク機能
この機能はGNU拡張です
Gawk では、ネットワーク接続の設定をネットワーク特殊ファイル名として記述します。
TCP 接続の書式は次のとおりです。
/inet/tcp/ローカルポート/接続先ホスト/接続先ポート
たとえば、ローカルホストの18080番ポートへ接続する場合は、次のように書きます。
socket = "/inet/tcp/0/127.0.0.1/18080"
ここでわかりにくいのは 0 でしょう。クライアント側のローカルポートに0を指定すると、空いているポートを OS が自動的に選びます。
この文字列は実際のファイルパスではありません。Gawk が文字列の形式を認識し、内部でソケットを作成します。
|& で送受信する
この機能はGNU拡張です
Gawk では、双方向通信に |& 演算子を使います。
送信は次のように書きます。
print "送信する文字列" |& socket
受信には getline を組み合わせます。
socket |& getline reply
print reply
まとめると、最小限の通信処理は次の形になります。
BEGIN {
socket = "/inet/tcp/0/127.0.0.1/18080"
print "hello" |& socket
fflush(socket)
if ((socket |& getline reply) > 0)
print reply
close(socket)
}
fflush(socket) は、出力バッファーに残っているデータをすぐに送信するための処理です。
例:メトロポリタン美術館 API を叩く
実用的な例として、API を叩いて情報を得るタスクを紹介します。
HTTP/1.x はテキストと解釈できるプロトコルです。HTTP リクエストを文字列として組み立て、送信する能力が Gawk にはあります。
現在の Web API の多くは HTTPS を使用しています。そのため、対応する必要があります。
HTTPS は、HTTP 通信を TLS で保護したものです。TLS には、通信内容の暗号化、改ざん検出、接続先サーバーの確認といった役割があります。
Gawk の /inet/tcp は TCP 通信を扱えますが、TLS を直接処理しません。そのため、HTTPS の API へ接続するときは、TLS を扱える別のプログラムを中継させます。
今回は socat を使います。
socat は、TCP 、 UDP 、ファイル、標準入出力、 TLS 接続など、二つのデータ経路を双方向に接続するコマンドです。
まず、別のターミナルで次の socat コマンドを起動しておきます。socat は終了せずに待ち受け続けるため、そのターミナルは開いたままにします。その後、別のターミナルから gawk スクリプトを実行します。
# 筆者の環境は Ubuntu
socat \
TCP-LISTEN:18080,bind=127.0.0.1,reuseaddr,fork \
OPENSSL:collectionapi.metmuseum.org:443,verify=1,snihost=collectionapi.metmuseum.org,cafile=/etc/ssl/certs/ca-certificates.crt
一例として、メトロポリタン美術館の葛飾北斎の作品の情報を得るスクリプトを書きます。
BEGIN {
api_host = "collectionapi.metmuseum.org"
proxy_host = "127.0.0.1"
proxy_port = 18080
path = "/public/collection/v1/objects/45434"
socket = "/inet/tcp/0/" proxy_host "/" proxy_port
# 読み取りタイムアウトは10秒
PROCINFO[socket, "READ_TIMEOUT"] = 10000
request = \
"GET " path " HTTP/1.0\r\n" \
"Host: " api_host "\r\n" \
"User-Agent: gawk-example/1.0\r\n" \
"Accept: application/json\r\n" \
"Accept-Encoding: identity\r\n" \
"Connection: close\r\n" \
"\r\n"
printf "%s", request |& socket
fflush(socket)
body_started = 0
status_code = 0
while ((result = (socket |& getline line)) > 0) {
# HTTP の行末に含まれる CR を削除
sub(/\r$/, "", line)
if (!body_started) {
if (line ~ /^HTTP\//) {
status_line = line
split(line, fields, /[[:space:]]+/)
status_code = fields[2] + 0
} else if (line == "") {
# ヘッダー後の空行を検出
body_started = 1
}
continue
}
# HTTP 本文だけを標準出力へ送る
print line
}
if (result < 0) {
print "受信エラー:", ERRNO > "/dev/stderr"
close(socket)
exit 1
}
close(socket)
if (status_code < 200 || status_code >= 300) {
print "HTTP エラー:", status_line > "/dev/stderr"
exit 22
}
}
次のような結果が返ってきます。
# gawk -f get_hokusai.awk | jq . のように jq を使うと見やすい
{
"objectID": 45434,
"isHighlight": false,
"accessionNumber": "JP1847",
"accessionYear": "1929",
"isPublicDomain": true,
"primaryImage": "https://images.metmuseum.org/CRDImages/as/original/DP130155.jpg",
"primaryImageSmall": "https://images.metmuseum.org/CRDImages/as/web-large/DP130155.jpg",
# 以下略
}