OmniAICreator/Qiita-1.07M huggingface dataset:
【雑記】データエンジニアが身に付けるべきと感じた技術

こんにちは、arcadia13です。

今回はデータエンジニアとして仕事をするために現状、必要だと痛感した知識を網羅したいと思います。

目的は備忘録ですが、雑記なのでまあこんなものか程度の内容です。


# 1. Ubuntu

Ubuntuは仮想サーバーを立ち上げる時に使います。
Hyper-VやWSLがあるので簡単に立ち上げることができます。

なお、Ubuntuを使う上で当然ながらLinuxの知識も必要です。
dockerとか、nanoコマンドとか、色々な知識が絡んできます。

ここが**第一にして最大の関門**だと私は考えています。
仕事とはいえ、ここを乗り越えるのってかなり大変なんですよ。

ですので分からないうちは色んな人に助けを求めましょう。

また、分からないことの解決方法をすぐ発見できるような**ネット検索力**もこの機会に身につけておきましょう。

でないと絶対に挫折します。
かく言う自分も挫折しかけましたので、アドバイスをくれた上司には感謝しています。
まあ、仕事なので挫折なんて許されないんですけどねー。


# 2. SQLとデータベース

仕事では必ずデータベースを使うのでその設計知識が必要です。
もちろんSQLもセットでついてきます。

とはいえ、データーベースやSQLの勉強はそこまで重くありません。

設計を極めるならともかく、そうでない場合、本気でやれば数か月で基礎をマスターできます。


# 3. Python

もはや書けることが前提となりつつあるプログラミング言語です。
それ故に情報もネット記事も書籍も豊富なので、自分なりに学習しやすい言語だとも言えます。

とはいえ、初心者にとってはできることが多すぎてどこから勉強すればいいか分からない言語かもしれません。

データエンジニアの立場から言わせてもらうと、配列やfor文など基礎、そしてpandasを用いてDatFrameを自由自在にいじれるようになれば楽じゃないかと思います。

理由は二つ。

まず、今時は優秀でオープンなAPIやライブラリがたくさんあるため、それを使いこなせるかどうかが重要だからです。

なので（そこまで）複雑なプログラムを書く必要はありません。
もっと言えば、ChatGPTも生まれましたし、もはや「書く」作業自体にそこまでの価値はないのかもしれません。

もちろん、プログラミングの知識や技術は大切ですけども。

二つ目に、Python上でデータ成形ができるようになりその作業時間を短縮できます。

データ成形はもちろん大切でデータの質を決める大事な工程です。
それでも分析の時間を食われるのも癪に障ります。
本命は分析ですし、この部分こそ人間である我々が時間を割くべきです。

機械的作業はロボットにやらせて自動化、効率化していき、人間にしかできない部分に時間を使いたいものです。


# 4. マクロ

Pythonではなく、エクセルVBA（マクロ）でデータ成形を行うという手段もあります。

その使用目的上、データエンジニアでマクロの知識が必須とまでは断言できません。

私もそうしているのですが、マクロを組んで自動化（あるいは半自動化）しておくと効率が段違いです。

いちいち集めたデータをコピペして成形して...なんてしてたら時間が足りません。

最初のうちは手作業でコピペ&マクロによる半自動化を併用するくらいでいいと思います。

全部手作業するよりはるかに効率的です。

そこから少しずつ自動化率を高めていくとよいでしょう。


# 5. webスクレイピング、HTML、CSS

Pythonやマクロを使ってwebページからデータを収集する際に使用します。

いちいち自分で検索して、データをチェックして、ダウンロードして...。

まあ収集速度を考えれば非効率です。
それを少しでも上げるための手段ということです。

しかし、予め断っておきますが、webスクレイピングはボタン一つで実現可能な"魔法"のような代物ではありません。

webページからデータを読み込む都合上、その構造、つまりHTMLとCSSを理解しないといけません。

Pythonやマクロだけ知っていればいい、というわけではないのです。

またwebページからデータを読み込む際に、読込先のサーバーへ大きな負荷がかかるためスクレイピング行為を**ハッキング行為（犯罪）とみなす**こともあります。

サイトによってはwebスクレイピングを**禁止**しているところもあるので、スクレイピング前に**必ず利用規約を確認**しましょう。

規約を守らないと**犯罪**です。

エンジニアとしてネットモラルは守りましょう。


# 6.データレイクと可視化ツール

データレイクとはデータを全てため込んでおく場所のことです（もちろんネット空間にです）。

一か所にため込んでおいて、必要に応じて引っ張り出すという目的があります。

ここには前処理前後、成形前後等、色んなデータをとりあえずぶちこんでためます。
故にデータレイク（データの湖）と呼ばれていくのです。

そして、ため込んだ後のデータを可視化するツールが必要です。

データ分析の基本の一つは可視化。
適切に可視化を行えてこそ真価を発見しやすくなるのです。

データレイクも可視化ツールも企業によって異なると思うので、そこは各自が知識と経験を身につけていきましょう。


# 7. リアルタイムなデータ分析

可視化ツールともつながるのですが、現代のビジネスにおいてデータ分析を用いたい場合、リアルタイムなデータ分析が絶対に必要だと私は確信しております。

リアルタイムでないデータを集めたり、成形したり、システムに利用するのは**当たり前**です。

現代ビジネスという戦場で生き残るには、リアルタイムなデータを集め、それを分析する事が絶対条件だと思います。


# まとめ

今回はデータエンジニアとして必要な知識や経験について、現時点の私の意見をまとめました。

1. Ubuntu
2. SQLとデータベース
3. Python
4. マクロ
5. webスクレイピング、HTML、CSS
6. データレイクと可視化ツール
7. リアルタイムなデータ分析

今後、私が経験を積む中で認識が間違っていた、追記したいと思ったことがあればしていこうと思います。

# おわりに

大学時代、データベースの講義で正規化とSQLについて徹底的に叩き込まれました。
その最終課題がなんと簡単なデータベースの実装（もちろん筆記試験もありましたが）。

PHPとSQLを使って実装したのです。

慣れない言語だったので当時は徹夜をするほど苦労しました。はっきり言えば辛かったです。

でも結果的に、それが今になって活きているのであの日々には感謝しています。
コマンドプロンプトとPowerShell

WinodwsのCUIにはコマンドプロンプトとPowerShellがあります。

今回はこの２つのCUIの歴史について簡単に解説します。

# コマンドプロンプト
コマンドプロンプトは別名cmd.exeと呼ばれるWindows NT系に使用されているCUIです。

<strong>NT系</strong>とはWindows 95～Windows Meがメインストリームだった当時、個人向けPC(95、98、Me)を<strong>9x系</strong>と呼ぶのに対し、<strong>サーバー向けまたはワークステーション向け</strong>のOSのことを指します。

Windows XP以降、内部バージョンはすべてNT系に統合されたため、XP以降のWindows OSはすべてNT系となります。

コマンドプロンプトはMS-DOS時代から9x系時代に使用されていた「<strong>COMMAND.COM</strong>」および「<strong>DOSプロンプト</strong>」の後継のCUIです。

COMMAND.COMはMS-DOSに標準装備されている16bitアプリのCUIです。

9x系は32bitコードでカーネルが構成されているため、9x系から直接COMMAND.COMを起動することはできませんでした。

そこで仮想86モードを利用して仮想DOSマシンを実現しています。

この9x系で提供されていた仮想DOSマシンを「DOSプロンプト」と呼びます。

コマンドプロンプトは「COMMAND.COM」に比べて

* 矢印キーでコマンドの履歴を確認
* エラー時の詳細メッセージを出力
* Tabキーを使用したファイルやフォルダパスの補完

などの機能が実装されています。


また、コマンドプロンプトは32bit版と64bit版の2つがあります。64bit版はWOW64(64bit版のOSでWin62APIを実行できるシステム)を用いて32bitも扱うことができます。

CUIは以下のように表のようにまとめられます。

| CUI | ビット | Windows OS |
|:------:|:------:|:----:|
|COMMAND.exe|16bit|MS-DOS|
|DOSプロンプト|32bit(仮想OSマシン)|Windows 9x系|
|cmd.exe(32bit版)|32bit|Windows NT系(XP~11)|
|cmd.exe(64bit版)|64bit|Windows NT系(Vista~11)|
|<strong>Powershell|<strong>32bit,64bit|<strong>Windows 7~WIndows11|

表には書いてありませんがOS/2にもDOSプロンプトとコマンドプロンプトが提供されています。OS/2とはIBMとMicrosoftが発売したPC用の16bitおよび32bitのMS-DOS後継のOSです。

# PowerShell
PowerShellはWindows 7から提供されているコマンドプロンプト後継のCUIです。

XPやVistaにも追加提供されています。

PowerShellは、.NET FrameWork(アプリケーション開発・実行環境)をベースにオブジェクト指向言語として開発されたシェル（カーネルとユーザーを繋ぐ伝達係）です。

PowerShellで用いられるコマンドは「<strong>コマンドレット</strong>」と呼ばれています。

コマンドレットはコマンドプロンプトのコマンドとは異なります。

コマンドレットを使用することでコマンドプロンプトでは難しいとされていた処理が簡単にできるようになりました。

それだけでなく、Unix系のシェルやコマンドプロンプトで使用しているコマンドがコマンドレットのエイリアスとして割り当てられています。

また、PowerShell自体がスクリプト言語であるため、PowerShell内に繰り返処理や条件分岐などの処理を実装させることができます。

PowerShellには実行ポリシーというものがあり、悪意あるスクリプトが実行できないようになっています。Powershellはコマンドプロンプトに比べ機能が大きく向上した反面、危険度も大きくなったのです。

デフォルトではRestricted(すべてのスクリプトが実行不可能)になっているため、PowerShellでスクリプトを実行したい場合、実行ポリシーを変更する必要があります。

# Windows 11でのCUI
Windows 10までは、直接コマンドプロンプトやPowerShellを開く必要がありました。

しかし、Windows11からはWindows TerminalがコマンドプロンプトやPowerShellをホストすることがデフォルト機能として実装されることになりました。

これにより、Windows Terminal1つ開けば、コマンドプロンプトとPowerShellを同時に扱いやすくなりました。

個人的にはすごくうれしいです。

# 最後に
コマンドプロンプトはPowerShellの登場によっていずれ消えてしまうのではないかと思われていましたが、

Microsoft公式からは「Windows OSとcmd.exeは依存性がとても高いため、消えることはない」としています。

どれだけOSがアップデートされてもコマンドプロンプトが消えることはないでしょう。
iOSでバックグラウンドにあるアプリがすぐにリスタートされる原因を突き止めた

どこにも解決法が載ってなかったのでここにメモ書き
# 要約
- iPhoneでアプリを切り替えると、その前のアプリがすぐにリスタート(最初の画面に戻される)する現象が起きていた
    - いちいちスプラッシュ画面を見せられ、色々厄介
- デベロッパモードをオンにし、設定→「デベロッパ」→「アプリの高速終了」をオフ
- 解決。快適なiPhoneライフが再び。

## どんな現象か
一年ほど前から、バックグラウンドに置いたアプリがすぐにリスタートされる現象が発生した。
アプリA→アプリBに切り替え、その後再びアプリAに戻ると、スプラッシュ画面(起動したときに出てくる画面)が出てくるといった具合。
その際、進捗状況がある程度戻されてしまう。

これで大きな影響を受けたのがメール認証を必要とするログインである。
複数のアプリを行き来するのでコードを確認したら、パスワードを打ち込む画面に戻されてるという現象が頻発し、ログインが神頼みレベルで絶望的になる。

#### 本来想定される動作
中断していたところから瞬時に操作できる。
例えばTwitter(現:𝕏)のタイムラインは、しばらくiPhoneを使っていなくとも再びアプリを立ち上げれば見ていた所から再開できるはず。
ゲームでも中断していたところからすぐに再開できるはずだ。
#### 環境
あんまり関係ない気がするが、念のため
- 現機種:iPhone 13
    - iOS 17 PublicBetaを導入
    
現象発生時 
- iPhone SE(第2世代)
- iOS 16.x(ここに関してはうろ覚え)



### 試したこと/想定したこと
- 再起動、強制再起動
- 立ち上げていたアプリをすべて終了
- OSの再インストール
- 「アプリのバックグラウンド更新」をオンにする
    - なおこの機能は名前はそれっぽいが全く関係ない機能らしい
- 低電力モードをオフにする
- アップルサポートに問い合わせ
    - 対応はとても丁寧だったが、電話でのサポートでも解決せず
    - ちなみにベータ版iOSを使っていて、「ベータ版はサポートはできないんです」とサポートの担当者さんに言われたがある程度対応してくれた。やさしい。
- redditなどでの情報収集
    - こういったマニアックな問題は海外の方が確かな情報を得られやすい(と思っている)
    - 調べるとほとんどが「メモリ不足です」か「バックグラウンド更新をオンにしろ」程度しか書いていない
- 外部アプリでの、メモリなどの監視
    - メモリのクリーンアップと謳う機能もあったが(笑)程度と思って信頼してない
- iOSのバグだと思って修正を待っていた
- ウイルスが入っていたという推測

## 原因と解決方法
アップルサポートのスペシャリストでも解決することが出来ず、泣き寝入りを覚悟したが、最後の望みで「デベロッパモード」の設定を見直してみることにした

デベロッパモードをオンにする方法は割愛。~~よくわかってない~~![IMG_2526.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667727/98ac7e15-ebc9-2c49-2787-399460fe4611.jpeg)
(「**プライバシーとセキュリティ**」というところからオンにできる。なのだが表示されたりされなかったり不確かなので[こちらの公式マニュアル](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device)などを参考にオンにしてほしい。がんばれ)

![IMG_2527.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667727/cd14d791-872d-271d-a7ff-737aabe20993.jpeg)
デベロッパモードをオンにすると、設定に「**デベロッパ**」という項目が追加される。
その中に「**アプリの高速終了**」というのがある(はず)ので、もしオンになっていたのなら**オフ**にしよう。

![IMG_2528.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667727/7b40c0c4-2550-1685-2af8-90b09c982213.jpeg)
(なぜオンになっていたのかは不明)

## 終わりに
本当に単純なことで驚いた。設定のたった1項目のオンオフだけで一年以上ストレスフルでスマホを使っていたとは...
こういうOSのトラブルは灯台下暗しで、案外初歩的なミスなどが引き金になってるパターンが多い気がするので、踏み込んだことをする前にアレコレ初歩的な事を見直してみるしてみるのもいいかもしれない。

もし同じような現象に悩まされているだったりする人の参考になれば幸いです。

**終わり**
【鯖缶向け】キャッシュでMisskeyの鯖が満杯になったらどうするのさ【最終手段】

:::note warn
この記事はMisskeyサーバ管理者のための最終手段を紹介するための記事です。
必ず ***コマンドの意味を理解してから実行*** してください
:::
# ・そもそもそんなこと起こすでない
筆者が運営している[みゃみーすきー](https://myamisskey.ddo.jp/)では500GBのSSDを用意しているが、VMware ESXiの都合により、常日頃から容量逼迫と戦っているものの、利便性の観点からリモートからのキャッシュを保存する設定になっているため、リアルで忙しくなった目を話した隙に満杯になってMisskeyが落ちてしまった。

Misskeyにはリモートサーバのキャッシュを削除する機能があるが、Misskeyが落ちてしまえばもはやどうしようもなく、他サーバにも迷惑が掛かってしまうため、早急な対応が求められた。

そもそも貧弱なのであればリモートサーバのキャッシュを切ればよかったのだが落ちてしまったものはしょうがない、対応が終わってから反省しよう。

# ・満杯になった時のエラーについて

落ちたら必ず以下のコマンドでエラー内容を確認しよう
```
# systemctl status example.jp
```

すると、容量満杯の場合、 **SECCOMPエラー** が出ています。
なぜSECCOMPエラーが出るかはわかりません。開発に関わっている人ならわかるかも？

# ・さあ対応しよう

### ・まずは容量の確認
```
# df -H
```
Ubuntuの ~~良くないところ~~ 制約にオートでパーティションを切るとメインのパーティションが100GBしか割り当てられないという制約がある(1敗)

そのため持て余しているパーティション未割当の個所がないか必ず確認をしよう。
もし未割り当てがあればpartedコマンドで拡張しよう。


### ・え？未割当もないって？
:::note alert
ここから下はおひとり様サーバやどうしようもなくなった時の最終手段デス
ローカル・リモート問わずキャッシュデータを吹き飛ばします。
:::
未割当もないならSSDを拡張するしかないわけだが財布的な都合でどうしようもなければそもそも早急に復旧しなければいけない場面でもある。

であれば秘儀　キャッシュ強制削除の術を行う。

:::note info
前提条件：
実行ユーザ名：Misskey
Misskeyのディレクトリ：Misskeyのホームディレクトリ
:::

Misskeyを管理者IDで動かしているバカはいないと思うので（そんな人が居ればすぐに変更しよう）、
まずは心を落ち着けるように心を込めてsuを実行する。
```
$ sudo su
```

そうしたらMisskeyのキャッシュファイルがあるところまで移動しよう。
```
# cd ../
# cd Misskey/Misskey/files
```


filesにたどり着いたら、findコマンドで一気に吹き飛ばそう。(日付はお好みで変更をどうぞ)
>コマンドの意味
>files内の55日より前のファイルをすべて削除
```
# find ./ -mtime +55 -delete
```

### ・すべてが終わったら容量の確認
```
# df -H
```

これで問題なく立ち上がるぐらい容量が空けば再起動しよう
```
# reboot
```
# ・当然だがこんなのは最終手段

この作業はローカル・リモート問わずユーザ活動により登録されたファイルを吹き飛ばす最終手段なので本来であれば、容量が枯渇する前にコントロールパネルからリモートのみキャッシュを吹き飛ばすべきである。

しかもこの手法は、他ユーザが居れば迷惑をかけ、ドライブに登録されているものをMisskeyに使っていれば登録しなおしである。

しかし、これをやらなければMisskeyが立ち上がらなければどうしようもない時は必ずやってくる。
そうならないための手法として覚えておくとよい。

:::note info
結論
・ちゃんと管理しよう
・身の丈に合った設定を行おう
・オブジェクトストレージを使おう
:::
【AWS】DynamoDB Streamのオススメの使いかた

# はじめに
髙橋愛理です！  :frog:
AWSサーバーレス開発のチームリーダーやってます。

AWSを代表するNoSQL、DynamoDB。
現在行っている開発では、こいつを使ってサーバレス開発を行っています。

今回は、私が実際開発して感じた、DynamoDB Streamのメリット、デメリット、使用方法などをまとめてみました。

# DynamoDB Streamとは
DynamoDBは、AWSを代表するNoSQLです。
DynamoDBには、DynamoDB Streamという機能があるのですが、これは、**DynamoDBにデータの更新があった際、Lambda関数を起動して非同期で処理を行うことができる**というものです。

DynamoDB Streamで起動したLambda関数では、INSERT（作成）、MODIFY（更新）、REMOVE（削除）のイベントと、新旧のデータを受け取ることができます。

# メリット
私的に、DynamoDBを使用する一番のメリットって、**コンポーネント間連携に使える**ことと、**データ変更時に一律で行いたい処理を一か所にまとめることが出来る**ことかと思います。

# デメリット
DynamoDB Streamを使うことのデメリットは、**タイムラグが生じるので結果整合性的になる**ことと、**処理が失敗した際に整合性が失われること**だと思います。

:frog: 💭
<font color="DarkOliveGreen ">よほど重たい処理でなければ、実際そこまでのタイムラグはないけどね</font>

# 実際の使用例
実際私が開発で、採用している実装例を紹介したいと思います。

### 総数の集計処理
例えば、チャットアプリの「いいね」の総数のincrement、decrementなどです。
APIから「いいね」の作成削除を行い、DynamoDB Streamで総数のincrement、decrement処理を行うことで、
**レコードの作成削除と、総数の処理を分離し、総数の処理を一か所にまとめることが出来ます**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529351/260d1938-f2b2-f290-b542-ea5c322ac414.png)
:frog: 💭
<font color="DarkOliveGreen ">DynamoDBの苦手な集計処理をカバーしてくれるよ</font>

### 機能間連携させる
いわゆるコンポーネント間連携です。
例えば、通知メッセージを格納するテーブルにDynamoDB Streamをアタッチし、
通知メッセージの作成があったら、通知送信処理を行うコンポーネント（SNS等）にメッセージを送信することで、**通知メッセージ作成と通知送信の処理の、別々のコンポーネント間を連携させることができます**。　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529351/cd1c635a-b1f5-4ae4-8697-d8a834c5a05c.png)
:frog: 💭
<font color="DarkOliveGreen">コンポーネントの疎結合化で変更に強いアーキテクチャになるね！</font>

### 関連リソースの作成削除
関連する別テーブルのリソースの作成削除を行う場合です。
ユーザが削除された時、ユーザテーブルのDynamoDB Streamから、削除されたユーザの関連リソースの削除を行うことによって、
**関連リソースの削除を待たず、ユーザ削除のレスポンスをクライアントへ返すことができます**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529351/37e17a28-e939-de31-0636-70528010c7fa.png)
:frog: 💭
<font color="DarkOliveGreen ">処理が重たくなりそうならSQSも組み合わせるよ！</font>

# 最後に
デメリットもありますが、うまく使えばそれ以上にメリットをもたらしてくれると思ってます。
あくまで私の考えや使い方ですが、参考にしていただけたら嬉しいです。
【ESPnet】dockerで日本語のTTSレシピを実行する方法【音声合成】

# はじめに
[公式ドキュメント](https://espnet.github.io/espnet/docker.html?highlight=docker)にあるDockerコンテナ内でレシピを実行する方法を試してみました。

この記事は**Dockerの環境構築が終わっている前提**で書いています。
Dockerが入っていない方は[<INS>**Install Docker Engine | Docker Document**</INS>](https://docs.docker.com/engine/install/)からインストールしてください。

また、windowsの方は「**WSL2 + Nvidia container toolkit + WSL2内にdocker**」を以下の記事を参考にそれぞれインストールしてください。

**WSL2：**[windows10,WSL2でESPNetの環境構築【音声合成】- qiita記事](https://qiita.com/seichi25/items/fc6c22042f926baa08e6#1-wsl2%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B)
**Nvidia container toolkit：**[Dockerを使って爆速でGPUを設定する方法 - qiita記事](https://qiita.com/tatsuya11bbs/items/3af03e704812b6c89965#3-nvidia-container-toolkit)
**WSL2内のdocker：**[wsl2にdocker, docker-composeをインストールする【メモ】 - qiita記事](https://qiita.com/seichi25/items/4c8d4a22ec18e4bfb771)

# リポジトリをクローンする
espnetでいろいろ学習を回すと100GBとか余裕でストレージを食うので、実験フォルダだけを別ストレージに退避しておくとか、余裕のあるストレージにcloneしておくことを推奨します。
```bash:git clone
git clone https://github.com/espnet/espnet
```
cloneするとespnetというフォルダが作成されます。

# run.shを少し変更する
おそらく日本語TTS用に書かれていないので、そのままだと「**pyopenjtalkないよ**」というエラーで処理が止まります。なので、内部のレシピ実行前にpyopenjtalkをインストールするコマンドを挿入します。
以下のように、178行目の後ろにインストールコマンドを追加する処理を書きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1604294/434b0866-a7b0-493b-b012-2410d1b42b99.png)

```bash:追加するコマンド
cmd="pip install pyopenjtalk; ${cmd}"
```

これだと学習を実行するたびに**毎回pipインストールが走る**ので、無駄な待ち時間が発生するので、もっとスマートなやり方がないか考えましたが、思いつきませんでした。有識者の方がいらっしゃったらコメントで教えていただけると助かります。

**※謎のエラーが出たとき↓**
<details><summary>Error utils/validate_data_dir.sh: text contains 7696 lines with non-printable characters</summary>
**espnet/egs2/TEMPLATE/asr1/utils/validate_data_dir.sh**
の中のnon_print判定のところでこけているので、そこを直す。これが正しい治し方なのかはわからないが、これで動く。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1604294/24ccdb3b-cdc6-abc0-ff01-b108f1ff992b.png)

```bash:置き換える行
if locale -a | grep "C.utf8" >/dev/null; then
```
</details>

# run.shを実行する
```bash:espnet/docker/
./run.sh --docker-gpu 0 --is-egs2 --docker-egs jsut/tts1
```
このコマンドで学習まで自動で実行されます。dockerイメージのpullからデータダウンロード、前処理など様々なタスクがあるので、学習が始まるまでもそこそこかかりますし、学習自体も2,3日くらいかかると思います。

# おわりに
既存のレシピでハイパーパラメータを変えて実験する程度であれば問題ないと思いますが、自作のレシピを開発する場合や既存レシピを少し変えたい場合などはこの実行方法だと不便かなと思いました。
レシピ自作マンになりたい人は、つよつよLinuxマシンに直接インストールして開発しましょう。
【flutter】xcrun: error: invalid active developer pathエラーの対処法

Macでflutterの環境構築中にこのエラーが出てきたので、対処法の備忘録です。


#### 【エラー内容】
```flutter doctor```実行時に以下のようなエラーが出てきた。
```
$ flutter　doctor

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
Doctor summary (to see all details, run flutter doctor -v):
```
#### 【原因】
MacOSをアップデートしたことによって、XCodeのxcrunコマンドが実行できなくなっている。
※筆者の場合はVenturaにアップデートしました。

#### 【対処法】
Xcodeのコマンドラインのツールがあるので、それをインストールするために以下コマンドを実行する。
```
$ xcode-select --install
```

解決です！:sunny:
Goのジェネリクス機能を試してみた

こんにちは！
Qiita初投稿です。

久しぶりにGoを触る機会あったのですが、Go 1.18からジェネリクス機能が追加されたということで、ジェネリクス機能を使って遊んでみました。

## 基本的な使い方

```Go
func display[T any](value T) {
	fmt.Println(value)
}
```
[T any]の部分が、任意の型（any）のT、を意味しています。
多くの言語では<>を使って書きますが、Goは[]なんですね。
anyはinterface{}の型エイリアスで、Go 1.18から追加されたものです。
受け取る型を制限したい場合は、下記のように書けます。

```Go
func sum[T int | float32 | float64](array []T) T {
	var result T
	for _, v := range array {
		result += v
	}
	return result
}
```
intあるいはfloatの配列を受け取って、合計値を返却する関数です。
int、float以外の配列を渡すと、型チェックでエラーになるので、安心ですね。


## Listを作ってみる
Goを使う時のつらみに、配列やスライスに少し癖があって、扱いに慣れが必要なところがありました。
そこで、ジェネリクスを使って、スライスをラップした便利Listを作ってみたいと思います。

```GO
type IList[T comparable] interface {
	Add(value T)
	Remove(value T)
	Get(idx int) T
	GetIndex(value T) int
	Replace(idx int, value T)
	Count() int
	Contains(value T) bool
	Mapper(f func(T) T)
	Filter(f func(T) bool)
	Clear()
	Display()
}
```
まずはinterfaceを定義します。
[T comparable]のcomparableですが、比較可能な型という意味で、==等で比較が使える型に制限しています。
要素の削除などを行う際に、==での値の比較が必要になったので制限をつけました。

Add...値の追加
Remove...値の削除
Get...値の取得
GetIndex...インデックス取得
Replace...値の置き換え
Count...要素数を取得
Contains...値の存在確認
Mapper...全要素に関数を適用
Filter...条件を満たす要素をフィルタリング
Clear...要素の全削除
Display...printlnで配列を出力

それではこのインターフェイスを元に実装していきます。
```Go
type List[T comparable] struct {
	list []T
}

// 要素の追加
func (l *List[T]) Add(value T) {
	l.list = append(l.list, value)
}

// 要素の削除
func (l *List[T]) Remove(value T) {
	for i, v := range l.list {
		if v == value {
			l.list = l.list[:i+copy(l.list[i:], l.list[i+1:])]
		}
	}
}

// 要素の取得
func (l *List[T]) Get(idx int) T {
	return l.list[idx]
}

// インデックスの取得
func (l *List[T]) GetIndex(value T) int {
	for i, v := range l.list {
		if v == value {
			return i
		}
	}
	return -1
}

// 要素の置き換え
func (l *List[T]) Replace(idx int, value T) {
	l.list[idx] = value
}

// 要素数の取得
func (l *List[T]) Count() int {
	return len(l.list)
}

// 要素の存在確認
func (l *List[T]) Contains(value T) bool {
	for _, val := range l.list {
		if val == value {
			return true
		}
	}
	return false
}

// 全要素に関数適用
func (l *List[T]) Mapper(f func(T) T) {
	for i, v := range l.list {
		l.list[i] = f(v)
	}
}

// 条件を満たす要素のみフィルタリング
func (l *List[T]) Filter(f func(T) bool) {
	for i, v := range l.list {
		if !f(v) {
			l.list = l.list[:i+copy(l.list[i:], l.list[i+1:])]
		}
	}
}

// 要素の全削除
func (l *List[T]) Clear() {
	l.list = []T{}
}

// Printlnで表示
func (l *List[T]) Display() {
	fmt.Println(l.list)
}
```

このListを取得するための関数を用意します。

```Go
func CreateList[T comparable]() IList[T] {
	return &List[T]{}
}
```

使い方はこんな感じです
```Go
func main() {
    // float64でリストを作成
	list := CreateList[float64]()

    // 値を追加
	list.Add(1.0)
	list.Add(3.14)

	fmt.Println(list.Count())
	fmt.Println(list.Get(0))

    // 全要素を３倍してみる
	list.Mapper(func(f float64) float64 {
		return f * 3
	})

    list.Display()

    // 5.0より小さい数は削除する
	list.Filter(func(f float64) bool {
		return f > 5.0
	})

	list.Display()

    // リストのクリア
	list.Clear()
}
```

楽しく実装できました！
ジェネリクスを使って、汎用的なライブラリを作ってみたいですね。

気になるところとしては、ジェネリクスを多用するとコンパイルや実行が遅くならないかというところです。
これについては、またいつか調査してみたいと思います。
デバイス追加～RAID1を構成するの手順(mdadm)

# デバイス追加～RAID1を構成するの手順


現在のディスクを確認
```
ls /dev | grep sd
```

仮想ディスクを２つ追加。構成システムを確認。
```
df -h
```

追加されたディスクを確認
```
ls /dev | grep sd
```



mdadmをインストール
```
apt -y install mdadm
```

RAID1を構築
```
mdadm --create /dev/md0 --level=raid1 --raid-devices=2 /dev/sdb1 /dev/sdc1
```

状態確認 ( [UU]となっていればOK )
```
cat /proc/mdstat
```

ディスクのサイズが大きくなればなるほど時間がかかります
`[_U]`から`[UU]`に変化すれば構築完了です
```
Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5] [raid4] [raid10] 
md0 : active raid1 sda1[2] sdb1[1]
      20952064 blocks super 1.2 [2/1] [_U]
      [>....................]  recovery =  3.8% (801536/20952064) finish=1.2min speed=267178K/sec

Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5] [raid4] [raid10] 
md0 : active raid1 sda1[2] sdb1[1]
      20952064 blocks super 1.2 [2/1] [_U]
      [======>..............]  recovery = 30.1% (6325632/20952064) finish=1.1min speed=204052K/sec

Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5] [raid4] [raid10] 
md0 : active raid1 sda1[2] sdb1[1]
      20952064 blocks super 1.2 [2/2] [UU]
```

再起動するとデバイス名が変更されることがある。それを回避するためにデバイス名の定義を設定ファイルに追加する。
```
sudo mdadm --detail --scan
```

出力される結果を/etc/mdadm/mdadm.confに貼り付ける。
情報を更新
```
sudo update-initramfs -u
```

OS再起動
```
sudo reboot
```

現在利用できるブロックデバイスを一覧表示する
```
lsblk
```
streamlitでタイトルと文章を表示する

# プログラムを作成
```python: main.py
import streamlit as st

st.title("Streamlit 超入門")

st.write("sample test")
```

# 実行結果
`streamlit run <実行したいファイル名>`を実行すれば画面が起動される
```
PS E:\作品\streamlit_practice> streamlit run main.py  

  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://<IPアドレス>:8501
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/354dbc47-d59c-04b8-c85f-1756a3d322e4.png)
Go 言語 interface 使い方のおぼえがき

```
package main

import (
    "fmt"
)

type IF interface {
    setter(any)
    getter() (any)
}

type struct_aaa struct {
    prop   string ;
}

type struct_bbb struct {
    prop   int ;
}

func (ctx *struct_aaa) setter(arg any){
    ctx.prop = arg.(string) ;
}

func (ctx *struct_aaa) getter() (any){
    return ctx.prop ;
}

func (ctx *struct_bbb) setter(arg any){
    ctx.prop = arg.(int) ;
}

func (ctx *struct_bbb) getter() (any){
    return ctx.prop ;
}

func set(i IF,v any){
    i.setter(v) ;
}

func get(i IF) (any){
    return i.getter() ;
}

func case_01(){
    fmt.Printf("case_01\n") ;
    var a struct_aaa ;
    var b struct_bbb ;

    var if_a IF = &a ;
    var if_b IF = &b ;

    set(&a,"ABC")
    set(&b,123)

    fmt.Printf("A-1-1[%V]\n",a.prop) ;
    fmt.Printf("B-1-1[%V]\n",b.prop) ;

    fmt.Printf("A-1-2[%V]\n",get(&a)) ;
    fmt.Printf("B-1-2[%V]\n",get(&b)) ;

    fmt.Printf("A-1-3[%V]\n",if_a.getter()) ;
    fmt.Printf("B-1-3[%V]\n",if_b.getter()) ;
}

func case_02(){
    fmt.Printf("case_02\n") ;
    var if_a IF = &struct_aaa{} ;
    var if_b IF = &struct_bbb{} ;

    if_a.setter("xyz") ;
    if_b.setter(222) ;

    fmt.Printf("A-2[%V]\n",if_a.getter()) ;
    fmt.Printf("B-2[%V]\n",if_b.getter()) ;
}

func case_03(){

    fmt.Printf("case_03\n") ;

    var if_a IF = &struct_aaa{"ppp"} ;
    var if_b IF = &struct_bbb{333} ;

    fmt.Printf("A-3[%V]\n",if_a.getter()) ;
    fmt.Printf("B-3[%V]\n",if_b.getter()) ;
}

func main(){

    case_01() ;
    case_02() ;
    case_03() ;

    fmt.Printf("Done.\n") ;
}
```

実行結果
```
case_01
A-1-1[%!V(string=ABC)]
B-1-1[%!V(int=123)]
A-1-2[%!V(string=ABC)]
B-1-2[%!V(int=123)]
A-1-3[%!V(string=ABC)]
B-1-3[%!V(int=123)]
case_02
A-2[%!V(string=xyz)]
B-2[%!V(int=222)]
case_03
A-3[%!V(string=ppp)]
B-3[%!V(int=333)]
Done.
```
Pythonで画像のdicomタグを表示してみた

# 完成イメージ
画面にdicomファイルをドラッグアンドドロップした際にdicomファイルの画像を表示し、タグを表示するようなプログラムを作りたい


# プログラムを作成
powershellを経由しないとdicomファイルをウィンドウにドラッグアンドドロップできないのでps1ファイルを作成
```powershell: excute_python.ps1
python .\dicomFileReader.py
```

```python: dicomFileReader.py
from tkinter import *
from tkinterdnd2 import *

from matplotlib import pyplot as plt
import pydicom

def text_view(event):
    # テキストを初期化
    textarea.delete("1.0","end")
    
    try:
        # dicomファイルを読み込み
        ds = pydicom.dcmread(event.data,force=True)
        # 画像を表示するためにdicomファイルを読み込み
        cr = pydicom.read_file(event.data)

        # dicomファイルの情報をテキストウィジェットに表示
        textarea.insert("end", ds)

        # 画像を表示
        plt.imshow(cr.pixel_array, cmap = "gray")
        plt.show(block=False) # block=Falseを指定しないとドラッグ状態が解除されない
    except pydicom.errors.InvalidDicomError:
        # dicomファイルが読み込めない場合
        textarea.insert("end", "dicomファイルの情報を読み込めませんでした。")

# メインウィンドウの生成
root = TkinterDnD.Tk()
root.title('DICOMファイルリーダー')
root.geometry('800x500')
root.config(bg='#66ffff')

# フレームウィジェットの生成
frame = Frame(root)

# テキストウィジェットの生成
textarea = Text(frame, height=500, width=800, font=("", "12", "")) # フォントの大きさを調整
textarea.drop_target_register(DND_FILES)
textarea.dnd_bind('<<Drop>>', text_view)
textarea.insert("end", 'DICOMファイルをここにドラッグ＆ドロップしてください。タグ情報と画像が表示されます。')

# スクロールバーの生成
scroll = Scrollbar(frame, orient=VERTICAL)
textarea.configure(yscrollcommand=scroll.set)
scroll.config(command=textarea.yview)

# ウィジェットの配置
frame.pack()
textarea.pack(side=LEFT)
scroll.pack(side=RIGHT, fill=Y)

root.mainloop()
```


# 実行イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/c455a548-d5ed-96ca-6d09-359f19307580.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/37661d1d-7e4b-0a4d-fe7c-3f6f7aa523a3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/ef316392-b534-b70d-7a63-78f691b3bfb1.png)
【Swift】iOS15以降のTableViewでsectionの上に空白ができる問題

## 概要
**※UIKitの話です**
iOS15以降のTableViewでは、デフォルトでsectionの上に謎の空白が生まれてしまうようです。
iOS17が発表されているぐらいなので2023にしては今更すぎる内容ですが、備忘録として記事にしています。
以下、問題の起きた画面をDebug View Hierarchyで確認しカーソルを当てた緑色の部分が該当の空白です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/333791/87e2ba5b-f9f3-8f33-61b8-5e5de5eafadf.png" width="50%">
※実際に画面に表示している文字や写真等は隠して適当なテキストに置き換えている状態のスクリーンショットです

## 背景
以前リリースしたアプリのレイアウトが崩れているのを発見し、かなり前にiOS15対応として行うべき問題にやっと手をつけました...

## 対処
```swift
tableView.sectionHeaderTopPadding = 0.01
```
iOS15より追加されたプロパティsectionHeaderTopPaddingにより、デフォルトで余白がつくようになっているため値を0にすることで対応しました。
余白0の場合はスクロールするとsectionが押し出されるように動くのに対し、0.01の場合はsectionのテキストが入れかわるように表示されます。0.01の方が自然な動きだと思いました。

## 学んだこと
たった一行加えるだけでしたが、趣味の範囲で作ったアプリではあるもののアプリを使ってくださるユーザーがいる以上、アップデートをいかにしっかりキャッチアップし運用することが大事か痛感しています。直近ではiOS17が控えているので、急いでキャッチアップしたいと思います💨

お読みいただき、ありがとうございました。
自動で秘密鍵のセットを生成する

# プログラムを作成
```powershell: create_ssh.ps1
ssh-keygen -f ./id_rsa
```

# 実行イメージ
```
PS E:\作品\develop\ssh> .\create_ssh.ps1
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

<省略>

PS E:\作品\develop\ssh> ls


    ディレクトリ: E:\作品\develop\ssh


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2023/09/03      0:23             38 create_ssh.ps1
-a----        2023/09/03      0:23           2610 id_rsa
-a----        2023/09/03      0:23            576 id_rsa.pub
```
【AWS WAF】なぜか本番環境だけ403エラーが発生

実務で扱っているプロダクトが、添付ファイル付きのマスタデータの保存時に403エラーを返すようになっていました。
ローカルや検証環境では再現せず・・・長期戦を覚悟しかけていたところ、本番環境でのみ導入しているAWS WAFのマネージドルールが原因であることが分かりました。


## 今回の教訓
 - AWS WAFのマネージドルールによって正常なリクエストが意図せずにブロックされてしまう場合がある。
 - マネージドルールの`SizeRestrictions_BODY`が有効な場合、リクエストボディが8KBを超えるリクエストは全てブロックされるため要注意。


## 原因特定までの経緯
ファイル添付機能があるデータの保存時には毎回403が発生していたため、S3の認証周りの不備か？と探っていましたが、ファイル添付なしでもPOSTするデータのサイズが比較的大きい場合には403が発生することに気づきました。
そして同様の事例がないか調べていたところ、よくある事例だそうで・・・

AWS WAFのマネージドルール`SizeRestrictions_BODY`が原因と判明しました。

## AWS WAFのマネージドルール
AWS WAFでは、いちいち利用者側でアクセスの許可/ブロックのルールを定義せずとも、AWSがデフォルトで用意してくれているルールセット、マネージドルールを適用することができます。その一つが`AWSManagedRulesCommonRuleSet`です。

https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/aws-managed-rule-groups-baseline.html

この`AWSManagedRulesCommonRuleSet`には一般的によくある不正アクセスから守ってくれるルールが多数定義されており、その中の一つに`SizeRestrictions_BODY`というものがあります。これによりリクエストボディが8KBを超えるリクエストは全てブロックされてしまっていました。

## 対策
リクエストボディが8KBを超えうるURIが非常に多く存在するため、今回はこの`SizeRestrictions_BODY`というルールを無効にする対応を取りました。8KBを超えうるURIが少数の場合はスコープダウンステートメントを使用してWAFの検査対象外とすることもできるそうです。

## 補足
調べたところ、WAFによって意図せずに正常なリクエストをブロックしてしまう事例は`SizeRestrictions_BODY`以外にもあるみたいです。（特にファイルアップロード系で引っかかることが多いそう）
適用しているルールセットは把握しておくようにします。
【Rails】rakeコマンドで定型作業をコマンド化する

:::note info
Rubyのrakeコマンドの基礎についてまとめてみました。
:::

### rakeコマンドとは
**rakeコマンド**はタスクを実行するためのタスクランナーで、実行する処理を**Rakeタスク**と呼ぶ。主にリリース作業などの定型処理をRakeタスクとして定義する。
DBの管理を行ったり、独自のタスクを作成することができる。


### タスクの作成方法
'Hello, Rake!'が出力されるhelloタスクを作成する
```ruby:Rakefile
desc 'Hello, Rake Task'
task :hello do
    puts 'Hello, Rake!'
end
```
|  | 用途 |
|:-----------|:------------|
| desc '...'   |タスクの一覧を表示する際に、どのようなタスクか説明するための記述        |
| task :task_name do ... end  | タスクの名称と実行する内容        |


##### タスク一覧の表示
```:
% rake -T
rake hello # Hello,Rake Task
```
`rake`コマンドはカレントディレクトリ内の`Rakefile`というファイルを参照する。
もし別のファイル名で記述されたRakeタスクを読み込む場合は`-f`オプションで明示する必要がある。

#### タスクの実行
```:
% rake　hello
Hello,Rake！
```
想定通りに文字列が出力される。


### Railsで標準で用意されたRakeタスク
Railsでは標準で様々なRakeタスクが用意されており、データベースの作成からテストの実行などの処理を自動化できるようになっている。
```:
> rails -T
rails about                              # List versions of all...
rails action_mailbox:ingress:exim        # Relay an inbound ema...
rails action_mailbox:ingress:postfix     # Relay an inbound ema...
rails action_mailbox:ingress:qmail       # Relay an inbound ema...
rails action_mailbox:install             # Installs Action Mail...
rails action_mailbox:install:migrations  # Copy migrations from...
rails action_text:install                # Copy over the migrat...
rails action_text:install:migrations     # Copy migrations from...
rails active_storage:install             # Copy over the migrat...
rails app:template                       # Applies the template...
rails app:update                         # Update configs and s...
rails assets:clean[keep]                 # Remove old compiled ...
rails assets:clobber                     # Remove compiled assets
rails assets:environment                 # Load asset compile e...
rails assets:precompile                  # Compile all the asse...
rails cache_digests:dependencies         # Lookup first-level d...
rails cache_digests:nested_dependencies  # Lookup nested depend...
rails db:create                          # Creates the database...
rails db:drop                            # Drops the database f...
rails db:encryption:init                 # Generate a set of ke...
rails db:environment:set                 # Set the environment ...
rails db:fixtures:load                   # Loads fixtures into ...
rails db:migrate
```
AccountManagerのsetPasswordでAuthTokenがInvalidateされる

[AccountManager](https://developer.android.com/reference/android/accounts/AccountManager)は、アカウントのアクセストークンやパスワードを保存するための機能を提供しています。具体的には、`setAuthToken`や`setPassword`などのメソッドが用意されています。

しかし、これらの情報を更新する際に、`setAuthToken`を実行した後に`setPassword`を行った場合、`getAuthToken`メソッドで設定したアクセストークンが取得できない問題が発生しました。一方、`setPassword`を行った後に`setAuthToken`を実行すると、アクセストークンが問題なく取得できるため、`setPasswordを`実行すると、`authToken`が`invalidate`（無効化）されている可能性が考えられます。
何回でゾロ目🎲x6がそろうかをPowershellに計算させてみた

# プログラムを作成
```powershell: dise_challenge.ps1
$same_num_dise = @();
#DISE NUMBER IS SAME
for ( $i=1; $i -le 6; $i++ )
{
	$same_num_dise += @($i,$i,$i,$i,$i,$i);
}
$game_count = 1;
$is_game_clear = $TRUE;

Write-Output "GAME START";
:OutLoop while (!($same_num_dise.Contains($dise_array)))
{	
	#CREATE DISE
	$dise_array = @();
	for ( $i=1; $i -le 6; $i++ )
	{
		$dise_array += Get-Random -Minimum 1 -Maximum 7;
	}

	#CHEAK IF DISE NUMBER IS SAME
	for ( $i=1; $i -le 6; $i++)
	{
		$same_num_count = ($dise_array | Where-Object {$_ -eq $i}).Count;
		if ($same_num_count -eq 6)
		{
			$is_game_clear = $TRUE;
			break OutLoop
		}
	}
	$game_count += 1;
}

Write-OutPut "======================"
Write-OutPut "SUMMARY"
Write-OutPut "======================"
if ($is_game_clear)
{
	#GAME CLEAR
	Write-Output "RESULT: Clear!!"
	Write-Host "NUMBER OF ATTEMPT: " $game_count;
	Write-Host "DISE RESULT: "  $dise_array;
}
Write-OutPut "======================"

Write-Output "GAME END";

```

# 実行イメージ
運が良ければ1000回未満でそろうが、悪ければ3000回目くらいでそろう
```
PS D:\test\dise_challenge> .\dise_challenge.ps1
GAME START
======================
SUMMARY
======================
RESULT: Clear!!
NUMBER OF ATTEMPT:  3468
DISE RESULT:  3 3 3 3 3 3
======================
GAME END
```
「ビーフ🐄 or チキン🐔？」をPowershellで表示する


```powershell: AskBeefOrChicken.ps1
# アセンブリの読み込み
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# フォームの作成
$form = New-Object System.Windows.Forms.Form
$form.Text = "Question"
$form.Size = New-Object System.Drawing.Size(260,150)

# Chickenボタンの設定
$ChickenButton = New-Object System.Windows.Forms.Button
$ChickenButton.Location = New-Object System.Drawing.Point(40,70)
$ChickenButton.Size = New-Object System.Drawing.Size(75,30)
$ChickenButton.Text = "Chicken"
$ChickenButton.Add_Click({[System.Windows.Forms.MessageBox]::Show("Chicken is selected", "Result")})

# Beefボタンの設定
$BeefButton = New-Object System.Windows.Forms.Button
$BeefButton.Location = New-Object System.Drawing.Point(130,70)
$BeefButton.Size = New-Object System.Drawing.Size(75,30)
$BeefButton.Text = "Beef"
$BeefButton.Add_Click({[System.Windows.Forms.MessageBox]::Show("Beef is selected", "Result")})

# ラベルの設定
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,30)
$label.Size = New-Object System.Drawing.Size(250,20)
$label.Text = "Would you like to eat Chicken or Beef?"

# ボタン等をフォームに追加
$form.Controls.Add($ChickenButton)
$form.Controls.Add($BeefButton)
$form.Controls.Add($label)

# フォームを表示させ、その結果を受け取る
$result = $form.ShowDialog()

switch($result){
    "Cancel"{Write-Output 'Nothing'}
}
```

# 実行結果
## 「ビーフ🐄 or チキン🐔？」が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/bfcd431f-ef14-9c87-0326-ed7fc485f125.png)

## ビーフを選択した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/a978c822-5b81-680c-6f00-c1c5a4fb5734.png)

## チキンを選択した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/b0c52699-85cf-7814-c64f-8847693c266d.png)
LINE Notifyでごみの日を通知するプログラムを作ってみた


# 通知プログラムを作成する
以下のプログラムを実装します。
- ごみの日を判定する処理
- LINE Notifyに命令を投げる処理

```python: notify_trash_day.py
import datetime
import requests
from calendar import Calendar

def numOfWeek(target_day: datetime.date) ->int :
  """第何回目の曜日なのかを取得する

  Args:
      today (datetime.date): 対象の日付

  Returns:
      int: 第何回目
  """
  return (target_day.day-1)//7 + 1

def send(message: str):
  """LINE Notifyでメッセージを通知する

  Args:
      message (str): メッセージ
  """
  url = "https://notify-api.line.me/api/notify"
  #公開用のトークルーム: 
  access_token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # <----ここにAPIキーを入力
  headers = {'Authorization': 'Bearer ' + access_token}
  payload = {'message': message}
  requests.post(url, headers = headers, params = payload)

def garbage_day(target_day: datetime.datetime) -> str:
  """対象の日時のごみの種類を取得する

  Args:
      target_day (datetime.datetime): 対象の日時

  Returns:
      str: ごみの種類
  """
  # 曜日を取得
  weekday = target_day.weekday()
  # 第何回目の曜日なのかを取得
  week_num = numOfWeek(target_day)

  if weekday == 1 or weekday == 4:#1:火曜、4:金曜
    return '生活ごみ'
  elif (weekday == 0) and ((week_num == 2) or (week_num == 4)):#0:月曜、2・4回目
    return '缶・ビン'
  elif (weekday == 0) and (week_num == 1):#0:月曜、1回目
    return '紙・ペットボトル'
  elif (weekday == 0) and (week_num == 3):#0:月曜、3回目
    return 'ペットボトル'
  elif weekday == 3:#3:木曜
    return 'プラ容器包装'
  elif (weekday == 5) and (week_num == 3):#5:土曜、3回目
    return '小型金属・スプレー缶'
  else:
    return 'なし'

if __name__ == "__main__":
  notify_message_list = []

  # 本日のごみの種類を取得
  today_garbage_type = garbage_day(datetime.datetime.today())
  if today_garbage_type == 'なし':
    notify_message_list.append("今日はごみの日、なし")
  else:
    notify_message_list.append("今日は" + today_garbage_type + "の日")

  # 明日のごみの種類を取得
  tomorrow_garbage_type = garbage_day(datetime.datetime.today() + datetime.timedelta(days=1))    
  if tomorrow_garbage_type == 'なし':
    notify_message_list.append("明日はごみの日、なし")
  else:
    notify_message_list.append("明日は" + tomorrow_garbage_type + "の日")

  # ごみの種類のメッセージをLINE Notifyで通知
  notify_message_list[0] = "\n" + notify_message_list[0]
  notify_message = "\n".join(notify_message_list)
  send(notify_message)
```


# プログラムを実行するサーバを用意する
おすすめは`ラズベリーパイ`です。
小さいプログラムを定期実行させるためだけなら、ラズパイで事足ります。
標準でPythonがインストールされていますし、amazonで探せば約2万円くらいでセット購入できます。



# プログラムをを定期実行するように設定する
crontabコマンド（Windowsでいうタイムスケジューラみたいな機能）を使います。
## エディタを起動
```
# Linux初心者におすすめのエディタはnano
crontab -e
```

## 時間を設定する
毎日6時と18時に実行したい場合は以下のように行を追加します。
空白は半角スペースで入力しないと実行されません。
通知プログラムは`/home/test/notify_trash_day.py`にある想定です。
```
0 6 * * * python /home/test/notify_trash_day.py
0 18 * * * python ./home/test/notify_trash_day.py
```

# 実行イメージ
実行されたらこんな感じに表示されます。
トークルームに`LINE Notify`ユーザを追加するのをお忘れなく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456441/5dd39ad5-77bf-677d-e1fe-cffa5fb78034.png)
【TailwindCSS】アニメーションを設定する方法


## 作成方法
`tailwind.config.js`に以下のように記述することでアニメーションを作成できます。

```js:tailwind.config.js
module.exports = {
 // ...
  theme: {
    extend: {
      animation: {
        'scale-in-center':
          'scale-in-center 0.25s cubic-bezier(0.250, 0.460, 0.450, 0.940) both',
        'scale-out-center':
          'scale-out-center 0.25s cubic-bezier(0.550, 0.085, 0.680, 0.530) both',
      },
      keyframes: {
        'scale-in-center': {
          '0%': {
            transform: 'scale(0)',
            opacity: '1',
          },
          to: {
            transform: 'scale(1)',
            opacity: '1',
          },
        },
        'scale-out-center': {
          '0%': {
            transform: 'scale(1)',
            opacity: '1',
          },
          to: {
            transform: 'scale(0)',
            opacity: '1',
          },
        },
      },
    },
  },
}

```


## 参考

https://tailwindcss.com/docs/animation#customizing-your-theme

https://tail-animista.vercel.app/play/basic/scale-up/scale-up-center
MS365のGraphAPIで高度なクエリを使ってユーザー検索をする

## はじめに
MS365のGraph APIを使ってユーザー検索を行いたかったけど地味にハマったのでまとめました。


### やりたかったこと
Azure ADに登録されているユーザーの中から
特定の「companyName(会社名)」でfilterして
そこから「displayName」で検索した結果を表示する。


### Graph Explorer　で試してみる
公式ドキュメント見ながらクエリの使い方を調べつつ

https://developer.microsoft.com/en-us/graph/graph-explorer

上記のGraphExplorerで実行してみる。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916357/cb81880e-590d-c3ce-82f3-5b788bae9b25.png)

右上のアカウントアイコンからサインインすれば自分が使用しているAADテナント内で実際にAPIの実行が行えるので
めちゃめちゃ便利です。


### 実行したリクエスト
```
https://graph.microsoft.com/v1.0/users?$filter=companyName eq 'Qiita' &$search="displayName: nami"
```
上記のリクエストで会社名が「Qiita」の中からdisplayNameが　「nami」であるオブジェクトを検索します。


上記のリクエストをgraph Explorer上で実行すると

```
{
    "error": {
        "code": "Request_UnsupportedQuery",
        "message": "Request with $search query parameter only works through MSGraph with a special request header: 'ConsistencyLevel: eventual'",
        "innerError": {
            "date": "2023-09-02T10:28:45",
            "request-id": "7389c0cf-fe86-468c-9e3d-d76526cefb24",
            "client-request-id": "7389c0cf-fe86-468c-9e3d-d76526cefb24"
        }
    }
}
```

上記の様なレスポンスが返ってきて正しくオブジェクトが取得できませんでした。



### 原因

レスポンスのメッセージに
「Request with search query parameter only works through MSGraph with a special request header: 'ConsistencyLevel: eventual'」
と書かれている通り
$searchクエリパラメータを持つリクエストは、特別なリクエストヘッダ: 'ConsistencyLevel: eventual'を付与する必要があるみたいです。

なので指示通りに
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916357/0dc46cda-ad8f-29c2-1768-4ee9a477fea7.png)

こんな感じでリクエストヘッダーを付与してあげます。

するとうまく該当のオブジェクトを取得できました。


### なぜ必要？
GraphAPIで高度なクエリを実行する場合に必要みたいです。
何が高度なクエリになるのかは以下の公式ドキュメントで確認してください。

https://learn.microsoft.com/en-us/graph/aad-advanced-queries?tabs=http

今回のケースではsearchを使うために必要だったみたいです。

ConsistencyLevel: eventual　が何者かについてはchatGPTがわかりやすく教えてくれました。



ConsistencyLevel: eventualが必要とされるのは、Microsoft Graph APIの特定の操作において、データの一貫性を確保するためです。具体的には、以下のシナリオや理由が考えられます：

分散データベースの性質: Microsoft Graph APIの背後には、分散データベースやストレージシステムが存在します。これらのシステムでは、データは複数の場所やノードにコピーされることが一般的です。このような環境では、すべてのノードが同時に更新されることは難しく、一貫性の確保が課題となります。

search クエリパラメータ: Microsoft Graph APIでの検索操作（$searchクエリパラメータを使用するもの）は、特にデータの一貫性に関する要件が厳しいです。これは、検索インデックスやキャッシュが関与するため、最新のデータがすぐには反映されない可能性があるからです。

パフォーマンスと一貫性のトレードオフ: ConsistencyLevel: eventualを指定することで、クライアントは最終的な一貫性を受け入れることを示します。これにより、APIは高速なレスポンスを提供できる一方、データが最新であることは保証されません。これは、パフォーマンスと一貫性の間のトレードオフとして考えることができます。

明示的な一貫性の指定: 一部のAPI操作では、クライアントが期待する一貫性のレベルを明示的に指定することが求められます。これにより、クライアントとサーバーの間での期待値の不一致や誤解を防ぐことができます。



要するこのリクエストヘッダーはGraphAPIの特定の操作（特に検索操作）の際にデータが最新じゃない可能性がある
ことを承知してます！！って宣言するために付与するものらしいです。
勉強になりますね。



### 補足情報
companyNameの取得にはUser.Read.Allのパーミッションが必要です。
ちなみに
複数の検索条件でどれかにマッチしたオブジェクトを取得したい場合は以下のように記述します
```
https://graph.microsoft.com/v1.0/users?$filter=companyName eq 'Qiita' &$search="displayName: nami" OR "mail:nami"
```

上記のリクエストだと
会社名　Qiitaの中からsidplayNameに「nami」　を含む、またはmailに「nami」を含むオブジェクトを検索します。

## まとめ
Graph API面白いですね。
【Git】addとcommitを同時に行う

## git commit -am
```
$ git commit -am "add test"
```

これでgit add と git commit -m を一気に行うことができるらしい。
ちなみに、このgit commit -am の aはallの頭文字らしい。

いつもは、git add . をしてからgit commit -m "*******"をしていたが、今度からgit commit -am を使ってみよう。
【Git】特定のコミットだけの変更を取り込みたい

# はじめに
cherry-pickというものを使用して、特定のコミットだけの変更を現在のブランチに取り込むやりかたを学んだので記録しておきます

# やりかた
```:ターミナル
git log --oneline
```

```:これがログ
db17d56d sample
57c1d808 sample
16913012 sample
a63757bb sample
d4b57338 sample
```

```:ターミナル
git cherry-pick db17d56d
```

# おわり
めっちゃ便利
基本的なDockerコマンド

# 基本的なDockerコマンド
よく使うDockerコマンドをまとめる
```docker
# コンテナの一覧を確認する
docker ps

# コンテナを起動する
docker start <コンテナのIDまたはコンテナ名>

# コンテナを停止する
docker stop <コンテナのIDまたはコンテナ名>

# コンテナを削除する
# コンテナを起動したときに「同じ名前のコンテナあるから消してね」と言われたときによく使う
docker rm <コンテナのIDまたはコンテナ名>

# 環境変数があっているか確認する
docker inspect <コンテナのIDまたはコンテナ名>

# コンテナの実行ログを確認する
# アプリが起動しない際に原因を探る際に使用する。通信ログなどを確認したい場合など。
# "-f"オプションはリアルタイムに表示したい場合に使用する
docker log <コンテナのIDまたはコンテナ名> -f
```
Raspberry Piにsnapをインストール

## 目的
ラズパイにsnapで管理されるソフトを入れたい。しかし、ラズパイにはsnapは入っていないので、今回はラズパイにsnapを入れたいと思う。

## インストール
```bash
sudo apt install snapd
```
## お作法
snapをインストールした場合は以下も実行しなければならない様です。
```bash
sudo snap install core
sudo snap install snap-store
```
CASAのpipelineが読み込めない時

CASA 6.1.1.15でパイプラインキャリブレーションをかけるとき、
```
ERROR: Pipeline not available. Make sure you start CASA with option --pipeline to activate the pipeline.
```
というエラーが出て止まった。もう少し詳しく見ると、どうやら
```python
import pipeline
```
が
```
 libssl.so.10: cannot open shared object file: No such file or directory
```
というようなエラーで止まっているらしい。```libssl.so.10```がなんなのかはよく知らない(OpenSSL関係。どこで使ってるんだろう)が、だいじなものらしい。

このインストール方法はググるとヒットするが、あまりうまくいかなかった。が、たまたま見つけたこの中国語のページの内容で一発だった。

https://henchat.net/找不到libssl-so-10的终极解决方案-ubuntu-18/

やってることはopensslの公式サイトからソースを落として自分でmakeしてインストールしてるだけだが、他に見当たらなかったのでとてもありがたい。

一応ここにも転記する:

```shell
wget https://www.openssl.org/source/old/1.0.1/openssl-1.0.1e.tar.gz
tar -zxvf openssl-1.0.1e.tar.gz
cd openssl-1.0.1e
./config shared zlib-dynamic
make
```
の後に
```
cp libssl.so.1.0.0 libcrypto.so.1.0.0 /lib/x86_64-linux-gnu
cd /lib/x86_64-linux-gnu
sudo ln -s libssl.so.1.0.0 libssl.so.10
sudo ln -s libcrypto.so.1.0.0 libcrypto.so.10
```
【GitHub Actions】トリガー`paths`の条件の指定がうまくいかない

## はじめに
- Github Actionsのワークフローが走る条件として、特定のディレクトリ配下のファイル変更が含まれている場合のみに動くようにしたいがうまくいかなかった。

## 問題点
- pathsによるディレクトリ指定を行うと、指定したパスに対する変更がトリガーとなるため、"api/**"のように指定するとapi配下のファイルが含まれている場合のみワークフローが動くようになるはずなのだがうまくいかない。
    - 具体的には、空コミットでもワークフローが動いたり他のディレクトリ以下の変更のみのときも動いてしまったり動かなかったり挙動が不安定でした、、、。

## 試したこと(現状)
- pathsの追加
```yml

name: "do PHPUnit"

on:
  pull_request:
    types: [opened, reopened, synchronize]
    // ここを追加
    paths:
      - "api/**" # api配下の変更が含まれている場合のみ実行

jobs:
//...(略)
```

## 解決策
- `pull_request`を`push`に変更

```yml
name: "do PHPUnit"

on:
  push: // ここを変更
    paths:
      - "api/**" # api配下の変更が含まれている場合のみ実行

jobs:
//...(略)
```

## 結果
- 無事api配下のファイルの変更がある場合にのみワークフローが走るようになった。

## 終わりに
なぜpull_requesだとうまくいかないかはわかりませんが、今回pushで問題なさそうなので今回の変更で対応することにしました。

### `push`と`pull_request`のトリガーされるタイミングの違い。

- **push**
    - リポジトリへのブランチやタグのプッシュ時にトリガー
- **pull_request**
    - プルリクエストの活動（作成、更新、同期、再オープンなど）に関連するイベント時にトリガー

### 動作の文脈

- **push**
    - 実行はプッシュされたブランチのコンテキスト
- **pull_request**
    - 実行はマージが行われた後のコード（すなわち、PRのブランチがベースブランチにマージされた状態）のコンテキスト
lua の unpack

微妙にわかりにくいので整理いたします。

:::note

lua-5.2 から unpack は消えて table.unpack になりました。

http://www.lua.org/manual/5.2/manual.html#8

> Function unpack was moved into the table library and therefore must be called as table.unpack.

:::

|impl|lua version|
|-|-|
|nvim|5.1(luajit)|
|wezterm|5.4|
|nyagos|5.1(gopher lua)|
|MoonSharp|5.2|

# unpack は table から関数の多値 return への変換である

`multiple result`

https://www.lua.org/manual/5.1/manual.html#pdf-unpack

unpack はそもそも何をしているのかというと、table を多値 return している。

## lua の関数引数と戻り値は stack 経由

lua からは、関数呼び出しの stack は見えないのだけど C の方はこうなっている。

```lua
function f(1, 2, 3)
  --- stack3 3
  --- stack2 2  
  --- stack1 1 👈 引数が stack につまれた状態で関数に入る

  --- 関数本体

  return 4, 5, 6
  --- stack6 6
  --- stack5 5
  --- stack4 4 👈 戻り値を stack に上乗せして関数から戻る
  --- stack3 3
  --- stack2 2  
  --- stack1 1
end
```

unpack の動作イメージ 👇

```lua
function unpack(t)
  --- stack1 t = {1, 2, 3} 👈 引数が stack につまれた状態で関数に入る

  --- 関数本体

  return t[1], t[2], t[3]  
  --- stack4 t[1] = 3
  --- stack3 t[2] = 2
  --- stack2 t[3] = 1 👈 戻り値を stack に上乗せして関数から戻る
  --- stack1 t = {1, 2, 3}
end
```

複数戻り値は複数代入で受けれるので、unpack と 複数代入を組み合わせることで、
table を分解するように動く。

```lua
local t = {1, 2, 3}
local n1, n2, n3 = table.unpack(t)
```

:::note
プログラミング用語的には、多値、多値返却というらしい？。
:::

:::note warn

table を直接分解することはできない。

```lua
local t = {1, 2, 3}
-- lua ではこれはできない
local n1, n2, n3 = t
```

他の言語の見た目が似たような機能、

`C#のDeconstruct`、
`c++の構造化束縛`、
`pythonのtuple分解`

とちょっと違う機能なのです。

:::


# `...` との関連

`vararg expression`

可変長引数。

> Vararg expressions, denoted by three dots ('...'), can only be used when directly inside a vararg function

```lua
function f(...) -- 👈 これ
end
```

## 別の関数にそのまま渡す

```lua
function f(...)
   print(...)
end
```

## table 化する

```lua
function f(...)
   local t = {...}
   print(t)
end
```

```
> f(1, 2, 3)
table: 0xa00050f90
```

## return する

めったに使わないと思うが OK。

```lua
function f(...)
   return ...
end
```

```
> print(t(1, 2, 3))
1       2       3
```

## 注意

```lua
function f(...)
   local x=... -- 👈 先頭の一個だけ代入する意味になる
   return x
end
```

```lua
function f(...)
   print('x', ..., 'y') -- 👈 先頭の一個だけ使われる
end
```

:::note warn
`...` は最後の値として使う場合のみ多値として渡せる

https://www.lua.org/manual/5.1/manual.html#2.5.8

に書いてある。
:::
【2023年9月版】NAT環境で、PASVモードのFTPサーバ構築【RHEL9.2】

# はじめに

NAT環境でFTPサーバ構築しよう。

からの、ChatGPTに質問しながら、NAT環境でPASVモードによるFTPサーバ構築のメモ

Ubuntu版は　↓

[【2023年9月版】NAT環境で、PASVモードのFTPサーバ構築【Ubuntu22.04】](https://qiita.com/nouernet/items/6df1dde9ecc3f6f1b39d)

# 環境

```plantuml
@startuml
node "FTPサーバ" as ftp_server
node "NATサーバ" as napt_server
node "FTPクライアント" as ftp_client
ftp_server "10.10.10.100" -- "10.10.10.254" napt_server
napt_server "192.168.100.254" -- "192.168.100.10" ftp_client
@enduml
```

## FTPサーバ

- Windows Server 2019 + IIS (10.10.10.100)

## NATサーバ

- RHEL9.2 + firewalld (eth0:10.10.10.254, eth1:192.168.100.254)

## FTP クライアント

- Ubuntu22.04 (192.168.100.10)
    - curl
    - ftp

## ftp用のカーネルモジュールを導入

```terminal
sudo modprobe nf_nat_ftp
sudo modprobe nf_conntrack_ftp
```

## パッケットのフォワードができるように設定

```terminal
$ echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
$ echo "net.netfilter.nf_conntrack_helper = 1" | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
net.ipv4.ip_forward = 1
net.netfilter.nf_conntrack_helper = 1
```

## firewalld を設定

- 内側(10.10.10.0/24)を trusted ゾーンに設定
- 外側（192.168.100.0/24）を external ゾーンに設定

```terminal
sudo firewall-cmd --permanent --zone=trusted  --add-interface=eth0
sudo firewall-cmd --permanent --zone=trusted  --add-port=1024-65535/tcp
sudo firewall-cmd --permanent --zone=external --add-interface=eth1
sudo firewall-cmd --permanent --zone=external --add-forward-port=port=21:proto=tcp:toaddr=10.10.10.100
sudo firewall-cmd --permanent --zone=external --add-forward-port=port=1024-65535:proto=tcp:toaddr=10.10.10.100
```

## FTPクラアントからアクセス

- `curl ftp://192.168.100.254`
- `ftp 192.168.100.254`

# さいごに

かんたんでしたね
パーティクルシステムをスクリプトで制御する(Unity)

# やり方

```php
public class Test : MonoBehaviour
{
    [SerializeField] private ParticleSystem particle;

    void Update()
    {
        //スペースキーを押すとparticleを再生
        if(Input.GetKeyDown(KeyCode.Space)) particle.Play();

        //左シフトキーをクリックするとparticleを停止
        if(Input.GetKeyDown(KeyCode.LeftShift)) particle.Stop();
    }
}
```

音楽を再生、停止するときとやり方は同じなので、一度やり方を覚えれば忘れないと思います！
Python 3.10.13をRocky Linux 8.6にインストールする手順

#### ステップ 1: サーバーをアップデートする
最初にシステムをアップデートしておきます。
```bash
sudo dnf update -y
```

---

#### ステップ 2: 依存パッケージをインストールする
Pythonをビルドするための依存パッケージをインストールします。
```bash
sudo dnf install curl gcc openssl-devel bzip2-devel libffi-devel zlib-devel wget make -y
```

---

#### ステップ 3: Python 3.10.13 ソースコードをダウンロードする
Pythonの公式ウェブサイトから3.10.13バージョンのソースコードをダウンロードします。
```bash
wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tar.xz
```

---

#### ステップ 4: ソースコードを展開する
ダウンロードしたソースコードのtarファイルを展開します。
```bash
tar -xf Python-3.10.13.tar.xz
```

---

#### ステップ 5: 展開したディレクトリに移動する
```bash
cd Python-3.10.13
```

---

#### ステップ 6: Pythonを構成する
```bash
./configure --enable-optimizations
```

---

#### ステップ 7: Pythonをビルドする
ビルドプロセスを開始します。`nproc`コマンドは利用可能なプロセッサー数を出力するため、`make -j $(nproc)`として最適化されたビルドが可能です。
```bash
make -j $(nproc)
```

---

#### ステップ 8: Pythonをインストールする
```bash
sudo make altinstall
```

---

#### ステップ 9: インストールの確認をする
```bash
python3.10 --version
```
出力で "Python 3.10.13" が表示されれば成功です。

---

### デフォルトのPythonバージョンを変更する

1. `update-alternatives`コマンドでPython 3.10.13を登録します。
    ```bash
    sudo update-alternatives --install /usr/bin/python3 python3 /usr/local/bin/python3.10 3
    ```

2. `update-alternatives`を使ってデフォルトのPythonバージョンを選択します。
    ```bash
    sudo update-alternatives --config python3
    ```
   この後、表示されるオプションから`3`を選択してEnterを押します。

---

**注意**: この手順はシステム全体に影響を与える可能性があります。依存関係や既存のプロジェクトに影響が出ないよう確認してから行ってください。
Flutter で app:mergeDebugNativeLibs エラーが出たときの対処法

Flutter で app:mergeDebugNativeLibs というエラーが出たときの対処法です。
Kotlinのコードを使用してビルドすると出ました。
原因は同じ名前の.soファイルがあるためです。

```console
Execution failed for task ':app:mergeDebugNativeLibs'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeNativeLibsTask$MergeNativeLibsTaskWorkAction
   > 2 files found with path 'lib/arm64-v8a/libcrypto.so' from inputs:
      - xxx\xxx\xxx\jni\arm64-v8a\libcrypto.so
      - xxx\xxx\xxx\jni\arm64-v8a\libcrypto.so
```

# 対処法
android/app/build.gradle に重複ファイルを追記します。 

```console:app/build.gradle
android {
   ...
   ...
   // Add
   packagingOptions {
        pickFirst '**/libcrypto.so' ←重複ファイル
        pickFirst '**/libssl.so'  ←重複ファイル
    }
}
```

# 他の対処法
2つの.soファイルのひとつを削除orリネームするといったん解決します。しかしこの方法ではクリーンビルドをすると再発してしまいます。