2023/09/24

Linuxのbootパーティションの空き容量を増やしたい

Linuxで /boot パーティションのサイズが足りず、さらに容量を増やすことも難しい場合は次のようにするとカーネルイメージのディスク使用量を多少減らすことができるかもしれません。

  1. /etc/initramfs-tools/initramfs.conf の COMPRESS の行を編集して圧縮手法をxzに変更します。
  2. update-initramfs -u

たとえば、Ubuntu20で、

initrd.img-5.15.0-43-generic
を使っていて、上の手順を実行すると、
217,952,335 bytes
から
155,488,300 bytes
にまで減らすことができました。

Ubuntu20からUbuntu22にアップグレードしようとしてbootパーティションの容量が足りなかったときは、この方法で回避ができました。

2023/03/12

ChatGPTとPerplexityの比較

ChatGPTとPerplexityに色々と聞いて、結果を比較してみました。

シングルクォートorダブルクォート


入力した文字列は
Pythonでは"と'のどちらが推奨されていますか?またその根拠を教えてください。
ChatGPTの出力は次の通り。
Pythonにおいて、 " "(ダブルクォーテーション)と ' '(シングルクォーテーション)の両方が文字列を表現するために使用されます。両方のクォーテーションの種類を使用することで、文字列内にクォーテーションが含まれる場合に便利になります。

しかし、PythonにはPEP 8と呼ばれるスタイルガイドがあり、その中で文字列の表現において、ダブルクォーテーションが推奨されています。PEP 8によると、ダブルクォーテーションがシングルクォーテーションよりも優れている理由は以下の通りです。

    Pythonの標準ライブラリの多くは、ダブルクォーテーションを使用しています。
    文字列内にシングルクォーテーションが含まれる場合、エスケープする必要があるため、ダブルクォーテーションの方が可読性が高くなることがあります。
    シングルクォーテーションは、文字列の代わりにシングルクォーテーションを使用することができるため、文法上の意味を区別するために混乱を招く可能性があります。

しかし、Pythonのコミュニティには、シングルクォーテーションを好む人もいます。この問題には、個人的な好みやスタイルガイドに従うなどの解決策がありますが、PEP 8に従ってダブルクォーテーションを使用することが一般的に推奨されています。
Perplexityの出力は次の通り。
Pythonでは、シングルクォーテーション(')とダブルクォーテーション(")の両方が文字列リテラルを表現するために使用されます。Pythonの公式ドキュメントによると、どちらの方法でも同じ結果が得られます[1]。
ただし、コード内で文字列を囲む引用符を一貫して使用することが推奨されています。一般的には、Pythonコミュニティではシングルクォーテーションがより一般的であると考えられています[2][3]。
これは、ダブルクォーテーションを文字列内で使用する必要性が少ないためです。また、Pep8スタイルガイドでは、シングルクォートを使用することが推奨されています[4]。
ただし、プロジェクト内で一貫性を保つことが重要です。プロジェクト内で既に採用されているスタイルに従うことが望ましいです[5]。
ちなみに[4]のリファレンスを見に行ってもどこに書いてあるのか見つけられませんでした。

https://pep8-ja.readthedocs.io/ja/latest/ を読むと、

Python では、単一引用符 ' で囲まれた文字列と、二重引用符 " で囲まれた文字列は同じです。この PEP では、どちらを推奨するかの立場は示しません。どちらを使うかのルールを決めて、守るようにして下さい。
ということなので、どちらかPEP8では推奨されているわけではないようです。ChatGPTとPerplexityのどちらも誤りですね。

きのこたけのこ


入力した文字列は
きのこたけのこ戦争について教えてください。
ChatGPTの出力は次の通り。
「きのこたけのこ戦争」とは、日本の童謡のひとつで、作詞:さとう宗幸、作曲:いずみたくによって作られた曲です。戦争を題材にした曲で、きのこ派とたけのこ派の戦いを描いています。

歌詞の冒頭には「山の手にはきのこ派 川の手にはたけのこ派」とあり、きのこ派とたけのこ派がそれぞれ勢力を持っていることが示されています。曲の進行に従い、両派の戦いが繰り広げられますが、最終的には「みんな仲良くしましょ きのこたけのこ戦争終わりました」という歌詞で、和解が成されるという内容です。

「きのこたけのこ戦争」は、子供たちに戦争を通して、相手を理解し、和解することの大切さを教えるために作られた曲で、日本の教育現場でもよく取り上げられています。
Perplexityの出力は次の通り。
「きのこたけのこ戦争」とは、日本の菓子メーカー明治製菓が販売する「きのこの山」と「たけのこの里」のどちらが美味しいかという消費者間の論争である[1][2][3][4]。この論争は1980年代から始まり、現在でもネット上で話題になっている[2]。また、明治製菓自身も「きのこたけのこ国民大調査」と称するアンケートを実施しており、その結果を公表している[5]。
ChatGPTの出力は面白いのですが、Perpelxityのほうが欲しい情報が得られています。

エディタどれがいいかな


入力した文字列は
エディタの宗教論争について説明してください。
ChatGPTの出力は次の通り。
エディタの宗教論争は、プログラマーやコンピューターエンジニアの間で長年続いている、どのエディタが最も優れているかという議論です。エディタとは、プログラムやテキストファイルを編集するためのソフトウェアのことです。

エディタの宗教論争は、個人的な好みや使い慣れている環境などによって、さまざまな立場があるため、しばしば激しい議論になります。一部のプログラマーやエンジニアは、彼らが使用するエディタについて非常に情熱的であり、そのエディタが最高であると信じて疑いません。

代表的なエディタの宗教論争としては、以下のものがあります。

    Vim vs Emacs: VimとEmacsは、両方とも高度な機能を備えたテキストエディタで、プログラマーやシステム管理者などの技術者に人気があります。両方のエディタには、熟練したユーザーによって開発されたカスタムコマンドがありますが、その開発手法や哲学に対する意見の違いにより、ユーザーは通常、どちらかを使用することになります。

    Sublime Text vs Atom vs Visual Studio Code: Sublime Text、Atom、およびVisual Studio Codeは、プログラマーの間で最も人気のあるエディタの1つです。これらのエディタには、豊富なプラグインとカスタマイズオプションがありますが、それぞれのエディタの利点と欠点についての議論が行われています。

    IDE vs エディタ: いくつかのプログラマーやエンジニアは、統合開発環境(IDE)を使用することを好む場合があります。IDEは、エディタの機能に加えて、コンパイラ、デバッガ、および他のツールを提供します。エディタを使用することを好む人は、軽量で使いやすく、シンプルなエディタを好みます。

最近では、エディタの宗教論争は、クラウドベースのエディタ(例えば、Google Docs、Office Online、CodePen)の使用に関しても続いています。これらのエディタは、ロ
Perplexityの出力は次の通り。
エディタ戦争とは、プログラミングを行う人々の間で、どのテキストエディタが最適かというテーマについての論争です。特にハッカー文化に属する人々の間で、自分たちが愛好するエディタを宗教的狂信とも言えるほど尊敬して扱う伝統があります。この論争は、vi派とEmacs派の二大陣営によって代表されます[1][2][3]。しかし、この論争は単なるジョークや冗談であることも多く、実際には多くのエディタが存在し、それぞれに優れた機能や使い勝手があるため、個人の好みや用途に合わ
ChatGPT、Perplexity共に出力途中でやめてしまいました。内容はどちらも正しそうです。

Git vs Mercurial vs Sapling


入力した文字列は
GitとMercurialとSaplingのProsとConsを教えてください。
ChatGPTの出力は次の通り。
Git、Mercurial、およびSaplingは、分散バージョン管理システムの一例です。以下に各システムのProsとConsを示します。

GitのPros:

    オープンソースであり、広く使用されている
    大規模なプロジェクトに最適で、高速で効率的なブランチとマージを提供
    GitHubなどの人気のあるプラットフォームで広く使用されているため、ツールやサポートが豊富にあります。

GitのCons:

    初心者には学習曲線が急である
    履歴の変更に対する管理が難しい場合がある

MercurialのPros:

    Gitに似た機能を提供するが、より使いやすいとされている
    Windowsにも対応しているため、開発者にとってはより利用しやすい

MercurialのCons:

    Gitほど普及していないため、サポートが少ない場合がある
    ブランチとマージの処理が遅い場合がある

SaplingのPros:

    GitとMercurialの長所を組み合わせたもので、Gitのブランチ戦略を使用できる
    ブランチとマージの速度が非常に速く、一般的なワークフローであっても効率的に動作する

SaplingのCons:

    GitやMercurialほど普及しておらず、サポートが限られている場合がある
    開発者が少ないため、コミュニティの活発性が低い場合がある

総合的に、Gitは最も広く使用されており、豊富なツールとサポートがあるため、大規模なプロジェクトに最適です。一方、MercurialはGitに似た機能を提供しながらも、より使いやすいため、初心者には適しているかもしれません。SaplingはGitやMercurialよりもブランチとマージの速度が速く、高速なワークフローが必要な場合に最適です。ただし、Saplingのサポートが限られているため、現在はGitまたはMercurialを選択することが一般的です。
Perplexityの出力は次の通り。
Git and Mercurial are both popular version control systems, each with their own pros and cons. Git is known for its speed, flexibility, and large user community[1].
Mercurial is praised for its ease of use, scalability, and support for multiple branches[2].
Sapling is a newer source control system developed by Meta that aims to be user-friendly and scalable[3].
It removes or changes some of the behavior of Mercurial commands to make them more consistent with modern expectations[4].
While Git and Mercurial are well-established systems with large communities, Sapling is a newer option that may appeal to those looking for a more user-friendly experience[5].
ChatGPTもPerplexityも正しそうな出力をしています。 Perpelxityは出力がなぜか英語になってしまいました。Prosとか書いたからかもしれません。

ChatGPTの出力の「初心者には学習曲線が急である」は誤用する多数派に引きずられて誤りを出力してしまっていますね。

歴史


入力した文字列は
1980年から10年ごとに1つずつ日本で起きた大きな出来事を教えてください。
ChatGPTの出力は次の通り。
以下は、1980年から2010年までの日本で起こった10年ごとの大きな出来事の例です。

    1980年代:バブル景気の到来
    1990年代:バブル崩壊、リーマンショックの影響による失業率上昇、そして阪神・淡路大震災
    2000年代:東京地下鉄サリン事件、小泉純一郎による構造改革、そしてリニア中央新幹線計画の発表
    2010年代:東日本大震災、福島第一原発事故、消費税率の引き上げ、そして東京オリンピック・パラリンピックの開催準備
Perplexityの出力は次の通り。
1980年代:
• 1989年1月7日:昭和天皇崩御、皇太子明仁が即位[1]。
1990年代:
• 1995年1月17日:阪神・淡路大震災発生[1]。
2000年代:
• 2003年3月20日:イラク戦争に自衛隊が派遣される[2]。
2010年代:
• 2011年3月11日:東北地方太平洋沖地震(東日本大震災)発生[1]。
2020年代:
• 2020年7月24日:東京オリンピック開催予定だったが、新型コロナウイルス感染症の影響で延期される[1]。
ChatGPTとPerplexity共にしれっと嘘をついています。

ChatGPTの出力を見てみます。まず、東京地下鉄サリン事件は
https://ja.wikipedia.org/wiki/%E5%9C%B0%E4%B8%8B%E9%89%84%E3%82%B5%E3%83%AA%E3%83%B3%E4%BA%8B%E4%BB%B6
によると、1995年(平成7年)3月20日なので、2000年代というのは嘘ですね。

リーマンショックは2008年なので、これも嘘。

リニア中央新幹線はhttps://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E6%96%B0%E5%B9%B9%E7%B7%9Aによると、「2011年5月26日に整備計画が決定され」と書かれているので、2000年代というのも誤りのようです。

Perplexityは日付付きで列挙してくれました。

Perpexityが参照したのは、
[1] https://www3.nhk.or.jp/news/special/heisei/chronology/
[2] https://www.shugiin.go.jp/internet/itdb_kenpou.nsf/html/kenpou/chosa/shukenshi033.pdf/$File/shukenshi033.pdf
とのこと。

出力結果のうち、1989年1月7日、1995年1月17日、2011年3月11日、2020年7月24日は正解。 2003年3月20日は
https://ja.wikipedia.org/wiki/%E8%87%AA%E8%A1%9B%E9%9A%8A%E3%82%A4%E3%83%A9%E3%82%AF%E6%B4%BE%E9%81%A3
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%A9%E3%82%AF%E6%88%A6%E4%BA%89
を読むと、軍事介入の開始日のようです。派遣日とごっちゃになってます。

正しい文章のなかに一部だけ間違えた内容を含めるとかされると、結局検証に時間がかかるので、こういったことの調査には不向きなようです。 調査の取っ掛かりには使えるのかもしれません。

いずも


入力した文字列は
「いずも」はどういった船ですか?
ChatGPTの出力は次の通り。
「いずも」とは、日本の海上自衛隊が運用するヘリコプター搭載護衛艦の名称です。正式名称は「いずも型護衛艦」で、海上自衛隊の中でも最大級の艦種となっています。

「いずも」は、航空機の発着が可能なフライトデッキと、ヘリコプターを格納できるハンガーを備えています。また、レーダーやソナーなどの最新の艦載電子機器や、対空・対水上戦闘能力を持つ艦砲やミサイルなどの装備も備えています。

「いずも」は、平時には海上自衛隊の護衛艦として、また災害時には災害派遣艦としても活躍しています。また、アメリカ海軍との共同訓練などにも参加しています。
Perplexityの出力は次の通り。
「いずも」とは、海上自衛隊が保有する護衛艦の一種であり、2019年時点で海上自衛隊史上最大の自衛艦である[1][2]。
全長248m、最大幅38m、満載排水量2万6000トンであり、軽空母に相当する大きさだ[2]。
F-35Bを搭載・運用可能とする改修工事を受けており、「いずも」と「かが」の2隻で61億円かかった[3][4]。
正しそうに見えます。

ニューロン


入力した文字列は
ニューロンのシナプスで使われているイオンの名前を列挙して
ChatGPTの出力は次の通り。
ニューロンのシナプスで使われているイオンの一部を以下に列挙します:

    ナトリウムイオン (Na+)
    カリウムイオン (K+)
    カルシウムイオン (Ca2+)
    塩化物イオン (Cl-)

これらのイオンは、神経細胞のシナプスを介して情報が伝達される際に重要な役割を果たします。具体的には、神経細胞の電気信号がシナプスを介して化学信号に変換され、シナプス前細胞から放出された神経伝達物質がシナプス後細胞の受容体に結合することで、これらのイオンが活動電位を誘発したり、神経伝達物質の放出を促進・阻害するなどの役割を担います。
Perplexityの出力は次の通り。
ニューロンのシナプスで使われているイオンの名前はカルシウムイオンです[1]。
Perplexityは1個しか挙げられませんでした。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%8A%E3%83%97%E3%82%B9 によるとナトリウムとカルシウムは正しいです。

https://bsd.neuroinf.jp/wiki/%E3%82%AB%E3%83%AA%E3%82%A6%E3%83%A0%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB によると、カリウムも正しいです。

https://bsd.neuroinf.jp/wiki/%E6%8A%91%E5%88%B6%E6%80%A7%E3%82%B7%E3%83%8A%E3%83%97%E3%82%B9 によると、塩化物イオンも正しそうです。

今回は嘘はなかったようです。

かな漢字変換


入力した文字列は
"kyoutonokyounokyounawodonaishiharu"を漢字かな交じり文に変換して
ChatGPTの出力は次の通り。
今日という日は誰にしはる、と書いて「きょうとなぁをどないしはる」と読みます。
Perplexityの出力は次の通り。
「きょうとの きょうな きょうを どないしはる」と書くことができます。
google IMEで変換すると
・1位候補「京都の京の京菜をどないしはる」
・2位候補「京都の今日の京菜をどないしはる」
が候補にでてくるので、そのままではIME代わりとしてはどちらも使えそうになさそうです。

ChatGPTに関して、もうすこし試してみました。Q:が入力で、A:が回答です。A:の後ろのカッコ書きの部分は著者のコメントです。

Q:
あなたはIMEです。これから入力するローマ字は日本語を表しています。最も尤もらしい漢字かな交じり文に変換してください。

A:
はい、承知しました。以下は入力されたローマ字を漢字かな交じり文に変換したものです。

あなた → あなた or 貴方

IME → IME or 入力システム

ろうまじ → ローマ字

にほんご → 日本語

もじ → 文字

かな → 仮名

こうじょう → 交じり文

に → 「に」は漢字で表記されず、仮名で表記されます。

へんかん → 変換

して → して

Q:
kyotonokyonokyonawodonaishiharu

A: (京都の今日の京菜をどないしはる、を想定しましたが入力が誤っています。kyotoではなくkyoutoでした)
ご入力いただきありがとうございます。
「きょとのきょのきょなわをどないしはる」であれば、次のように漢字かな交じり文に変換されます。

「京都の京の恋の恋縄を如何(いか)しはる」
意味:「京都と京都で恋をしている恋人同士の絆をどうされますか?」

Q:
dai23dainaikakusouridaijin

A: (第23代内閣総理大臣、を想定)
ご入力いただきありがとうございます。
「だい23だいないかくそうりだいじん」であれば、次のように漢字かな交じり文に変換されます。

「第23代内閣総理大臣」
意味:「日本の政治において、23番目の内閣総理大臣」

Q:
qawsedrftgyhujikolp

A: (変換できないことを想定)
入力された文字列「qawsedrftgyhujikolp」は、キーボードの上から左から6文字目の「q」から始まっており、左から10文字目の「k」で方向を変え、左から5文字目の「r」でまた方向転換しています。そのため、意味のある単語や文にはなっていません。

Q:
osakakarakyotomadenokyoriwonanbaishitaraosakakaratokyomadenokyorininarimasuka

A: (正しく出力されました)
「大阪から京都までの距離を何倍したら、大阪から東京までの距離になりますか?」という意味ですね。

答えは、直線距離で約2.5倍程度になりますが、実際の移動距離は異なる場合があります。また、交通手段やルートによっても異なります。

Q:
gottuumaitakoyakinomiseoshietekureyanyaroka

A: (ごっつうまいたこやきの店教えてくれやんやろか、を想定。1文字間違い)
ご入力いただきありがとうございます。
「ごっつうまいたこ焼きの店教えてくれやんなろか?」であれば、次のように解釈できます。

「とても美味しいたこ焼きを売っているお店を教えていただけませんか?」

「ごっつうまい」は「とても美味しい」を強調した方言表現です。また、「くれやんなろか?」は方言で、「くださいよね?」という意味になります。

Q:
yoruniasageba mane

A: (夜にあさげばまね、を想定。まね、が誤って変換されました。解説は間違い。「夜に歩いたら駄目だ」なら正解)
ご入力いただきありがとうございます。
「夜にあさげば真寝」という文言であれば、そのままの意味で「夜遅くに寝ると、本当に寝てしまう」という意味になります。
ただし、文脈によっては異なる解釈があるかもしれません。

Q:
ototoiyasumiyattakedo,dogenshitato

A: (一昨日休みやったけど、どげんしたと、を想定。解釈できなかった模様)
ご入力いただきありがとうございます。
「弟がやすみやったけど、道元師太と」であれば、文脈によっては意味が通じない可能性がありますが、文法的には正しい文です。
一般的には、次のように解釈できます。

「弟が休みだったけど、道元師太と一緒に過ごしました。」

ただし、「道元師太」という人物が誰かによってはわからない場合があるため、文脈によっては異なる解釈があるかもしれません。

Q:
wattihahanadearinsu

A: (わっちは花でありんす、を想定)
ご入力いただきありがとうございます。
「わっちは母なんであーりんす」という文言であれば、次のように解釈できます。

「わたしは母親なのであります(です)」

「わっち」は方言で「わたし」を表す表現で、「あーりんす」は方言で「です」を表す表現です。

Q:
wattiwahanadearinsu

A: (「は」を「わ」に変えたが、読み取られなかった)
ご入力いただきありがとうございます。
「わっちわ母なんであーりんす」という文言であれば、次のように解釈できます。

「わたしは母親なのであります(です)」

「わっち」は方言で「わたし」を表す表現で、「わ」が重ねられていることで親しみやすさが表現されています。「あーりんす」は方言で「です」を表す表現です。ただし、方言によっては異なる言葉遣いがあるため、文脈によっては異なる解釈があるかもしれません。

Q:
asunoasakarataihuugayattekimasu

A: (明日の朝から台風がやってきます、を想定)
ご入力いただきありがとうございます。
「明日の朝から体調がやってきます」という意味になります。
ただし、文脈によっては異なる解釈があるかもしれません。
やはりIMEとしてはイマイチですね。

日本の財政


入力した文字列は
日本国の貸借対照表を見せて
ChatGPTの出力は次の通り。
申し訳ありませんが、私は見ることができません。私は人工知能のプログラムであり、物理的な場所に存在するわけではありません。また、日本国の貸借対照表にアクセスする権限もありません。

しかしながら、日本国の貸借対照表は、日本の国会図書館や経済産業省など、公的な機関のウェブサイトで入手可能です。そちらを参照してください。
Perplexityの出力は次の通り。
日本国には貸借対照表が存在します。財務省のウェブサイトで公表されており、誰でも閲覧することができます[1]。
また、経済産業省のウェブサイトでも貸借対照表が掲載されています[2]。
ただし、国全体のフローとストックの財務状況を把握するためには、一般企業には存在しない国特有の仮想資産なども考慮する必要があるとされています[3]。
[1]のリンクからたどることはできました。参照URLがあるというのは検索目的では便利です。

四国で最も高い山


ChatGPTが参照したURLを出せるか試してみます。
Q:
四国で最も高い山の名前は?

A: (漢字は合っているものの読みを間違えています)
四国で最も高い山は、石鎚山(いしのぶさん)です。標高は1,982メートルです。

Q:
根拠となるWebページのURLを教えてください。

A: (出力されたURLは金峯山のページで間違い)
以下のURLが参考になります。

https://www.yamakei-online.com/yamanavi/yama.php?yama_id=230

キャッシュレス決済


入力した文字列は
銀行振込はキャッシュレス決済にあたりますか?
ChatGPTの出力は次の通り。
はい、銀行振込はキャッシュレス決済の一種です。キャッシュレス決済とは、紙幣や硬貨を使用せずに電子的な手段で支払いを行うことを指します。銀行振込は、支払いのために紙幣や硬貨を使用せずに、銀行口座を介して電子的な取引を行う方法です。そのため、銀行振込はキャッシュレス決済の一つとして位置付けられます。
Perplexityの出力は次の通り。
銀行振込はキャッシュレス決済にあたります。キャッシュレス決済とは、現金以外で支払う決済手段のことであり、クレジットカードや銀行振込などがこれにあたる[1][2][3][4]。
日本のキャッシュレス決済比率は約20%と言われていますが、銀行口座を使った支払いを含めれば約50%まで引き上がるとも言われています[3]。
どちらも正しそうです。

オセロ


入力した文字列は
オセロの初期配置を絵文字で描いて
ChatGPTの出力は次の通り。
Sure, here's a visual representation of the starting position for Othello using emojis:

⚪️⚫️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚫️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚫️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚫️
Perplexityの出力は次の通り。
セロの初期配置を絵文字で描くことができます。以下は一例です。
⚪️⚫️⚫️⚫️⚫️⚫️
⚫️⚪️⚪️⚪️⚪️⚫️
⚫️⚪️⚪️⚪️⚪️⚫️
⚫️⚪️⚪️⚪️⚪️⚫️
⚫️⚪️⚪️⚪️ ⚪ ⬜
 ⬜ ⬜ ⬜ ⬜ ⬜
 (白石を「○」、黒石を「●」、「 」を空白、「■」を壁などに置き換えています。)
どちらもハズレですね。白の絵文字が環境によっては見えないかもです。

将棋の初期配置


入力した文字列は
将棋の初期配置を絵文字で書いて
ChatGPTの出力は次の通り。
将棋の初期配置を絵文字で書くと以下のようになります:

🐴🩹🦫🥋🩸🥋🦫🩹🐴
🐺🩹🩹🩹🩹🩹🩹🩹🐺
🐗🩹🦊🥋🩸🥋🦊🩹🐗
🐦🩹🩹🩹🩹🩹🩹🩹🐦
🎖️🩹🎖️🩹🎖️🩹🎖️🩹🎖️
🩸🩹🩸🩹🩸🩹🩸🩹🩸
🐭🩹🩹🩹🩹🩹🩹🩹🐭
🐱🩹🦊🥋🩸🥋🦊🩹🐱
🐵🩹🩹🩹🩹🩹🩹🩹🐵

解説:

🐴: 先手の馬
🐺: 先手のライオン(本将棋ではライオンと呼びます)
🐗: 先手の豹
🐦: 先手の鳥
🎖️: 先手の玉将
🩸: 先手の兵
🐭: 後手のねずみ
🐱: 後手の猫
🐵: 後手の猿

🦫: 「絵文字」には将棋の駒の絵文字が存在しないため、「アナグマ」を代用しています。
🥋: 同様に「道着」を表す絵文字を使用しています。
どうなっているのでしょうか。

Perplexityの出力は次の通り。

将棋の初期配置は以下のように絵文字で書くことができます。
🐴🉑🉐🉑🉐🎖️🉑🉐🉑🉐
(先手:歩兵2、香車2、桂馬2、銀将1、金将1、王将1)
(後手:歩兵2、香車2、桂馬2、銀将1、金将1、王将1)
こっちも変。絵文字というのが良くないようです。

将棋の初期配置 その2


入力した文字列は
将棋の初期配置を文字で描いて。罫線を使って枠も描いて。
ChatGPTの出力は次の通り。
はい、以下に将棋の初期配置を文字で描き、罫線で枠を描きます。

┏━┯━┯━┯━┯━┯━┯━┯━┓
┃ │香│桂│銀│金│王│金│銀│桂│香│
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃歩│歩│歩│歩│歩│歩│歩│歩│歩│
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃歩│歩│歩│歩│歩│歩│歩│歩│歩│
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │飛│角│金│ │金│ │馬│飛│
┗━┷━┷━┷━┷━┷━┷━┷━┛

※棋譜や駒落ちのルール等によっては、初期配置が異なる場合があります。
Perplexityの出力は次の通り。
将棋の初期配置は以下のようになります。罫線を使って枠も描きました。
┏━┳━┳━┳━┳━┳━┳━┳━┓
┃香│桂│銀│金│玉│金│銀│桂│
┣━╋━╋━╋━╋━╋━╋━╋━┫
┃ │飛│ │ │ │ │角│ │
┣━╋━╋━╋━╋━╋━╋━╋━┫
┃歩│歩│歩│歩│歩│歩│歩│歩 │
┗━━━━━━━━━━━━━━─
※将棋盤は9×9マスです。
ChatGPTはいろいろずれています。Perplexityは8x3の出力になっています。雰囲気は出ていますが、どちらも正しくないです。

2023/03/06

subprocess.runの文字列長の制限

Pythonのsubprocess.runで長い引数を使うと、エラーが発生して実行できません。

そこで、手元のLinux環境で限界を調べてみました。

最初に引数の最大値を調べてみます。

$ getconf ARG_MAX
2097152
と表示されました。

次に、Python経由でコマンドを実行してみます。

subprocess.run(["echo", "a"*131071]) 
はエラーが発生せずに動作しました。

一方、

subprocess.run(["echo", "a"*131072])
だと、
<省略>
  File "/usr/lib/python3.9/subprocess.py", line 505, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/usr/lib/python3.9/subprocess.py", line 951, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 7] Argument list too long: 'echo'
となり、長すぎというエラーが出ました。ARG_MAXの値よりもずいぶんと短い値でエラーになります。

複数の引数ならどうなるかを試してみます。

subprocess.run(["echo"]+["a"*131071]*15)
はエラーが発生せずに動作しました。 runに渡した文字列長はリストの要素をスペース1個でつなげるとすると1966084バイトで、ARG_MAXを下回っています。

一方、

subprocess.run(["echo"]+["a"*131071]*16)
だと先程と同じエラーが発生します。 この場合の文字列の長さは2097156でARG_MAXを超えています。

というわけで、引数の長さには2つ制限があり、

  • 引数単位での文字列長の上限
  • コマンド全体での長さの上限
の2つがあることがわかりました。

2023/01/09

Saplingを試してみる

Metaで開発されているSaplingが公開されましたので、使ってみます。

  1. インストール
  2. 初期設定
  3. リポジトリの作成
  4. ファイルを追加
  5. 編集してコミット
  6. コミットログの表示
  7. headの表示
  8. 分岐の作成
  9. 分岐で次に進む
  10. ブランチのマージ
  11. マージを元に戻す
  12. rebaseにてブランチをマージ
  13. コミット間の移動
  14. 操作の取り消しと再実行
  15. コマンド一覧

今回はWindowsで試してみます。

インストール


インストールは、
https://github.com/facebook/sapling/releases/tag/0.2.20221222-152408-ha6a66d09
からダウンロードしてきたzipを展開してPATHを通すだけです。

頻繁に更新されているようなので、新規にインストールするなら最新のものを利用するのがよさそうです。

初期設定


まずは使用者名の設定をします。
$ sl config --user ui.username "yourname <yourname@mail.address.com>"
今回は、メールアドレスを省略して、
$ sl config --user ui.username "yourname"
としています。

リポジトリの作成


新しいリポジトリを作っていきます。
https://sapling-scm.com/docs/introduction/getting-started
ではGithubからリポジトリを取得するように書かれていますが、ここでは実験的な機能として入っているgitのリポジトリを1から作成する方法を使ってみます。
$ sl init --git
これは実験的な機能であるためか、sl helpでも今回試しているバージョンではコマンドが出てきません。

何もファイルをコミットしていない状態だと、logもなにも表示されないので、sl showを実行してみたところ、

$ sl show
changeset:   0000000000000000000000000000000000000000  (@)
user:
date:        木, 01 1 1970 09:00:00 +0900
といった表示がされました。

ファイルを追加


お試し用のファイルを
$ echo aaa > a.txt
のように作成します。

次にSaplingに登録します。

$ sl add a.txt
最後にコミットします。
$ sl commit
Windowsでは、デフォルトではメモ帳が開くので、コミットメッセージを書いて保存し、メモ帳を終了します。 すると、コミットが完了します。

ログを見ると、

$ sl log
changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5  (@)
user:        yourname <>
date:        木, 05 1 2023 15:31:25 +0900
summary:     Initial commit
と表示されます。

単にslを実行すると、

$ sl
@  5bbaee36d  2 minutes ago  >
   Initial commit
となります。コミット時間が相対時間で表示されるので、時刻表示のほうが良い場合はlogのほうがよさそうです。
$ sl head
changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5  (@)
user:        yourname <>
date:        木, 05 1 2023 15:31:25 +0900
summary:     Initial commit
とすると、logと同じ内容が表示されます。

編集してコミット


次に、
$ echo "bbb" >> a.txt
で、コミットしているファイルを編集してみます。diffをとると、
$ sl diff
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1,1 +1,2 @@
 aaa
+"bbb" <0D>
と表示されてbbbが追加されていることがわかります。Windowsなので、改行記号が\r\nになっており、0Dが追加で表示されているようです。

statusを表示してみると、

$ sl status
M a.txt
となり、変更されていると表示されます。

今回はsl commit -iにて、対話的なコミット画面を表示してみます。

$ sl commit -i
starting interactive selection
実行すると、
Select hunks to record - [x]=selected **=collapsed  c: confirm  q: abort  arrow keys: move/expand/collapse  space: deselect  ?: help
[x]    diff --git a/a.txt b/a.txt                                                                                                   
       1 hunks, 1 lines changed                                                                                                     

   [x]     @@ -1,1 +1,2 @@                                                                                                          
            aaa ^M                                                                                                                  
      [x]  +"bbb" ^M                                                                                                                
と表示されます(色は省略)。Mercurialのhg com -iや、git crecordと同じ操作感の画面です。部分コミットを簡単にできます。 今回は1箇所しか変更していないので、そのままcを押して、コミットします。

コミットログの表示


単にログを表示します。
$ sl log
changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa  (@)
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update

changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5
user:        yourname <>
date:        木, 05 1 2023 15:31:25 +0900
summary:     Initial commit
のようになり、履歴がわかります。

--graphにてグラフ表示もできるようです。

$ sl log --graph
@  changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa  (@)
│  user:        yourname <>
│  date:        木, 05 1 2023 15:45:49 +0900
│  summary:     Update
│
o  changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5
   user:        yourname <>
   date:        木, 05 1 2023 15:31:25 +0900
   summary:     Initial commit
単にslを実行すると、
$ sl
@  f4adbb569  100 seconds ago  >
│  Update
│
o  5bbaee36d  16 minutes ago  >
   Initial commit
と表示されます。

headの表示


headを表示してみます。分岐の先頭の一覧が表示されますが、今の時点では分岐がないので、
$ sl head
changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa  (@)
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update
のように1つだけ表示されます。

prevコマンドで最初のコミットに戻ってみます。

$ sl prev
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[5bbaee] Initial commit

$ sl head
changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update
headは変わりません。slコマンドでは
$ sl
o  f4adbb569  13 minutes ago  >
│  Update
│
@  5bbaee36d  27 minutes ago  >
   Initial commit
@の場所が変化します。

分岐の作成


最初にa.txtに文字を追加します。
$ echo "ccc" >> a.txt
を実行し、
$ sl commit
にてコミットします。
$ sl head
changeset:   1cae689a8bab379edcc7e1c63c305f78d37d608a  (@)
user:        yourname <>
date:        木, 05 1 2023 16:00:24 +0900
summary:     Add ccc

changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update
となり、headが複数できていることがわかります。この表示はMercurialと同様です。

slを実行すると、

$ sl
@  1cae689a8  67 seconds ago  >
│  Add ccc
│
│ o  f4adbb569  15 minutes ago  >
├─╯  Update
│
o  5bbaee36d  30 minutes ago  >
   Initial commit
となります。罫線の幅を半角表示にするときれいに位置がそろうのですが、CSSでうまく書けなかったため、ずれたままで。

また、─╯ は2文字ですが、コマンドプロンプトウィンドウでは、重ねて表示されており、分岐した線のように見えます。 一方、WSL Terminalで当該文字列を表示すると重ならないです。

話はそれますが、 ╯ はUnicodeで256F、UTF-8でE2 95 AFです。

分岐で次に進む


まず、今の状態でsl prevを実行すると、
$ sl
o  1cae689a8  4 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  18 minutes ago  >
├─╯  Update
│
@  5bbaee36d  33 minutes ago  >
   Initial commit
となり、最初のコミットに戻ってきます。

この位置では分岐があるのでnextを実行するとどうなるかというと、

$ sl next
commit 5bbaee36d524 has multiple children, namely:
(1) [f4adbb] Update
(2) [1cae68] Add ccc
which commit to move to [1-2/(c)ancel]?
というメッセージが出力され、どちらに進むか選ぶようになっています。

(1)側に進んでみると、

which commit to move to [1-2/(c)ancel]?  1
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[f4adbb] Update
と表示され、(1)側に移動できます。
$ sl
o  1cae689a8  6 minutes ago  >
│  Add ccc
│
│ @  f4adbb569  20 minutes ago  >
├─╯  Update
│
o  5bbaee36d  35 minutes ago  >
   Initial commit
となり、たしかに、f4adbb569 に移動できていることが確認できます。

隣の分岐に移動するには、

$ sl goto 1cae
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
でよさそうです。
$ sl
@  1cae689a8  9 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  24 minutes ago  >
├─╯  Update
│
o  5bbaee36d  38 minutes ago  >
   Initial commit
となっており、移動できていることが確認できます。

ブランチのマージ


さきほど作成したブランチをマージします。Saplingではマージよりrebaseを使うように設計されているようですが (https://sapling-scm.com/docs/introduction/のCaveatsより)、 mergeコマンドはあるのでそれでマージしてみます。
$ sl merge
を実行すると、kdiff3をインストールしているためか、kdiff3が立ち上がって、コンフリクトを解消するよう指示が出ます。

手動でマージすると、

$ sl merge
merging a.txt
running merge tool kdiff3 for file a.txt
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
と表示され、マージができました。この段階ではコミットされておらず、
$ sl
@  1cae689a8  25 minutes ago  >
│  Add ccc
│
│ @  f4adbb569  39 minutes ago  >
├─╯  Update
│
o  5bbaee36d  54 minutes ago  >
   Initial commit
のように表示されます。@が2箇所に表示されます。
$ sl commit
でコミットすると、
$ sl
@    b84d24eda  15 seconds ago  >
├─╯  Merge
│ │
o │  f4adbb569  41 minutes ago  >
│ │  Update
│ │
│ o  1cae689a8  26 minutes ago  >
├─╯  Add ccc
│
o  5bbaee36d  55 minutes ago  >
   Initial commit
と表示され、めでたくマージできたことが確認できます。

マージを元に戻す


rebaseが推奨であるようなので、一旦これを元に戻してみます。

インタラクティブにできそうなので、-i付きで実行してみましたが、

$ sl undo -i
abort: interactive ui is not supported on Windows
となり残念ながら実行できません。-iなしで試してみると、
$ sl undo
undone to 木 1 05 16:09:49 2023 +0900, before commit
hint[undo-uncommit-unamend]: undoing commits discards their changes.
to restore the changes to the working copy, run 'sl revert -r b84d24eda1a8 --all'
in the future, you can use 'sl uncommit' instead of 'sl undo' to keep changes
hint[hint-ack]: use 'sl hint --ack undo-uncommit-unamend' to silence these hints

$ sl
@  1cae689a8  30 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  44 minutes ago  >
├─╯  Update
│
o  5bbaee36d  59 minutes ago  >
   Initial commit
と表示され、マージ処理を元に戻せています。マージしたファイルを取得する方法もメッセージに書かれており、 消えていてもリビジョン(=コミットのハッシュ値)さえわかっていればrevertで戻せるようです。

試して見ると、

$ sl revert -r b84d24eda1a8 --all
reverting a.txt

$ sl diff
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
 aaa
+"bbb" <0D>
 "ccc"
となり、たしかにマージ処理したa.txtが戻ってきます。

さらにundoできるか試してみると、

$ sl undo
abort: uncommitted changes
となります。revertで戻したファイルが未コミットのファイルとなっており、この状態ではundoはできないようです。

rebaseにてブランチをマージ


先程はブランチのコミットを維持したままマージしましたが、今度はrebaseでマージしてみます。 枝を別の場所に継ぎ変えるということです。

今の状態は

$ sl
@  1cae689a8  38 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  52 minutes ago  >
├─╯  Update
│
o  5bbaee36d  67 minutes ago  >
   Initial commit
となっています。ここで
$ sl rebase f4ad
abort: you must specify a destination (-d) for the rebase

$ sl rebase -r f4ad
abort: you must specify a destination (-d) for the rebase

$ sl rebase -r 1cae
abort: you must specify a destination (-d) for the rebase

$ sl rebase
abort: you must specify a destination (-d) for the rebase
としてみると、すべてエラーとなります。

ヘルプを見ると

sl rebase [-s REV | -b REV] [-d REV] [OPTION][-i]
と表示されるので、エラーメッセージの通りrebase先を-dで指定する必要があるようです。
sl rebase -r f4adbb569 -d 1cae689a8
rebasing f4adbb569e88 "Update"
merging a.txt
abort: must use on-disk merge for this rebase (hit merge conflicts in a.txt), but you have working copy changes
(commit, revert, or shelve them)
さきほどローカルのa.txtをマージ後のものに戻していたので、エラーとなってしまっています。
$ sl revert a.txt
で元に戻します(マージ後のローカルのa.txtの内容を破棄します)。

再度rebaseしてみます。

$ sl rebase -r f4adbb569 -d 1cae689a8
rebasing f4adbb569e88 "Update"
merging a.txt
hit merge conflicts (in a.txt); switching to on-disk merge
rebasing f4adbb569e88 "Update"
merging a.txt
running merge tool kdiff3 for file a.txt
f4adbb569e88 -> f292f8d2ac45 "Update"
今回もkdiff3が立ち上がるので、手動でマージすると、rebaseが完了します。
$ sl
o  f292f8d2a  88 seconds ago  >
│  Update
│
@  1cae689a8  49 minutes ago  >
│  Add ccc
│
o  5bbaee36d  78 minutes ago  >
   Initial commit
となり、一直線になりました。 ヘルプを読むとrebaseしたコミットを適用先にコピーし、さらにrebaseしたブランチを隠すという処理が行われているようです。
$ sl head
changeset:   f292f8d2ac45f92a9737cf4d2dd38be2e1a6fb11
user:        yourname <>
date:        木, 05 1 2023 16:47:58 +0900
summary:     Update
のように、headも1つになりました。コミット履歴が読みやすいので、マージよりもコミット履歴の管理としてはこの方が良いのかもしれません。 一方で、リビジョン番号は変わってしまうので、リビジョン番号を維持しておきたい場合はmergeのほうがよいかもしれません。 rebaseでも元のコミットは隠されているだけで残ってはいますが、コピーができてしまってどれとどれが同じコミットなのかを後から追いにくくなりそうですので、リビジョン番号が重要な場合はmergeでしょうか。 状況に合わせて適切な方法を利用することになりそうです。

コミット間の移動


これまでの説明で既に使用していますが、コミット間の移動は近くのコミットであればnextとprevで移動するのが簡単です。
$ sl next
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[f292f8] Update

$ sl
@  f292f8d2a  15 minutes ago  >
│  Update
│
o  1cae689a8  62 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit

$ echo ddd >> a.txt
$ sl commit

$ sl
@  da3ea5b13  22 seconds ago  >
│  Add ddd
│
o  f292f8d2a  15 minutes ago  >
│  Update
│
o  1cae689a8  63 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit

$ sl prev
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[f292f8] Update

$ sl
o  da3ea5b13  50 seconds ago  >
│  Add ddd
│
@  f292f8d2a  16 minutes ago  >
│  Update
│
o  1cae689a8  64 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit

操作の取り消しと再実行


なんらかの制限はあるようですが、操作の取り消し(undo)と再実行(redo)も簡単にできます。

先程の操作を元に戻していきます。

$ sl undo
undone to 木 1 05 17:03:41 2023 +0900, before prev

$ sl
@  da3ea5b13  81 seconds ago  >
│  Add ddd
│
o  f292f8d2a  16 minutes ago  >
│  Update
│
o  1cae689a8  64 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
確かにprevコマンドを実行する前の状態に戻りました。さらに取り消すと、
$ sl undo
undone to 木 1 05 17:02:45 2023 +0900, before commit

$ sl
@  f292f8d2a  17 minutes ago  >
│  Update
│
o  1cae689a8  65 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
dddを追加したコミットが消えました。もう一度実行すると、
$ sl undo
undone to 木 1 05 16:48:26 2023 +0900, before next

$ sl
o  f292f8d2a  18 minutes ago  >
│  Update
│
@  1cae689a8  65 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
となり、参照している場所も戻りました。

今度はredoで一挙にundoをundoしてみます。

$ sl redo 10
undone to 木 1 05 17:05:19 2023 +0900, before undo

$ sl
@  f292f8d2a  19 minutes ago  >
│  Update
│
o  1cae689a8  67 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
再実行はできましたが、残念ながら一括ではundo操作を戻せませんでした。
$ sl redo 2
undone to 木 1 05 17:04:52 2023 +0900, before undo

$ sl
@  da3ea5b13  5 minutes ago  >
│  Add ddd
│
o  f292f8d2a  21 minutes ago  >
│  Update
│
o  1cae689a8  69 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
残りのundoした回数である2を指定しても1回再実行できるだけで引数の値には効果がないようです。再度redoすると、
$ sl redo
undone to 木 1 05 17:04:19 2023 +0900, before undo

$ sl redo
abort: nothing to redo

$ sl
o  da3ea5b13  6 minutes ago  >
│  Add ddd
│
@  f292f8d2a  21 minutes ago  >
│  Update
│
o  1cae689a8  69 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
となり、undoする前に戻ってくることはできました。
You can run 'sl redo' multiple times to undo a series of 'sl undo' commands. Alternatively, you can explicitly specify the number of 'sl undo' commands to undo by providing a number as a positional argument.
とヘルプには書いてありましたが、できないようです。

コマンド一覧


slコマンドのヘルプ表示では表示されないコマンドを含めたコマンド一覧が
https://sapling-scm.com/docs/category/commands/
に載っています。

2022/12/10

目玉を動かさずに視線を変える

ショウジョウバエ(Drosophila)は複眼の網膜を動かすことで、体や頭を動かさずに見るところを変えられるらしい。

Fenk, L.M., Avritzer, S.C., Weisman, J.L. et al. Muscles that move the retina augment compound eye vision in Drosophila. Nature 612, 116–122 (2022).
https://www.natureasia.com/ja-jp/nature/highlights/118350

網膜を動かす筋肉自体は1970年代に発見されていたが、今回、動きの追跡や固視微動を行っていることがわかったとのこと。

目玉は動かせないけどちょっとだけにしても視線を変えられるとは。

2022/08/19

ルートにマウントする暗号化LVM領域の拡張

目的


Debian 10や11をインストールするとき、「ディスク全体を使い、暗号化LVMをセットアップする」ことができます。 もし、このディスクがいっぱいになってしまうと、容量を増やしたくなります。

ここでは、ディスクを追加し、暗号化したうえで、ルートにマウントしたディスクの容量を増やしてみます。

失敗するとOSの起動ができなくなるので、元に戻せるようバックアップをとってから実行しましょう。実施する場合は自己責任で!

方法


構成


Physical volumeをPV1、Volume groupをVG1、Logical volumeが2つでLV-rootとLV-swapとし、構成が
[PV1]─→[VG1]┬→[LV-root]
              └→[LV-swap]
であるとき、これを
[PV1]┬→[VG1]┬→[LV-root]
[PV2]┘       └→[LV-swap]
のように変更し、増えた容量をLV-rootに割り当てます。 PV2が新しく追加したディスクをLVMのPhysical volumeにしたものになります。

ディスクの暗号化と起動時の暗号化解除のための手順


以下、root権限で実行します。

最初に追加したディスクを暗号化します。追加したディスクは/dev/sdcであるとします。

$ cryptsetup --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat /dev/sdc
実行するとパスフレーズの入力を要求されるので、設定したいパスフレーズを入力します。

なお、暗号化アルゴリズムとキーサイズの設定は参考文献のままですので、必要に応じて変更します。

さきほど設定したパスフレーズが正しく入力できたか確認するために、次のコマンドを実行します。

$ cryptsetup luksOpen /dev/sdc sdc_crypt
成功したら次のステップに進みます。
(省略してもよいかもしれませんが、もし暗号化を解除できない状態でこれ以降の作業を進めていくと今動かしているOSが起動しなくなります)

次に、暗号化ディスクのUUIDを確認します。

cryptsetup luksDump /dev/sdc
を実行すると、暗号化ディスクの情報が表示されます。
UUID:          	xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
のxxx部分がUUIDですので、この値を$(UUID)と書くことにします。

UUIDがわかったので、/etc/crypttabに次の一行を追加します。

sdc_crypt UUID=$(UUID) none luks,discard,initramfs
行の最後にinitramfsと書いておくのがポイントです。

編集を終えたら、

$ update-initramfs -u
を実行して、OSの起動時の処理内容を更新します。 これにより、追加したディスクのパスフレーズによる暗号化の解除処理をOSの起動時に実施できるようになります。

リブートして、起動時に暗号化を解除できるか確認します。

リブートすると、これまで通り、Debianインストール時に暗号化したディスクに対するパスフレーズの入力を求められ、 続いて、今回追加したディスクのパスフレーズの入力を求められます。

もし、パスフレーズの入力回数が増えていない(=今回追加したディスクのパスフレーズの入力が求められない)状態の場合、 なにか失敗しているので、これ以降のLVMの変更手順は実行してはいけません。OSが起動しなくなります。

今回追加したディスクのパスフレーズの入力を求められ、暗号化を解除したうえでOSが起動すると、 /dev/mapperにsdc_cryptが加わっているはずです。

LVMの構成変更


追加したディスクを暗号化し、それを解除できるようになったので、次はルートにマウントしているディスクの容量を増やします。

まず、Physical volumeにsdc_cryptを追加します。

pvcreate /dev/mapper/sdc_crypt
追加したPhysical volumeを既存のVolume group (ここではxxx-vg)に追加します。
$ vgextend xxx-vg /dev/mapper/sdc_crypt
追加できたか確認します。
$ vgdisplay -v
の最後のほうに、Volume groupを構成するPhysical volumeが表示されますので、それで確認ができます。

さらに、Total PE / Free PEの行を見ると、Free PEの値がわかるので、この値を覚えておきます。 ここではこの値を$(Free)とします。

Logical volumeのうち、rootのvolumeの容量を増やします。

$ lvextend -l +$(Free) /dev/xxx-vg/root
もし、$(Free)が1000なら
$ lvextend -l +1000 /dev/xxx-vg/root
とします。

最後に、ファイルシステムの容量を増やします。ext2またはext3、ext4であれば、

$ resize2fs /dev/xxx-vg/root
を実行することで、ルートのディスク容量が追加したディスクの容量分だけ増えます。 他のファイルシステムを使っている場合は、適切なコマンドを実行して容量を増やします。

これで容量が増えているはずなので、dfなどでディスク容量が増えていることを確認します。

容量が予定通り増えていれば、最後にリブートして、OSが問題なく起動するか確認します。

正常に起動すれば、作業は完了です。

参考文献


https://qiita.com/ynakayama/items/74096212b8177234be8a
https://blog.id774.net/entry/2014/05/13/481/

2022/06/05

Debian11の端末でのTERMとemacs

目的


Debian11をクリーンインストールした状態で、端末(gnome-terminal)の背景を黒にすると、emacsの文字が読みにくい状態になるので、視認性を上げてみます。

3つ方法を挙げますが、方法3が最も良い方法でしょう。

初期の状態


何もしていない手元のDebian11環境では、環境変数TERMが
TERM=xterm-256color
となっていました。

方法1


一時的にどうにかしたい場合の方法です。環境変数TERMに
TERM=screen-256color
を設定するだけです。この方法の欠点は、一部がイタリック体になってしまうことです。 また、lessにも影響があり、文字列検索時の強調表示がイタリック体になってしまいます。

方法2


次のコマンドを実行します。
$ mkdir -p ~/.terminfo/s/
$ cp /usr/lib/terminfo/x/xterm-256color ~/.terminfo/s/screen-256color
そして
TERM=screen-256color
を設定します。こうすると、設定内容はxterm-256colorのままで、名前だけscreen-256colorに変更したものが参照されるようになります。

emacsがTERMの名前でカラーテーマを決めているようで、端末の全体的な設定はもとのまま、emacsの色だけTERM=screen-256colorと同じにできます。

方法3


素直にemacsの設定を変更します。TERMの名前に依存してカラーテーマが決まらないように変更するだけです。

https://unix.stackexchange.com/questions/53467/emacs-colors-based-on-term-environment-variable によると、emacsを起動している状態で、

M-x customize-variable [ENTER]
frame-background-mode [ENTER]
を実行します。次のような画面が表示されるので、
For help using this buffer, see [Easy Customization] in the [Emacs manual].

                                         [ Search ]

Operate on all settings in this buffer:
[ Revert... ] [ Apply ] [ Apply and Save ]

Hide Frame Background Mode: [Value Menu] automatic
   [ State ]: SAVED and set.
   The brightness of the background. Hide
   Set this to the symbol ‘dark’ if your background color is dark,                                                    
   ‘light’ if your background is light, or nil (automatic by default)                                                 
   if you want Emacs to examine the brightness for you.                                                                 
                                                                                                                        
   If you change this without using customize, you should use                                                           
   ‘frame-set-background-mode’ to update existing frames;                                                             
   e.g. (mapc 'frame-set-background-mode (frame-list)).
Groups: [Faces]
[Value Menu]のところにカーソル(キャレット?)を合わせて、[ENTER]を押すと、
Available choices:

0 = dark
1 = light
2 = automatic
のように選択肢が表示されます。今回は背景が黒であるという前提ですので、0を押します。 その後、[ Apply and Save ] にカーソルを合わせて[ENTER]を押せば、設定が保存されます。

不思議なこと


infocmpでxterm-256colorの内容を見てみると、イタリック体にするためのエスケープシーケンスが
$ infocmp xterm-256color|egrep "sitm|ritm"
	rev=\E[7m, ri=\EM, rin=\E[%p1%dT, ritm=\E[23m, rmacs=\E(B,
	sgr0=\E(B\E[m, sitm=\E[3m, smacs=\E(0, smam=\E[?7h,
のようにsitmとritmに設定されているにも関わらず、対応するところでは文字の色が反転してしまいます。一方、screen-256colorでは
$ infocmp screen-256color|egrep "sitm|ritm"
のように項目が設定されていません。このscreen-256colorをTERMに使うとなぜかイタリック体になってしまいます。

さらに、https://gist.github.com/riobard/9166725を参考に、次の内容をscreen-256color-noitalic.terminfoとして保存し(ファイル名は任意)、

screen-256color-noitalic|screen with 256 colors with no italic,
	sitm=\E[7m, ritm=\E[27m,
	use=screen-256color,
次のコマンド
$ tic screen-256color-noitalic.terminfo
を実行すると、
~/.terminfo/s/screen-256color-noitalic
というファイルができ、TERMに設定できるようになるので、
TERM=screen-256color-noitalic emacs nanika-file
で実行してみても、上部に表示されるemacsのメニューの文字列がイタリック体のまま変化しません。lessでも同じです。
$ infocmp screen-256color screen-256color-noitalic
comparing screen-256color to screen-256color-noitalic.
    comparing booleans.
    comparing numbers.
    comparing strings.
	ritm: NULL, '\E[27m'.
	sitm: NULL, '\E[7m'.
のように差があるにも関わらずです。不思議ですね。どこかで何か設定があり、優先されているのでしょう。

なお、\E[3mがイタリックの開始、\E[23mがイタリックの終了、\E[7mが色反転の開始、\E[27mが色反転の終了です。 例えば、

echo -e "\E[3mitalic\E[23m \E[7mrev\E[27m"
を端末で実行すると、イタリック体や色反転で表示されることが確認できます。

2022/05/29

Self-attentionの計算方法と行列の形状

いまさらではあるものの、Self-attention (自己注意)の計算方法について途中の行列の形状に着目して調べてみました。

結果だけ知りたい方は、最後のまとめに進んでください。

参考文献


元論文はAttention Is All You Needです。

論文の行間を読むか参考文献を遡っていけば、具体的にどういう計算をするのか分かるのかもしれませんが、 論文の最後に、コードへのURL https://github.com/tensorflow/tensor2tensor が記載されていますので、こちらを主に参考にして、計算方法を調べていきます。

Q, K, Vはどうやって計算するの?


論文の式(1)で使われているQ=Query、K=Key、V=Valueの3つの値の計算方法を調べてみます。

論文のFig.1を見ると、入力が3つに分岐しているので、何かをどうにかして入力をQ, K, Vの3つにしていることはわかります。

まず、transformer_encode関数を見てみます。コメントによると、入力であるinputsの形状は(batch_size, input_length, 1, hidden_dim)とのことです。

関数の呼び出し直後に形状を変換していて、結局、inputsは(batch_size, input_length, hidden_dim)になっています。

その後、encoder_functionが呼び出されるのですが、これの中身は、 transformer_encoder です。この関数内の213行目から、common_attention.multihead_attentionを呼び出します。

キャッシュがなく、self-attentionの場合であれば、4650行目にて、compute_qkvが呼び出されます。 ここで、Q, K, Vが計算されているようです。

定義は

def compute_qkv(query_antecedent, ←これがcommon_kayers.layer_preprocess(x, hparams)
                memory_antecedent, ←これがNone
                total_key_depth,
                total_value_depth,
                ...
となっていて、memory_antecedentがNoneであることは、呼び出し元のtransformer_encoder:213に戻るとわかります。

memory_antecedentがNoneならquery_antecedentにしているので、self-attentionの場合、入力はquery_antecedentと考えればよさそうです。

さて、Q, K, V (コード中ではq, k, v)はcompute_attention_componentで計算されていて、入力は

antecedent: a Tensor with shape [batch, length, channels]
戻り値が
c : [batch, length, depth] tensor
となっています。filter_widthによって処理内容が異なるようですが、filter_width == 1のケースを見てみると、 4415行目
    return common_layers.dense(
        antecedent, total_depth, use_bias=False, name=name,
        layer_collection=layer_collection)
のように書かれています。バイアスなしなので、単にMatMulの計算をしているだけになります。

Tensorflowのドキュメントによると、

Dense implements the operation: output = activation(dot(input, kernel) + bias) <中略> kernel is a weights matrix created by the layer,
なので、
depth channels depth length [Output] = length [antecedent] × channels [W]
となります。ここで、[ ]は行列を表しています。[ ]の左が行数、[ ]の上が列数です。

以上を考慮してcompute_qkvを読むと、q, k, vの形状は

q = (batch, length_q, total_key_depth)
k = (batch, length_kv, total_key_depth)
v = (batch, length_kv, total_value_depth)
となっていることがわかります。

Self-attentionの計算はどうなるの?


multihead_attentionの引数に指定するattention_typeには色々種類があるようですが、デフォルト指定されているdot_productを見てみます。

dot_product_attentionの引数の説明には、

  Args:
    q: Tensor with shape [..., length_q, depth_k].
    k: Tensor with shape [..., length_kv, depth_k]. Leading dimensions must
      match with q.
    v: Tensor with shape [..., length_kv, depth_v] Leading dimensions must
      match with q.
と書かれています。

まず、1648行目で、

logits = tf.matmul(q, k, transpose_b=True)
と計算しています。論文中の式(1)の\(QK^T\)の部分です。

length_kv depth_k length_kv length_q [logits] = length_q [q] × depth_k [\({\rm k}^T\)]
のように計算されるので、logitsの形状は (..., length_q, length_kv) となります。

式(1)の\(\sqrt{d_k}\)で割る部分が見当たりませんが、どこかで計算されているとして、次にsoftmaxの計算をみてみます。これは、1654行目

weights = tf.nn.softmax(logits, name="attention_weights")
で計算されています。weightsの形状はlogitsと同じです。

ドロップアウトの処理をした後、1667行目

return tf.matmul(weights, v)
で、式(1)の計算が完了します。これは、
depth_v length_kv depth_v length_q [Attention] = length_q [weights] × length_kv [v]
を計算していますので、この関数の戻り値の形状は(..., length_q, depth_v)となります。

dot_product_attentionのコメント部分にも

 Returns:
    Tensor with shape [..., length_q, depth_v].
と書いてあります。

以上の計算の途中で得られる[weights]の形状が(..., length_q, length_kv)となっており、 self-attentionの場合はlength_q=length_kvですので、系列長の2乗で必要になるメモリや計算量が増えていくことになります。

まとめ


コードを調べたことで、論文の式(1)の各行列の形状は、

\(Q \in \mathbb{R}^{L_{\rm q} \times d_{\rm k}} \)
\(K \in \mathbb{R}^{L_{\rm kv} \times d_{\rm k}} \)
\(V \in \mathbb{R}^{L_{\rm kv} \times d_{\rm v}} \)
\(QK^T \in \mathbb{R}^{L_{\rm q} \times L_{\rm kv}} \)
\((QK^T)V \in \mathbb{R}^{L_{\rm q} \times d_{\rm v}} \)

であることが明確になりました。ここで、\(L_{\rm q}\)は\(Q\)の系列長、\(L_{\rm kv}\)は\(K\)と\(V\)の系列長です。 \(d_{\rm k}\)と\(d_{\rm v}\)は論文と同じです。

さらに、self-attentionの場合、\(L_{\rm q} = L_{\rm kv} \)ですので、単に\(L\)とすれば、

\(Q \in \mathbb{R}^{L \times d_{\rm k}} \)
\(K \in \mathbb{R}^{L \times d_{\rm k}} \)
\(V \in \mathbb{R}^{L \times d_{\rm v}} \)
\(QK^T \in \mathbb{R}^{L \times L} \)
\((QK^T)V \in \mathbb{R}^{L \times d_{\rm v}} \)

のように\(L\)の添字をなくせるのですっきりします。

\(Q\)と\(K\)と\(V\)は、入力\(X \in \mathbb{R}^{L \times C}\)をバイアスなしのDenseレイヤーに通すことで得ることができます。 つまり、Denseレイヤーの重み行列をそれぞれ \(W_{\rm q} \in \mathbb{R}^{C \times d_{\rm k}} \)、 \(W_{\rm k} \in \mathbb{R}^{C \times d_{\rm k}} \)、 \(W_{\rm v} \in \mathbb{R}^{C \times d_{\rm v}} \)とすると、

\(Q = X W_{\rm q}\)
\(K = X W_{\rm k}\)
\(V = X W_{\rm v}\)

となります。ここで、\(C\)は、\(X=\{x_1, x_2, ... ,x_i ,... x_L\}\)としたときの特徴ベクトル\(x_i\)の次元数です。

2022/05/21

Raspberry Pi OSをdocker内で動かす

Intel CPUの64bit環境下で、Dockerを利用してRaspberry Piの32bit OS環境を動かしてみます。

Dockerの準備


Debian11環境で作業します。

Dockerがインストールされていなければ、例えば https://docs.docker.com/engine/install/debian/ を参考にDockerをインストールします。

イメージの準備


最初に、https://downloads.raspberrypi.org/から必要なRaspberry Pi OSのroot.tar.xzをダウンロードします。

Raspberry Pi OS Lite (32-bit)のバージョンbullseyeであれば、 https://downloads.raspberrypi.org/raspios_lite_armhf/archive/2022-04-07-11:57/ にあります。

Dockerのイメージへの変換


次に、ダウンロードしたroot.tar.xzをDockerのイメージへと変換します。例えば、
$ docker image import ./root.tar.xz pi11-32bit:2022-04-07
とします。インポートできたかは、
$ docker images
REPOSITORY    TAG          IMAGE ID       CREATED          SIZE
pi11-32bit    2022-04-07   d2b599f3a584   23 seconds ago   1.16GB
のようにして確認できます。

この時点では、まだ実行できません。

$ docker run -it pi11-32bit:2022-04-07 bash
exec /usr/bin/bash: exec format error

(以下、方法2を実施し、その後、リブートした後に方法1を実行しましたが、もしかすると方法2の影響が残っている可能性があります)

方法1


パッケージqemu-user-staticをホストPCにインストールするだけです。 インストールすると、
$ docker run -it --rm pi11-32bit:2022-04-07 uname -m
armv7l
のようにエラーが起きずに実行できるようになります。

方法2


$ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
を実行します。イメージがダウンロードされて、その後、実行されます。

実行が完了すると、ホストPC側に/proc/sys/fs/binfmt_misc/qemu-*が作成され、

$ docker run -it pi11-32bit:2022-04-07 bash -c ls
bin  boot  dev	etc  home  lib	lost+found  media  mnt	opt  proc  root  run  sbin  srv  sys  tmp  usr	var
のように動くようになります。

なお、ホストPCをリブートすると、ホストPCで

$ ls /proc/sys/fs/binfmt_misc/
python3.9  register  status
となり、qemu-で始まるファイルがなくなります。また、
$ docker run -it pi11-32bit:2022-04-07 bash
exec /usr/bin/bash: exec format error
となり、実行できない状態になります。

方法2の仕組み


multiarch/qemu-user-staticコンテナ内で、ホストのbinfmt_miscとqemu-user-staticを設定することで動作させているようです。

まず、 https://github.com/multiarch/qemu-user-static/blob/master/containers/latest/register.sh を見ると

mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
と書かれており、カーネルモジュール binfmt_misc をマウントしています(lsmodを実行するとbinfmt_miscが存在することが確認できます)。

少なくともここでホストのルート権限が必要になり、--previlegedをつけることになります。

また、register.shの最後に呼び出されているファイルはおそらく https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh で、この中のqemu_register_interpreter()にて

qemu_generate_register > /proc/sys/fs/binfmt_misc/register
という記述があることから、最終的にbinfmt_miscに対して自動的にqemuを動作させるよう登録をしています。

参考


https://www.koatech.info/blog/raspbian-on-docker/
https://qiita.com/autch/items/c8c9cdc7b8e5821e81a4
https://github.com/multiarch/qemu-user-static
https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
https://wiki.bit-hive.com/north/pg/binfmt_misc
https://qiita.com/yuyakato/items/5dd06fb179922206044d

2022/02/20

gir

gir は GObjectIntrospection Repository の略とのこと。

DebianやUbuntuのパッケージの中に先頭に gir1.2- が付くパッケージが多数あり、このgirとは何だろうということで調べてみたところ、
https://mail.gnome.org/archives/gnome-shell-list/2011-April/msg00101.html
に説明を見つけました。

Introspectionの説明は
https://developer.gnome.org/documentation/guidelines/programming/introspection.html
にあります。C言語のコードからAPI情報を抽出して、C言語以外の言語から呼び出せるようにする仕組みのようです。

2022/02/15

Firefox 97でペンを使う

Windows10環境でFirefox 97にアップデートしてペンでタブを移動させられなくなってしまった場合は、about:configから

dom.w3c_pointer_events.dispatch_by_pointer_messages
をfalseに設定しましょう。これで、ペンでタブを移動させられるようになります。

タッチ操作でできる画面のスクロールがペンではできなくなりますが、マウスと同様にテキストを選択できるようになるので、マウスの代わりに利用したい方にはこちらのほうが都合が良いでしょう(そもそも以前はこちらがデフォルトの動作でした)。

2022/02/08

WiMAX+5Gを4Gのみで使う

1行まとめ


WiMAX+5GをSpeed Wi-Fi 5G X11で利用されていて、頻発する4Gと5Gの切り替えに難儀されている方は、回線がまともになるまでは「エコ」モードを使用しましょう。

発生している現象


WiMAX+5Gを利用してみました。速くていいですね。

しかしながら、利用場所によっては落とし穴があります。 中途半端に5Gが受信できる場所では、5Gと4Gが頻繁に入れ替わる現象が発生します。

これが理由なのかどうか明確にはわからないのですが、通信が引っかかる現象が頻発します。 ブラウザで検索したりどこかのウェブページを表示したり、ゲームで通信が発生したときなど、 全く反応がなく、結局接続できず失敗することが起きます。おそらくタイムアウトしているのでしょう。

不思議なことに、その現象が発生しているときに別の端末で別のウェブページを開くことは普通にでき、 pingにも影響がみられませんでした。

想像ではありますが、通信そのものは常にできているけれど、ときおりパケットが行方不明になってしまっていて、 行方不明になったパケットに依存している処理がずっと止まっているのかもしれません。

切り替え周期を測定してみたところ、おおむね25秒〜35秒で4Gから5Gへ、または、5Gから4Gへの切り替えが発生していました。 だいたい1分でもとに戻ってくるということですね。

アンテナが2本しか立たないところで使ってみると、4Gのみになり、切り替えは発生しないものの、 ダウンロードはともかくアップロード速度が100kbps未満というほぼ使えない状態になりました。

他に事例は?


こちらの方 https://hudetoro.com/life/2019/ が同じような現象にみまわれているようです。記事を読む限り、通信を提供している側ですぐになんらかの対処が実施されることはなさそうです。

対処方法


通信が切れる問題の原因が仮に4Gと5Gとの切り替えにあるとすると、切り替えが頻発しないようにすれば安定すると思われます。 そもそも5Gを利用しなければ問題ないだろうということです。

調べたところAndroidであれば5Gを使わない設定ができるようです。WiMAX+5Gの端末はAndroidではないので同じ方法は使えそうにありません。

そこで、X11の取扱説明書を読んでみたところ、なんとp.64に

省電力モードが「エコ」モードを選択した場合、WAN 側回線「5G」は無効となります。
と記載がありました(最初の「が」は「で」の誤植っぽいですが、原文ママです)。

というわけですので、通信が切れて困っている方、X11であれば「エコ」モードにすればもしかしたら問題がましになるかもしれません。

2021/12/18

zmqの通信の認証と暗号化

目的


ZMQで認証&通信暗号化をしてみます。言語はPythonを使います。

コード


REQ-REPで試してみます。Public keyとSecret keyを作成するコード、サーバーのコード、クライアントのコードが必要になります。

キー作成


次のgen_keys.pyを作成し、pythonで実行します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os
import zmq
import zmq.auth

def main():
    os.makedirs("cert", exist_ok=True)
    server_public_file, server_secret_file = zmq.auth.create_certificates("cert", "server")
    client_public_file, client_secret_file = zmq.auth.create_certificates("cert", "client")

if __name__ == '__main__':
    main()
すると、certディレクトリに
client.key
client.key_secret
server.key
server.key_secret
の4つのファイルが作成されます。

サーバー


サーバーのコードは以下の通り。サーバー側に認証機能をつけます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import zmq
import zmq.auth
from zmq.auth.thread import ThreadAuthenticator

def main():
    ctx = zmq.Context.instance()
    auth = ThreadAuthenticator(ctx)
    auth.start()
    auth.configure_curve(location="cert") # Need only public keys
    server = ctx.socket(zmq.REP)
    server.curve_publickey, server.curve_secretkey = zmq.auth.load_certificate("cert/server.key_secret")
    server.curve_server = True
    server.bind('tcp://*:9000')

    while True:
        msg = server.recv_pyobj()
        print("server:", msg)
        server.send_pyobj("123")

if __name__ == '__main__':
    main()
無限ループをCtrl+Cで止める前提なので入れていませんが、普通はauth.stop()で認証用スレッドを終了しましょう。

クライアント


クライアントのコードは以下の通り。サーバーのpublic keyが必要です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import zmq
import zmq.auth

def main():
    ctx = zmq.Context.instance()
    client = ctx.socket(zmq.REQ)
    client.curve_publickey, client.curve_secretkey = zmq.auth.load_certificate("cert/client.key_secret")
    client.curve_serverkey, _ = zmq.auth.load_certificate("cert/server.key")
    client.connect('tcp://127.0.0.1:9000')

    client.send_pyobj("abc")
    msg = client.recv_pyobj()
    print("client:", msg)

if __name__ == '__main__':
    main()

サーバーを実行し、その後、クライアントを実行すると、サーバー側に

server: abc
クライアント側に
client: 123
と表示されます。

クライアント側で指定するサーバーのPublic keyを間違えるとサーバーには接続できません。 また、Wiresharkで通信内容を見た限りでは、abcや123は平文では通信されていませんでした。 一方、認証関連のコード(authやcurve関連)を削除して実行すると、abcや123を平文で読むことができました。

参考


https://github.com/zeromq/pyzmq/blob/main/examples/security/generate_certificates.py
https://github.com/zeromq/pyzmq/blob/main/examples/security/ironhouse.py

2021/12/11

bashでN進数表記

整数計算に限りますが、bashで$(( ))による計算をするときに# (ASCIIコードで0x23、シャープ、井桁、番号記号、ナンバーサイン、ハッシュ、どう呼べばよいのか?)を使うとN進数で記載できます。計算結果の表示は10進数になります。

2進数

$ echo $((2#1110))
14

3進数

$ echo $((3#201))
19

4進数

$ echo $((4#123))
27

5進数

$ echo $((5#234))
69

8進数

$ echo $((8#17))
15

10進数

$ echo $((10#17))
17

16進数

$ echo $((16#fe))
254

20進数

$ echo $((20#ji))
398

40進数

$ echo $((40#D))
39

50進数

$ echo $((50#N))
49

60進数

$ echo $((60#X))
59

64進数

$ echo $((64#Z))
61
$ echo $((64#@))
62
$ echo $((64#_))
63

基底の最大値は64で、それより値が大きいと

$ echo $((65#1))
bash: 65#1: 無効な基底の数値です (エラーのあるトークンは "65#1")
とエラーになります。

2021/12/07

OzoneのデータにC++でアクセス

Apache Ozone上にあるデータにC++でアクセスしてみます。Ozoneの準備は、「Ozoneを試す」を参照ください。

ライブラリのビルド


https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/setup-linux.htmlを参考にビルドしていきます。

まず、ダウンロードします。

$ git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp.git
この日の時点では、1.4GBありました。大きいですね。

ビルドにはUbuntuでは libcurl4-openssl-dev, libssl-dev, uuid-dev, zlib1g-dev をインストールしておく必要があるようです。

ビルドします。

$ cd aws-sdk-cpp
$ mkdir sdk_build
$ cd sdk_build
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../sdk -DBUILD_ONLY="s3" ..
$ make
$ make install

Ozoneにアクセス


https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/build-cmake.htmlなどを参考に、ビルドしたライブラリを使ってOzoneにアクセスしてみます。

すべてのBucketのすべてのObjectを読み込んで標準出力に出力するソースコードmain.cppは次の通りです。 なお、テキストデータの表示のためヌル終端していますが、単に読み込むだけであれば不要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <aws/core/Aws.h>
#include <aws/core/utils/logging/LogLevel.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/ListObjectsRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <iostream>

class Initializer{
public:
  Initializer(){
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
    Aws::InitAPI(options);
  }
  ~Initializer(){
    Aws::ShutdownAPI(options);
  }
private:
  Aws::SDKOptions options;
};

int main(void){
  Initializer initializer;
  Aws::Client::ClientConfiguration config;
  config.endpointOverride = "http://localhost:9878";

  // client cannot access objects if useVirtualAddressing is true
  Aws::S3::S3Client client(config, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, false/*useVirtualAddressing*/);
  Aws::S3::Model::ListBucketsOutcome buckets = client.ListBuckets();
  if(!buckets.IsSuccess()){
    std::cout << "Fail to get buckets" << std::endl;
    return 1;
  }
  Aws::S3::Model::ListBucketsResult listBucketsResult = buckets.GetResult();
  for(const Aws::S3::Model::Bucket &bucket : listBucketsResult.GetBuckets()){
    std::cout << "Bucket name: " << bucket.GetName() << std::endl;

    // Get objects
    Aws::S3::Model::ListObjectsRequest request;
    request.WithBucket(bucket.GetName());
    Aws::S3::Model::ListObjectsOutcome listObjectsOutcome = client.ListObjects(request);
    if(!listObjectsOutcome.IsSuccess()){
      std::cout << "Fail to get objects" << std::endl;
      continue;
    }
    Aws::S3::Model::ListObjectsResult listObjectsResult = listObjectsOutcome.GetResult();
    std::cout << "Bucket name: " << listObjectsResult.GetName() << std::endl;
    std::cout << "Max keys: " << listObjectsResult.GetMaxKeys() << std::endl;
    const Aws::Vector<Aws::S3::Model::Object> &objects = listObjectsResult.GetContents();
    std::cout << "# of objects: " << objects.size() << std::endl;
    for(const Aws::S3::Model::Object& object : objects){
      // Get the attributes of the object
      std::cout << "Object key: " << object.GetKey() << std::endl;
      long long size = object.GetSize();
      std::cout << "Object value size: " << size << std::endl;

      // Get the value of the object
      Aws::S3::Model::GetObjectRequest objectRequest;
      objectRequest.SetBucket(listObjectsResult.GetName());
      objectRequest.SetKey(object.GetKey());
      Aws::S3::Model::GetObjectOutcome getObjectOutcome = client.GetObject(objectRequest);
      if(!getObjectOutcome.IsSuccess()){
        std::cout << "Fail to get content" << std::endl;
        continue;
      }
      Aws::S3::Model::GetObjectResult getObjectResult = getObjectOutcome.GetResultWithOwnership();
      long long contentLength = getObjectResult.GetContentLength();
      std::cout << "Object value size: " << contentLength << std::endl;
      Aws::IOStream& stream = getObjectResult.GetBody();
      std::vector<char> buff(contentLength+1); // +1 for null terminator
      stream.read(&buff[0], buff.size());
      std::replace(buff.begin(), buff.end(), '\n', '!');
      std::cout << "Object value: " << &buff[0] << std::endl;
    }
  }
  return 0;
}
CMakeLists.txtは次の通りです。
cmake_minimum_required(VERSION 3.3)
set(CMAKE_CXX_STANDARD 11)
project(s3)

add_executable(s3 main.cpp)
target_include_directories(s3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../aws-sdk-cpp/sdk/include)
target_link_libraries(s3 ${CMAKE_CURRENT_SOURCE_DIR}/../aws-sdk-cpp/sdk/lib/libaws-cpp-sdk-s3.so
  ${CMAKE_CURRENT_SOURCE_DIR}/../aws-sdk-cpp/sdk/lib/libaws-cpp-sdk-core.so)
最初に準備したaws-sdk-cppが、このファイルのあるディレクトリの親ディレクトリ内にあることを前提としています。
┬ aws-sdk-cpp
└ example
   ├CMakeLists.txt
   └main.cpp

今、exampleディレクトリにいるとして、

$ mkdir build
$ cd build
$ cmake ..
$ make
でビルドし、作成されるs3コマンドを実行すると、
$ ./s3
Bucket name: bucket1
Bucket name: bucket1
Max keys: 1000
# of objects: 1
Object key: test.txt
Object value size: 10
Object value size: 10
Object value: test test!
と表示されます。正しく読み込めました。

参考


https://qiita.com/kai_kou/items/095e409539fbe77f0d59

2021/12/04

Ozoneを試す

Apache Ozoneを試してみます。Dockerを事前に使えるようにしておいてください。

準備


Ozoneの立ち上げは https://ozone.apache.org/docs/1.1.0/start/startfromdockerhub.html に従って進めます。

といっても、

$ docker run -p 9878:9878 -p 9876:9876 apache/ozone
を実行するだけです。

操作にはawsコマンドを使用しますので、Ubuntuの場合であれば、

$ apt install awscli
インストールします。また、設定ファイルを作成する必要があるため、例えば、
$ aws configure
AWS Access Key ID [None]: default
AWS Secret Access Key [None]: default
Default region name [None]: default
Default output format [None]: text
のようにします。今dockerで立ち上げたOzoneは ozone.security.enabled=false となっているため、適当な設定でもアクセスできるようです。今は試したいだけなので、このままで進めます。

バケット作成


最初にデータを保管するためのバケットを作る必要があります。バケットの説明は https://www.ipswitch.com/jp/blog/understanding-how-aws-s3-buckets-work が参考になりました。

以下のコマンドでbucket1という名前のバケットを作成できます。

$ aws s3api --endpoint http://localhost:9878/ create-bucket --bucket=bucket1

オブジェクトの格納


データをバケットに格納します。まずコピーするファイル test.txt を作ります。
$ echo "test test" > test.txt
次に、このファイルをバケット内にコピーします。
$ aws s3 --endpoint http://localhost:9878 cp test.txt  s3://bucket1/test.txt
upload failed: ./test.txt to s3://bucket1/test.txt An error occurred (500) when calling the PutObject operation (reached max retries: 4): Internal Server Error
すると、エラーが発生します。今立ち上げているOzoneがシングルコンテナであるため、エラーが発生するようです。そこで、
$ aws s3 --endpoint http://localhost:9878 cp --storage-class REDUCED_REDUNDANCY test.txt  s3://bucket1/test.txt
upload: ./test.txt to s3://bucket1/test.txt
とすると成功します。
$ aws s3 --endpoint http://localhost:9878 ls s3://bucket1/test.txt
2021-12-04 22:20:53         10 test.txt
でファイルが存在することが確認できました。また、
$ aws s3 --endpoint http://localhost:9878 cp s3://bucket1/test.txt -
test test
でファイルの中身を表示できます。出力ファイル名を-にすることで標準出力に流してくれるようです。 これで正しくコピーできていることが確認できました。

Pythonから利用


boto3というライブラリを用いるとPythonからアクセスできます。
$ pip3 install boto3
でインストールします。

次のコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import boto3

s3 = boto3.resource('s3', endpoint_url="http://localhost:9878")
for b in s3.buckets.all():
    print(" 1:", b)
    print(" 2:", b.name)
    for obj in b.objects.all():
        print(" 3:", obj.key)
        print(" 4:", obj.size)
        print(" 5:", obj.get()["Body"])
        print(" 6:", obj.get()["Body"].read())
        body = obj.get()["Body"]
        print(" 7:", body.read().decode("utf-8"))
        print(" 8:", body.read())
    print(" 9:", b.Object("test.txt").get()["Body"].read())
print("10:", s3.Object("bucket1", "test.txt").get()["Body"].read())
をpython3で実行すると、
 1: s3.Bucket(name='bucket1')
 2: bucket1
 3: test.txt
 4: 10
 5: <botocore.response.StreamingBody object at 0x7f9ae2a71d00>
 6: b'test test\n'
 7: test test

 8: b''
 9: b'test test\n'
10: b'test test\n'
という結果が出力されます。キーをリストアップしてオブジェクトの中身を表示することも、キーを指定してオブジェクトの中身を表示することもできます。 なお、取得したbodyに対して2回readすると、1回目で読み終わっているため、2回目は空が返ってきます。

参考


https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html
https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html#botocore.response.StreamingBody
https://medium.com/towards-data-engineering/get-keys-inside-an-s3-bucket-at-the-subfolder-level-7be42d858372