うなすけとあれこれ

2020年08月17日

UTF-8 の文字列をできる限り Shift_JIS に変換したい(実践編)

unicode nomalize by uconv

先日、きりきりやままさんがこのような記事を公開していました

UTF-8 の文字列をできる限り Shift_JIS に変換したい - きりきりやま

それでは実際にそのような文字列変換を行うにはどうすればよいのか、またコメントでiconvについて触れられていたので、この記事ではUnicodeにおけるNFKC正規化をどうやって行うのか試してみることにしました。

追記

Ruby

僕にとって文字列処理といえばRubyなので、まずは以下のようなscriptを書いてみました。

puts "\u304c"
puts "String#encode('Shift_JIS') => #{"\u304c".encode('Shift_JIS').inspect}"
puts "codepoints => #{"\u304c".codepoints}"
puts "NFKC normalized codepoints => #{"\u304c".unicode_normalize(:nfkc).codepoints}"
puts "Encode to SJIS with NFKC => #{"\u304c".unicode_normalize(:nfkc).encode('Shift_JIS').inspect}"

puts "=" * 20

puts "\u304b\u3099"
puts "String#encode('Shift_JIS', undef: :replace) => #{"\u304b\u3099".encode('Shift_JIS', undef: :replace).inspect}"
puts "codepoints => #{"\u304b\u3099".codepoints.inspect}"
puts "NFKC normalized codepoints => #{"\u304b\u3099".unicode_normalize(:nfkc).codepoints}"
puts "Encode to SJIS with NFKC => #{"\u304b\u3099".unicode_normalize(:nfkc).encode('Shift_JIS').inspect}"

puts "=" * 20

puts "\u0063\u006d"
puts "String#encode('Shift_JIS') => #{"\u0063\u006d".encode('Shift_JIS').inspect}"
puts "codepoints => #{"\u0063\u006d".codepoints}"
puts "NFKC normalized codepoints => #{"\u0063\u006d".unicode_normalize(:nfkc).codepoints}"

puts "=" * 20

puts "\u339d"
puts "String#encode('Shift_JIS', undef: :replace) => #{"\u339d".encode('Shift_JIS', undef: :replace).inspect}"
puts "codepoints => #{"\u339d".codepoints}"
puts "NFKC normalized codepoints => #{"\u339d".unicode_normalize(:nfkc).codepoints}"
$ ruby script.rb
が
String#encode('Shift_JIS') => "\x{82AA}"
codepoints => [12364]
NFKC normalized codepoints => [12364]
Encode to SJIS with NFKC => "\x{82AA}"
====================
が
String#encode('Shift_JIS', undef: :replace) => "\x{82A9}?"
codepoints => [12363, 12441]
NFKC normalized codepoints => [12364]
Encode to SJIS with NFKC => "\x{82AA}"
====================
cm
String#encode('Shift_JIS') => "cm"
codepoints => [99, 109]
NFKC normalized codepoints => [99, 109]
====================
㎝
String#encode('Shift_JIS', undef: :replace) => "?"
codepoints => [13213]
NFKC normalized codepoints => [99, 109]

https://wandbox.org/permlink/CQaSM6ffOHc0zLu6

Rubyにおいては、Unicode正規化を行うには String#unicode_normalize によって行うことができます。その際にoptionとして正規化の形式を指定することができます。とても簡単ですね。

https://docs.ruby-lang.org/ja/latest/class/String.html#I_UNICODE_NORMALIZE

Python

import unicodedata

print('\u304c (U+304c)')
print('codepoints => ', end='')
for char in '\u304c'.strip():
  print(hex(ord(char)) + ' ' , end='')
print()
print('NFKC normalized codepoints => ', end='')
for char in unicodedata.normalize('NFKC', '\u304c').strip():
  print(hex(ord(char)) + ' ' , end='')
print()
print('=' * 20)

print('\u304b\u3099 (U+304b U+3099)')
print('codepoints => ', end='')
for char in '\u304b\u3099'.strip():
  print(hex(ord(char)) + ' ' , end='')
print()
print('NFKC normalized codepoints => ', end='')
for char in unicodedata.normalize('NFKC', '\u304b\u3099').strip():
  print(hex(ord(char)), end='')
print()
print('=' * 20)

print('\u0063\u006d (U+0063 U+006d)')
print('codepoints => ', end='')
for char in '\u0063\u006d'.strip():
  print(hex(ord(char)) + ' ' , end='')
print()
print('NFKC normalized codepoints => ', end='')
for char in unicodedata.normalize('NFKC', '\u0063\u006d').strip():
  print(hex(ord(char)) + ' ' , end='')
print()
print('=' * 20)

print('\u339d (U+339d)')
print('codepoints => ', end='')
for char in '\u339d'.strip():
  print(hex(ord(char)) + ' ' , end='')
print()
print('NFKC normalized codepoints => ', end='')
for char in unicodedata.normalize('NFKC', '\u339d').strip():
  print(hex(ord(char)) + ' ' , end='')
print()

https://wandbox.org/permlink/cMc7S5blWLZLLObD

Pythonにおいては、unicodedata モジュールをインポートすることによって使用できる unicodedata.normalize により、形式を指定して正規化を行うことができます。

unicodedata — Unicode データベース — Python 3.8.5 ドキュメント

Go

package main

import (
    "fmt"

    "strings"

    "unicode/utf8"

    "golang.org/x/text/unicode/norm"
)

func printCodepoints(str string) {
    fmt.Print("codepoints => ")
    for i, w := 0, 0; i < len(str); i += w {
        runeValue, width := utf8.DecodeRuneInString(str[i:])
        fmt.Printf("%U ", runeValue)
        w = width
    }
    fmt.Print("\n")
}

func main() {
    fmt.Println("\u304c (U+304c)")
    printCodepoints("\u304c")

    fmt.Print("NFKC normalized ")
    printCodepoints(norm.NFKC.String("\u304c"))

    fmt.Println(strings.Repeat("=", 20))

    fmt.Println("\u304b\u3099 (U+204c u+3099)")
    printCodepoints("\u304b\u3099")

    fmt.Print("NFKC normalized ")
    printCodepoints(norm.NFKC.String("\u304b\u3099"))

    fmt.Println(strings.Repeat("=", 20))

    fmt.Println("\u0063\u006d (U+0063 U+006d)")
    printCodepoints("\u0063\u006d")

    fmt.Print("NFKC normalized ")
    printCodepoints(norm.NFKC.String("\u0063\u006d"))

    fmt.Println(strings.Repeat("=", 20))

    fmt.Println("\u339d (U+339d)")
    printCodepoints("\u339d")

    fmt.Print("NFKC normalized ")
    printCodepoints(norm.NFKC.String("\u339d"))
}

https://play.golang.org/p/xG255G32mlJ

Goでは、norm packageを使用することで正規化を行うことができます。

JavaScript

// function from https://jsprimer.net/basic/string-unicode/#code-point-is-not-code-unit
function convertCodeUnits(str) {
    const codeUnits = [];
    for (let i = 0; i < str.length; i++) {
        codeUnits.push(str.charCodeAt(i).toString(16));
    }
    return codeUnits;
}


console.log('\u304c (U+304c)')
console.log('codepoints => ' + convertCodeUnits('\u304c'))
console.log('NFKC normalized codepoints => ' + convertCodeUnits('\u304c'.normalize('NFKC')))
console.log('=' .repeat(20))


console.log('\u304b\u3099 (U+304b U+3099)')
console.log('codepoints => ' + convertCodeUnits('\u304b\u3099'))
console.log('NFKC normalized codepoints => ' + convertCodeUnits('\u304b\u3099'.normalize('NFKC')))
console.log('='.repeat(20))

console.log('\u0063\u006d (U+0063 U+006d)')
console.log('codepoints => ' + convertCodeUnits('\u0063\u006d'))
console.log('NFKC normalized codepoints => ' + convertCodeUnits('\u0063\u006d'.normalize('NFKC')))
console.log('='.repeat(20))


console.log('\u339d (U+339d)')
console.log('codepoints => ' + convertCodeUnits('\u339d'))
console.log('NFKC normalized codepoints => ' + convertCodeUnits('\u339d'.normalize('NFKC')))
console.log('='.repeat(20))

https://wandbox.org/permlink/JLQH8LasdQo9ewgS

JavaScriptでは、 String.prototype.normalize() によって正規化を行うことができます。

それでは他のツールはどうでしょうか。

nkf

nkfはNetwork Kanji Filterの略で、古くからある文字コード変換ツールです。

https://ja.osdn.net/projects/nkf/

Rubyはnkfを同梱しているので、手軽に試すことができます。今回は一度Shift_JISに変換してからUTF-8に戻すことで、正しく変換できているかを確認してみます。

require 'kconv' # kconvはnkfのラッパーです

puts "\u304c (U+304c)"
puts "Endoce to SJIS by nkf => #{"\u304c".tosjis.inspect}"
puts "=" * 20

puts "\u304b\u3099 (U+304b U+3099)"
puts "Endoce to SJIS to UTF-8 by nkf => #{"\u304b\u3099".tosjis.toutf8}"

puts "=" * 20

puts "\u0063\u006d (U+0063 U+006d)"
puts "Endoce to SJIS by nkf => #{"\u0063\u006d".tosjis.inspect}"

puts "=" * 20

puts "\u339d (U+339d)"
puts "Endoce to SJIS to UTF-8 by nkf => #{"\u339d".tosjis.toutf8}"
$ ruby script.rb
が (U+304c)
Endoce to SJIS by nkf => "\x{82AA}"
====================
が (U+304b U+3099)
Endoce to SJIS to UTF-8 by nkf => 縺九y
====================
cm (U+0063 U+006d)
Endoce to SJIS by nkf => "cm"
====================
㎝ (U+339d)
Endoce to SJIS to UTF-8 by nkf => ㎝

このように、「が」(U+304B U+3099) の変換に失敗していることがわかります。そもそもnkfはUnicodeにおける正規化形式を指定できるのでしょうか。

nkfは2006-03-27にリリースされた 2.0.6 以降 (正確には2.0.6-beta2以降) においてUnicodeの正規化に対応するようになりましたが、「UTF8-MACの範囲のみ」と明言されています。

ここでの UTF-8-MAC は、macOSがAPFS以前1に採用していた HFS+ というファイルシステムにおいて使用されている正規化形式の通称2で、一見NFD形式のようで互換性のない正規化3を行っています。

nkfは入力においてのみUTF-8-MACを受け付けるようになっているようで、他の正規化形式に対応していません。

nkfにオプションから文字コードを指定した変換をして確かめてみましょう。

# nkf.rbとして保存
require 'nkf'

puts "\u304b\u3099 : U+304b U+3099"
puts "nkf --ic=UTF-8 --oc=Shift_JIS"
ga_to_sjis_from_utf8 = NKF.nkf('--ic=UTF-8 --oc=Shift_JIS', "\u304b\u3099")
puts ga_to_sjis_from_utf8.inspect
puts ga_to_sjis_from_utf8.encode('UTF-8')

puts "=" * 20

puts "nkf --ic=UTF-8-MAC --oc=Shift_JIS"
ga_to_sjis_from_utf8mac = NKF.nkf('--ic=UTF-8-MAC --oc=Shift_JIS', "\u304b\u3099")
puts ga_to_sjis_from_utf8mac.inspect
puts ga_to_sjis_from_utf8mac.encode('UTF-8')

puts "=" * 20

puts "\ufa19 (U+fa19)"
puts "NFD normalized => #{"\ufa19".unicode_normalize(:nfd).inspect}"
puts "NFKC normalized => #{"\ufa19".unicode_normalize(:nfkc).inspect}"
puts "nkf convert => #{NKF.nkf('--ic=UTF-8-MAC --oc=UTF-8', "\ufa19").inspect}"
$ ruby nkf.rb 
が : U+304b U+3099
nkf --ic=UTF-8 --oc=Shift_JIS
"\x{82A9}"
か
====================
nkf --ic=UTF-8-MAC --oc=Shift_JIS
"\x{82AA}"
が
====================
神 (U+fa19)
NFD normalized => "\u795E"
NFKC normalized => "\u795E"
nkf convert => "\uFA19"

ところで、㎝ (U+339D)の変換にも失敗しそうな気がしますが、成功しています。これはどういうことなのでしょうか。 Shift_JISに含まれる文字列の集合はJIS X 0201とJIS X 0208です。このどちらにも1文字で"cm"となる字体は定義されていません。4ではこの「㎝」はどこからやってきたのでしょうか。

「㎝」はNEC特殊文字に含まれており、NECやIBMによるShift_JIS拡張が統合された文字コードであるWindows-31Jに含まれています。これをCP932と呼ぶこともあり5、CP932からUnicodeへの文字変換表には CP932における 0x8770 をUnicodeでの 0x339D に変換すると定義されています。

https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT

Shift_JISの規定において、0x8770 は「保留域」となっています。6 このことからも、JISに規定されているShift_JISにはなく、それの拡張であるWindows-31Jに含まれている文字であることがわかります。7

また余談として、JIS X 0213にて規定されたShift_JISX0213における 0x8770 に「㎝」の字形が含まれています。8

これもnkfで確認することができ、CP932において拡張された文字を扱わないオプション --no-cp932ext を指定することで文字が消えていることが確かめられます。

require 'nkf'
puts "\u339d : U+339d"
cm_to_sjis_from_utf8 = NKF.nkf('--ic=UTF-8 --oc=Shift_JIS --no-cp932ext', "\u339d")
puts cm_to_sjis_from_utf8.inspect
puts cm_to_sjis_from_utf8.encode('UTF-8').inspect
puts "=" * 20
$ ruby nkf.rb
㎝ : U+339d
""
""
====================

nkfでは、事前にNFKC正規化を行ってからでないと正しくShift_JISに変換できないことがわかりました。

iconv

iconvは、以前はRubyの標準添付ライブラリでしたが、2.0で削除されました。

https://www.ruby-lang.org/ja/news/2013/02/24/ruby-2-0-0-p0-is-released/

現在でもgemとしてインストールできるようにはなっていますが、String#encode を使用することが推奨されているので、今回はコマンドラインの結果をみることにします。

https://github.com/ruby/iconv

# iconv.rbとして保存
require 'open3'

puts "\u304c (U+304c)"
puts "String#encode('Shift_JIS') => #{"\u304c".encode('Shift_JIS').inspect}"
Open3.popen2e('iconv --from-code=UTF-8 --to-code=SHIFT-JIS') do |stdin, stdout_e, _|
  stdin.print "\u304c"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by iconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

puts "=" * 20

puts "\u304b\u3099 (U+304b U+3099)"
puts "String#encode('Shift_JIS', undef: :replace) => #{"\u304b\u3099".encode('Shift_JIS', undef: :replace).inspect}"
Open3.popen2e('iconv --from-code=UTF-8 --to-code=SHIFT-JIS') do |stdin, stdout_e, _|
  stdin.print "\u304b\u3099"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by iconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

puts "=" * 20

puts "\u0063\u006d (U+0063 U+006d)"
puts "String#encode('Shift_JIS') => #{"\u0063\u006d".encode('Shift_JIS').inspect}"
Open3.popen2e('iconv --from-code=UTF-8 --to-code=SHIFT-JIS') do |stdin, stdout_e, _|
  stdin.print "\u0063\u006d"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by iconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

puts "=" * 20

puts "\u339d (U+339d)"
puts "String#encode('Shift_JIS', undef: :replace) => #{"\u339d".encode('Shift_JIS', undef: :replace).inspect}"
Open3.popen2e('iconv --from-code=UTF-8 --to-code=SHIFT-JIS') do |stdin, stdout_e, _|
  stdin.print "\u339d"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by iconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

Open3.popen2e('iconv --from-code=UTF-8 --to-code=SHIFTJISX0213') do |stdin, stdout_e, _|
  stdin.print "\u339d"
  stdin.close
  result = stdout_e.read
  puts "Convert to ShiftJISX0213 by iconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('CP932').encode('UTF-8')}"
end
$ bundle exec ruby iconv.rb
が (U+304c)
String#encode('Shift_JIS') => "\x{82AA}"
Convert to Shift_JIS by iconv => "\x82\xAA"
Re-convert to UTF-8 => が
====================
が (U+304b U+3099)
String#encode('Shift_JIS', undef: :replace) => "\x{82A9}?"
Convert to Shift_JIS by iconv => "\x82\xA9iconv: illegal input sequence at position 3\n"
Re-convert to UTF-8 => かiconv: illegal input sequence at position 3
====================
cm (U+0063 U+006d)
String#encode('Shift_JIS') => "cm"
Convert to Shift_JIS by iconv => "cm"
Re-convert to UTF-8 => cm
====================
㎝ (U+339d)
String#encode('Shift_JIS', undef: :replace) => "?"
Convert to Shift_JIS by iconv => "iconv: illegal input sequence at position 0\n"
Re-convert to UTF-8 => iconv: illegal input sequence at position 0
Convert to ShiftJISX0213 by iconv => "\x87p"
Re-convert to UTF-8 => ㎝

nkfと同様に「が」(U+304B U+3099) の変換に失敗している様子がわかります。「か」までの出力には成功していることから、濁点 U+3099 の変換に失敗していそうですね。 またnkfについての説明で触れた「㎝」 (U+339D) については、Shift_JISへの変換は失敗していますが、Shift_JISX0213への変換は成功していますね。

他に指定できそうなoptionもないので、iconvでも事前にNFKC正規化しておく必要がありそうです。

uconv

それでは、Rubyを使用せずコマンドラインから使用できる、Unicodeの正規化形式も扱うことのできるツールはないのでしょうか?

これを行うことのできる uconv というものがあります。これはUnicode Consortiumが保守しているInternational Components for Unicodeというコンポーネント(?)に含まれており、Debianにおいては icu-devtools というパッケージ名で入手できます。

https://packages.debian.org/buster/icu-devtools

uconvに対して -x nfkc というふうに正規化形式を指定する(正確には、適用したいTransliterationを指定する)ことによって、NFKC正規化がされた上で文字コードの変換ができます。

require 'open3'

puts "\u304c (U+304c)"
puts "String#encode('Shift_JIS') => #{"\u304c".encode('Shift_JIS').inspect}"
Open3.popen2e('uconv --from-code UTF-8 --to-code Shift_JIS -x nfkc') do |stdin, stdout_e, _|
  stdin.print "\u304c"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by uconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

puts "=" * 20

puts "\u304b\u3099 (U+304b U+3099)"
puts "String#encode('Shift_JIS', undef: :replace) => #{"\u304b\u3099".encode('Shift_JIS', undef: :replace).inspect}"
Open3.popen2e('uconv --from-code UTF-8 --to-code Shift_JIS -x nfkc') do |stdin, stdout_e, _|
  stdin.print "\u304b\u3099"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by uconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

puts "=" * 20

puts "\u0063\u006d (U+0063 U+006d)"
puts "String#encode('Shift_JIS') => #{"\u0063\u006d".encode('Shift_JIS').inspect}"
Open3.popen2e('uconv --from-code UTF-8 --to-code Shift_JIS -x nfkc') do |stdin, stdout_e, _|
  stdin.print "\u0063\u006d"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by uconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8')}"
end

puts "=" * 20

puts "\u339d (U+339d)"
puts "String#encode('Shift_JIS', undef: :replace) => #{"\u339d".encode('Shift_JIS', undef: :replace).inspect}"
Open3.popen2e('uconv --from-code UTF-8 --to-code Shift_JIS -x nfkc') do |stdin, stdout_e, _|
  stdin.print "\u339d"
  stdin.close
  result = stdout_e.read
  puts "Convert to Shift_JIS by uconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('Shift_JIS').encode('UTF-8').inspect}"
  puts "codepoints => #{result.codepoints}"
end

Open3.popen2e('uconv --from-code UTF-8 --to-code cp932') do |stdin, stdout_e, _|
  stdin.print "\u339d"
  stdin.close
  result = stdout_e.read
  puts "Convert to CP932 by uconv => #{result.inspect}"
  puts "Re-convert to UTF-8 => #{result.force_encoding('CP932').encode('UTF-8')}"
  puts "codepoints => #{result.codepoints}"
end
$ bundle exec ruby uconv.rb
が (U+304c)
String#encode('Shift_JIS') => "\x{82AA}"
Convert to Shift_JIS by uconv => "\x82\xAA"
Re-convert to UTF-8 => が
====================
が (U+304b U+3099)
String#encode('Shift_JIS', undef: :replace) => "\x{82A9}?"
Convert to Shift_JIS by uconv => "\x82\xAA"
Re-convert to UTF-8 => が
====================
cm (U+0063 U+006d)
String#encode('Shift_JIS') => "cm"
Convert to Shift_JIS by uconv => "cm"
Re-convert to UTF-8 => cm
====================
㎝ (U+339d)
String#encode('Shift_JIS', undef: :replace) => "?"
Convert to Shift_JIS by uconv => "cm"
Re-convert to UTF-8 => "cm"
codepoints => [99, 109]
Convert to CP932 by uconv => "\x87p"
Re-convert to UTF-8 => ㎝
codepoints => [34672]

このように、NFKC正規化を行ったうえで、正しくShift_JISに変換できていることが、またCP932を指定したときには正規化を行わなくても「㎝」 (U+339D)を1文字のまま相互に変換できていることがわかります。

ちなみにTransliterationを活用するとこのようにひらがなをローマ字に変換するという面白いこともできます。

$ echo おはようございます | uconv -x '::hiragana-latin;'
ohayougozaimasu

余談 Unicodeにおける正規化形式について

元記事や現在において指定できる正規化形式、NFC、NFD、NFKC、NFKDの4つは、それぞれの名前が規格に登場するのはUnicode 3.0.1からであり、そのリリースは 2000-08-31 です。

https://web.archive.org/web/20050211134342/http://www.unicode.org/unicode/reports/tr15/tr15-19.html

それ以前のリリースにおいては、正規化形式について触れられている記述がなく、前述のUnicode Standard Annex #15から参照できる"Previous Version" においても、 “It is a stable document and may be used as reference material” などの記述が存在しないことから、正規化形式というものが存在するのはUnicode 3.0.1 以降ということになります。

https://web.archive.org/web/20050207015030/http://www.unicode.org/unicode/reports/tr15/tr15-18.html

このあたりの話は、技術評論社から出版されている[改訂新版]プログラマのための文字コード技術入門 の Appendix 4「Unicodeの諸問題」にて詳細に説明されています。この本はとても面白いのでぜひ読んでみてください。

まとめ

あるUnicode文字列に対してNFKCなどの正規化を適用したい場合、Rubyでは String#unicode_normalize にオプションとして、コマンドラインでは uconv を使用することで目的を達成できます。文字コード変換で良く知られるnkfやiconvでは適切に正規化が行われていない文字列を変換することができません。


  1. ではAPFSではどうなのかというと、規格書には j_drec_hashed_jey_t 構造体に格納される name_len_and_hash を計算するときにNFD正規化を行うとこが記載されていますが、ファイル名そのものの正規化についての記述は見付かりませんでした。 https://developer.apple.com/support/downloads/Apple-File-System-Reference.pdf 正規化が行われず、与えられたバイト列をそのまま保持するようになっているのか、同じ名前に見えるファイルを複数作成することができるという記事もあります。 https://eclecticlight.co/2017/04/06/apfs-is-currently-unusable-with-most-non-english-languages/ 

  2. 軽く目を通しましたが、Apple側でこの正規化形式に名前をつけたりはしていないようです。 https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFileSystem/BPFileSystem.html#//apple_ref/doc/uid/10000185-SW1 ただし、Appleの配布しているiconvのencodingには UTF-8-MAC を指定できるので、ほぼ公式だとしていいでしょう。 

  3. https://developer.apple.com/library/archive/qa/qa1173/_index.html より 

  4. JIS X 0208-1997 附属書3 表1より https://www.jisc.go.jp/pdfb6/PDFView/ShowPDF/5gMAAIAz9AGm_33fhhRp 

  5. 「呼ぶこともあり」というのは、当初定められたCP932をいくつかのベンダが独自拡張したあと、それをMicrosoftが統合したWindows-31JのこともCP932と呼ぶからです。現代においてはCP932 = Windows31J としていいと思いますが。 

  6. JIS X 0208-1997 附属書1より https://www.jisc.go.jp/pdfa8/PDFView/ShowPDF/7gIAAKR6fwk8ZKtaZttC 

  7. MSDNに記載されていたCP932の文字一覧より https://web.archive.org/web/20180405180457/https://msdn.microsoft.com/en-us/library/cc194892.aspx 

  8. JIS X 0213-2000 附属書4 表23より https://www.jisc.go.jp/pdfa5/PDFView/ShowPDF/5AIAAHLePslwm6mc6z4g 

Tweet
2020年08月17日
2020年07月19日

unasuke.fmの再始動

unasuke.fm

再始動

https://unasuke.fm を公開しました。ロゴは衰咲ふち(@otoroesaki)さんに作成していただきました。この場を借りてお礼申しあげます。ありがとうございます。

一旦は今までのエピソードを聞けるようにしただけになります。今後、エピソードごとのサマリやshownoteの記載、RSS feedの公開などの改善を進めていきます。

加えて、次の収録についても日程含め検討中になります。

プラットフォームに乗るかどうか

Podcastは、なぜだかまたブームが来ているようで、AnchorやTransistor、stand.fmなどのプラットフォームが登場しています。 プラットフォームに依存することで、労力の削減や便利な機能を使うことができますが、自分はいちから構築することにしました。

自分が発信する場を他人に委ねる怖さ、というものも多分どこかにあって、一度Twitterのアカウントが凍結されたときにはそれはもう気が気ではありませんでした。ブログをMiddlemanでbuildしてS3にホストしているのも、大して有効活用できていないMastodonを維持し続けているのも、そのためかもしれません。

Next.js を選択した理由と、時間がかかった理由

今回は今までの自分の選択であるMiddlemanによる構築ではなく、Next.jsによる構築を選択しました。

理由は、Webサイトをつくるのであれば、Webの一級市民言語であるJavaScriptでつくるほうがいいのではないかという判断、自分の技術の幅を広げたくて、まだ仕事でも触ったことのないReactに慣れておきたかったという学習目的の2つが主です。また、Next.js を選択するのであれば、素JavaScriptよりは、TypeScriptで書くほうがいいと思い、そのようにしました。

そうなると、TypeScriptとReactとNext.jsの3つを初めて触ることになるので、2つのことを同時に学ばない (by ところてん)ようにするどころか3つのことを同時に学ぼうとしてしまっています。 実際、書いては思うように動かず、何度も友人に助けを求めたり、ひたすらWeb上の記事を読んだりして、それでも全然わからず、多忙を言い訳にしてしばらく放置してしまったりしました。

image.png (23.1 kB)

半分エタりかけていましたが、公式ドキュメントを何度も何度も読んでいくうちになんだかある程度「わかる」ようになりました。結局自分は、悩んで泥臭く試行錯誤を繰り返して手を動かさないと技術を身に付けることはできないんじゃないかと思います。

正直まだまだ機能面や見た目の面でも足りていない部分が多く構築中ですが、オリジナルのファイルがSoundCloud上にもう存在しないので、とりあえずWeb上に参照可能な形で復活させることを優先しました。RSSもないのでPodcastとしては片手落ちですが、"Done is better than perfect" と言いますし、まずは公開することにしました。

使用している技術

まず、Next.jsを静的サイトジェネレーターとして採用しました。そうなると必然、Viewを記述するためにReactを採用することになります。また前述のように、主にTypeScriptで記述しています。ESLintとPrettierを用いてコードフォーマットをしています。

音声ファイルのホスティングについては、転送量による課金のないWasabiを、WebサイトそのもののホスティングについてはFirebase Hostingを選択し、DeployはGitHub Actions経由で行うようにしました。確認用にmaster branchをNetlifyにてHostingしています。

また Content Security Policy を有効化にし、Report Onlyの状態にして report-uri.io に収集させるようにしました。

余談ですが、開発はほぼ全てをWindows 10(not WSL)上で行いました。開発環境において特にハマることはありませんでしたが、ESLintのruleに改行コードをUnix styleかWindows Styleかに設定するものがあり、これの扱いが悩ましいところでした。

参考にした記事・コード

Tweet
2020年07月19日
2020年06月30日

パソコンが壊れて、直した話

windows

前提条件

起こったこと

pic.twitter.com/Fm7KgLQe77

— うなすけ (@yu_suke1994) June 28, 2020

まずはこの動画の状況になるまでの出来事を書いていきます。

  1. Windows Updateが来ているので実行する
  2. Windows Updateの途中で何度か再起動される
  3. 再起動のどこかのタイミングで、「Synaptics ドライバーのアンインストールに失敗しました」というエラーメッセージが出て、そこで止まってしまった
    1. 文言はあやふや、今思えばこの写真を撮っておくべきだった。
  4. どのような操作もできないまま固まってしまったので泣く泣く電源ボタン長押しによる中断
    1. 多分これが原因だが、他にどうすればよかったのだろうか。
  5. 再度起動すると、「前回のWindows Updateを修復しています」、のようなメッセージが出る
  6. ログイン画面に遷移するが、一切の操作ができない状態になる
    1. マウス操作不可能、キー入力不可能
  7. 電源ボタン操作で終了、起動し、UEFIメニューから回復オプションに入る
  8. システムの復元ポイントからWindows Updateを実行する前の状態に戻すも状況変化せず
  9. 「インストールされている Windows Update を削除する」を実行するも状況変化せず
  10. 「インストール メディアを使用して PC を復元する」も効果なし
  11. 別のマウス、キーボードを接続するも認識せず
  12. セーフモードで起動するも状況変化せず
  13. セーフモードで起動した結果、UEFIメニューに入れなくなる
    1. 起動直後のキー入力がすっ飛ばされるようになる。これ仕様なの?
  14. 詰み……

一度セーフモードに入ると抜けられなくなるというのは結構な絶望感がありました。

どう直したか

  1. セーフモード画面のまま放置して電池が切れるのを待つ
    1. ここで一旦寝る
  2. 起動し、UEFIメニューに入れること、回復オプションに入れることを確認
    1. これは賭けだったが、入れてよかった。
  3. 「個人用ファイルを保持する」設定で「PC を初期状態に戻す」を実行
  4. キー入力を受け付けるようになり、復活!

という流れだったのでした。イチから環境構築をやりなおしになってしまいましたが、ひとまず直ってよかったです。

※ この記事は復旧させたWindowsのWSL上で書かれました。

Tweet
2020年06月30日
2020年05月31日

Itamae v1.10.8 リリース

itamae v1.10.8

久々に自分がReleaseしたので、なにが入ったのかを軽くまとめます。

mergeしたもの

Print “(in action_XXX)” as a debug log by pocke · #315

debug logにどのactionによる実行なのかを表示するものです。descriptionに貼ってあるlogを見るとわかりやすいですね。

Reduce check_package_is_installed calling when package version is not specified by pocke · #314

package resourceにおいて、そのresourceのversionが指定されていない場合には不要となる処理を実行せずfast returnすることによって処理の高速化を図るものです。具体的には。installするactionにおいては、versionが指定されていない場合にはどのversionが存在するかの確認はしなくていいですし、removeするactionにおいてはinstallされているかどうかのみ確認すればよいだけです。 patch authorによるbenchmarkでは1.3倍高速になったとか。

Simplify Git resource’s get_revision method by pocke · #313

git resourceにおいて、git rev-list の最初行を取得するのではなく、git rev-parse の結果を使用するように変更するものです。無駄な出力をさせなくてよくなり、実行速度も少し向上するようです。

mergeしなかったもの

Fix to set “verifyhostkey” based on “stricthostkey_checking” value by musaprg · #312

SSH時の設定について、 StrictHostKeyCheckingno に設定されている場合にはその設定値を削除し、代わりにverify_host_key:never に設定するものです。 net-ssh gemの v6.0.2 において ArgumentError: invalid option(s): strict_host_key_checking が出てしまったことからいただいたpull requestではあるのですが、itamaeが依存しているSpecinfra側で対応がされたため、こちらでは特に対処しないこととしました。

Remove stricthostkey_checking option when net-ssh does not support it by mizzy · Pull Request #717 · mizzy/specinfra

蛇足

基本的に僕のレスが遅れがちでちょっと申し訳なさがあります。そして6月もリリースする予定です。

Tweet
2020年05月31日
2020年04月17日

Rubyのdistroless imageをmagicpakで構築できるのか?

distroless-ruby

こんにちは、趣味でこのようなものを作っている者です。

https://github.com/unasuke/distroless-ruby

作るだけ作って、実用はしていませんけど。

Distrolessとは何か、ということについては以下の記事をご参照ください。

distrolessイメージを使って、ランタイムDockerイメージを作ってみる - Qiita

さて私がこれまでこのimageをどのように作成していたのかと言いますと、この記事に書いたように人力で依存関係を調べて頑張っていました。 Rubyが動くdistroless image は作ることができるのか - Qiita

Ruby 2.6までは順調だったのですが、2.7ではリンクされるライブラリが増えたのかなんなのか、これまでのように単純なバージョンの変更では上手くいかずに手が止まってしまっていました。

そこに登場したのがこのツール、magicpakです。

これを使えば、Rubyが実行するために必要なファイルを列挙でき、distrolessなimageの作成が楽になるのではないかと考え、試してみました。

magicpak -v $(which ruby)

では早速次のようなDockerfileを作成し、buildの様子を眺めてみます。

FROM ruby:2.7.0-buster
ADD https://github.com/coord-e/magicpak/releases/latest/download/magicpak-x86_64-unknown-linux-musl /usr/bin/magicpak
RUN chmod +x /usr/bin/magicpak
RUN /usr/bin/magicpak -v $(which ruby) /bundle

docker buildが……

Sending build context to Docker daemon   2.09MB
Step 1/4 : FROM ruby:2.7.0-buster as ruby
 ---> ea1d77821a3c
Step 2/4 : ADD https://github.com/coord-e/magicpak/releases/latest/download/magicpak-x86_64-unknown-linux-musl /usr/bin/magicpak
Downloading [==================================================>]  4.222MB/4.222MB
 ---> bd6ae35e14fe
Step 3/4 : RUN chmod +x /usr/bin/magicpak
 ---> Running in e75094fba6fd
Removing intermediate container e75094fba6fd
 ---> 8280a91988f3
Step 4/4 : RUN /usr/bin/magicpak -v $(which ruby) /bundle
 ---> Running in 818a39c7466f
[INFO] exe: loading /usr/local/bin/ruby
[INFO] action: bundle shared object dependencies of /usr/local/bin/ruby
[INFO] exe: loading /usr/local/lib/libruby.so.2.7

################ snip #############################

[INFO] exe: using default interpreter /lib64/ld-linux-x86-64.so.2
[INFO] action: bundle executable /usr/local/bin/ruby as None
[INFO] action: emit /bundle
[INFO] action: emit: creating /bundle as it does not exist
[INFO] emit: link /lib/x86_64-linux-gnu/libpthread-2.28.so => /bundle/lib/x86_64-linux-gnu/libpthread.so.0
[INFO] emit: copy /lib/x86_64-linux-gnu/libpthread-2.28.so => /bundle/lib/x86_64-linux-gnu/libpthread-2.28.so
[INFO] emit: link /usr/local/lib/libruby.so.2.7.0 => /bundle/usr/local/lib/libruby.so.2.7
[INFO] emit: copy /usr/local/lib/libruby.so.2.7.0 => /bundle/usr/local/lib/libruby.so.2.7.0
[INFO] emit: link /lib/x86_64-linux-gnu/libdl-2.28.so => /bundle/lib/x86_64-linux-gnu/libdl.so.2
[INFO] emit: copy /lib/x86_64-linux-gnu/libdl-2.28.so => /bundle/lib/x86_64-linux-gnu/libdl-2.28.so
[INFO] emit: link /lib/x86_64-linux-gnu/libm-2.28.so => /bundle/lib/x86_64-linux-gnu/libm.so.6
[INFO] emit: copy /lib/x86_64-linux-gnu/libm-2.28.so => /bundle/lib/x86_64-linux-gnu/libm-2.28.so
[INFO] emit: link /lib/x86_64-linux-gnu/ld-2.28.so => /bundle/lib64/ld-linux-x86-64.so.2
[INFO] emit: copy /lib/x86_64-linux-gnu/ld-2.28.so => /bundle/lib/x86_64-linux-gnu/ld-2.28.so
[INFO] emit: link /lib/x86_64-linux-gnu/libz.so.1.2.11 => /bundle/lib/x86_64-linux-gnu/libz.so.1
[INFO] emit: copy /lib/x86_64-linux-gnu/libz.so.1.2.11 => /bundle/lib/x86_64-linux-gnu/libz.so.1.2.11
[INFO] emit: link /lib/x86_64-linux-gnu/libcrypt-2.28.so => /bundle/lib/x86_64-linux-gnu/libcrypt.so.1
[INFO] emit: copy /lib/x86_64-linux-gnu/libcrypt-2.28.so => /bundle/lib/x86_64-linux-gnu/libcrypt-2.28.so
[INFO] emit: link /lib/x86_64-linux-gnu/librt-2.28.so => /bundle/lib/x86_64-linux-gnu/librt.so.1
[INFO] emit: copy /lib/x86_64-linux-gnu/librt-2.28.so => /bundle/lib/x86_64-linux-gnu/librt-2.28.so
[INFO] emit: link /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2 => /bundle/usr/lib/x86_64-linux-gnu/libgmp.so.10
[INFO] emit: copy /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2 => /bundle/usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2
[INFO] emit: copy /usr/local/bin/ruby => /bundle/usr/local/bin/ruby
[INFO] emit: link /lib/x86_64-linux-gnu/libc-2.28.so => /bundle/lib/x86_64-linux-gnu/libc.so.6
[INFO] emit: copy /lib/x86_64-linux-gnu/libc-2.28.so => /bundle/lib/x86_64-linux-gnu/libc-2.28.so
Removing intermediate container 818a39c7466f
 ---> 077d681487ae
Successfully built 077d681487ae

多いですね。これを /bundle にまとめて展開すれば動くのでしょうか?次のようなDockerfileを使って試してみましょう。

FROM ruby:2.7.0-buster as ruby

ADD https://github.com/coord-e/magicpak/releases/latest/download/magicpak-x86_64-unknown-linux-musl /usr/bin/magicpak
RUN chmod +x /usr/bin/magicpak
RUN /usr/bin/magicpak -v $(which ruby) /bundle

FROM gcr.io/distroless/base-debian10

COPY --from=ruby /bundle /.
RUN ["/usr/local/bin/ruby", "-v"]
RUN ["/usr/local/bin/gem", "install", "sinatra"]

buildしてみると……

# ...snip...
Step 5/8 : FROM gcr.io/distroless/base-debian10
 ---> 5bb0e81ff6e4
Step 6/8 : COPY --from=ruby /bundle /.
 ---> f2a8875bdfdc
Step 7/8 : RUN ["/usr/local/bin/ruby", "-v"]
 ---> Running in 2cce67b240b1
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux]
Removing intermediate container 2cce67b240b1
 ---> 145cba6957e9
Step 8/8 : RUN ["/usr/local/bin/gem", "install", "sinatra"]
 ---> Running in e445421dbc07
OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"/usr/local/bin/gem\": stat /usr/local/bin/gem: no such file or directory": unknown

/usr/local/bin/gem がないようです。確かにmagicpakの対象にしたのはrubyコマンドのみで、gemやbundlerに対しては何もしていません。これらも含めてあげましょう。

FROM ruby:2.7.0-buster as ruby

ADD https://github.com/coord-e/magicpak/releases/latest/download/magicpak-x86_64-unknown-linux-musl /usr/bin/magicpak
RUN chmod +x /usr/bin/magicpak
RUN /usr/bin/magicpak -v $(which ruby) /bundle

FROM gcr.io/distroless/base-debian10

COPY --from=ruby /bundle /.
COPY --from=ruby /usr/local/bin/ /usr/local/bin # ここを追加
RUN ["/usr/local/bin/ruby", "-v"]
RUN ["/usr/local/bin/gem", "install", "sinatra"]

結果は……

Step 9/9 : RUN ["/usr/local/bin/gem", "install", "sinatra"]
 ---> Running in e638dbdb65fd
<internal:gem_prelude>:1:in `require': cannot load such file -- rubygems.rb (LoadError)
        from <internal:gem_prelude>:1:in `<internal:gem_prelude>'
The command '/usr/local/bin/gem install sinatra' returned a non-zero code: 1

こんどは rubygems.rb がみつかりません。これは /usr/local/lib/ruby/以下にあります。これも含めます。

FROM ruby:2.7.0-buster as ruby

ADD https://github.com/coord-e/magicpak/releases/latest/download/magicpak-x86_64-unknown-linux-musl /usr/bin/magicpak
RUN chmod +x /usr/bin/magicpak
RUN /usr/bin/magicpak -v $(which ruby) /bundle

FROM gcr.io/distroless/base-debian10

COPY --from=ruby /bundle /.
COPY --from=ruby /usr/local/bin/ /usr/local/bin
COPY --from=ruby /usr/local/lib/ruby/ /usr/local/lib/ruby # ここを追加
RUN ["/usr/local/bin/ruby", "-v"]
RUN ["/usr/local/bin/gem", "install", "sinatra"]

さてどうか……

Step 10/10 : RUN ["/usr/local/bin/gem", "install", "sinatra"]
 ---> Running in 888f7427612a
/usr/local/lib/ruby/2.7.0/yaml.rb:3: warning: It seems your ruby installation is missing psych (for YAML output).
To eliminate this warning, please install libyaml and reinstall your ruby.
/usr/local/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require': libyaml-0.so.2: cannot open shared object file: No such file or directory - /usr/local/lib/ruby/2.7.0/x86_64-linux/psych.so (LoadError)

libyamlが見付からないというエラーが出ました。gemコマンドに対してはmagicpakを通してないからでしょうか。ちなみにそれをやってもエラーが出ます。

root@4033859f93d6:/# ./magicpak -v /usr/local/bin/gem /bundle
[INFO] exe: loading /usr/local/bin/gem
[ERROR] error: The executable is malformed: unknown magic number: 7795575320214446371

とりあえず、libyamlを足してやると……

FROM ruby:2.7.0-buster as ruby

ADD https://github.com/coord-e/magicpak/releases/latest/download/magicpak-x86_64-unknown-linux-musl /usr/bin/magicpak
RUN chmod +x /usr/bin/magicpak
RUN /usr/bin/magicpak -v $(which ruby) /bundle

FROM gcr.io/distroless/base-debian10

COPY --from=ruby /bundle /.
COPY --from=ruby /usr/lib/x86_64-linux-gnu/libyaml* /usr/lib/x86_64-linux-gnu/ # ここを追加
COPY --from=ruby /usr/local/bin/ /usr/local/bin
COPY --from=ruby /usr/local/lib/ruby/ /usr/local/lib/ruby
RUN ["/usr/local/bin/ruby", "-v"]
RUN ["/usr/local/bin/gem", "install", "sinatra"]

今度は……

6 gems installed
Removing intermediate container 1a7823880c0d
 ---> 680de9256898
Successfully built 680de9256898
Successfully tagged unasuke/distroless-ruby:2.7.0-buster

sinatraがインストールできました!ここでは省きますが、ちゃんとリクエストも受けつけるようになっています。imageのサイズは次のようになりました。

image size
ruby:2.7.0-buster 842MB
ruby:2.7.0-slim-buster 149MB
ruby:2.7.0-alpine 51.4MB
rubylang/ruby:2.7.0-bionic 359MB
unasuke/distroless-ruby:2.7.0-buster 59.4MB

alpineには敵わないものの、slim-busterのおよそ半分のサイズになりました。まあ、実用性については疑問点が残りますが。

まとめ

magickpakを使うことで、比較的楽にdistroless-rubyのDocker imageを作成することができました(それまでのtry-and-errorはこの比ではなかったので……)。distroless自体は、Goなどのシングルバイナリが動けばいいimageを作るときには便利に使えるのではないかと思います。

GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.

Tweet
2020年04月17日
2020年04月16日

Arctis 7で通話をしながらDJ配信をする

arctis 7

オンライン飲み会でDJをする

皆さん、自宅待機生活はいかがお過ごしでしょうか。昨今はZoomやDiscordなどでビデオ通話をしながら飲み会をするというのが流行っていますね。 そんな会を盛り上げるために、DJをして会話のBGMをいい感じにしたいということがありますね。 ただ、DJをしながら通話に参加するには、ちょっと色々と工夫しないといけなかったので、それをメモとしてまとめます。

方法

準備するもの

まず前提として、DJはWindows上のrekordbox 5及びDDJ-RRで行いました。 これがmacOSの場合は多分こんな苦労は不要です。あくまでもWindows環境での話になります。 また、試せてはいませんがrekordbox 6でもおそらく同様の手順が使えると思います。

以上!

やりかた

  1. 標準のサウンドデバイスをPCのスピーカーにする
    • 要はこういう状態ですね
  2. PCのスピーカーの音量を絞っておく
    • ヘッドセットのマイクが音を拾わないようにするためです
  3. ビデオ通話に使用する音声デバイスをArctis 7のChat側にする
    • Discordだとこのような感じです
  4. rekordboxのサウンド設定をこのように設定する
    • いわゆる「いつもの」設定
  5. DJコントローラーのPHONESにArctis 7のステレオミニ端子を接続する
    • 「コンソールケーブル」は使いません。フォン端子とステレオミニを繋ぎます。
  6. DJをする
    • この状態だと、CUEがGameサウンドとして、通話の音声はChatとして、MasterがPCのスピーカーから流れる状態になります。
  7. 配信をする
    • 通話に参加しているみんなに聞いてもらう音声はストリーミングサービスのURLを渡すのが簡単だと思います。OBSなどでPCのmasterを配信すれば会話の内容は配信には乗らず、安心しておはなしできます。

最後に

AG03などのミキサーや、物理マシンがもうひとつあればもうすこしうまいことできる気がします。皆さんそれぞれのやり方を公開していただけると有り難いです。

Tweet
2020年04月16日
2020年04月13日

自分がリモートワークに慣れているというだけの話

desk

触発

■ - masawadaの日記 を読んで触発され、記録の目的も兼ねて書きます。

一時期、半年ほど1人でリモートワークをしていた時期がありますが、精神に異常をきたしたりしておりません。

— うなすけ (@yu\_suke1994) April 6, 2020

僕自身、一時期半年近くリモートワークをしていたのですが、特に閉塞感がどうこう、というのはありませんでした。(他の要因もあります) ただ昨今の外出自粛な状況になって同様のメンタルを保っていられるかはまた別の話だと思います。

とりあえず最近の状況についてまとめていきます。

リモートワーク力

「リモートワーク力」というのがあると思います。これは筋力のような身体的なものというよりは、自宅の設備などの話であったり、仕事のスタイルであったりを指して言っています。

僕自身、新卒入社したところでリモートワークが盛んに行われていたこともあり、また将来の展望から、自宅での作業環境についてはそれなりに投資してきています。それはベッドに寝ながら作業をできるようにしたり、身体にあったオフィスチェアを買ったりなどです。これらは数年かけて整えてきたものなので、ここ数ヶ月でいきなりリモートに切り替えるとなると、どうしてもパフォーマンスが落ちたり身体を痛めたり、チームでの仕事がうまくいかなかったり、ということが起こるでしょう。

現時点での作業環境は冒頭画像のようになっています。このPCで通話をしたり、Slackやコラボレーションツールを表示したりして、実際の作業は支給されたマシンを机や膝に置いて行っています。 (手頃な箱を卓上に置いて、その上にマシンを置くことでスタンディングデスクもできます)

この環境のためにベッド上のディスプレイを流用したので、寝ながらの作業はもうできなくなっています。

仕事がどう変わったか

現在、契約関係にあるのは3社です。このうち1社については感染症の流行以前から完全なリモートワークになっていました。 2社目は、あまり出社しなくてもよい状態だったのが、完全に出社しなくてもよくなりました。 そして3社目(以下A社)では、ここは基本的に出社をしていたのですが、東京都のロックダウン宣言の噂が出た時期あたりから原則としてリモートワークに切り替わりました。

これにより、契約関係のある全ての会社でリモートワークをすることになりました。

一番混乱が大きかったのはA社です。突然リモートワークに切り替わったので、masawadaさんのように家での作業環境が整っていないメンバーがそこそこ居たり、コミュニケーションの方法を検討し直さざるを得なかったりと、多分まだ落ち着いてはいないのかな、という様子を感じています。一番厄介なのがインターネット回線の問題だと思っています。

具体的に起こった問題を挙げます。

A社で、僕が発熱し(COVID-19ではありません)、解熱してから感染防止のため1週間ほどリモートワークをしたときは、ビデオ通話をつけっぱなしにして作業をしていました。これはオフィスのメンバーはオフィスの回線で繋いでいたので問題がなかったのですが、全員自宅勤務に変わってからは、回線の太さやそもそも容量制限のかかっている回線しかなかったりして、ビデオ通話を接続したままということが難しくなってしまいました。いわゆる「空気感」の共有が難しくなったような気がします。

僕も長時間のビデオ会議ということはこれまでやったことがなく、常用のヘッドホンは長時間つけていると耳が痛くなるという問題が発生しました。ひとまず社内でリモートワークにおすすめの機材を教えてもらい、良さそうなものを購入しました。

また、おやつどうする問題があると思います。出社すればおやつが用意してあった環境だと、家で作業するときに口さみしいとか糖分が欲しいのになにもないという状況になりがちです。あとは、用意してあっても飽きるとかですね。 なので、たまたまTwitterでみかけた、スナックミーというサービスを契約してみました。

おやつ体験BOX snaq.me

おやつ体験BOX https://t.co/7oS7mgoJBY のプレミアムドライパイン #うなすけのおやつ #異常独身男性の菓子 #もちぷにゃGET #スナックミー pic.twitter.com/iQnID04uPR

— うなすけ (@yu\_suke1994) April 3, 2020

おやつの好みを選べるし、小分けになってて食べすぎず、美味しいのでオススメです。

インターネットの話

全てがつらい。怒っているひとがあまりに多すぎる。特にTwitter。

同感です。これについては、身内しかいないSlackやDiscordでうまいこと発散することで心を穏やかに保つようにしています。公に怒りを増幅させたり、いわゆる冷笑とかをしたりせず、自分自身は普段通りの発言を日々続けていこうと考えています。これは東日本大震災のときの経験が大きいのかもしれません。

生活の話

ここ最近、食事についてはほとんどUberEatsやChompyなどの出前に頼るか、最寄りのコンビニで済ますようになっています。コンビニ食ばかりだとさすがに飽きがくるので出前を頼りがちですが、それだとどうしても高額になってしまい、これがずっと続くのがよくないことは感じています。

ただ料理をしようにも、コンロが1口しかない狭いキッチンなので、あまり手の込んだものを作ることができません。ホットクックなどを導入するか、キッチンが整った物件に引っ越すこともうっすら考えています。

外に出られない鬱憤については、いわゆる「引きこもり耐性」のある側の人間だと思っているのでそこまで苦ではないですが、イベントが中止になったり中止したりすることがあり、昨今の情勢と重なって気分が沈みがちかもしれません。なので最近は配信をするというていでDJをして楽しんでいます。

これからのリモートワークの話

圧倒的な実力、この仕事はこの人にお願いしたいという立場になり、リモートワークを認めてもらうことを当初は考えていたが、そういう能力によって待遇に勾配をつけるのもよろしくないように思えてきて、つまり普通にみんながリモートワークを認めてもらえる社会になればいいのに、もしくはそういう会社に勤めればいいという考えに変わってきた。

とある場所でこのような意見を書きました。この、リモートワークが実は可能だったということが判明していき、もっとリモートワークへの障壁が低くなっていくのは、僕にとってはありがたいことです。

また、これを見据えていることもあり、masawadaさんが感じている、終わりが見えないことへの閉塞感というものは、リモートワークに関してはずっと続くことを覚悟していたのであまり感じてはいません。外に出られないのはしんどいですが。

一度引っ越しをしてから地元に戻るのか、今の物件のままでUターンの時期を見計らうかが若干迷いどころですが、いずれは戻りたいです。(実家に、ではない)

その他

完全リモートワークに慣れてから出社生活に切り替わると、通勤の過程がつらくなります。家から出る勇気が必要になります。満員電車は本当にしんどいです。

まとめ

触発されたといいつつ、書き出してみると「自分は大丈夫ですけど?」みたいな感じになってしまいました。まあ個人差があるということで……

Tweet
2020年04月13日
2020年03月27日

OCI Runtime Spec v1.0.2 is coming soon

以前、udzuraさんがOCI Runtime Specを簡単にまとめた以下のようなブログを書かれていました。

OCI Runtime Specification を読む - ローファイ日記

この記事にてまとめられているのは 95a6ecf であり、このrevisionから一番近いreleaseはpre-releaseである v1.0.0-rc2 です。

さて、 v1.0.2 のリリースがそろそろということもあり、ここでは簡単のため、v1.0.0-rc2からv1.0.2までの間に入った変更について見てみようと思います。ただし全部は取り上げません。

v1.0.0-rc3

https://github.com/opencontainers/runtime-spec/releases/tag/v1.0.0-rc3

rcの間は駆け足で行きます。 Windowsについての記述が追加されています。 #565, #573 それに関してか?consoleのサイズについての情報を格納するfiledが #563 で追加されています。これらはMicrosoftの人によるcommitですね。

v1.0.0-rc4

https://github.com/opencontainers/runtime-spec/releases/tag/v1.0.0-rc4

いくつかのresouceに負の値を指定できるようになっています。でもmemory usage limitとかに負を指定できて嬉しいんだろうか? #648

v1.0.0-rc5

https://github.com/opencontainers/runtime-spec/releases/tag/v1.0.0-rc5

platformについての記述がREQUIEDに指定されています。 #695 それに関連してWindowsやSolarisについての記述が追加されたり修正されたり。 #673

あ、rc4 で負の値を指定できるようになったいくつかのfieldで、またunsignedに戻されていますね。#704

libseccompのバージョンが v2.3.0からv2.3.2に上がっています。 #705

v1.0.0-rc6

https://github.com/opencontainers/runtime-spec/releases/tag/v1.0.0-rc6

最後のrc release。 WindowsでのCPU使用率についての記述がperecnt指定からmaximum指定になっていたり。#777 指定したいパーセントに100を掛けた値を設定するように読めますけども。

やっぱりmemory usageに負の値を指定できるようになりました。-1 を指定するとunlimitedの意味になるようですね。 #876

platformについてのfiledが削除されました。runcでは使用されておらず、image-specのみが気にすることだろうということで? #850

v1.0.0

https://github.com/opencontainers/runtime-spec/releases/tag/v1.0.0

first release!

disableOOMKiller がmemory section配下に移動しました。#896 breaking chnageとされているのはこれだけ。

v1.0.1

https://github.com/opencontainers/runtime-spec/releases/tag/v1.0.1

ほぼほぼwordingやtypo fixですね。当然ですけど。

v1.0.2 (予定)

https://github.com/opencontainers/runtime-spec/pull/1037

v1.0.1に比べるとたくさんあります。 差分は(多分)これ https://github.com/opencontainers/runtime-spec/compare/v1.0.1…c4ee7d1

hookが追加されました。 createContainer, startContainer, createRuntimeの3つです。代わりにprestartがdeprecatedに指定されています。LXCと同じ名前だとか。 #1008

memoryに関してcgroupのuse_hierarchyが使用できるようになっています。 #985 第3回 Linuxカーネルのコンテナ機能[2] ─cgroupとは?(その1):LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社

WindowsにおいてDeviceをschemaに含められるようになっています。 #976

libseccompが v2.4.0になり、SMCP_ACT_LOGが使用できるようになっています。#1019 syscallのlogを残すようにする設定? http://man7.org/linux/man-pages/man3/seccomp_init.3.html

また、実装の一覧にgVisor、kara-container、crunが追加されています。crunはOCIのC実装で、runcより高速に動作するようです。元々はRedHatのgiuseppe氏による個人project?がContainers org配下に移動しています。 https://github.com/containers/crun

まとめ

取り上げられていない変更がめちゃくちゃあるので気になった人は直接見てください。

Tweet
2020年03月27日
2020年02月25日

EC2インスタンスにRDPで手軽に接続したい

download rdp file dialog

どういうことがしたいのか

適当なWindowsが動いているEC2インスタンスがあり、それにリモートデスクトップしてもらいたいということが皆さんにもあるかと思います。そういう時、冒頭の画像にあるように、AWSコンソールからEC2インスタンスを作成し、「接続」からの「リモートデスクトップファイルのダウンロード」からダウンロードしたファイルを開くことで、ユーザーのパスワードを入力すればもう接続できる、というお手軽便利機能があります。

しかし、ざっとaws-sdkを眺めてみましたが、このファイルをAPI経由で取得する方法が見当りませんでした。

では、リモートデスクトップで作業してもらう人にAWSのコンソールには入ってほしくない、というときにはどうすればよいでしょう。

RDPファイルの生成

ところで、ダウンロードしたRDPファイルを見てみると、中身はテキストになっていました。

auto connect:i:1
full address:s:ec2-192-0-2-1.ap-northeast-1.compute.amazonaws.com
username:s:Administrator

なんとなく自動で生成できそうに見えます。そして、それぞれがどのような値なのかがここに記載されていました。

サポートされるリモート デスクトップ RDP ファイルの設定 | Microsoft Docs

auto connect:i:1 についてはこのドキュメントに記載がないので触らないようにして、full address:s:には いわゆるPublic DNS (IPv4)を設定してやればよさそうです。

また接続するユーザーも、 username:s: で自由に設定できそうですね。

まとめ

パスワードだけは別で伝えないといけないのは仕方ありませんが、作業のおまかせが手軽にできそうでよかったです。

Tweet
2020年02月25日
2020年01月01日

初めての同人誌作成、サークル参加を振り返って

ブースの様子

参加を決めるまで

C96で友人のサークルをお手伝いしているとき、「サークル参加申込書買ってくるけど、どうする?」と聞かれ、勢いで「僕のぶんもお願い!」と言ったのが全ての始まりでした。 この時、そもそもサークルが存在しない状態であり、その上、何を同人誌として頒布するのかなどは何一つ決まっていませんでした。

その後、直近にあった出来事からサークル名を「キリンセル」とし、まずは日々更新しているpixivFANBOXのまとめを冊子にし、余力があればその他にも技術的なことをまとめて本にしようということになりました。

何なんだマジで pic.twitter.com/DeuNKCpVl3

— うなすけ(C97火曜日南リ17a) (@yu_suke1994) July 16, 2019

結果としては余力などなく。

FANBOXまとめを入稿するまで

正直なところ、楽観視していました。というのも、本にする内容は既に大量に存在しているからです。

実際にはログインしないと読めない秘密のコンテンツをどのようにローカルにdumpし、組版ツールに流し込むかなどの部分で大幅にてこずりました。

組版自体はRe:VIEWを使うことにし、TechBooster/ReVIEW-Template の構成をベースにしました。ここで大分楽ができました、ありがとうございます。

このとき、印刷会社の入稿締め切りスケジュールを見ながら作業していたのですが、できあがった原稿が200ページを超えることがまだわかっていませんでした。そしていざ入稿しようとすると、「そのページ数では入稿できない」という事態になり、あわてて別の印刷会社を探すことになったりもしました。

入稿締め切りのギリギリまで表紙のデザイン作業をすることになったりもして、余裕をもったスケジュールがどれだけ大切かを思い知らされました。 (ページ数的に背表紙が存在することが判明したのが結構遅かった)

うなすけファンブックを入稿するまで

ファンブックは、主体として動いていたのは蜘蛛糸まなさんなので僕はされるがまま、という感じではありましたが、まれに進捗をつついたり、インタビューの文字起こしをしたりなどのお手伝いをやっていました。これも結構ギリギリまで作業することになってしまい、朝5時くらいまで手を動かす日もありました。

当日までの準備

上記の作業と平行して、当日必要なさまざまなものの準備をしていました。

当初、交通系ICカードが使えることからCoineyを申請したのですが、審査が通らずSquareの導入となりました。また、審査に必要であることからサークルWebサイトの作成も平行して行っていました。 (このカード決済用端末に使えるので、iPad Proを買ったり……)

他にも、会計用のコインケース、サークルクロス、お品書き用のポスタースタンドなどなどの購入、当日売り子として手伝ってくれる友人(コミケ初参加)のためにどのようなことをすべきか、どのようなスケジュールでの行動かなどをまとめたものの作成、両替など、様々なタスクにずっと追われていました。

あと、告知配信とか。

当日

オペレーションについて

キャッシュレス決済は、事前告知配信に来ていただいた(であろう)方々や、同業の方々はすぐクレジットカードを出してくださいました。が、イベント全体としてはやはり現金決済が主流なので、はじめは現金で支払おうとした方も多かった印象です。

単純な決済にかかる時間としては、EMV Contactless = pixiv PAY < 現金決済 < Squareカード決済(要サイン) の順で所要時間が短い(早い)印象でした。

このあたりは、iDやQUICPayが使用できるようになるとまた変わるかもしれないですね。

売上げについて

売上げの管理は、クレジットカードと現金による決済はSquareにて一元管理するようにしました。これだと、レポート画面でどの時間帯にどれだけ売上げがあったのかなどの詳細な情報が入手できてとても便利でした。

事前決済について

事前決済はpixiv PAYによるものを、FANBOXまとめのほうでだけ受け付けていました。利用があってよかったです。

ファンカードについて

当日、ファンカードによるディスカウントはありませんでした。みんな電子書籍で欲しいのか、それとも来れなかったのか…… ただ、事前決済リンクからの購入であればファンカード提示はなくてもよいので、それも若干あるかもしれません。

コミケそのものについて

ほぼ立ちっぱなしの6時間でした。自分のサークルに居たい気持ちとあちこちに挨拶&買い出しに行きたい気持ちとの戦いで、簡単な挨拶でそそくさ退散してしまったブースがいくつもあります。サークル出展って大変ですね。 当日、差し入れを下さった皆さんありがとうございました。全て美味しく頂きました。

振り返ってKPT

Keep

Problem

Try

Tweet
2020年01月01日
古い投稿