OmniAICreator/Qiita-1.07M huggingface dataset:
Xserverでpythonを使う

# はじめに

Xserverでpythonを使って画像処理をしたい！と思ったので、そのメモ。

# 前提

- xserverにssh接続できる。[できない人は前書いたのでこちらから](https://qiita.com/yuu_1st/items/95865adfbe5ddb3eeeb0)
- ローカル環境だと実行できる状態までの知識がある。



# インストール

Xserverにはpython3が入っているので、そのまま使える。わけもなく。
管理権限がないので、python3自体は使用できても、pipは使えない。インストールできない。

そのため、別途python3をインストールする必要がある。

## brewを使う

参考サイト：[xserverにpythonをインストールしてpipを使えるようにする - Qiita](https://qiita.com/kusumoto-t/items/b262b8878ccdfd137fa3)

```bash
$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"
```

でインストール。

```bash
==> Checking for `sudo` access (which may request your password)...
残念ですが、ユーザー (ユーザー名) は (サーバー番号) 上で sudo を実行できません。
Insufficient permissions to install Homebrew to \"/home/linuxbrew/.linuxbrew\" (the default prefix).

(略)
```

ローカルでインストールする手段すら提示してくれませんでした。
ということで諦めて、別の方法を探す。

## anacondaを使う

参考サイト：[XserverのサーバーにPythonを導入する - 理系のための備忘録](https://science-log.com/pc%E9%96%A2%E9%80%A3/xserver%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%ABpython%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%99%E3%82%8B/)

やることは参考サイトと同じで、[公式サイト](https://repo.anaconda.com/archive/)から最新バージョンを確認して、wgetでダウンロードして、インストールするだけ。
(公式サイトを眺めてると、年に3,4回くらいバージョン更新があるようなので、おそらく今これ見ているあなたの時の最新バージョンは異なるでしょう。。)

```bash
$ wget https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh
---
`Anaconda3-2023.03-1-Linux-x86_64.sh' へ保存完了 [902411137/902411137]

$ bash Anaconda3-2023.03-1-Linux-x86_64.sh
```

なぜ開始はバッククォートで、終了はシングルクォートなのだろう。。。
とりあえずインストールは基本的にEnterで進めていけばOK。利用規約や、セットアップの有無などが聞かれる。

これでインストールは完了。インストール先は`/home/(ユーザー名)/anaconda3`にあるはず。

なお、この後`conda`と打ってもコマンドが見つかりません。と言われる場合は、接続しなおすか、パスを読み直す(`source ~/.bashrc`？)。
もしくは、`/home/(ユーザー名)/anaconda3/bin/conda`と打つとか。。

インストールの最後にも表示されるが、もし起動時にcondaを自動起動したくない場合は、一度以下のコマンドを使用しておく。

```bash
$ conda config --set auto_activate_base false
```

# pythonの実行

pythonを実行する前にanacondaに入る必要がある。

```bash
$ conda activate
(base) $
```

と、(base)表示になれば、condaに入れている。

```bash
(base) $ python --version
Python 3.10.9
```

これで、無事に実行できる。pipも使える。

ただ、pythonを実行するために毎回anacondaに入らないといけないかというと、そうでもなさそう。

```bash
$ /home/(ユーザー名)/anaconda3/bin/python3.10 --version
Python 3.10.9
```

つまり、パスをここに通しておけば、pythonを実行できる。pipは試していない。

# 終わりに

管理者権限がないので、結構複雑になった。
最近Xserverにはvpsが出来たので、そちらを使った方がいい気がする。
個人的にはレンタルサーバーを契約して、他にも使っているということもあり、移行できていないのだが。。。
lambda Node.js18 値をbcryptアルゴリズムでハッシュ化する

# 概要

- lambdaのランタイムNode.js18にてbcryptアルゴリズムを使って値をハッシュ化する方法を簡単にまとめる。

# 前提

- npmコマンドが実行できる環境があること。（筆者はローカルのMacにVoltaを使ってNode.js18を入れている。）

# 方法

1. npmコマンドが実行できる環境で下記のコマンドを実行しnodejsディレクトリを作成し、移動し、npm init -yを実行する。

    ```shell
    mkdir nodejs
    cd nodejs
    npm init -y
    ```

1. 下記コマンドを実行してbcryptjsをインストールする。

    ```shell
    npm install bcryptjs
    ```

1. 下記コマンドを実行してnodejsディレクトリをzip化する。

    ```shell
    cd ../
    zip -r nodejs.zip nodejs
    ```

1. ランタイムNode.js18で、x86_64アーキテクチャでlambda関数を作成する。
1. lambdaのページのサイドバーで「レイヤー」をクリックする。
1. 「レイヤーの作成」をクリックする。
1. 下記の様に設定して「作成」をクリックする。
    1. 名前： 任意のわかりやすい名前でOK
    1. アップロード種別： zipファイルをアップロードを選択
    1. zipファイルのアップロード： 「アップロード」をクリックし先程zip化した「nodejs.zip」を選択してアップロード
    1. 互換性のあるアーキテクチャ： x86_64を選択
    1. 互換性のあるランタイム： Node.js18を選択
1. 当該lambda関数の関数の概要の「Layers」をクリックする。

    ![getSeacretManagerApiKey_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/327c434b-4ee6-f7c4-4758-d817f71aa678.png)

1. 「レイヤーの追加」をクリックする。

    ![getSeacretManagerApiKey_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/274707c6-96ff-4b30-0cf0-a17d13638aee.png)

1. レイヤーソースで「カスタムレイヤー」を選択し、プルダウンから先に作成したレイヤーを選択し、「追加」をクリックする。
1. 下記の内容をlambdaのコードに記載する。

    ```index.mjs
    import bcrypt from "bcryptjs";
    
    let hashed = bcrypt.hashSync('hogefuga', 8);
    console.log(hashed);
    
    export const handler = async(event) => {
        // TODO implement
        const response = {
            statusCode: 200,
            body: JSON.stringify('Hello from Lambda!'),
        };
        return response;
    };
    ```

1. lambda関数のHello-Worldなどのテストを作成し、lambda関数を実行する。
1. 出力された文字列の中に、下記の様にハッシュ化された文字列が出力されれば完了である。

    ```
    undefined	INFO	$2a$08$JaBQnnj9aLXx/N1nZuPf1uzIgwnOVJAr1hsLXvGRYNqZQ5kL4L/KG
    ```

# 参考文献

https://www.npmjs.com/package/bcryptjs
AtCoder入緑記事

はじめまして！546と申します。先日行われたARC160にて、晴れて緑コーダーになることができました！
入緑するまで1年以上かかったので、この長い道のりを振り返っていこうと思います。
# レーティング
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323487/e6c0f109-29df-d5e4-8c31-c3818f176127.png)
# AtCoder Problems
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323487/e3875634-8146-2a60-dac5-a91c19a1b980.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323487/e8d0a6b1-9163-2f09-97e2-5cd06a727155.png)
# 自己紹介
- 一度大学を中退し、現在はMarchの情報科学科の3年生
- 学部1年の冬にAtCoderに出会う
- 競プロがきっかけでインターン中
# 競プロとの出会い
- 一番初めに競プロの存在を知ったのは、大学の教授が授業中にちらっとAtCoderの話をしていたからでした。しかし、そのころは特に興味を持つこともなく、「こんなものもあるんだ」程度で聞いていました。

- 次に私が競プロと再会したのは[テレ東Bizの動画](https://www.youtube.com/watch?v=WGNQrH0AV2k&t=461s)でした。私はもともと数学が好きだったので、この動画を見て、「競プロ面白そう！」と思い、AtCoderに登録しました。
# 競プロに出会ってから、一度辞めるまで
- 競プロに出会ってから、一番初めに取り組んだのは[APG4b](https://atcoder.jp/contests/APG4b)でした。C++の基本文法や、標準ライブラリまで、B問題くらいまでを解くのに必要な知識や考え方が揃ってて、初心者の人におすすめです。APG4bを終わらせたのちに、ABCのA,B問題の過去問をひたすら解いていたような気がします。
- この時期は競プロにものすごく熱中していました。プログラミングを使って問題を解いていくことの楽しさを覚えたのもこの時期でした。
- しかし、上のレーティングの画像からわかるように、競プロをはじめてから3か月くらいで辞めています。昨年の5月あたりから私生活やバイトが忙しくなり、競プロに割く時間が取れなくなり、一度辞めてしまいました。
# 出戻り、そして茶色へ
- 骨身を削ったバイトも辞め、怠惰な日常を送っていた10月ごろ、久しぶりにAtCoderに参加しました。しかし、結果は惨敗。半年ほどのブランクはなかなか埋められないものでした。この悔しさから、私は退屈な日常に別れを告げ、データ構造とアルゴリズムの勉強に本腰を入れることを決意しました。
- AtCoderに出戻りをしてからは、グラフのアルゴリズムや動的計画法、ビット全探索など、茶色～緑に必要なアルゴリズムを1通り勉強しました。特に、Union Findが毎週コンテストに出る時期があり、そこでUnion Findはマスターしました。[アルゴ式のグラフの問題](https://algo-method.com/courses/19)は初心者にもわかりやすく、おすすめです。この頃の貯金があったから、あまり精進できていない今、入緑することができたのだと思います。
- 入茶するまでは、ABCのC問題は解けるけど、D問題には歯が立たないといったレベルで、大体3000番代後半から4000番代の順位でした。
# 入茶してから入緑するまで
- 2022年の12月24日に入茶しました。クリスマスイブに茶色コーダーというプレゼントを貰えました。この頃は入茶したこともあり、嬉しくなってABCのD問題や、E問題を解きまくっていました。
- そしてついに、2023年1月7日、[ABCのD問題](https://atcoder.jp/contests/abc284/tasks/abc284_d)をコンテスト中に解くことに成功します。素数の問題で、Nの3乗根の前計算をすることで計算量を減らせる問題でした。D問題を初めて解けたことが嬉しすぎて、信じられませんでした。
- この頃に[Twitter](https://twitter.com/8pGCJmg4nVU2o37)も始めました。競プロ用のTwitter垢を作っている人はかなりいて、モチベの維持や、競プロ以外の有益な情報も入ってくるのでおすすめです。実際東大の松尾研の[GCI](https://ut-base.info/programs/29)や、現在私が働いている長期インターンは、Twitterから応募しました。
- 1,2月頃はAtCoder Problemsの[Boot Camp for Beginners](https://kenkoooo.com/atcoder/#/training)を解いていました。昔の緑diffや水diffの問題がたくさんあり、非常に力が付きました。
- 3月以降は、ハッカソンやインターンなどが忙しく、あまり精進できていませんが、コンテストには参加するようにしています。
- 最近は、ABCのD問題は7割くらい解けるという感じで、2000番代～3000番代前半という順位です。
# 今後について
今後についてですが、Web系の開発や、機械学習の勉強をしようと思っているので、競プロの優先度は下がっていく予定です。ですが、毎週ABCやARCには出ようと思っているので、1年くらいかけて入水できたらいいなと思っています。
# さいごに
入緑記事という名の、よくわからないポエムを読み終えていただきありがとうございました。最後に、灰色や茶色の方々に向けて応援メッセージを送ろうと思います。

私はAtCoderを始めて1年半という長い時間をかけて入緑しました。なので、灰色や茶色の方々の、精進しても全然レートが上がらないという感覚が痛いほどわかります。焦る気持ちもわかりますが、まずは自分のペースで精進して、毎週コンテストに出ることを心がけてください。他人と比べて萎えることもあるかもしれませんが、AtCoderのコンテストに参加して、それを楽しいと思えている時点で十分にあなたはすごいと思います。焦る気持ちを抑えて、アルゴリズムやデータ構造の勉強を楽しんでほしいと思います。

以上入緑記事でした。
【Flet】ドロップダウンでタブを切り替える

タイトルの通り、Fletでドロップダウンを使ってタブを切り替える方法について書きます。Fletについてはこちらの記事がわかりやすいです。

https://qiita.com/NasuPanda/items/48849d7f925784d6b6a0

こんな感じで動作します。
![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960906/914abc6a-379e-0578-e623-26cc554b2b79.gif)

公式リファレンスはこちら

https://flet.dev/docs/controls/

この記事は自分用の備忘録に近いのでご容赦ください。

# コード
ドロップダウンやタブの書式、配置については（英語ですが）公式リファレンスを見ていただきたいです。

https://flet.dev/docs/controls/input-and-selections

```py
import flet as ft


class change_tab(ft.UserControl):
       

    def build(self):

        self.tab_list = ["A","B","C"] #タブと選択肢のリスト

        #ドロップダウンの作成
        self.select_tab = ft.Dropdown( 
            label="選択したいタブ",
            on_change=self.dropdown_changed, #ドロップダウンが変更されたときの処理
            options=[
                ft.dropdown.Option(text="A"), #選択肢1
                ft.dropdown.Option(text="B"), #選択肢2
                ft.dropdown.Option(text="C"), #選択肢3
                ]
        )

        #タブの作成
        self.ABC_tab = ft.Tabs( 
            selected_index=0, #選択されているタブの指定
            on_change=self.tabs_changed,
            tabs=[
                ft.Tab(text="A"), #タブ1
                ft.Tab(text="B"), #タブ2
                ft.Tab(text="C"), #タブ3
                ]
        )

        return ft.Column(
            controls=[
                self.select_tab,
                self.ABC_tab
            ]
        )

    def dropdown_changed(self,e): 
        dropdown_value = self.select_tab.value #ドロップダウンで何を選択したかを取得

        for i in range(len(self.ABC_tab.tabs)): #タブの数だけループ
            if dropdown_value == self.tab_list[i]: #選択した内容とリストの中身が一致したとき
                self.ABC_tab.selected_index=i #選択されているタブを指定しなおす
        self.update() #表示を更新する

    def tabs_changed(self,e):
        tabs_index = self.ABC_tab.selected_index #タブで何を選択したか取得
        
        self.select_tab.value=self.tab_list[tabs_index] #ドロップダウンの値を指定しなおす
        self.update() #表示を更新する



def main(page: ft.Page):
    page.add(change_tab()) #ページに中身を追加

ft.app(target=main) #ページを表示
```
できたらリストを使ってドロップダウンとタブの表示を同じ変数から参照させたかったんですがそこまで技術力がないのでこの形になりました。

ドロップダウン→タブの変更とタブ→ドロップダウンの変更の両方に対応しています。
それぞれ選択している内容がドロップダウンは文字列、タブは整数なので注意してください。私は引っかかりました。
文学賞　受賞履歴の一覧

# 目的

- 過去の受賞作を図書館で借りて読みたい。
- 賞タイトル混在の一覧を求めるも、見つからない

# 更新方法

- wikipediaから（勝手に）取り込む
    - importHtml,importXML
- 手動で追加

- 重複回避
    - 受賞一覧シートの H列(key) を参照

# 公開方法

- google spreadsheet（リンクを公開）
    - https://docs.google.com/spreadsheets/d/18ckWqRTeNi4d8SYyPJKwDNpktk1lQ7do1jXRX2VSMpw/edit?usp=sharing
    - シート
        - 賞タイトル
            - 対象としている賞タイトル
        - 受賞一覧
            - 書籍１冊が複数タイトルを受賞した場合、複数行で表現
        - 書籍一覧
            - 書籍１冊で１行

# 「賞タイトル」シートより
2023/10/10時点

- 『このミステリーがすごい!』大賞
- Bunkamuraドゥマゴ文学賞
- GA文庫大賞
- KAPPA-ONE
- KAPPA-TWO
- SFが読みたい!・ベスト10
- SQEXノベル大賞
- Twitter文学賞
- アガサ・クリスティー賞
- エネルギーフォーラム小説賞
- このミステリーがすごい!
- サントリーミステリー大賞
- すばる文学賞
- センス・オブ・ジェンダー賞
- ダイヤモンド経済小説大賞
- ネット小説大賞
- ネビュラ賞_長編小説部門
- ハヤカワ・SFコンテスト
- ばらのまち福山ミステリー文学新人賞
- ヒューゴー賞_長編小説部門
- ファンタジア大賞
- ボイルドエッグズ新人賞
- ポプラ社小説新人賞
- ミステリが読みたい!
- メフィスト賞
- 鮎川哲也賞
- 一二三書房 WEB小説大賞
- 横溝正史ミステリ&ホラー大賞
- 河合隼雄物語賞・学芸賞
- 芥川賞
- 角川春樹小説賞
- 楽天Kobo電子書籍Award
- 吉川英治文学賞
- 吉川英治文学新人賞
- 警察小説新人賞
- 警察小説大賞
- 古書つけ（気がつけば○○）ノンフィクション賞
- 江戸川乱歩賞
- 講談社BOX新人賞
- 講談社ラノベ文庫新人賞
- 講談社児童文学新人賞
- 山田風太郎賞
- 山本周五郎賞
- 司馬遼太郎賞
- 柴田錬三郎賞
- 舟橋聖一文学賞
- 週刊文春ミステリーベスト10
- 女による女のためのR-18文学賞
- 小学館ライトノベル大賞（ガガガ文庫部門）
- 小学館ライトノベル大賞（ルルル文庫部門）
- 小学館文庫小説賞
- 小説すばる新人賞
- 小説現代長編新人賞
- 小説推理新人賞
- 小説宝石新人賞
- 小説野性時代新人賞
- 松本清張賞
- 城山三郎経済小説大賞
- 新潮ミステリー大賞
- 新潮新人賞
- 新田次郎文学賞
- 星雲賞（短編）
- 星雲賞（長編）
- 星新一賞
- 川端康成文学賞
- 泉鏡花文学賞
- 創元SF短編賞
- 太宰治賞
- 大藪春彦賞
- 谷崎潤一郎賞
- 中央公論文芸賞
- 直木賞
- 電撃小説大賞
- 渡辺淳一文学賞
- 読売文学賞
- 日経小説大賞
- 日本SF新人賞
- 本SF大賞
- 日本おいしい小説大賞
- 日本ファンタジーノベル大賞
- 日本ホラー小説大賞
- 日本ミステリー文学大賞新人賞
- 日本推理作家協会賞
- 日本翻訳大賞
- 文學界新人賞
- 文藝賞
- 暮らしの小説大賞
- 本屋大賞
- 本格ミステリ・ベスト10
- 本格ミステリ大賞
- 野間文芸賞
- 野性時代フロンティア文学賞
レーザーカッターでおしゃれスタンプ作り

## はじめに
レーザーカッターというと何となくIoTやマイコンボードのハンドメイドケースのようなものを思い浮かべますが（自分だけ？）、レーザーカット可能なゴムシートを使うことで、おしゃれなスタンプを作ることが可能です。
MDF等でもスタンプ自体は作成可能ですが、実際に使ってみた結果、ゴムシートを用いたほうが圧倒的にきれいで押しやすいスタンプになりました。

実際にスタンプを押した布袋が上記右側のものになります。
結構目が粗い素材ですが、かなりきれいにスタンプできました。

![bag_logo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/940c90d5-5bf1-1fe2-a9f4-bcac3b3633f4.jpeg)

ちなみに上記は現在キックスターターで募集を行っている[ボードゲーム](https://www.kickstarter.com/projects/758013216/rootile)のものです。


## 必要なもの
・スタンプ素材
・Inkscapeなどのsvgデータ作成用ツール
・レーザーカット用ゴムシート（またはMDF）

## 手順
まずはスタンプのもとになるデータを作成する必要があります。
お絵描きツールを使うなり取り込むなりして、用意しておきましよう。
![ROOTILE_icon_S.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/ee87684b-a979-fa7b-34c5-34787c75e830.png)

次に、これをレーザー加工用の.svgに加工します。
今回は[Inkscape](https://inkscape.org/ja/)というフリーソフトを使用しました。
.png画像をInkscapeにドラッグ＆ドロップすると下記のようなダイアログが表示されるので、そのままOKを押してインポートします。

インポートしたら「ビットマップのトレース」というタブを選択し、「画像を反転」をチェックします。
これで白いところがスタンプで残る部分（インクが載る面）になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/6050a917-3077-c0f1-7e4b-d0f5f0740d26.png)

次に、「左右に反転」のアイコンを押して、画像そのものを左右反転します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/b9fa4679-59a7-2eb8-b35e-ab1f5b750514.png)

これを
ファイル->名前を付けて保存で保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/611fe1a3-d30d-a7ca-fc33-55fcb072a123.png)

できたものがこちらです。MDF加工用に赤線を入れてありますが、ゴムシートの場合はハサミで簡単にカットできるので赤線不要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/0fa5f504-e29c-5db8-994d-e2c4f52404d0.png)

.svgデータができたら、これをレーザーカッターで加工します。
[レーザーカット可能なゴムシートはAmazonなどで販売しています](https://www.amazon.co.jp/gp/product/B0B2DZWZRS/)が、実際に使用してよいかは一応レーザー加工機の責任者に聞いてみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/0b205f44-cfab-40ba-a377-41a25f25452e.png)

カットができたら、適当な土台にはりつけます。
スタンプの余分な部分はインクがのってしまったりするので、カットしておいたほうが無難です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/efe1fb09-bf31-c778-8eec-c883f5c8282e.png)

これをスタンプ台に押し付けてインクを付けます。
今回はダイソーの[布にも押せるスタンプインクパッド](https://jp.daisonet.com/products/4904342007009)を使用しました。インクがネットリしっとりしていてムラなくおせて最高です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/657a65de-977c-2021-fb02-f4d263965d7d.png)

## 結果

実際に無地のルーズリーフに押したものがこちら。
左がゴム、右がMDFです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/901b3f9e-c82a-d6b3-dfd8-d014d8b71993.png)

上記のネットリしっとりインクパッドだとどちらもきれいに押せていますが、
朱肉やビジネス用のインクパッドだとMDFはかすれがちになります。

ゴムシートもスタンプサイズを考慮するとそれほど高価でもないですし、MDFなんかはたいていのレーザーカット室に端材が転がっていますので、気になった方は是非チャレンジしてみてください。
GithubActionsの8084番ポートはデフォルトで使用されている

# 概要
Github Actionsにてテストのために8084番ポートでプロセスを起動しようとした際、すでにポートが使用されている趣旨のエラーが発生した。
調査してみるとubuntu-latest（22.04）では8084番ポートはデフォルトで使用されていることがわかった。

# 発生したエラー
以下のようなワークフローを実行しようとした。
8084番ポートにてPub/Subエミュレーターを起動してテストをするというものである。

```yaml
  unit-test:
    runs-on: ubuntu-latest
    steps:
  　　 - uses: actions/checkout@v3
  　　 - name: Setup gcloud
 　　    uses: google-github-actions/setup-gcloud@v1
 　　    with:
　　       install_components: 'beta,pubsub-emulator'
　　   - name: Start PubSub emulator
 　　    run: gcloud beta emulators pubsub start --project=hogehoge --host-port=localhost:8084 &
    ...
```

すると、以下のようなエラーが発生した。すでにポートが使用されているらしい。もちろんPub/Subエミュレータ起動より前に明示的にプロセスを起動したりはしていない。
```bash
[pubsub] Exception in thread "main" java.net.BindException: Address already in use
```


# 調査
lsofコマンドにて8084番ポートを使用しているプロセスを調査したところ、`mono`というプロセスが起動していた。

```bash:実行結果
COMMAND PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
mono    762 www-data    5u  IPv4  23433      0t0  TCP *:8084 (LISTEN)
```

なので、Pub/Subエミュレータを8084番ポート以外を指定して起動したら問題なく起動できた。

ちなみに8084番ポート以外の808x番ポートを調査したがデフォルトで使用されているものはなかった。

# まとめ
GithubActionsで808x番ポートを利用する際、8084番ポートはデフォルトで使用されていることに注意したい。
プロトコルとは

みなさん、こんにちは
インフラエンジニアのRyoと申します。

第1回目はプロトコルについて記事を書いてみようと思います。
よく、プロトコルってコンピュータやネットワークの基礎的なことと言われますが、
正直ピンとこないですよね。。

**■　プロトコルとは**

プロトコルって具体的に何なのって言われたら、代表的なものであれば、
インターネットなどでも利用されている「IP」や「TCP」「HTTP」などですかね。
この辺はみなさん、なんとなく名前は聞いたことあるのではないでしょうか。

で、なぜこのプロトコルが必要になってくるかというわけですが、
正直、普段生活をしていてプロトコルについて意識することはないです。

どっちやねんというつっこみはあるかと思いますが
メールを送ったりWeb検索したりするとき、
SMTPが～とかHTTPが～とか考えて使わなくても問題はないですもんね。
でも、ネットワークを利用してコミュニケーションをとるためには、この「プロトコル」が重要なものになってくるんです。

よく言われているのは、プロトコル＝「約束ごと」と言われますね。
これは、メーカーやOSが違うコンピュータ同士でも、同じプロトコルを使えばお互い通信はできるということです。逆をいえば同じプロトコルを使わないと通信が出来ないということです。


**■　プロトコルを会話で考えると**
日本語しか話せない太郎さん、英語しか話せないJonさんがいたとします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3351883/1ffdb273-1ecc-e736-e36f-e8d97eded1c2.png" width="150" >    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3351883/81121427-713c-e6a8-c5b8-c707c2fb3184.png"width="150">



太郎さんとJonさんが仕事のミーティングで会話をしたらどうなるか。このときの

・日本語や英語が「プロトコル」
・言語によってコミュニケーションをとることを「通信」
・話の内容を「データ」

と考えてみると、仕事の会話をしようとしても、
双方の話している言葉(プロトコル)が理解できないかと思います。
伝えたいことが伝わらないのでコミュニケーション(通信)が成立しないです。
その結果話したい内容(データ)を伝えることが出来ません。

ここで救世主の登場です。日本語も英語も話せるJames次郎さんが現れたらどうでしょう。
太郎さんとJames次郎さんは「日本語」のプロトコル、
JonさんとJames次郎さんは「英語」のプロトコルを使えばコミュニケーションが成立して、話したい内容が伝わりますね。

コンピュータ同士でもこの、「約束ごと」が非常に大切でそれが「プロトコル」なのです。

今回はプロトコルについてお話をしました。
イメージとして、人と人との会話をイメージしてもらえれば、なんとなく想像しやすくなるのではないのかな～と思います。

次回は、OSI参照モデルについて書いてみようかと思います。

>参考書籍
>[マスタリングTCP/IP(入門編)](https://www.amazon.co.jp/%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0TCP-IP%E2%80%95%E5%85%A5%E9%96%80%E7%B7%A8%E2%80%95-%E7%AC%AC6%E7%89%88-%E4%BA%95%E4%B8%8A-%E7%9B%B4%E4%B9%9F/dp/4274224473/ref=sr_1_1?adgrpid=118241306560&hvadid=655164653222&hvdev=c&hvqmt=e&hvtargid=kwd-334746806305&hydadcr=27706_14668857&jp-ad-ap=0&keywords=%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0+tcp%2Fip&qid=1683635985&sr=8-1)
rubyエンジニアになって１ヶ月目でruby silver 合格できたのでそのまとめ(2023年5月)

最初の記事です
私は2023/02　にruby のエンジニアになりました。
インプットに集中した３ヶ月でしたが、
この記事を書いている段階で3ヶ月が経ったので、
少しずつアウトプットをはじめていきます。
同じくruby初心者の方の参考になれば幸いです。

インプットをはじめて約1ヶ月
2023/03 にruby silver に合格できました。
点数は7６点でした

## 勉強時間
実務でrubyをさわりながら、
平日は電車の中か、むりやりカフェに行って１時間くらい勉強
休日はカフェに行って２〜３時間くらい勉強
を１ヶ月くらい続けました。

## 勉強方法
・Ruby技術者認定試験合格教本 **全勉強時間の6割**
https://www.amazon.co.jp/dp/B0756VF9Y3/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
→この本のsilverの範囲を細かく読めば、そこそこの点数を取れると思います。
私はKindleで買って、移動時間等は必ず見ていました。

・rex **全勉強時間の3割**
https://www.ruby.or.jp/ja/certification/examination/rex
→模擬試験を受けることができます。

・公式模擬問題集 **全勉強時間の1割**
https://gist.github.com/sean2121/945035ef2341f0c39bf40762cd8531e0


## **結論**
Ruby技術者認定試験合格教本がとても大事です。


## 最後に

全部が全部、実務で使うわけではなかったのですが、
全く知らない言語を体系的に学び
資格として認められることは
自身につながりました

## 参考記事
https://qiita.com/jonakp/items/7f7550eeea78973a0a7f
AWS Secrets Manager APIキーを登録する

# 概要

- Secrets Managerを使ってAPIキーを登録する方法を簡単にまとめる。

# 前提

- Secrets Managerでシークレット値を作成する権限を持っていること。

# 方法

1. AWSにログインする。
1. Secrets Managerと検索し、Secrets Managerの画面に遷移する。
1. 「新しいシークレットを保存する」をクリックする。
1. 下記の様に設定し、「次」をクリックする。
    1. シークレットのタイプ： その他のしーークレットのタイプ
    1. キー/値： キーを`API-KEY`とする。値を`hogefuga`に設定
1. 次ページで下記の様に設定し、「次」をクリックする。
    1. シークレットの名前： 任意の内容を入力（筆者は「API-KEY」とした）
1. 自動ローテーションは特に設定せず、「次」をクリックする。
1. 設定内容を確認し「保存」をクリックする。
1. 一覧に今登録したシークレットのが表示される。
コンパラマップを考える

# はじめに

コンパラマップとは、母集団において、組織・個人（出願人・権利者・発明者等）から任意に2 つ抽出し、縦軸に任意に定めた項目、横軸に任意の計数値をとり、棒グラフで示したもの、といわれております。

横軸は中心を0として、組織・個人から任意に抽出した1 方を左側に、もう1 方を右側に示すことで、これらを比較するため用いるそうです。

今回は、このコンパラマップをpythonで書きたいと思います。
# プログラム

## データフレームの作成
まず、仮データを使ってコンパラマップを作成したいと思います。データとしては、出願人は甲と乙、課題はA～Dの４つとし、これらの適当な組合せのデータフレームを作成します。
```python
# 必要なライブラリをインポート
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
import seaborn as sns

# データを定義
data = {
    '課題': ['A', 'A', 'B', 'A', 'A', 'A', 'B', 'B', 'C', 'D', 'C', 'D', 'D', 'C', 'C', 'C'],
    '出願人': ['甲', '乙', '甲', '甲', '乙', '乙', '乙', '甲', '甲', '甲', '甲', '乙', '甲', '甲', '甲', '甲']
}

# データフレームを作成
df = pd.DataFrame(data)
```

## 課題件数のカウント
出願人ごとの課題の件数をカウントします。データフレームに２社以上の出願人が含まれる場合にはエラーを出力することにしました。
```python
# 出願人の一覧を取得
unique_applicants = df['出願人'].unique()

# 出願人が2社でない場合、エラーを表示
if len(unique_applicants) != 2:
    raise ValueError("データには2つの異なる出願人が含まれている必要があります。")
    
# 2つの出願人を変数に格納
company1, company2 = unique_applicants

# 各出願人の課題件数を取得
p_count_1 = df[df['出願人'] == company1]['課題'].value_counts().sort_index()
p_count_2 = df[df['出願人'] == company2]['課題'].value_counts().sort_index()
```
## グラフの設定
wspaceを0として、左右のグラフを密着させます。また、グラフの最大値を計算して、左右のグラフの最大値を合わせます。
```python
# グラフを描画するための設定
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5), sharey=True, gridspec_kw={'wspace': 0})

# クールな色を使うための設定
cool_colors = sns.color_palette("cool", 2)

# グラフの最大値を計算
max_count = max(max(p_count_1.values), max(p_count_2.values))
x_limit = max_count * 1.2  # 余裕のある値
```
## 左のグラフの描画
左のグラフについては横棒グラフの向きを反転させます。
```python
# 左側のグラフを描画
ax1.barh(p_count_1.index, p_count_1.values, color=cool_colors[0])
ax1.set_title(f'{company1}')
ax1.set_xlabel('件数')
ax1.set_ylabel('課題')
ax1.invert_xaxis()
ax1.set_xticks(range(0, int(x_limit) + 1))
ax1.set_xlim(x_limit, 0)
```
## 右のグラフの描画
右のグラフについては通常の横棒グラフとします。
```python
# 右側のグラフを描画
ax2.barh(p_count_2.index, p_count_2.values, color=cool_colors[1])
ax2.set_title(f'{company2}')
ax2.set_xlabel('件数')
ax2.yaxis.tick_right()
ax2.set_xticks(range(0, int(x_limit) + 1))
ax2.set_xlim(0, x_limit)

# グラフを表示
plt.show()
```

# コンパラマップ
出力されたコンパラマップは以下となります。甲と乙の課題ごとの出願件数の比較がしやすい図となっております。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3110569/ec8bbdb2-04c9-0107-ce73-52516db3e3fe.png)
# 感想
プログラムとしては、比較的シンプルで、わかりやすいものとなりました。次回は実際の特許データを使用して、コンパラマップを作成したいと思います。
WhisperとChatGPTを使って、LINEBOTの文字起こしを作る方法

完全にメモ用ですが、別に外部公開してもいいと思ったため公開します。

# やりたいこと
LINEで音声データを送ると、それがテキストとして返ってくる

# 実装方法
https://note.com/wanwanland/n/na649dd8f2eb8

無料外部公開したい場合はngrokでhttpsで外部公開できる。
https://qiita.com/mininobu/items/b45dbc70faedf30f484e

格安ではherokuにアップ
maya c++ チュートリアル

https://github.com/aizwellenstan/cpp_maya_template.git

https://www.autodesk.co.jp/developer-network/platform-technologies/maya

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399596/96bac8b9-b54b-4eca-c562-b50b4179e086.png)

https://visualstudio.microsoft.com/ja/downloads/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399596/dcd78d05-c4bb-27c0-6fb9-535ac8109195.png)

管理者権限で実行する
```setenv.bat
set PACKAGE_PATH=C:\Autodesk_Maya_2023_3_Update_DEVKIT_Windows\devkitBase

set VS_VERSION=2022
set CMAKE_PATH=C:\Program Files\Microsoft Visual Studio\%VS_VERSION%\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin

setx PATH "%PACKAGE_PATH%;%CMAKE_PATH%;%PATH%" /M
```
PHP8.1でHelloWorldプログラムを実装してみた

# 概要
PHP8.1でHelloWorldプログラムを実装してみました。以下のページを参考にしました。
https://programmercollege.jp/column/12960/

# 環境構築
以下のコマンドを実行しました。UbuntuでApache2は入っていたのでそのインストールは省略しています。
```
$ sudo apt install libapache2-mod-php8.1
```

# ソースコード実装
以下の通りソースコードを作成しました。
```hello2.php
<?php
 echo "Hello World！";
?>
```

# コマンドラインでの動作確認
以下のコマンドを実行しました。
```
$ php8.1 hello2.php 
Hello World！
```

# ブラウザで動作を確認
まずApache2が見られるディレクトリにファイルをコピーしました。
```
$ sudo cp hello2.php /var/www/html/
```
ブラウザで開くと、以下のように「Hello World!」という文字列が表示されました。
![Screenshot from 2023-05-15 19-25-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3192024/150993f4-0b77-c594-52ff-d64ef8513543.png)

# まとめ
PHPプログラムを実装して、ブラウザからアクセスして実行できていることを確認できました。
何かの役に立てばと。
【Swift】CustomViewをタップしたら画面遷移したい

# はじめに
こちらの記事を参考に、MapViewとCustomViewを作成したのですが、そこから「CustomViewをタップしたら画面遷移」したくなったのでやってみました。

https://blog.shota-ito.com/2021/06/21/mapview-callout/

参考記事に記載されている`CustomView`は`UIView`なので、画面遷移処理をかけません。

```swift:PlaceCalloutView.swift
import UIKit

final class PlaceCalloutView: UIView {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

    @IBAction func buttonDidTap(_ sender: Any) {
        print("button was tapped")
    }
}
```
さて、どうしたもんか。

# 実装
対応策としては`Delegate`を使って「`UIView`にできないことは`UIViewController`に任せちゃおう」ということにしました。

```swift:PlaceCalloutView.swift
import UIKit

final class PlaceCalloutView: UIView {    
    @IBOutlet weak var title: UILabel!
    @IBOutlet weak var viewButton: UIButton!
    var delegate: PlaceCalloutViewDelegate? = nil
    
    @IBAction func tepedViewButton(_ sender: Any) {
        self.delegate?.showNextView()
    }
}

protocol PlaceCalloutViewDelegate {
    func showNextView()
}
```

```swift:TopViewController.swift
import UIKit

class TopViewController: UIViewController {
    // 省略
}

extension TopViewController: PlaceCalloutViewDelegate {
    func showNextView() {
        // 画面遷移処理
        let storyboard = UIStoryboard(name: "NextView", bundle: nil)
        let nextVC = storyboard.instantiateViewController(withIdentifier: "NextView") as! NextViewController
        self.present(congestionGraphVC, animated: false)
    }
}
```
## まとめ
`Delegate`の使い方としてより良い使い方等ありましたら、ご指摘願います。
laravelでログインを実装する時のメモ

laravelで本格的にログイン実装をする必要が出てきたので自分用にメモ

<!--more-->

## プロジェクトの作成
バージョンは最近10が出たりしてるけど、9でやる

```
comopser createproject laravel/laravel "PROJECT NAME" 9.x.x
```

## breezeのインストール
laravelのlogin用のモジュールbreezeをインストールする

```
composer require laravel/breeze
artisan install breeze
```


ここまででログイン機能は一通り入る。

### DBの設定 
mysqlを利用するのでdockerのmysqlの初期設定を追加
default-character-setなどを追記


```
[mysql]
default-character-set=utf8mb4

[mysqld]
sql-mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
character-set-server=utf8mb4
collation-server=utf8mb4_bin
#default-authentication-plugin=mysql_native_password
```

### .envの設定
laravelの.envのmysqlパスワードを入れる

```
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=****
DB_USERNAME=****
DB_PASSWORD=****
```


### migrationのメモ

migrationの作成方法

```
artisan make:migration NAME
```

### テーブルの作成
YYYY_MM_DD_****_create_roles_table.phpというファイルができるので、そこに追加したいカラムがあれば追記する

```
artisan make:migration craete_roles_table
```

### 既存テーブルにカラムの追加をする
これもartisanでやる。そこに追加したいカラムがあれば追記する
追加したカラムをforeignを使って紐づける

```
artisan make:migration add_roleid_to_users
```


```
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->unsignedBigInteger('role_id')->after('id');
            $table->foreign('role_id')->references('id')->on('roles');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            //
            $table->dropForeign('users_role_id_foreign');
            $table->dropColumn('role_id');
        });
    }
};
```


### Seederの作成

データベースにテストデータを流し込んだりする

```
artisan make:seeder ****Seeder
```

作成したseederを動作させる場合はDatabaseSeederに記述する

```
namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        #こんな形で記入
        $this->call([RolesSeeder::class]);
    }
}
```


とりあえずここまで。
あとはメール機能周りをまとめたい。

## 参考
[laravelのチートシート](https://quickref.me/laravel)
Mac Vagrant コマンドを実行した際にエラーポップアップが出る

# 概要

- `$ vagrant up`コマンドを実行しようとしたときに「"vagrant"を開くとコンピュータが破損します。」というエラーポップアップがでてコマンドが通らなかったので自分場合の解決方法をまとめておく。

# 問題発生までの経緯

- Macを起動しVagrantを起動しようとおもった。

# 問題

- `$ vagrant up`コマンドを実行したときに下記画像のポップアップが表示され、Vagrantが起動しない。

    ![SyncSFEstateService_php_—_temposmart-next-src.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/aa7880c7-8946-f87a-4912-eee3e75762fa.png)

- すでにVagrantが起動している状態で再度起動させようとしてしまったかと思い`$ vagrant halt`を実行しVagrantの終了を試みたが同じエラーが出てコマンドが通らない。

# 問題解決までの経緯

- 下記にアクセスする。

https://developer.hashicorp.com/vagrant/downloads

- 「Operating System」でmacOSを選び、Vagrantのバージョンを選び「Download」を選ぶ。

![Install___Vagrant___HashiCorp_Developer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/8e4fd3f4-8672-eeec-8941-44155a6928f9.png)

- ダウンロードされた「vagrant_X.X.X_darwin_amd64.dmg」を開く。
- 「vagrant.pkg」を開きインストールする。

    ![Vagrant.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/3927fa54-db53-e097-6090-0eca709870fb.png)

- 必要であればvagrantのプラグインもインストールする。（xxxの部分にプラグイン名）

    ```
    vagrant plugin install xxx
    ```

- `$ vagrant up`や`$ vagrant halt`コマンドを実行したら無事に動作した。
AWS CDKでAPI GatewayのLambdaプロキシ統合とAPIキーを同時に使う

## やり方

Stackだけ抜粋しますが、これでOK！

```ts
import { Stack, App, StackProps } from 'aws-cdk-lib';
import { Runtime, Architecture } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';

export class ExampleApiStack extends Stack {
  constructor(scope: App, id: string, props?: StackProps) {
    super(scope, id, props);

    const lambda = new NodejsFunction(this, 'Lambda', {
      runtime: Runtime.NODEJS_18_X,
      architecture: Architecture.ARM_64,
      handler: 'handler',
      entry: 'lambda/index.ts',
      functionName: `example-api`,
    });

    // ↓ここから大事！！！

    const api = new LambdaRestApi(this, `example-api-endpoint`, {
      handler: lambda,
      // APIキーをすべてのメソッドに対して必須に設定
      defaultMethodOptions: { apiKeyRequired: true },
    });
    // APIキーを作成（中身は自動生成されるのでWebコンソールから確認）
    const apiKey = api.addApiKey('ApiKey', { apiKeyName: `example-api-key` });
    // 使用量プランを作ってAPIキー・API・ステージを紐付け
    const plan = api.addUsagePlan('UsagePlan', { name: `example-api-usageplan` });
    plan.addApiKey(apiKey);
    plan.addApiStage({ stage: api.deploymentStage });
  }
}
```

「`new LambdaRestApi()`のオプションに`defaultMethodOptions: { apiKeyRequired: true }`を渡す」って情報が、探しても全然見つからなくてハマりました。分かってみれば簡単ですね。

## 余談

例えばこんな記事とかあるんですが、
https://blog.exploringserverless.com/aws-cdk-101-api-gateway-construct-throttle-quota-usageplans-api-keys

```ts
const eventGateway = new apigw.LambdaRestApi(this, 'EventEndpoint', {
      handler: eventEntry,
      proxy: false
    });
```
`proxy: false`してLambdaプロキシ統合を外して、

```ts
const eventHandler: apigw.LambdaIntegration = new apigw.LambdaIntegration(eventEntry);
const event = eventGateway.root.addResource('event');

const eventMethod: apigw.Method = event.addMethod('POST', eventHandler, {  
   apiKeyRequired: false
});
```

自分で`addMethod('POST')`とかしてるんですけど、
こんなことしなくても、最初の例でシンプルに行けますからね！

けっこう需要あると思うんだけど情報が少ない。みんな割り切ってLambdaの中で認証書いてるのかなぁ？
誰かのお役に立てば幸いです。

ではまた！
Java Arrays.asListでboolean[]を使ったら期待する結果が表示されなかったがBoolean[]を使ったら対処できた

# はじめに
booleanとBooleanの違いを分からずコードを書いて期待する結果を出力するのに時間がかかってしまったので備忘録を兼ねて記事にします。


# 問題
さっそくですが問題です。
①と②で出力結果を考えてみて下さい

		①Boolean[] Boolean_results = {true, false};

		if(Arrays.asList(Boolean_results).contains(false)) {
		    System.out.println("false - Found!");
		} else {
		    System.out.println("false - Not Found!");
		}

        ②boolean[] boolean_results = {true, false};
		
		if(Arrays.asList(boolean_results).contains(false)) {
		    System.out.println("false - Found!");
		} else {
		    System.out.println("false - Not Found!");
		}

# 答え
① false - Found!
② false - Not Found!

# 解説
Arrays.asList()メソッドは、引数として渡された配列をListオブジェクトに変換します。


しかし、Arrays.asList()メソッドは、プリミティブ型の配列を直接受け取ることができずオブジェクト配列でなければなりません。

つまり、Arrays.asList()を使用する場合、boolean[]ではなく、Boolean[]を使用する必要があります。


**【プリミティブ型とは】**
※プリミティブ型とは、Javaのデータ型の一つでJavaのいわゆる基本な型のことで、値型とも言う。boolean以外に、int, char, floatなどが存在します。


# まとめ
配列をListに変換する際にArrays.asList()を使用する場合、プリミティブ型の配列を使用する場合は、適切なラッパークラスの配列に変換する必要がある。
複数項目を各項目ごとに7z圧縮する

# 概要

- ドラッグされた複数項目を7-zipを利用して、各項目ごとに圧縮する

# 使い方

- vbsファイルに圧縮したい複数の項目をドラッグする。送る(`shell:sendto`）などに配置してもよい

# ソース

## 日時なし

```vb: 複数項目を圧縮.vbs
Call CompressListFile

Sub CompressListFile
	Dim fso
	Set fso = CreateObject("Scripting.FileSystemObject")

	Dim WshShell
	set WshShell = WScript.CreateObject("WScript.Shell")
	
	Dim scriptPath
	scriptPath = Replace(WScript.ScriptFullName,WScript.ScriptName,"")

	Dim ts
	Dim compressFileOrFolderName
	Dim archiveFileName

	Dim oParam
	Set oParam = WScript.Arguments

	Dim i
	For i = 0 to oParam.Count - 1
		compressFileOrFolderName = oParam(i)
		If compressFileOrFolderName <> "" then
			If Left(compressFileOrFolderName, 1) = """" then
				compressFileOrFolderName = Mid(compressFileOrFolderName, 2)
			End If
			If right(compressFileOrFolderName, 1) = """" then
				compressFileOrFolderName = Mid(compressFileOrFolderName,1,Len(compressFileOrFolderName) - 1)
			End If

			If fso.FileExists(compressFileOrFolderName) then
				Dim f
				Set f = fso.GetFile(compressFileOrFolderName)

				archiveFileName = f.ParentFolder.Path & "\" & fso.GetBaseName(compressFileOrFolderName)
			Else
				Dim d
				Set d = fso.GetFolder(compressFileOrFolderName)
				
				archiveFileName = d.Path
			End If
			
			Dim cmdline
			cmdline = """C:\Program Files\7-Zip\7z.exe"" a """ & archiveFileName  & ".7z"" """ & compressFileOrFolderName  & """"
			Call WshShell.Run(cmdline, 1, True)
		End if
	Next
	
End Sub
```

## 日時付き

```vb: 複数項目を圧縮（日時あり）.vbs
Call CompressListFile

Sub CompressListFile
	Dim fso
	Set fso = CreateObject("Scripting.FileSystemObject")

	Dim WshShell
	Set WshShell = WScript.CreateObject("WScript.Shell")
	
	Dim scriptPath
	scriptPath = Replace(WScript.ScriptFullName, WScript.ScriptName,"")

	Dim ts
	Dim compressFileOrFolderName

	Dim baseNitiziName
	baseNitiziName = FormatDateTime(now, vbLongDate) & Replace(FormatDateTime(now, vbShortTime), ":", "時") & "分"

	Dim archiveFileName

	Dim oParam
	Set oParam = WScript.Arguments
	Dim i
	Dim base

	For i = 0 to oParam.Count - 1
		compressFileOrFolderName = oParam(i)
		If compressFileOrFolderName <> "" then
			If Left(compressFileOrFolderName,1) = """" then
				compressFileOrFolderName = mid(compressFileOrFolderName,2)
			End If
			If Right(compressFileOrFolderName,1) = """" then
				compressFileOrFolderName = mid(compressFileOrFolderName,1,len(compressFileOrFolderName)-1)
			End If

			If fso.FileExists(compressFileOrFolderName) then
				Dim f
				Set f = fso.GetFile(compressFileOrFolderName)

				archiveFileName = f.ParentFolder.Path & "\" & baseNitiziName & fso.GetBaseName(compressFileOrFolderName)
			Else
				Dim d
				Set d = fso.GetFolder(compressFileOrFolderName)
				archiveFileName = d.ParentFolder.Path & "\" & baseNitiziName & d.Name
			End If
			
			
			Dim cmdline
			cmdline = """C:\Program Files\7-Zip\7z.exe"" a """  & archiveFileName  & ".7z"" """ & compressFileOrFolderName  & """"
			Call WshShell.Run(cmdline,1,true)
		End if
	Next
	
End Sub
```
冷たい飲み物が温まるまでの過程

## 結論
熱伝導は、物質内で熱が伝わり、温度差があるほど伝わりやすい。例えば、冷たいジュースを置くと最初は速く温まり、次第にゆっくりになる。


## 内容
熱伝導は、物質内部で熱が伝わる現象です。熱は高温から低温へと自然に流れる傾向があります。温度差があるほど、熱はより速く伝わることが特徴です。

具体例として、冷たい飲み物をテーブルの上に置いた場合を考えましょう。

ある日、夏の暑い日に冷たいジュースをテーブルの上に置いたとします。テーブルの上には室温の空気があります。

冷たいジュースは高温から低温へと熱が移動する性質を持っています。そのため、冷たいジュースと室温の空気の間には温度差が生まれます。

この温度差によって、ジュースから熱がテーブルの上の空気へと伝わります。温度差が大きいほど、ジュースから熱がより速く伝わることになります。

たとえば、最初にジュースをテーブルの上に置いた直後は、ジュースが冷たく、テーブルの上の空気は室温です。温度差が大きいため、ジュースから熱が迅速に伝わり、ジュースの温度が上がっていきます。

しかし、時間が経つにつれてジュースの温度と室温の空気の温度が近づいてくると、温度差が小さくなります。その結果、ジュースから熱が伝わる速度も遅くなります。

このように、冷たいジュースと室温の空気の間の温度差が大きいほど、ジュースから熱がより速く伝わることがわかります。熱の伝導は温度差に依存する現象であり、温度差があるほど熱は伝わりやすくなります。結果、ジュースの温度は最初は早く温まりますが、次第に温まる速度はゆっくりになります。
cos7θ,sin7θ「2015大学入試センター試験本試験数学IIB・旧数学IIB【１】［１］」をsympyでやってみた。(3/3)計算のみ

(途中です)はじめて、sympyの「Line angle_between」を使いました。
角度の計算は、今まで、内積公式で「acos」を使ってきました。

大学入試数学問題集成＞【1】テキスト 
https://mathexamtest.web.fc2.com/2015/201510000/2015100000200mj.html

# sympyで
```python
from sympy import *
from sympy.simplify.fu import TR5
myInterval=Interval(Rational(1,2)*pi/4,pi/4)
t    =symbols('t'    ,real=True)
cos7t=symbols('cos7t',real=True)
sin7t=symbols('sin7t',real=True)
cost =symbols('cost' ,real=True)
sint =symbols('sint' ,real=True)
x,y  =symbols('x y'  ,real=True)
#
O =Point(0                ,0                )
P =Point(2*cos(t)         ,2*sin(t)         )
Q =Point(2*cos(t)+cos(7*t),2*sin(t)+sin(7*t))
OP=Point(O).distance(P).simplify()
PQ=Point(P).distance(Q).simplify()
OQ=Point(O).distance(Q).simplify()
# (1)
mySub1  ={cos(t):cost,sin(t):sint,cos(7*t):cos7t,sin(7*t):sin7t}
OQ2     =((Point(O).distance(Q.subs(mySub1)))**2).expand()
mySub2  ={cost:cos(t),sint:sin(t),cos7t:cos(7*t),sin7t:sin(7*t)}
OQ_max  =maximum (OQ,t,myInterval)
OQ_t    =solveset(Eq(OQ,OQ_max),t,myInterval).args[0]
# (2)
myLine  =Line(O,P).equation()/(-2)
myLine_t=solveset(Eq(myLine,0).subs({x:Q.x,y:Q.y}),t,myInterval).args[0]
# (3)
myDOt_t=solveset( Eq(Line(Q,O).angle_between(Line(Q,P)),pi/2)  ,t,myInterval ).args[0]
#
print("#(1)アイ  ",OP,",",PQ)
print("#(1)ウエ  ",TR5(OQ2.simplify().subs(mySub2)))
print("#(1)ウエオ",(OQ**2).simplify())
print("#(1)カキ  ",OQ_t,",",OQ_max)
print("#(2)ク    ",myLine  )
print("#(2)ケ    ",myLine_t)
print("#(3)コサシ" ,myDOt_t,OQ.subs({t:myDOt_t}))
#(1)アイ   2 , 1
#(1)ウエ   4*sin(t)*sin(7*t) + 4*cos(t)*cos(7*t) + 5
#(1)ウエオ 4*cos(6*t) + 5
#(1)カキ   pi/4 , sqrt(5)
#(2)ク     x*sin(t) - y*cos(t)
#(2)ケ     pi/6
#(3)コサシ 2*pi/9 sqrt(3)
```
# 参考

Line angle_between(l2)

https://docs.sympy.org/latest/modules/geometry/lines.html#sympy.geometry.line.LinearEntity.angle_between
【Paiza問題集】標準入力メニュー/半角スペース区切りの入力

# Paiza問題集
Pythonで回答
## 標準入力メニュー/半角スペース区切りの入力
### Step1 1つの入力
```Python
"""
1つの入力
https://paiza.jp/works/mondai/stdin_primer/stdin_primer__split_input_step1
"""

# 文字を受け取る
string = input()

# 文字を表示する
print(string)
```

### Step2 半角スペース区切りの2つの入力
```Python
"""
半角スペース区切りの2つの入力
"""

# 文字を受け取り、スペースで区切る
str1, str2 = input.split(' ')

# 文字を表示する
print(str1)
print(str2)
```

### Step3 半角スペース区切りの3つの入力
```Python
"""
半角スペース区切りの3つの入力
https://paiza.jp/works/mondai/stdin_primer/stdin_primer__split_input_step3
"""

# 文字を受け取り、スペースで区切る
str1, str2, str3 = input().split(' ')

# 文字を表示する
print(str1)
print(str2)
print(str3)
```

### Step4 半角スペース区切りの10個の入力
```Python
"""
半角スペース区切りの10個の入力
https://paiza.jp/works/mondai/stdin_primer/stdin_primer__split_input_step4
"""

# 文字列をスペース区切りで受け取る
string = input().split(' ')

# 文字を10個表示する
for i in range(10):
    print(string[i])
```

### Final問題 半角スペース区切りの1000個の入力
```Python
"""
半角スペース区切りの1000個の入力
https://paiza.jp/works/mondai/stdin_primer/stdin_primer__split_input_boss
"""

# 文字列をスペース区切りで受け取る
string = input().split(' ')

# 文字を1000個表示する
for i in range(1000):
    print(string[i])
```
[Rails]bundle install でCould not locate Gemfile

Railsのbundle installをしようと思ったら、"could not locate Gemfile"というエラーが出た。
そのエラー文をそのまま検索すると、http://stackoverflow.com/questions/3329953/could-not-locate-gemfile?newreg=affbc7fbfe8040e6966c1e0ff5fcba2a
 、「ジェムファイルがそのコマンドを実行させているフォルダにありません。 ジェムファイルっていうのは、現在のプログラムのジェム設定を含んでいるファイルです。」的な感じ。
「それは分かってるんですよ、、、」
何でだろーって考えてたら、

あ、bundle installを実行するときの、ディレクトリ間違えてた。
階層一個下だった･･･！！！

こうした初歩的なミスが僕を苦しめていくのだろう、、、
CodeIgniter3をビルトインサーバーで起動したときにサーバーログにメッセージを表示する方法

※CodeIgniter3は新規案件で採用しないほうがいいです。

適当なhelperファイルに以下の関数を追加しておき、ログを吐き出したいところで呼び出す。

```php
/**
 * Write message to server log. Easy debugging!!
 *
 * @param mixed $message
 * @return void
 */
function server_log($message)
{
    if (!is_string($message)) {
        $message = var_export($message, true);
    }
    $date = date("Y-m-d H:i:s");
    file_put_contents("php://stdout", "[$date] $message" . PHP_EOL);
}
```
AWSユーザがGCPを使うときに便利な小ネタ



AWSはわかるけどGCPのサービス名がわからない方に向けた小ネタです。

# GCPのサービス名検索する方法
GCPの管理コンソールで検索バーにAWSのサービス名を入力しましょう。

## VMサービス(EC2)の場合
Compute Engine が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408925/32a29f95-d9cb-a688-02ef-529ba12b59b6.png)

## RDBサービス(RDS, Aurora)の場合
RDS を検索すると Cloud SQL が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408925/45465d1a-6ad6-b24a-06f3-f07b5c0a1547.png)

Auroraの場合は Cloud Spannerも表示されます。
AlloyDBが表示されても良い気もしますが出てきません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408925/e42ba82d-4947-bdba-e1da-eec7fc9cd4fc.png)

## オブジェクトストレージサービス(S3)の場合
Cloud Storageが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408925/6d277924-7782-f425-a1ad-3103983715a0.png)

# 期待はずれのケース
ELBを検索してもドキュメントしか出てきません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408925/751da613-3a2b-eff2-ac60-7f3117423c4a.png)

ALBならどうか
Cloud Load Balancingのドキュメントは出てきますが、プロダクトとページの項目は表示されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408925/c24ec299-184b-eb76-c8cb-6d652fb65b80.png)

# まとめ
単純にgoogle検索やAIに聞くなどしてもよいですが、こんな方法もありますのでお試し下さい。
[Python] LoRA 学習用タグファイルからタグを一括削除

# はじめに

LoRAを学習させる時に画像毎のタグファイルが必要になります。
ひとつひとつ削除するのが面倒なので一括削除するスクリプトを作りました。

自分用に作ったので適当な作りですが、短いスクリプトなのでまぁ大丈夫でしょう。
Python触って一ヶ月くらいなのでもっと良い書き方があれば教えてください。

- 元のファイルを上書きします。
- Dataset Tag Editor みたいに親切ではないのでバックアップは取ってません。
- Tagger とかでスパっと出力できますし「間違えたら出力しなおせばいいや」という精神で。

# スクリプト

以下の内容を `removeTag.py` として保存します。
`IMG_PATH`、`REMOVE_TAG_FILE` 自分の環境に合わせて変更してください。

`images\**\*.txt` というのは `images\` 以下のサブフォルダも対象にするという意味です。


```py:removeTag.py
# タグファイルから特定のタグを削除するだけのツール

import glob
import re

# 学習画像フォルダの１個上のフォルダ
IMG_PATH = "{学習ファイルのフォルダ}\images\**\*.txt"
# 削除対象を記述したファイル
REMOVE_TAG_FILE = "{学習ファイルのフォルダ}\remove_tag_list.txt"

_targetTags = []


# ---------------
# 除外タグに一致しないものを書き出す
def removeTag(filename, tags):
    removedTags = []

    for tag in tags:
        if not tag in _targetTags:
            removedTags.append(tag)

    return removedTags


# ---------------
# カンマと改行で分割してリスト化
def fileToList(filename):
    handl = open(filename, "r")
    list = re.split(" *[,\n]+ *", handl.read())
    handl.close()
    return list


# ---------------
def main():
    global _targetTags

    # 除外タグ一覧読み込み
    _targetTags = fileToList(REMOVE_TAG_FILE)

    # タグファイルを開く
    for filename in glob.glob(IMG_PATH):
        print("\n" + filename + "\n")

        tags = fileToList(filename)
        removedTags = removeTag(filename, tags)

        tagFile = open(filename, "w")
        tagFile.write(",".join(removedTags))
        tagFile.close()
        print(",".join(removedTags))


main()
```

# 削除対象タグ記述ファイル

`remove_tag_list.txt` というファイルに削除対象を記述します。
以下が記述例です。

```text:remove_tag_list.txt
school swimsuit, swimsuit,swimsuit under clothes,
blue one-piece swimsuit,
one-piece swimsuit
old school swimsuit
sailor school uniform,
```

- カンマ「,」か改行でタグを区切ります
- 前後にある空白は無視します



# 使い方

```shell
> python removeTag.py
```

## 出力結果
```
slskmz, 1girl, solo, swimsuit, sailor school uniform
```
👇
```
slskmz,1girl,solo
```
【IBM Security Verify】Adaptive Accessで「非常に高い」と判定されたユーザーアクセスをSlackに通知する。

# はじめに

通知Webhookの利用方法として、Adaptive Accessのリスク判定で「非常に高い」と判断されたユーザーをSlackに通知してみました。
![qiita_webhook_a2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678692/49eee530-c45f-9bd6-9ca4-dc5fd400b369.png)


デフォルト設定でご利用いただいている場合、「非常に高い」と判定されたユーザ―のアクセスはブロックされ、以下のようなエラー画面が表示されます。
![qiita_webhook_a2(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678692/745dce49-94b8-d856-954b-81a5dc322f89.png)

システム管理者の方が、ユーザーの問い合わせの前に状況を把握するために、この通知Webhookが利用できるのではと思います。

# IBM Security Verifyの通知Webhook設定について
SlackのIncommingWebhookの設定の流れは、前回の記事[【IBM Security Verify】ユーザーの登録・変更・削除イベントをSlackに通知する。](https://qiita.com/fitz/items/1dfc4179995cb891724c) と同じため割愛させて頂きます。

APIを使って、IBM Security Verifyに通知Webhookを登録します。

設定内容は以下の通りです。
interestsで、"event_type"が"adaptive_risk"であるイベントを指定しています。
transform-outgoingの設定の中で、risk_level == 'VERY_HIGH' で、非常に高いと判定されたイベントを抽出してSlackに通知を行っています。

```
{
    "name": "adaptive_access_high_webhook",
    "type": "notification",
    "transform": {
        "outgoing": "statements:\r\n- context: \"isEventType := (body.data.risk_level == 'VERY_HIGH')\"\r\n- if:\r\n    match: context.isEventType \r\n    block:\r\n      - context: \"d := body['data']\"\r\n      - return: \"{'body': {'text': '<!channel>該当ユーザーのログインが、「リスクレベル:非常に高い」 と判定されました'+'対象ユーザー：'+ context.d.username }}\"\r\n- return: \"{'body':  {'text':'other'}}\""
    },
    "urls": [
        "https://hooks.slack.com/services/xxxxxx/xxxxxx/xxxxxx"
    ],
    "purpose": [
        "notifications"
    ],
    "notification": {
        "interests": [
            {
                "name": "adaptive access events",
                "description": "",
                "clauses": [
                    {
                        "operation": "include",
                        "key": "event_type",
                        "value": "adaptive_risk"
                    }
                ]
            }
        ],
        "enabled": true
    }
}
```

# 最後に

interest/transform の設定を考える際には、実際イベントのデータを出力して確認しながら行うと設定しやすいです。
イベント・ペイロードやAPIのマニュアルも参考になります。

- [Get all events for a tenant.](https://docs.verify.ibm.com/verify/reference/getallevents) 
- [イベント・ペイロード](https://www.ibm.com/docs/ja/security-verify?topic=reports-event-payloads)
【Tips】コミットは基本的には「Xcodeから」ではなく「ターミナルから」やろう

【Xcodeからコミットできるが、ターミナルでコミットした方がいい理由】

理由としては、

Xcodeのバグとかが入ってきたり、
それこそSPMエラーとかのエラーが入ってくるから。

あと、XcodeでしかGitをやれないとなると、
就職先のGitの使い方/Web開発でのGitの使い方が分からなくなるから。
tauriのmsiを日本語にする方法

# Tauriで生成したWindowsのインストーラを日本語にしたい

## 結論

bundle.windows.wix.languageにja-JPと入れる。

```json
{
  ...いろいろ
    "bundle": {
      "active": true,
      "icon": [
        "icons/32x32.png",
        "icons/128x128.png",
        "icons/128x128@2x.png",
        "icons/icon.icns",
        "icons/icon.ico"
      ],
      "identifier": "s.tauri.dev",
      "targets": "all",
      "windows": {
        "wix": {
          "language": "ja-JP" //←こんな感じ
        }
      }
    }
  }
}
```
EC-CUBEのE2Eテストを実行する

# 前提
PHPのインストール済み
Docker環境でEC-Cubeを作成済み
Docker環境ではない場合、Mailcatcherをローカルで立ち上げれば大丈夫だと思います。

# 本編
## インストール関係
### ChromeDriverをインストールする
```
$ brew install chromedriver
```

### Symfonyのインストール
公式サイトを参考に

https://symfony.com/download
```
$ curl -sS https://get.symfony.com/cli/installer | bash
```
これでもいけるらしい
```
$ brew install symfony-cli/tap/symfony-cli
```

### Symfonyがインストールできない場合（Xcodeのバージョンが低い場合）
```
$ sudo rm -rf /Library/Developer/CommandLineTools
$ xcode-select --install
```
__sudo rmするとインストールしますか？ポップアップが出てくる。__
__全てのターミナルを閉じてから、ポップアップをクリックすればいける、、はず。__


## サーバを建てていく
### Docker環境のMailcatcherを起動する
```
$ docker run --rm -p 1080:1080 -p 1025:1025 schickling/mailcatcher
```

### ChromeDirverを起動する
```
$ chromedriver --url-base=/wd/hub
```

#### 検証元が不明のエラーが出た場合
システム環境設定 > プライバシーとセキュリティー > セキュリティー > chromedreiverをこのまま開く

### PHPサーバを立てる
```
$ php -S 127.0.0.1:8080
```

127.0.0.1:8080にアクセスして、EC-Cubeの画面が立ち上がったらOK

#### `composer not install`が出た場合
```
$ composer install
$ symfony console eccube:install -n
```
再度PHPサーバを立てて表示されればOK

### local.envの作成
`codeception/_envs/local.yml`のファイルを作成する
今回はこのように設定した

*2025年3月19日追記
ChromeDirverの標準ポートが58065になりました。
9515で接続できない場合は、ポート番号を変えるか、Driver起動時にポートを設定してください。
```
modules:
    config:
        WebDriver:
            host: 'localhost'
            port: 9515
            url: 'http://localhost:8080'
        MailCatcher:
            url: 'localhost'
            port: 1080
```


## テスト実行
### 試しにEA01TopCestのテストケースを実行する
2023/05/17 追記
↓おすすめはこちら↓
```
$ vendor/bin/codecept run acceptance --env chrome-headless,local --html report.html -g admin01 codeception/acceptance/EA01TopCest.php          
```

↓これは画面が切り替わります。（あとうまくいかないことが多いです）
```
$ vendor/bin/codecept run acceptance --env chrome,local -g admin01 codeception/acceptance/EA01TopCest.php
```


### テストが落ちるケース1
```
$ vendor/bin/codecept run acceptance --env chrome,local -g admin01 codeception/acceptance/EA01TopCest.php

[Facebook\WebDriver\Exception\SessionNotCreatedException] session not created: This version of ChromeDriver only supports Chrome version 113
Current browser version is 110.0.5481.177 with binary path /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
```

Chrome本体とChromeDriverのバージョンが不一致のエラー
Chromeの更新をしたら治るかもしれない。
どうしてもバージョンが合わない場合、Driverのモジュールを直でDLすると良いらしい。

### No tests executed!が表示される場合
2023/05/17 追記
実行するファイルが存在しない場合はエラーがおきます。
それ以外では、↑にあるlocal.envの作成を忘れている場合があります。（１敗）


### メールが飛ばない場合
.envファイル等に以下の設定を追加 （2023/5/24 追記）
```
MAILER_DSN=smtp://localhost:1025
```
## バージョン不一致の場合
chromedriverのバージョンアップをお願いします。
```
error内容
[1698432118.565][WARNING]: This version of ChromeDriver has not been tested with Chrome version 118.
```
実行コマンド
`brew upgrade chromedriver`

#### 背景
EC-CubeのE2Eテストを実行する機会があったので、作業手順をメモしたものになります。
参考にさせて頂いた記事（？）
https://github.com/EC-CUBE/ec-cube/pull/3048
EC2 Amazon Linux2にVoltaでnode18を入れたけどエラーが出る

# 概要

- 下記の方法でAL2のEC2にVoltaをインストールした。その後`$ volta install node@18`を実行したディレクトリで`$ node -v`を実行したところエラーが発生した。解決方法を簡単にまとめてみる。

https://qiita.com/miriwo/items/15da92c4640488491f72

# 情報

- どうやらEC2はNode.js18をサポートしてない模様。。。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html

![チュートリアル__Amazon_EC2_インスタンスでの_Node_js_のセットアップ_-_AWS_SDK_for_JavaScript.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/1cdaa53d-bf07-9a1f-6ff4-5bec70015e7e.png)


# 方法

1. 下記のコマンドをEC2インスタンスにsshして実行する。

    ```shell
    sudo amazon-linux-extras install docker
    sudo systemctl enable docker
    sudo systemctl start docker
    sudo usermod -a -G docker ec2-user
    ```

1. 一旦EC2インスタンスへのssh接続を終了する。
1. EC2インスタンスへssh接続する。
1. 下記を実行してNode.jsのDockerイメージを取得する。

    ```shell
    docker pull node:18
    ```

1. 下記を実行してNode.jsのコンテナを起動する。

    ```shell
    docker run -it --rm node:18 /bin/bash
    ```

1. コンテナ内で`$ node -v`を実行したところ問題なくバージョンが返された。

    ```shell
    # node -v
    v18.16.0
    ```