JUNのブログ

JUNのブログ

活動記録や技術メモ

Next.js で.envからいい感じに環境変数を読み込みたい

Next.js で .env ファイルから環境変数を読み込んで使う時にちょっと迷ったのでやり方をメモ代わりに書いとく.

やり方

基本的なやり方は

  1. dotenvでサーバー起動時に.envファイルから環境変数を自動的に読み込む.
  2. 読み込んだ環境変数を next.config.js で設定して Next.js の中で使えるようにする

って感じ.

実際にやってみる

Next.js のアプリケーションは既にある前提でやります.

まず dotenv を追加

yarn add dotenv

プロジェクトルートの.envファイルに適当に環境変数のキーバリューのセットを書いとく

ADMIN_API_HOST=127.0.0.1:8080

プロジェクトルートに next.config.js というファイルを作って以下のような内容にする.

require('dotenv').config();
module.exports = {
  env: {
    // Reference a variable that was defined in the .env file and make it available at Build Time
    ADMIN_API_HOST: process.env.ADMIN_API_HOST,
  },
}

これでNext.jsのアプリケーション内から値を読めるようになった.

使う時は process.env.ADMIN_API_HOST みたいな感じで値にアクセスできる.

C言語でわかる? UTF-8

そういえばC言語でマルチバイト文字(UTF-8)の出力ってどうやってするんだろうと思って, 色々試したのでここに書き残しておく.

ちなみに今回は基本的に画面出力にはUNIXシステムコールを呼び出す write() を使う. write()に関しはmanコマンドでマニュアルを見るか以下のページを見るなりして適当に雰囲気を掴んでくれ.

miyanetdev.com

では本題のUTF-8について書いていくぞ

UTF-8 について

UTF-8はみんな普段から使っていると思うが, じゃあUTF-8がどういう風にデータで表現されてるか知ってるか? 俺は知らんかった.

ということでWikipedia見てみた.

ja.wikipedia.org

1バイト目の先頭の連続するビット "1"(その後にビット "0" が1つ付く)の個数で、その文字のバイト数がわかるようになっている。また、2バイト目以降はビットパターン "10" で始まり、1バイト目と2バイト目以降では値の範囲が重ならないので、文字境界を確実に判定できる。すなわち、任意のバイトの先頭ビットが "0" なら1バイト文字、"10" なら2バイト以上の文字の2番目以降のバイト、"110" なら2バイト文字の先頭バイト、"1110" なら3バイト文字の先頭バイト、"11110" なら4バイト文字の先頭バイトであると判定できる。

ということらしい.

こういうのは例があるとわかりやすいので日本語ひらがなのを例にすると

まず, UTF-8では次のように表される (わかりやすいように8bit(1Byte)ごとに:で区切っている)

11100011:10000001:10000010

さっきのWikipediaを見ながら読み解いていくと まず先頭バイトである1バイト目の最初に1が3つ続いているのでUTF-8で表現するには3バイト必要なことがわかる.

ちなみに1バイト目全部がデータのバイト数表すわけではなく,先頭4bitがデータ長を表し,残りは対象の文字を表現するのに使うらしい...?

また、5-6バイトの表現は、ISO/IEC 10646による定義[4]とIETFによるかつての定義[5]で、Unicodeの範囲外を符号化するためにのみ使用するが、Unicodeによる定義[6]とIETFによる最新の定義[7]では、5-6バイトの表現は不正なシーケンスである。

一応4バイト以上の表現も可能ではあるが基本的に不正なシーケンスらしいので今回は考えなくて良さそう.

で, の話に戻ると, 先頭4bitがデータサイズを表していることがわかったので,残りは 1バイト目の後半4bitとその後ろの16bitを読めばいいらしい.

Wikipediaに載ってる表がわかりやすかったから, 今の説明でわからなかった人は貼ったWikipediaの記事を見てくれ.

余談だが以下のサイトもわかりやすかったので載せておく

ferret-plus.com

C言語UTF-8の文字を出力

ということで, UTF-8で表現される文字を出力するには以下の流れで処理すれば良さそう.

  1. 先頭4バイトを調べてその文字が何バイトで表現されるかを取得する
  2. そのバイト数分データをOSに渡す

はい.簡単(なぜなら難しい処理は全てOSがやってくれるため).

というわけでコードです.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

// マルチバイト文字のバイナリを標準出力
void print_bin_from_char(char *c, int bytes){
    char *bin = calloc(sizeof(char), 8 * bytes);
    int i, j;
    unsigned int num;
    int bin_digit = 0;
    unsigned char *unsigned_c = (unsigned char *)c;
    for (i = bytes - 1; i >= 0; i--)
    {
        num = (unsigned int)unsigned_c[i];
        for (j = 7; j >= 0; j--)
        {
            bin[8 * i + j] = '0' + num % 2;
            num = num / 2;
            bin_digit++;
        }
    }
    i--;
    j--;
    write(1, "0b", 2);
    for (i = 8 * bytes; i > bin_digit; i--) // 0埋め
        write(1, &"0", 1);
    for (i = 0; i < bin_digit; i++)
        write(1, bin + i, 1);
    write(1, "\n", 1);
    free(bin);
}

// UTF-8 で何バイトで表現されるか取得
int get_byte(char *c){
    if (!(*c & (1 << 7))) // 1ビット目が0の時は1バイト
        return (1);
    int bytes = 0;
    for (int i = 7; i >= 4; i--)
    {
        if (*c & (1 << i))
            bytes++;
        else
            return (bytes);
    }
    return (bytes);
}

int main(){
    char *c = "あ"; // 11100011:10000001:10000010
    printf("\n%s bytes: %d\n", c, get_byte(c));
    print_bin_from_char(c, get_byte(c));
    char c_aa[3] = {0b11100011, 0b10000001, 0b10000010};  // テスト用
    write(1, c_aa, 3);
    
    char *c1 = "a";
    printf("\n%s bytes: %d\n", c1, get_byte(c1));
    print_bin_from_char(c1, get_byte(c1));
    char c_a = 0b01100001;
    write(1, &c_a, 1);
    
    char *c2 = "À";
    printf("\n%s bytes: %d\n", c2, get_byte(c2));
    print_bin_from_char(c2, get_byte(c2));
    
    char *c3 = "🤔";
    printf("\n%s bytes: %d\n", c3, get_byte(c3));
    print_bin_from_char(c3, get_byte(c3));
    char thinking[4] = {0b11110000, 0b10011111, 0b10100100, 0b10010100};  // テスト用
    write(1, thinking, 4);

    return (0);
}

実行結果

あ bytes: 3
0b111000111000000110000010
あ
a bytes: 1
0b01100001
a
À bytes: 2
0b1100001110000000

🤔 bytes: 4
0b11110000100111111010010010010100
🤔

という感じで出力できた.

バイト数取得の部分は文字列リテラルからして最後にNULL文字 \0 が入っていることが確定しているのでまぁそれで判定しても良かったのだが, 今回はUTF-8のフォーマットを知りたいというのが目的としてあるのであえて各ビットを調べてバイト数を取得するようにした.

UTF-8の素晴らしい部分としてはASCIIと互換性があることかなぁって感じ. 今回のプログラムの実行結果を見ればわかるんだが, a の値がASCIIと同じなんだなぁ. これはUTF-8Wikipediaのページを見れば書いてあるんだが基本的にASCIIコードというのは 0x00~0x7F までで表現されている. これをバイナリにで見ると 0b00000000 ~ 0b01111111 となり, なんと先頭1bit目が0なので1バイトで表現するというのが表現出来ていて, ASCIIと互換があるという感じ.

いや〜考えた人マジ天才っすな.

UTF-8の素晴らしさを知ったところで今日はこのへんで閉めまーす. ばいばーい👋

👋 bytes: 4
0b11110000100111111001000110001011
👋

ASCIIコードについてはWikipediaを見てくれ

ja.wikipedia.org

プログラム完全版

感想

今回はWikipediaのページを参考にしながらバイナリレベルでUTF-8の文字表現について見てみた.

いや〜マジで賢い仕様だなぁという感想(小並感).

今回はC言語UTF-8で表現されたバイナリを見て実際に仕様通りにバイナリデータを取得でき, それを標準出力に出力するというところまでやってみた.

UTF-8に関する記事を上げるなら, 自作OSでUTF-8対応とかやってみたいなぁなんて思ったり...

printfを使わずにメモリアドレスを取得する

はい. こんにちは. 今日は printf を使わずにメモリアドレスを表示する方法について書きます.

大体C言語の教科書などでは変数のメモリアドレスを表示するのに以下のようなコードを書くと思います.

#include <stdio.h>

int main(){
    int a = 10;
    printf("data: %d  address: %p\n", a, &a);
}

これをコンパイルして実行すると以下のようなものが出力されます.

data: 10  address: 0x7ffe894eadd4

このプログラムは printf%p フォーマットを使って変数のアドレスを16進数で表示しています. 簡単ですね.

しかし, これをprintf を使わずにしようとすると結構メモリやポインタについて理解していないと出来ず, 苦労したのでここにやり方を書き残しておこうと思ったわけです.

そもそもポインタとは?

そもそもポインタってなんなんでしょうか? Wikipediaを見るとこんなことが書いてあります.

C言語のポインタは「特定のメモリ領域を指し示す」ものである。

ふむふむ, ポインタがメモリ領域を指すことはわかったけど, 結局ポインタってなんなんだ? 数値としてのメモリアドレスはどこに入っているんだ? って思ったのでもう少し読んでみると面白いことが書いてありました.

C言語の関数では、引数は、値渡しだけをサポートし、参照渡しをサポートしない。これは、アドレスの数値を取得すれば、参照に可能な全てを行えるため、実質的に参照を数値と同一視できるからである。実際、初期のC言語では、アドレス値は、整数型互換するものとして扱われていた。

なんと! つまり, 変数aのポインタ&a は実際にはメモリアドレスを数値として取得しているに過ぎなかったわけだ!

はい. これがわかればもう出来そうですね? じゃあ上記WIkipediaからの情報を信じてコードを書いてみましょう.

#include <stdio.h>

int main(){
    int a = 10;
    unsigned int addr = (unsigned int)&a;

    printf("%p\n", &a);  // 確認用
    printf("%dx\n", addr);  // 取得したメモリアドレスを16進数表示
}

これをコンパイルしようとすると

test.c: In function ‘main’:
test.c:5:16: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    5 |     int addr = (int)&a;
      |                ^

あれ, コンパイルに警告が出てますね? とりあえず気にせず実行してみましょう.

0x7ffcf5629e90
-178086256x

あれ, メモリアドレスが printf を使ったものと一致しませんね?

warning: cast from pointer to integer of different size とのことですが, 要は ポインタからint型へのキャストはサイズが違うけど大丈夫か?コンパイラちゃんは言っているわけです.

サイズが違う???? 整数型と言っているのだからint型で良いのでは?

では実際にサイズを見てみましょう.

#include <stdio.h>

int main(){
    int a = 10;

    printf("a: %ld\n", sizeof(a));
    printf("&a: %ld\n", sizeof(&a));
}

このプログラムをコンパイルして実行すると以下のようになりました.

a: 4
&a: 8

どうやら int型 が4バイトで, int*型が8バイトみたいですね.

これで, コンパイラが怒っていた「ポインタとint型はサイズが違うからキャスト出来ないよ」というメッセージの解決策がわかりましたね.

そんで, 先程のプログラムをポインタと整数型のサイズが合うように修正するとこうなります

#include <stdio.h>

int main(){
    int a = 10;
    unsigned long long addr = (unsigned long long)&a;

    printf("%p\n", &a);  // 確認用
    printf("%llx\n", addr);  // 取得したメモリアドレスを16進数表示
}

long long型 は8バイトの整数型です.

これでコンパイルして実行してみると

0x7fff1216619c
7fff1216619c

おお! これでメモリアドレスを整数のポインタ型としてではなく, 整数型として取得出来ました!!

もう少し細かいお話

ポインタのアドレスが8バイトとということがわかり, そして8バイトの整数型である long long型 を使うことで整数としてのメモリアドレスを取得することが出来ました.

しかし, なぜ8バイトなのでしょうか?

実は32bitマシンでポインタのサイズを表示するプログラムをコンパイルして実行すると面白いことがわかります.

a: 4
&a: 4

あれれ, ポインタのサイズが4バイトになりましたね?

そう, 実はポインタのサイズは必ず8バイトというわけではなく, 32bitCPUではポインタは4バイトなのです!!

なので, 32bitCPUでは以下のコードが普通にコンパイル出来ます.

#include <stdio.h>

int main(){
    int a = 10;
    int addr = (int)&a;
    
    printf("%p\n", &a);
    printf("%x\n", addr);
}

コンパイルして実行すると

0xbfa1ccc4
bfa1ccc4

はい. ちゃんと表示出来ましたね.

しかし, これだと, 32bit向けのコードと64bit向けのコードで毎回 int型 と long long 型 を書き分けなきゃいけないのでめんどいですね.

そんなあなたに long型〜〜

printf("%d\n", sizeof(long));

このコードを32bitでコンパイルして動かすと以下が出力されます

4

64bitでコンパイルして動かすと以下が出力されます

8

ということで, long型 だとコンパイル対象のシステムによってバイト数が変わるので, int型 や long long型 を気にしなくてもかけるので, もしこういうことを気にしなくてはいけない機会があるのであれば使ってみるといいかもですね.

サイズ (32 ビット) サイズ (64 ビット)
short 2 バイト 2 バイト
int 4 バイト 4 バイト
long 4 バイト 8 バイト
long long 8 バイト 8 バイト

データ型とサイズ

追記 (2021/01/07)

long型の大きさはコンパイラによって例え64bit環境向けにコンパイルしたとしても32bitになる場合があるとの指摘を受けました.

なので, long型が必ずしも対象のアーキテクチャのビット数と等しくなるとは限らないようなので気をつけた方が良いかも知れません.

ja.wikipedia.org

sqlx のインストールには気を付けよう

最近は sqlx というライブラリを使ってDB周りの操作をしているのですが, 標準ライブラリ database/sql にあるsql.Conn を使いたいと思ったのですが, なんと sqlx にはありませんでした.

「マジか...」と思ったのですが, 調べたら sqlx.Conn が見つかって無事解決しました. しかし, これを見つけるまでに詰まったので解決方法を書いときたいと思います.

解決方法

go get github.com/jmoiron/sqlx@master で sqlx をインストールする.

これで sqlx.Conn が使えます.

結局何が原因だったのか

sql.Conn を含む database/sql にある構造体や関数に対応する構造体や関数は sqlx に存在します. ドキュメントにも書いてあります. しかし, 普通にREADMEに従って go get github.com/jmoiron/sqlx でインストールするとうまく行きません. この問題の原因は sqlx のタグが v1.2.0 から更新されていないからです.

f:id:JUN_NETWORKS:20200804052020p:plain
v1.2.0からバージョンが更新されていない

github.com

しかし, コード自体は更新され続けているので, リポジトリのmasterブランチを指定してインストールすればきちんと更新されたコードを元にインストールできるのです. go get github.com/jmoiron/sqlx@master

protocで .proto ファイルから service に関するGoのコードが生成されない問題への対処方法

gRPC触ろうと思って.protoファイルから protoc をGoのコード使って生成したら service に関するコードが一切ないし, plugins オプションを指定するとエラーが出るしで2時間くらい詰まったのでここに解決方法をメモしておく.

エラー

$ protoc -I proto --go_out=plugins=grpc:. proto/control.proto
--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC

エラーが出た.

$ protoc -I proto --go_out=pb/ proto/control.proto

これだとエラーは出ないけど, 生成されたコードに service に関するコードが無い.

同じ現象はいくつかの issue でも報告されている.

github.com

環境

  • libprotoc 3.12.3
  • protoc-gen-go v1.25.0-devel

原因

最初に原因を言ってしまうと protoc-gen-go のバージョンが原因でした.

以下の記事のおかげで解決したので貼っておきます.

qiita.com

で, 上記のサイトにも書いてあるのですが, protocolbuf-go のリリースノート に以下のようなことが書いてありました.

gRPC support The v1.20 protoc-gen-go does not support generating gRPC service definitions. In the future, gRPC service generation will be supported by a new protoc-gen-go-grpc plugin provided by the Go gRPC project.

The github.com/golang/protobuf version of protoc-gen-go continues to support gRPC and will continue to do so for the foreseeable future.

つまり, 「v1.20(以降)の protoc-gen-go は gRPC サービス定義のコード生成をサポートしてないよ(現時点では). でも, 将来的には新しいプラグインによりサポートするよ. github.com/golang/protobuf バージョンは引き続き gRPCのサポートするよ.」的なことらしい.

自分は protoc-gen-gogo install google.golang.org/protobuf/cmd/protoc-gen-go という風にインストールしてしまったのですが, これでは APIv2 の protoc-gen-go を使ってしまうので service に関するコードが生成されませんでした.

なので, 解決方法としては APIv1 の protoc-gen-go を入れれば解決しました.

# APIv2のprotoc-gen-goを削除
go clean google.golang.org/protobuf
# APIv1のprotoc-gen-goをインストール
go get github.com/golang/protobuf/protoc-gen-go

APIv1 の protoc-gen-go を入れ直した後コマンドを実行すると無事service関連のコードも含む全てのコードが想定どおり生成されました!!

Kindle for PC on Ubuntu 20.04

Kindle for PC は基本的にはWindows用とMac用しかありません。

しかし、Wineを使うことで可能となります。

How to

まずUbuntu Software でWineをインストールします。

f:id:JUN_NETWORKS:20200603040950p:plain

次にWindowsのバージョンを決めます。

$ winecfg

でWineの設定画面が出てくるので、8.1にします。

f:id:JUN_NETWORKS:20200603041423p:plain

初期状態だとwineには必要なフォントが入っていないのでフォントをとりあえずwinetricksを使ってフォントを一通りインストールします。

$ sudo apt install -y winetricks
$ winetricks allfonts

次にKindle for PCをインストールします。

ただし、バージョン1.19以上だとうまくいかないので、バージョン1.17をダウンロードして使います。

kindle-for-pc.jp.uptodown.com

そしたら

$ wine <kindle for PCインストーラへのパス>

を叩けばKindle for PCがインストールされて、Ubuntuのアプリケーションに自動的に登録されるようになります。

f:id:JUN_NETWORKS:20200603041953p:plain

お疲れ様でした。

なお、コードなどが上手く表示されていないようなので、この辺知ってる人教えてほしいです。

Airpods Pro を Ubuntu に接続する方法

Airpods (Pro) を Ubuntu に接続する方法についての日本語情報が無かったのでここに書いておきます。

  1. /etc/bluetooth/main.conf内にあるControllerModeの値をbredrに変更する。
  2. sudo systemctl restart bluetoothBluetoothを再起動
  3. Airpodsのケースの後ろのボタンを長押ししてペアリングモードにして、UbuntuBluetooth設定から接続する。

これでいけると思います。

f:id:JUN_NETWORKS:20200601020839p:plain
この方法で接続できました。

levlaz.org