OmniAICreator/Qiita-1.07M huggingface dataset:
Nvidia mellotron で日本語転移学習をしてみた2

「自分の好きな声優さんの音声合成作りたいなー」という純粋な欲望により最近音声合成について調べている者です。
#学習方法を少し変えて試してみた話
[前回の記事](https://qiita.com/seichi25/items/6802ede8a74473f9b466)の続き？というかその後の話です。
前回の学習では、事前学習データと目的話者の両方を再学習させていて、データ数の比率的に目的話者の学習が全然進まなかったんじゃないかなーと思い、今回は事前学習モデルから目的話者データだけを転移学習させてみた結果を載せます。

私がmellotronで日本語学習したモデルを試せるgoogle colaboratryのサンプルコードを用意しました。
https://drive.google.com/file/d/1H4PHb1ADRncDx-DZedWKKhYnO_Nzhopj/view?usp=sharing
試すには以下のセクションをすべて実行してください。

- ワークスペースの設定
- 環境構築
- 推論
 - 録音する場合
 - 学習済みモデルを読み込み、推論する
 - 生成音声
 
「録音する場合」のセクションの中に「録音」というセクションがあります。そこでテキスト入力とマイク入力を求められるので適宜操作を行ってください。

#学習について
**2段階で転移学習**
前回の記事でも書きましたが、英語の事前学習モデルからJSUT+JVSコーパスによる日本語の転移学習を行い、さらにその日本語学習済みモデルから目的話者への転移学習、という2段階に分けて学習を行いました。
前回と違うところは、[DeNAさんのこの記事](https://engineer.dena.com/posts/2020.03/speech-synthesis-for-entertainment/)を参考に、複数話者のデータを学習させて、平均の声モデルを作成するというのを試しました。
具体的には、JSUT,JVSコーパスのデータに対して、スピーカーidをすべて0にして学習したということです。

**実行環境**
OS : Ubuntu 20.4
GPU : GTX 1080 Ti 11か12GB

学習所要時間：約一週間
####1.英語から日本語の転移学習
**データセット**
ここは前回と同様なので省略します。

**学習ステップ数**
batchが8で、57kステップ学習させました。
100kステップ以上学習させてみたのですが、試しに音声を生成してみたら非常に不安定だったので、比較的安定だったcheckpointのモデルを次の転移学習に採用しました。

この結果と、話者idを指定したり、GSTを組み込んでいるというmellotronの構造から、mellotronは平均的な音声の学習は向いてないのだろうと思いました。

####2. 日本語モデルから目的話者への転移学習
**データセット**
1.のモデルを読み込んで、目的話者データ594+α文で学習。
594+αのうち、すべてを教師データ、αの一部をテストデータとしました。

テストデータに関してはそもそもデータが少ないので、テスト用に未知のデータとして分けちゃうと、データがもったいないというのが１点、
データが少ないがゆえに、未知データに対してほとんど無力でることは想定されるので、目安としては学習データに対してのlossでよいと考えた、という２点の理由により上記の分け方になってます。

**学習のパラメータ**
前回同様で

- batch_size = 8

のみ変更しました。
あ、ちなみにバッチサイズふやした、いや増やせたのは、GPUを隣のやつからぱｋ
交換していただいて、使えるメモリ量が増えたからです。

**学習ステップ数**
SSDの空き容量と相談して

- checkpoint = 3000
-  1000エポック
- 途中経過セーブデータ54個(約20GB)

で回しました。
エポックは1000に設定しましたが、800後半あたりで、lossの変化がほとんど見られなくなっていたので、そこらへんで打ち切ってます。

#結果
**「声の特徴をとらえた日本語が生成できる程度の品質」**

サンプル音声を作ってみたのでよろしければご試聴ください
[mellotron_IMC_150k_sample.wav](https://drive.google.com/file/d/1yeoyotaxIZI-HGI2XGkzaS7QsjO-vCcm/view?usp=sharing)

ところどころ苦手そうな発音が見られますが、おおむねうまく生成できているかと。

前回よりましになりましたが、話題になっている音声合成モデルたちと比べると全然です。
もともと、Tacotron2でやっていたものを、データ数少ないながらもイントネーションの改善がしたくて、mellotronに手を出したのですが、
こっちはこっちで構造が少し複雑だったり、入力が増えたことによる必要とされる、データに含まれる特徴量の変化や量の増加。具体的に言うと、ピッチに対応するそれぞれの発音、つまり、同じセリフでも高い声バージョンと低い声バージョンみたいな。

とかなんだかんだで、どのみちそう簡単にはいかないということがわかりましたね。
#今後
ESPNetやMB MelGANなどのみんな使ってる新しめのモデルに手を出してみようと思ってます。
あ、でも飽きたらやめます。
#参考
https://engineer.dena.com/posts/2020.03/speech-synthesis-for-entertainment/
https://note.com/npaka/n/n8c94da051c68
https://github.com/NVIDIA/mellotron
https://tosaka-mn.hatenablog.com/entry/2020/04/29/160050
https://shirowanisan.com/
https://qiita.com/kan-bayashi/items/536acaf165344a6d6460
https://qiita.com/K2_ML/items/2804594454b39180c909
新しいLinuxの教科書C1

##Linuxとは
Linuxとは厳密には「Linuxカーネル」のみを指す。
ここに加え、コマンドやアプリケーションを含めてパッケージングしたものが**「Linuxディストリビューション」**であり、複数提供されている。

##仮想化ソフトウェア
環境を整える際に、既存の環境を壊す危険があるため仮想的なコンピュータを構築する。

**・VirtualBox**
仮想化ソフトウェアを動作させているOS(ホストOS)。
ここに実装するのに新たに仮想的なOS(ゲストOS)を作成する。

（補足）
**・Docker**
ホストOS内でコンテナという仮想環境を作成する。
ゲストOSを必要としないため、速度が速い。

##GUIとCLI
・GUI
グラフィカルユーザーインターフェイス。
画面上のメニューやアイコンをクリックして操作を行う。

・CLI
コマンドラインインターフェイス。
キーボードからコマンドを入力し、文字列として結果を出力する。
LPICレベル1を勉強しているあなたへ - systemdでデフォルトのランレベルを設定する方法 -

systemdでは、

「/lib/systemd/system/runlevel[0-6].target」ファイル
([0-6]部分はランレベルを指定)

のシンボリックリンクを

「/etc/systemd/system/default.target」

として作成することでデフォルトのランレベルを設定できます。

例）デフォルトのランレベルを3に設定する場合

# ln -s /lib/systemd/system/runlevel3.target /etc/systemd/system/default.target

デフォルトのランレベルに0または6を設定してはいけません。
設定すると、起動してもすぐに停止または、
再起動を繰り返してしまいます。
LPICレベル1を勉強しているあなたへ - Upstartでのデフォルトのランレベルを設定する方法 -

最近のシステムではinitプログラムとして、
初期化処理を高速化した

「Upstart」

「systemd」

を採用している場合があります。

その場合は

「/etc/inittab」

ファイルが存在しません。

Upstartは

「/etc/event.d/rc-default」

ファイルの

「telinit 2」

の部分を編集することでデフォルトの
ランレベルを設定できます。

ただし、UpstartはSysVinitと互換性があるため、
「/etc/inittab」ファイルを新規に作成し、
先述のSysVinitの場合と同じように記述することで
デフォルトのランレベルを設定することもできます。

その場合、「/etc/inittab」ファイルの
ランレベルが優先されます。

なお、「/etc/inittab」ファイルは
通常Linuxシステムの起動時に読み込まれます。

initまたはtelinitコマンドに

「q」か「Q」オプション

を指定して実行すると、「/etc/inittab」を
再読み込みさせ即座に
変更を反映させることができます。

例）システムを再起動せずに
「/etc/inittab」を再読み込みさせる場合

# init q
# init Q

または

# telinit q
# teliniit Q
FlutterのDartからWebViewのJavascript関数を呼ぶのはonPageFinished以降にする

Flutter始めたばかりで「[FlutterのDartからWebViewへ画像を送るにはData URIを使う](https://qiita.com/picpie/items/e148fc1806ec0a1845e4)」という記事を書きましたが、その後、注意した方がいいことに気づいたので書きます。

以下のサンプルはDartでHtmlを作成してWebViewを表示し、そこにDartから画像を転送するものです。WebView 構築時のonWebViewCreated:パラメータで指定するコールバックの中で処理されます。

```dart
    // まずHtmlを作成
    String html = "<html><head><script>"+
                  "function putimg(id,src){document.getElementById(id).src=src;}"+
                  "function test(){alert('test');}</script></head><body>"+
                  "<img id='i00'></body></html>";
    // HtmlをData URIに変換してサーバー経由しないで取得できるようにする
    final fileurl = Uri.dataFromString(
        html, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')
    ).toString();
    // それをWebViewで表示
    webviewcontroller.loadUrl( fileurl );

    // 画像をダウンロードし、Data URIでWebViewに転送
    http.get(Uri.parse('http://example.com/test.jpg')).then((response) { 
        var dataurl = Uri.dataFromBytes(response.bodyBytes, mimeType: 'image/jpeg').toString();
        webviewcontroller.evaluateJavascript("putimg('"+ item['id'] +"','"+ dataurl +"');");
      });
    webviewcontroller.evaluateJavascript("test();");
```

WebViewで表示されるHtmlには、イメージ表示用のputimg(id,src)関数と、テスト用のtest()関数が定義されています。ところが実際にコーディングして実行してみると、putimg(id,src)は動作してimgタグに画像が表示されますが、test()ルーチンは以下のようなエラーになることがあります。

```
I/chromium( 7851): [INFO:CONSOLE(1)] "Uncaught ReferenceError: test is not defined", source:  (1)
```

何度かくり返していると、タイミングによってはputimg()まで同じエラーになったりします。

これはwebviewcontroller.loadUrl( fileurl );の実行が終わらないうちに、evaluateJavascriptがコールされたので、Html内に対象の関数が見つからなかったからです。text()はエラーになるがputimg()がエラーにならないのは、putimg()はhttp.getの終了を待ってからコールされるので、そのころはwebviewcontroller.loadUrl()の実行が完了しているからでしょう。

ではどうすればいいかというと、onWebViewCreated:ではなく、onPageFinished:のタイミングで呼べばいいとマニュアルに書いてありました。

https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebView/onPageFinished.html

Androidでは描画が終わってない可能性がありますが、Html内に埋め込まれたJavascriptは存在しているので、evaluateJavascriptでコールしても大丈夫ということです。

```dart
  onPageFinished: (String url) {
    http.get(Uri.parse('http://example.com/test.jpg')).then((response) { 
        var dataurl = Uri.dataFromBytes(response.bodyBytes, mimeType: 'image/jpeg').toString();
        webviewcontroller.evaluateJavascript("putimg('"+ item['id'] +"','"+ dataurl +"');");
      });
    webviewcontroller.evaluateJavascript("test();");
  },

```

https://qiita.com/official-events/d523df99d6479293ffa7
LPICレベル1を勉強しているあなたへ - SysVinitでのランレベル変更フロー -

SysVinitでは、システムの起動時や
ランレベルの変更時にinitが
「/etc/inittab」を参照し、
ランレベルに応じたスクリプトを実行して
各種サービスを順に起動/停止していきます。

各ランレベルに応じたスクリプトは
「/etc/rc[0-6].d」に入っています。

スクリプトのファイル名（実際はシンボリックリンク）は
次の規則で命名します。

例）K01smartd
- 1文字目：S（Start: サービスを起動）、K（Kill: サービスを停止）
- 数字：実行優先順位。若番のものが先に実行される
- サービス名：任意の名前をつける
LPICレベル1を勉強しているあなたへ - 現在のランレベル変更のやり方 -


現在のランレベルはinitまたは
telinitコマンドで変更できます。
書式は以下のとおりです。

init ランレベル
telinit ランレベル

例）ランレベルを5に変更する場合

# init 5
または
# telinit 5

「SysVinit」と呼ばれる従来のinitプログラムを
採用しているシステムでは、
デフォルトのランレベルは「/etc/inittab」
ファイルのinitdefautの行で設定します。

例）デフォルトのランレベルを5に設定する場合

id:5:initdefault:
CloudFront+ALB+ECS環境で意図しないリダイレクトが発生するとき確認すること



## 構成
CloudFront → ALB → ECS(Nginx用コンテナ→App用コンテナ)

- CloudFrontのドメイン → test.vamdemic.com
- ALBのドメイン → test-alb.vamdemic.com

## 事象
CloudFrontでOriginに指定しているドメイン名でアクセスし、302リダイレクト処理がかかる部分で、ALBのカスタムドメイン名が返ってくる。test-alb.vamdemic.comでリダイレクトされてしまう。
もっと具体的にいうと、nginxへのリクエストの応答ヘッダーのlocationが`test-alb.vamdemic.com`で返ってくる。

## 設定
- Nginxコンテナのコンフィグ設定はこのような感じ
- なのでhostヘッダーはさらに後ろのApp用コンテナへはわたっている

```nginx.conf
server {
  listen 80;
  server_name _;


  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }
}
```
## 原因
CloudFrontからHostヘッダーをALBへ渡す設定をしていなかったため、リクエストを実際に返すNginxはALBのドメインから来たと認識して、そのドメインをlocationヘッダーに入れていた（リダイレクトさせていた）
分かればすごく難しいということではないけれど、CloudFrontでハマる

## 対処
CloudFrontのBehevier→WhiteList設定でhostヘッダーを設定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/4d7b4682-aa2a-4c01-1739-2af389ffdee5.png)
LPICレベル1を勉強しているあなたへ - SysVinitでのランレベル変更 -


SysVinitでは、システムの起動時やランレベルの変更時に
initが「/etc/inittab」を参照し、
ランレベルに応じたスクリプトを実行して
各種サービスを順に起動/停止していきます。

各ランレベルに応じたスクリプトは
「/etc/rc[0-6].d」に入っています。

例えばランレベル3では、
「/etc/rc3.d」配下のスクリプトが実行されます。
LPICレベル1を勉強しているあなたへ - SysVinitとは -

 initという言葉は sysvinit のことを指します。 
inittabは /etcに置かれた init の
起動設定ファイルのこと。

古くから使われていたのがSysvinitで、
Sysvinitの代替えとして近年Ubuntuなどで
採用されていたのがUpstartです。
そしてFedora 15やCentOS 7、
Red Hat Enterprise Linux 7で
採用されたのがSystemdです。
goofys の --use-content-type オプションが限られた拡張子でしか有効にならない問題の解消方法

# 問題

Amazon Linux 2 上で [goofys](https://github.com/kahing/goofys) を使ってマウントした S3 バケットにファイルをアップロードし、これを直接ダウンロードさせる場合、 S3 オブジェクト保存時に指定された Content-Type 設定が利用される。
しかし、何のオプションも渡さずに goofys で S3 オブジェクトをマウントした場合、デフォルト値として `binary/octet-stream` が利用されてしまい、予期せぬ挙動が起こることがある。 例えば PDF ファイルが binary/octet-stream 形式だと、ブラウザによっては別ページで開かずにダウンロードしてしまう。
これを解消するために、goofys の実行時に `--use-content-type` オプションを利用することができる。 少し古いが、以下の記事を参照。

https://dev.classmethod.jp/articles/goofys-auto-setting-metadata-content-type/

これを付けておけばOKと思っていたのだが、メジャーな拡張子、例えば `.mp3` のファイルをアップロードした場合にContentTypeが識別されず、binary/octet-stream になってしまう状況に直面した(ただし、.jpg などの一部の拡張子は image/jpeg として設定されるなど、正常に識別される時もあった)。

この問題の解決方法を探る。

# 原因と解決策

このあたりを読めばわかるのだが、拡張子から ContentType を推論するのには `/etc/mime.types` ファイルを利用している（先の DevelopersIO の記事にもちゃんと書いてある)。
 
https://github.com/kahing/goofys/issues/54

しかし、記事執筆時点でレコメンドされる Amazon Linux 2 (amzn2-ami-hvm-2.0.20210525.0-x86_64-gp2, ap-northeast-1 の場合は `ami-001f026eaf69770b4` ) **この辞書ファイルがデフォルトで入っていない。**

```bash
$ ls /etc/mime.types
ls: cannot access /etc/mime.types: No such file or directory
```

なので、これを手動でインストールしてやる必要がある。 
`yum provides` で引いてみると、幸い、`amzn2-core` にあるので、`mailcap` をインストールした後にファイルをチェック。

```bash
$ sudo yum provides /etc/mime.types
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
mailcap-2.1.41-2.amzn2.noarch : Helper application and MIME type associations for file types
Repo        : amzn2-core
Matched from:
Filename    : /etc/mime.types

$ sudo yum -y install mailcap
(インストールの過程は略)

# コンテンツの中身を確認
$ head -n 30 /etc/mime.types 
# This is a comment. I love comments.    -*- indent-tabs-mode: t -*-

# This file controls what Internet media types are sent to the client for
# given file extension(s).  Sending the correct media type to the client
# is important so they know how to handle the content of the file.
# Extra types can either be added here or by using an AddType directive
# in your config files. For more information about Internet media types,
# please read RFC 2045, 2046, 2047, 2048, and 2077.  The Internet media type
# registry is at <http://www.iana.org/assignments/media-types/>.

# IANA types

# MIME typeExtensions
application/1d-interleaved-parityfec
application/3gpp-ims+xml
application/activemessage
application/andrew-insetez
application/applefile
application/atom+xmlatom
application/atomcat+xmlatomcat
application/atomdeleted+xmlatomdeleted
application/atomicmail
application/atomsvc+xmlatomsvc
application/auth-policy+xmlapxml
application/batch-SMTP
application/beep+xml
application/calendar+xmlxcs
application/call-completion
application/cals-1840
application/ccmp+xmlccmp
```

これをインストールした後に goofys を再起動してファイルをマウントされたバケットに goofys 経由でコピーすれば、拡張子に応じた ContentType が S3 上のオブジェクトに反映されるようになった。

デフォルトで入っていないケースもあるようなので、 goofys 経由でファイルをアップロードする場合は、ちゃんと `/etc/mime.types` があるか否かをチェックする癖をつけた方がよいかもしれない。
パワーポイントをTeXで書こう

# はじめに
数式の多いスライド資料をパワーポイントで書くのは大変ではないですか？
そんなときには「Beamer」というパッケージがあります．
TeX Live環境ばかりでw32tex民と明示してあるサイトが見つからなかったため書こうと思った次第です．
導入については特にすることがないのでテーマを入れるところまでやります．
今回は「Metropolis」というテーマを入れます．

# Beamerのテスト
Beamer パッケージについてはTeXを入れた時点で入っています．
Beamer が入るのはTeX Liveもw32texも同じだと思います．

以下のコードをTeXファイルにコピペしてコンパイルできればOKです．
```
\documentclass{beamer}
\begin{document}
\begin{frame}
    \titlepage
\end{frame}
\begin{frame}
    \frametitle{frametitle}
    text
\end{frame}
\end{document}
```

コンパイルできない人は「Beamer 入れ方」とかでググってください．

# 「Metropolis」テーマを入れる
テーマを入れて見栄えをよくします
このあたりにちょっとだけ手間取ったので同じく困っている人は参考にしてください

## テーマをダウンロードする
さんざんいっておりますが，今回は「Metropolis」テーマを入れます．
ダウンロードは[こちら](https://github.com/matze/mtheme)からどうぞ．
code→Download ZIPでzipで落とせます．
ダウンロード先の場所はちょっと大事なので覚えておきましょう．

## テーマをインストールする
ここからすこし複雑な作業をします．
ダウンロードしてきたmtheme-master\sourceのなかにある「beamerthememetropolis.ins」をLaTeXでコンパイルする必要があります．
やり方は複数ありそうですが私のやり方を書いておきます．

1.コマンドプロンプトを開く
windowsマークの右にある「ここに入力して検索」にコマンドプロンプトと入力し起動する

2. カレントディレクトリを「beamerthememetropolis.ins」のあるフォルダにする
さっき出てきた黒い画面に以下のように入力してエンターキーを押す
cd C:\Users\'ユーザ名'\Downloads\mtheme-master\mtheme-master\source
ユーザ名のところは自分のパソコンのユーザ名を入力します（windowsマーク押して設定や電源の欄の一番上を選択して待ってると表示される名前です）

3. LaTeXを開く
黒い画面に「LaTeX」と打ってエンターキーを押す

4. インストールする
黒い画面に「beamerthememetropolis.ins」と入力してエンターキーを押す

解凍しておいたmtheme-masterの中のmtheme-master\sourceの中に「.sty」みたいなファイルができていれば完璧です
それを今度はTeXのフォルダに移していきましょう

1.スタイルファイルをコピーする
そのフォルダの中にある「.sty」ファイルを全部選択してコピーしておきましょう

2.TeXの中に入れる
ファイルエクスプローラで次のようにクリックしていきましょう
PC→Windows(C:)→w32tex→share→texmf-local
ここまで来れたら，新しいフォルダを作って，名前を「tex」にしましょう
その中に，さらに新しいフォルダを作って，名前を「latex」にしましょう
その中に，コピーしてきたスタイルファイルを張り付けましょう
フォルダ階層としては次のようになります
C:\w32tex\share\texmf-local\tex\latex\*.sty

ディレクトリは各自の環境に合わせてください．
デフォルトならこれでうまくいくと思います．

作業としてはもう一つあるのですが，入れとかないといけないものがもう一つあるのでそれを入れます．
というわけで[ここ](https://github.com/jlelong/appendixnumberbeamer)から拾ってこよう．
同じくcode→Download ZIPでダウンロードできます．

その中にある「appendixnumberbeamer.sty」をさっきと同じ場所に入れましょう．(C:\w32tex\share\texmf-local\tex\latex)

最後にもう一つ作業をします．
もう一度コマンドプロンプトを開きます．
「mktexlsr」と打ってエンターキーを押しましょう．
最後に「Done.」と出ていればおっけーです．

これでbeamerおよびmetropolisの導入はおしまいです．
mtheme-masterの中にdemoがあるのでコンパイルしてみましょう．
（入れてないツールボックスのところはとりあえずコメントアウトして試してみるといいです）

#結果
+ beamerが使えるようになった．
+ beamerのテーマを入れられるようになった．
コンストラクタを値にもつオブジェクトから型判定させる

# やりたいこと

言葉で説明するのが難しい。
コンストラクタを値にもつオブジェクトを渡してその形に基づく戻り値を得たい。
例えばplainToClassに使うオブジェクトを一緒に渡してインスタンスを生成するみたいなことがしたい。

```typescript
const instances = cstrToInstance(
  {dog: Dog, owner: Owner},
  {dog: {name: 'tarou'}, owner: {age: 20}}
);

console.log(instances); // { dog: Dog { name: 'tarou' }, owner: Owner { age: 20 } }
```

# 実装
```typescript
import { ClassConstructor, plainToClass } from 'class-transformer';

function cstrToInstance<
  S extends keyof T,  // ※1
  T extends { [key: string]: ClassConstructor<T[S]> }
>(
  m: T,
  params: { [key: string]: Record<string, unknown> }  // ※2
): { [k in keyof T]: InstanceType<T[k]> } {  // ※3
  const r = {} as { [k in keyof T]: InstanceType<T[k]> };  

  for (const [key, type] of Object.entries(m)) {
    const param = params[key] || {};
    r[key as keyof T] = plainToClass(type, param) as InstanceType<T[keyof T]>;
  }

  return r;
}
```

※1 `S`を宣言せず直接`ClassConstructor<T[keyof T]>`とやるとエラーになる。正直よくわかってない。
※2 要はただのオブジェクト
※3 `k in keyof T` と `T[k]`でキーに対応する型を一致させるのがポイント


# 動作
```typescript
describe('cstrToInstance', () => {
  it('型をplanToClassして返す', () => {
    const instances = cstrToInstance(
      { dog: Dog, owner: Owner },
      {
        dog: { name: 'tarou' },
        owner: { age: 20, dog: { name: 'jirou' } },
      }
    );

    expect(instances.dog).toEqual(new Dog('tarou'));
    expect(instances.owner).toEqual(new Owner(20, new Dog('jirou')));
  });
});
```

エディタでの推定もされる。
![Screen Shot 2021-06-22 at 23.11.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51303/b10af2c0-24ca-0f73-62dd-f8758c6eba53.png)
HTMLをマークダウン形式に変換「reverse_markdown」

## 参考
Github
[reverse_markdown](https://github.com/xijo/reverse_markdown)

×　マークダウン形式　→ 　HTML形式　  変換ではなく
○　HTML形式　→  　マークダウン形式 　変換したい
そんな時に便利なgemでした。

## 実装経緯

- ウィジウィグ使用して保存されているHTML形式データを取り出してマークダウン形式に変換
- それをCSV出力

(※今回ウィジウィグ・CSV出力については割愛させていただきます。)



上記を行う事があったため
**reverse_markdown（gem)**
を使用して実装してみた。ありがたや...
忘れないようにメモ。

## 結論
テスト的に下記のようなHTML文を変換すると...

```html
<h3>項目タイトル</h3>

<p><a href="https://www.yahoo.co.jp/">test</a></p>

<p><img alt="【画像出典元】" height="165" src="https://www.google.com/"></p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p><u>小見出し線</u></p>

<p><strong>太字</strong></p>

<p>&nbsp;</p>

<p>改行<br />
改行</p>

<p><a href="http://www.yahoo.co.jp" rel="nofollow noopener" target="_blank"><img alt="test" loading="lazy" src="sakura.jpg" /></a></p>

<p><a href="sakura.jpg" rel="noopener noreferrer" target="_blank"><img alt="test" src="test.jpg" style="max-width:100%;" /></a></p>
```

こうなった（CSV出力データ）

```
1.項目タイトル

### 項目タイトル

## &nbsp;項目タイトル

[test](https://www.yahoo.co.jp/)

![【画像出典元】](https://www.google.com/)

&nbsp;

&nbsp;

&nbsp;

<u>小見出し線</u>

**太字**

&nbsp;

改行  
 改行


[![test](test.jpg)](http://www.yahoo.co.jp)

[![test](test.jpg)](test.jpg)

&nbsp;
```


マークダウン形式を変換してくれるサイト（Qiita)にコピペしたらこんな感じ（下記）

---

[test](https://www.yahoo.co.jp/)

![【画像出典元】](https://www.google.com/)

&nbsp;

&nbsp;

&nbsp;

<u>小見出し線</u>

**太字**

&nbsp;

改行  
 改行

[![test](test.jpg)](http://www.yahoo.co.jp)

[![test](test.jpg)](test.jpg)

&nbsp;

---


##　わかったこと(箇条書き）
- brタグ、pタグは変換時に消える(gsubで対応すればなんとかなる）
- figureタグに囲まれている画像データ(img要素）は変換されない

その他は試してないので分からない。（ご自身でお試しください。）

## 使い方（基本機能のみ）
```ruby
ReverseMarkdown.convert 変数　#変数にはHTML要素を格納した変数
```
私はこんな感じで使用させていただきました。
他にもオプションなどがある様でしたが、今回は基本的なものだけを使わせていただきました。
興味がある方ははじめに記載した参考Github「[reverse_markdown](https://github.com/xijo/reverse_markdown)」からご確認ください。
【Php】フォームからDBへデータ保存(2)

# 初めに
phpでフォームの入力内容をDBに保存する方法について学習した内容のoutput用記事です。

※内容に間違いなどがある場合はご指摘をよろしくお願いします。
※こちらの記事はあくまでも個人で学習した内容のoutputとしての記事になります。

前回の記事:
https://qiita.com/redrabbit1104/items/a6e57aa1fd1771ef90ff
https://qiita.com/redrabbit1104/items/a3eaf2bba51fac0b3c51
https://qiita.com/redrabbit1104/items/6e9c85c7503ed9043f38
https://qiita.com/redrabbit1104/items/c9b7bb7a89922521c003
https://qiita.com/redrabbit1104/items/3c83883ab811d7bd1337

# phpファイルの読み込みとDB保存のための関数
入力フォームの完了画面のあたりに前回作成したデータベースへ保存処理が書かれているphpファイルを読み込みます。

```php
 <?php require 'insert.php'; ?>
```

また、insert.phpファイルにはDBに入力フォームの内容を保存する関数を作成します。関数の引数にはフォームの入力値が入っている\$_POSTを渡します。

```php
 <?php require 'insert.php'; 
   insertPost($_POST);
 ?>
```

# insertPost()関数の作成と$paramsのvalueを修正
insert.phpファイルのデータベースにデータを保存する処理が書かれている部分をinsertPost関数の処理部分として{}で囲みます。

```php
// insert.php
<?php

    const DB_HOST = 'mysql:dbname=udemy_php;host=localhost:8889;charset=utf8';
    const DB_USER = 'php_user';
    const DB_PASSWORD = 'wlfkfakfk12';

function insertPost($request){                  //追記
    try{
        $pdo = new PDO(DB_HOST, DB_USER, DB_PASSWORD, [ 
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
        ]);
    }catch(PDOException $e){
        echo $e -> getMessage() . "\n";
        exit();
    }

     $params = [
        'id' => null,
        'your_name' => '田中',
        'email' => 'test@test.com',
        'url' => 'http://www.abc.com',
        'gender' => '1',
        'age' => '2',
        'contact' => 'いい',
        'created_at' => null
    ];

    $count = 0;
    $columns = '';
    $values = '';

    foreach(array_keys($params) as $key){
        if($count++>0){
            $columns .= ',';
            $values .= ',';
        }
        $columns .= $key;
        $values .= ':'.$key;
    }

    var_dump($columns);
    var_dump($values);

    $pdo->beginTransaction();

    try {

        $sql = 'insert into contacts ('. $columns .')values('. $values .')';
        var_dump($sql);  
        //string(143) "insert into contacts (id,your_name,email,url,gender,age,contact,created_at)values(:id,:your_name,:email,:url,:gender,:age,:contact,:created_at)"
        $stmt = $pdo->prepare($sql);     
        $stmt->execute($params);

        $pdo->commit();
        $result = $stmt->fetchall();
        var_dump($result);
    } catch (PDOException $e) {
        $pdo->rollback();    
        echo $e;
    }
}
?>
```

また、\$paramsのキーに対するvalueの値を修正します。

```php
//中略
    $params = [
        'id' => null,
        'your_name' => $request['your_name'],     //修正
        'email' => $request['email'],
        'url' => $request['url'],
        'gender' => $request['gender'],
        'age' => $request['age'],
        'contact' => $request['contact'],
        'created_at' => null
    ];
//中略
```

# 参考サイト
https://www.php.net/manual/ja/reserved.variables.post.php
PostgreSQLのJDBCドライバでの接続文字列でwork_memなどを指定する

こういったことをする人はあまりいないと思うんですけど、仕事で必要になったので調べたことをメモ代わりに。
あと、PostgreSQLのJDBCの接続文字列に指定できるoptionsの記事があまり見つからなかったので。

多分、皆さんJavaでPostgreSQLに接続する場合、接続文字列ってこう書くと思います。

```
jdbc:postgresql://host:port/dbname
```

これにパラメタを指定できるんですね(最近知りました)

```
jdbc:postgresql://host:port/dbname?param1=value1&param2=value2&...
```

例えば、アプリケーション名を指定する場合以下のように指定します。

```
jdbc:postgresql://host:port/dbname?ApplicationName=hogehoge
```
これを指定すると、SELECT * FROM pg_stat_activity;なんかを実行すると、application_nameカラムにhogehogeと表示されます。
複数のシステムから接続するようなDBなんかでは重宝しますね。
(これはやってる人がいっぱいいそう)

で、本題。
接続文字列でwork_memとかも指定可能です。

```
jdbc:postgresql://host:port/dbname?ApplicationName=hogehoge&options=-c work_mem=1GB
```

さらに複数指定したい場合はこんな感じで書くことができます。

```
jdbc:postgresql://host:port/dbname?ApplicationName=hogehoge&options=-c work_mem=1GB -c enable_nestloop=off
```

ちなみに、今回はJava 1.6で動かさないといけなくて、どこから動くか確認したところ、postgresql-42.2.6.jre6.jarから動くようです。
ただ、なぜかpostgresql-42.2.15.jre6.jarはエラーになりました。なんだったんだろう・・・？
(たまたま古いJDBCドライバを使っているシステムでoptionsを指定したくなってやったらできなかった)
時間があるときに、Java1.6以外も調べたいところ。

複数のシステムから接続するPostgreSQLがある場合、あるシステムではwork_memを多めに、または少なめに設定したい、といった場合に使ったり、あるシステムではクエリプランを制御するために特定の設定を変える、というようなシーンで利用できると思います。

##参考
https://jdbc.postgresql.org/documentation/head/connect.html
vscodeのprettierでcannot format 'xxxx'と表示され、フォーマットができない問題の解決方法

# 背景
vscodeでいつも通り編集していたらコードがフォーマットされない...。
画面の下を見るとprettierが`Extension 'Prettier - Code formatter' cannot format 'package.json'`というエラーを吐いていました。
![prettier.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600358/d4ee982f-9c6f-f0a7-e64e-9f2fd0554464.png)

コンソールにも以下のようなエラーが出ていました。

```
[2021-06-22 23:56:05.392] [exthost] [error] Activating extension esbenp.prettier-vscode failed due to an error:
[2021-06-22 23:56:05.392] [exthost] [error] Error: Cannot find module 'prettier'
```

# 原因と解決方法
原因は`prettier-vscode`のバージョンが8.0.0になっておかしくなったようです。
一旦は、バージョンをさげることで解消します。

バージョンを下げるには拡張機能の一覧を開いて、prettierを右クリックすると表示される「Install Another Version」からインストールするバージョンを指定します。

![version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600358/a42306cb-3418-85f8-099d-14346b46785f.png)
![specify-version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600358/72e8ec23-190d-e89e-2cbf-bf9573daa4f3.png)

最後に、自動的に拡張機能がアップデートされないように拡張機能設定の「...」を押すと表示される「Auto Update Extensions」を「None」にしておきましょう。

![disableautoupdate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600358/44c50cdb-7f06-d85a-1480-d05c4bb36c71.png)
M5StackでLチカ

手元にある部材を使って、M5StackでLチカしてみました。

## 1.部材接続
- LEDと抵抗をM5Stackに接続します(砲弾型LEDは足の長いほうがアノードです)
    - LEDアノード(+)とM5Stackのポート2
    - LEDカソード(-)と抵抗とM5StackのGnd
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665304/50978324-cf79-d665-338d-8f4bd7cd6311.png)

## 2.ソースコード
ライブラリの追加インストールは不要です

```
#include <M5Stack.h>

#define LED_PIN 2

void setup() {
    M5.begin();
    pinMode(LED_PIN, OUTPUT);
}
/* 1秒ごとに点滅 */
void loop() {
    digitalWrite(LED_PIN, HIGH);
    delay(1000);
    digitalWrite(LED_PIN, LOW);
    delay(1000);
}
```

簡単でも、実際にLEDが点滅すると嬉しいですね。
[Rails] Arrayメソッドで使うfirstとlastとそれ以外について

##環境

```
ruby 2.6.6
Rails 6.0.3.6
```

## 本題
Rubyには配列メソッドとして`first`と`last`というものが存在します。

```
[0,1,2].first
=> 0

[0,1,2].last
=> 1
```

それぞれ配列の要素の最初と最後を取得するメソッドです。
それに加えてRailsには新たなメソッド`second`,`third`,`fourth`,`fifth`があります。
※これらはRailsのメソッドであり，Rubyの組込みメソッドではありません。

使い方は以下のようになります

```
[1,2,3,4,5].second
=> 2

[1,2,3,4,5].third
=> 3

[1,2,3,4,5].fourth
=> 4

[1,2,3,4,5].fifth
=> 5
```
それぞれ配列の2,3,4,5番目から要素を取得します。
Parameter 0 of constructor in XXXDao required a bean of type XXXMapper that could not be found.

myPCで環境構築中に発生したので載せておきます。

####1つ目
SpringBootApplicationのパッケージ構成が宜しくない場合があります。
一つ上の階層に移動させてみたり色々確認してみてください。

```java:com/example/demo/DemoApplication.java
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
```

####2つ目
必要なプラグインが入っていない
maven,gradleに必要なプラグインが入っていない可能性があるので入れましょう
例：mavenでの場合
記載して更新してあげればこれだけで解決する場合があります。
ペースト時に赤字の警告が出るかもですが気にしないで更新かけてみてください

```yml:pom.xml
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>2.2.0</version>
	</dependency>
```
ReactのUseEffect()を使って特定の処理を一回だけ実行可能にする

#UseEffectとその副作用

Reactでアプリケーションを作成するときに使用することになるUseEffect、これがすごく大事みたいなので、備忘録として記事にしました。

```
UseEffect(() => {
  処理A
}, [var])
```

上記のようにコンポーネントの中で宣言することで、UseEffectは使用することができます。
そして、varが変化した時だけ、処理Aが実行されます。
(varが変化しなければ、処理Aは実行されません。)

UseEffectには副作用があり

```
UseEffect(() => {
 　処理A
}, []);
```

のように、[]に、何も渡さないで宣言することで、コンポーネントがマウントされた最初のタイミングで処理Aが一回だけ実行され、それ以降は二度と実行されないようにできます。

この副作用を使うことで、例えばAPIから情報を取得するという処理を、初回マウント時のみ実行されるようにすることができます。
これで、コンポーネントがマウントされるたびに毎回APIを取得するという無駄が防げます。
残プロ　第-21回　~Flaskでhtmlを表示する~

# 簡単な流れ

- Flaskインスタンスを用意
- render_templeteにhtmlファイルのパスを渡す


# サンプルコード

### .py

[前回](https://qiita.com/R1nY1x1/items/454fb22f0673374c5f16)のhello.pyから変更した点は関数show_htmlのみです．hello.pyでは返り値を文字列にしていましたが，render_templateを用いることによってhtmlが表示できます．

~~~ python:show_html.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def show_html():
    return render_template("btn.html")

if __name__ == '__main__':
    app.run()
~~~


### .html

htmlの内容に特に制約はないので，普段通りcssやJavaScriptが利用できます．
※htmlファイルはtemplateフォルダを作成し，そこに置く必要があります


~~~ html:btn.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>JavaScript</title>

</head>

<body>
    <div class="frame">
        <h1 class="frame__title">title</h1>

        <div class="frame__inner">
            <button id="buy">Button</button>
            <script>
                const btn = document.getElementById('buy');
                btn.addEventListener('click', () => {
                    btn.disabled = true;
                });
            </script>
        </div>
    </div>
</body>

</html>
~~~


# 実行結果

![html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1578927/6aa8c714-6506-87d6-c85f-a0d7b27063a3.png)

無事表示できました！
Azure Active Directory B2Cを使いこなそう 第3回「基本的な概念（テナント、アプリケーション、ユーザー）を理解する」

# 連載予定

1. [Azure Active Directory B2Cの特徴と使いどころ](https://qiita.com/yyukawa/items/78a52275b9dc9c37cd9e) 
1. [基本的な使い方（標準ユーザーフロー）を知る](https://qiita.com/yyukawa/items/5996fd6558a9f5fa8631) 
1. [基本的な概念（テナント、アプリケーション、ユーザー）を理解する](https://qiita.com/yyukawa/items/da96b4666ace678db167)★
1. [ユーザーフローの日本語化](https://qiita.com/yyukawa/items/f5a6960f5e457b5cc262) 
1. [パスワードレス認証](https://qiita.com/yyukawa/items/7c96f9599630420f37c3)
1. 条件付きアクセスを使って環境に応じた認証を要求する
1. 柔軟な認証フローを実現する：カスタムポリシー

# B2Cの環境構築の流れ

B2Cの環境構築の流れを図にしてみました。
これまでみてきたようにカスタムポリシーを利用しようとしなければ簡単に高度な認証機能を持った基盤を構築することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16609/1d94421b-8bc0-b1f1-5acc-79654c7099db.png)


# B2CのテナントとAzure Active Directory

B2Cの管理画面から離れて、同じディレクトリのAzure Active Directoryをみてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16609/ac750510-7bf8-f90c-09df-d092fe89d2ce.png)

なぜかサインインした痕跡がありますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16609/cbe4e08e-9e98-c59e-980f-de9fc4b68cb2.png)

ユーザーとアプリの登録をみてみましょう。前回登録したものが表示されると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16609/bb281deb-f198-8baa-1ff4-c9cfb3df1780.png)

つまり前回登録したユーザーというのはAzure Active Directoryのユーザーで、前回登録したアプリケーションはAzure Active Directoryのアプリケーション（サービスプリンシパル）です。
このことを確認した上で、B2Cの管理画面に戻り、左メニューにある「ロールと管理者」をみてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16609/6caaf4b2-d70e-5073-971f-3263cbdbfedc.png)

B2Cには関係なさそうなロールがたくさん並んでいますね。でも、B2CのユーザがAzure ADのユーザであると考えると何も違和感はないと思います。B2Cの管理画面はB2Cに関連するものを集めたものになっているだけで、Azure Active Drectoryと同じものを操作しているのです。

# B2Cのユーザー

B2CのユーザーはAzure Active Directoryのユーザーであり、以下の3つの種類のユーザーを扱うことができます。

1. ローカルユーザー。自分自身のテナントのユーザー
1. ゲストユーザー。このテナントに招待された自分以外のAzure Active Directoryのユーザー
1. コンシューマーユーザー。MicrosoftアカウントのユーザーやFacebookのユーザーなどAzure Active Directory以外で管理されているユーザーがID連携をしてこのテナントに参加したもの

# B2Cのアプリケーション

B2CのアプリケーションはAzure Active Directoryのアプリケーションであり、B2Cで登場するアプリケーションには以下の3つの種類があります。

1. B2Cを認証基盤として利用するWebなどのアプリケーション。前回登録したjwtappもこちらです。MSALとアクセストークンの検証方法を説明する際に取り上げます。
1. Azure Active Directory GraphやMicrosoft Graphと呼ばれるAzure ADを操作するためのAPIを利用してテナントを操作する際に利用するアプリケーション（サービスプリンシパル）。Graph APIによるテナント操作について説明する際に取り上げます。
1. B2Cの内部で利用されるアプリケーション。B2Cのテナントを作成すると一緒に作られる「b2c-extensions-app. Do not modify. Used by AADB2C for storing user data.」といういかにもな名前のものと、カスタムポリシーを利用する場合に必要になる「IdentityExperienceFramework」と「ProxyIdentityExperienceFramework」があります。これらについてはカスタムポリシーについて説明する際に取り上げます。
【第1回】RailsのN+1問題と解決するためのメソッド完全版

この記事では、N+1問題の正しい理解と、解決するためのメソッドを紹介します。
第1回では、N+1問題とはなにか、そしてどのような状態で発生するのかを具体例とともに解説します。
この記事を読むことで、N+1問題の正しい理解、ケースに応じたN+1問題を解決するメソッドの使い分けができるようになるはずです。

##本記事の内容
* N＋1問題とは
* N＋1問題が発生する状況
* SQLが発行されるタイミング

##開発環境
* Ruby 2.7.2
* rails 6.1.0

##アソシエーション
![スクリーンショット 2021-06-22 22.36.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642155/f4609885-52cf-59ef-fdba-a37e001d40d0.png)
``users``テーブルと``posts``テーブルが1対多の関係でアソシエーションされています。
ユーザー1人に対して、投稿を複数持っている基本的な構成です。

それではさっそくいきましょう。

##N+1問題とは
**N+1問題とは、1つのテーブルへのSQL発行と、複数回（N回）の関連テーブルへのSQL発行がおこなわれてしまうことです。**
わかりやすくいうと、``eachメソッド``などのループ処理のときに、大量のSQLが発行されてしまうことです。
具体例をみていきましょう。

##N+1問題が発生する状況

```ruby:ruby.rb
User.limit(5).each do |user|
 p user.posts
end
```

SQLを確認してみましょう。

```ruby:ターミナル
User Load (1.7ms)  SELECT "users".* FROM "users" LIMIT $1  [["LIMIT", 5]]
Post Load (1.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (1.4ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (2.8ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2 
Post Load (2.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2
Post Load (1.7ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 /* loading for inspect */ LIMIT $2 

```
``users``テーブルに対して1回SELECT句を発行し、それぞれのユーザーに紐づく投稿を``posts``テーブルから5回取得しています。N+1問題とは、はじめの1回で軸となるモデル（テーブル）を取得し、そのモデルに対するデータ数分(N回)のSQLが実行されてしまう状態のことです。

今回は``limit``メソッドでデータ取得数を制限しましたが、N+1問題は、そのデータ量に比例してパフォーマンスが劣化してしまいます。初学者のポートフォリオレベルでは気にならないかもしれませんが、実務では何万、何十万とデータを扱うので、決して無視できません。

現状データ量が少ないとしても、サービスの成長とともにパフォーマンスの劣化は避けられないです。
また、大量のSQLを発行することはDBに高負荷を与えることにもつながり別な問題を発生させる原因にもなります。
とくに1対多のアソシエーションで発生することが多いです。

##SQLが発行されるタイミング
**RailsでSQLが発行されるタイミングは、SQLの実行結果をアプリケーションで使用するときです。**

```ruby:ruby.rb
User.limit(5).each do |user|
 p user.posts #=>このタイミングでSQLが発行される
end
```

どのタイミングでSQLが発行されるのか理解することで、パフォーマンスを意識した開発が可能になります。
ぜひ自分のポートフォリオを見直してみて、改善できそうなところを見つけてみてください。
N+1問題を解決するメソッドについては、次回まとめていきます。

##まとめ
* N+1問題とは、1つのテーブルへのSQL発行と、複数回（N回）の関連テーブルへのSQL発行がおこなわれてしまうこと。
* とくに1対多のループ処理で発生しやすい。
* RailsでSQLが発行されるタイミングは、SQLの実行結果をアプリケーションで使用するとき。

##参考文献
*[Railsガイド](https://railsguides.jp/association_basics.html)*
*[【Ruby on Rails】N+1問題ってなんだ？](https://qiita.com/massaaaaan/items/4eb770f20e636f7a1361)*
openvpnでVPNの初期化後、スクリプトを実行してVPNを終了する

特定のshell実行時のみVPN接続を行いたいパターンが出てきましたので作成しました。
※何らかの理由でopenvpnに接続しっぱなしになることも想定しておいてください。

```diff_shell
  FILE_OVPN=/tmp/test.ovpn
  FILE_LOG=$(mktemp)
  # VPN接続後に実行したいコマンド
  CMD_JOB="sh /tmp/test.sh"
  # openvpnのオプションは適当に設定してください
  CMD_VPN="sudo openvpn --config ${FILE_OVPN}"
  # 初期化完了時のキーワード
  KEYWORDS=" Initialization Sequence Completed"

  # openvpnをバックグランドで処理し、PIDを「WORK_PID」に受け取ります
  WORK_PID=$((nohup ${CMD_VPN} > ${FILE_LOG} 2>&1) & echo "$!")
  # 初期化が完了するまで待機します。
  # grepに「-m1」を付けていますので、「KEYWORDS」に引っかかると処理を抜けます

-  tail -n0 -f ${FILE_LOG} | grep -q -m1 "${KEYWORDS}"
+  # 下記の待機方法では『SIGPIPE』を受け取らずに？動作しないことがあるようです
+  # github actions の ubuntu ではマッチしても終了が出来ませんでした。
+  #tail -n0 -f ${FILE_LOG} | grep -q -m1 "${KEYWORDS}"
+  # 待機方法を変更しました
+  # こちらのパターンの場合、github actions でも動作します
+  timeout 20s grep -q -m1 --line-buffered "${KEYWORDS}" <(tail -n0 -f ${FILE_LOG})

  # スクリプト実行
  eval ${CMD_JOB}

  # 1. openvpnのプロセスを終了
  # 2. ゴミのログファイルを削除
  kill ${WORK_PID} \
    && rm -f ${FILE_LOG}

```

::: note info
AWS VPN Clientは「openvpn」の実行ファイル？を実行しているだけのようです。
※AWS VPN Clientをインストールするとインストール先のディレクトリに「openvpn」の実行ファイルが入っています。
※接続に使用するovpnファイルはopenvpnと同じです。
:::

## 最終的には

上記の処理を`vpn.sh`に記載をしたとします。
`CMD_JOB`の部分のみ下記のように修正します。

```vpn.sh
  # CMD_JOB="sh /tmp/test.sh"
  CMD_JOB=$1

  # 上記のように実行したいコマンドを引数で受け取るようにします
```

下記のように実行が出来るようになります。
※ 引数の部分を変更するだけでいろいろなコマンドを実行できるようになります。

```bash
# /tmp/test.sh を実行
sh vpn.sh "sh /tmp/test.sh"
# /tmp/test2.sh を実行
sh vpn.sh "sh /tmp/test2.sh"
```
レトロなゲーム風の掲示板を作った

８ビットゲーム風の掲示板を作りました。

データベースが面倒だったので、CSVファイルを使いました。

一応スレッドも立てられます。

CSSはNES.cssを使いました。

フォントはPress + Start + 2Pを使いました。

ご使用の際はモラルを守って使用してください。

あとLGTMしてください
お願いします

![10907D5F-1FCB-45BF-9F9A-B9566B6C811E.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/795753/d5563275-930a-f454-7d85-350a895227da.png)

![5FBADC20-6640-4682-A0DE-7684D955FFAD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/795753/6057fa30-2abd-21ad-aa4a-aa0c215a2339.png)

![83D43C75-01D4-4B7D-A425-9D27894439C6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/795753/cdd5ad1d-9ca6-00dc-7777-1cbc8461e013.png)

http://saccda1111.php.xdomain.jp/
ドローンをジョイスティックを使って飛ばす時のコマンド

#コントローラーを使ったドローン飛行
$roscore  ＃ROSmasterの実行
$rosrun joy joy_node　＃joy_nodeの指定、実行
$rosrun ardrone_autonomy ardrone_driver #ドローンとの接続
$rosrun command ardrone_controller.py #ドローンコントローラーコマンドの実行

#Joystick　入力値の確認
$roscore
$rosrun joy joy_node
$rostopic echo /joy
ColabにKaggleのデータをダウンロードする

ノートPCしか持っていなくGPUが使えないので、Colabを使ってみることにしました。
Colab NotebookでKaggleのデータをダウンロードする手順を以下にメモリました。

## 1. Kaggle APIをインストール

```shell
!pip install kaggle
```

## 2. kaggle.jsonを使った認証

kaggleのユーザープロファイルの「Account」タブを開き、「Create API Token」を選択します。
API認証情報を含むファイルkaggle.jsonがダウンロードされます。

以下のセルを実行して、Colabランタイムにkaggle.jsonをアップロードします。

```python
from google.colab import files

uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))
  
# Then move kaggle.json into the folder where the API expects to find it.
!mkdir -p ~/.kaggle/ && mv kaggle.json ~/.kaggle/ && chmod 600 ~/.kaggle/kaggle.json
```

## 3. Kaggle APIを使ってデータをダウンロード

コンペリスト

```shell
!kaggle competitions list
```

各コンペのDataページにAPIコマンドがあります。これを使用してデータをロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260307/3e6c0566-f54e-c1a0-f6a0-63adf86dcc57.png)

```shell
!kaggle competitions download -c titanic
```

## ColabからKaggleへ結果提出

```shell
!kaggle competitions submit titanic -f my_submission.csv -m "comment"
```

## 参考情報

* [How to use the Kaggle API from Colab](https://colab.research.google.com/github/corrieann/kaggle/blob/master/kaggle_api_in_colab.ipynb#scrollTo=Oqt7Yfi5aQuM)
* [Google Colab上でKaggleのデータをロード、モデル訓練、提出の全てを行う](https://qiita.com/katsu1110/items/a8d508a1b6f07bd3a243)
WSL + django + mod_wsgiで環境構築の練習を行う

# はじめに
本番環境の環境構築を行う前に一度WSLで環境構築の練習を行ったのでその方法を残す

# 前提
- pyenvを使用してPython or Anacondaをインストールしている
- pipでdjangoをインストールしている

# apacheのインストール
apacheをインストールして起動します。

```
# インストール
sudo apt install apache2 apache2-dev
# 起動
sudo service apache2 start
```

起動したら[http://localhost](http://localhost)にアクセスして起動しているか確認する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684236/3a432ecd-335e-3e5e-cfb2-3f7f007d2402.png" width="500">


# プロジェクトの作成
djangoのプロジェクトを作成する。
プロジェクトは`/home/{UserName}/`に作ります。
(今回はRest Frameworkの練習をしていたので`django_rest_framework_test`とした)

```
# django-admin startproject {プロジェクトの名前}
django-admin startproject django_rest_framework_test
```

# mod_wsgiのインストール
mod_wsgiをインストールします。

```
pip install mod_wsgi
```

インストールしたら以下を実行します

```
mod_wsgi-express module-config
```

実行したら以下のようなものが表示されると思います。
これはどこかにコピーしておいてください。

```
LoadModule wsgi_module "/home/{UserNane}/.pyenv/versions/anaconda3-2021.05/lib/python3.8/site-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so"
WSGIPythonHome "/home/{UserName}/.pyenv/versions/anaconda3-2021.05"
```

# apacheとmod_wsgiの紐づけ
`/etc/apache2/apache2.conf`に追記します。

```
LoadModule wsgi_module /home/{UserName}/.pyenv/versions/anaconda3-2021.05/lib/python3.8/site-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so
WSGIPythonHome /home/{UserName}/.pyenv/versions/anaconda3-2021.05

WSGIScriptAlias / /home/{UserName}/django_rest_framework_test/django_rest_framework_test/wsgi.py
WSGIPythonPath /home/{UserName}/django_rest_framework_test


<Directory /home/{UserName}/django_rest_framework_test/django_rest_framework_test>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
```

# 確認
`apache2.conf`に追記したらapacheを再起動します。

```
sudo service apache2 restart
```

再起動後、[http://localhost](http://localhost)にアクセスしてdjangoの画面が出たら成功です。
Katalon Studio で「Update webdrivers to upgrade and try again」Webドライバーを更新してからリトライしてね

##エラー文言の例
Katalon Studio実行時に以下のような表示が出ることがあると思います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699916/9be1aa90-1190-d5dd-eb0d-03c2a75aebf5.png)


```
It seems like your Chrome or Edge Chromium Webdriver is not up to date with your current browser. Please go to Tools > Update webdrivers to upgrade and try again
```

##Webドライバーをアップデートする
Webドライバーがブラウザのバージョンより古いため、アップデートが必要なようです。
アップデートは画面から簡単にできます。
Tools > Update WebDrivers > 利用するブラウザを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699916/53bb6405-4d9c-ce57-7208-469862032487.png)

これだけで大丈夫です。

WebUI.openBrowser('')
で失敗する場合は、大抵ブラウザとWebドライバーのバージョンが合っていないことが多いので試してみましょう。
配列vsオブジェクト(使い分けについて)

##はじめに
現在JavaScriptを学習＆復習をしています。
プログラミング学習においてこれとこれの使い分けは？という疑問が生まれてくると思います。
その中の一つである複数のデータを一つにまとめるデータ、配列とオブジェクト。
使い分けとそれぞれの特徴について深ぼれたら良いなと思います。
　結論、一つにまとめるデータですが配列とオブジェクトは持っている機能が違う為、データを処理する際にどちらが向いているのか考える必要があるのですが、判断するのはなかなか難しい場合もあります。

##選びやすい分類方法
　あくまでもイメージですが
エクセルなどの表計算を思い出してください。
あるデータをJavaScriptで使うときに、そのデータをエクセルに入力するとして、
「縦に並べて入力したいか」「横に並べて入力したいか」を思い浮かべてください。
そのデータを縦に並べたくなるのであれば、配列が向いています。

##配列
###例　都道府県などのリスト

|1|北海道|
|:---:|:---:|　
|2|青森|
|3|沖縄|
↓

```
<script>
const prefectures=['北海道','青森　','沖縄'];
console.log(prefectures);
</script>
```

###例　やることリスト

|1|美容院|
|:---:|:---:|　
|2|犬のトリミング|
|3|ジム|


以上は縦に並べたくなるデータですが、一方で横に並べたくなるものですとオブジェクトが向いています。

データベースなどがいい例えです
1つのものに紐づく複数のデータを管理する場合はオブジェクトの方が好ましいです。

##オブジェクト
###例　商品の価格や在庫数など

|item|cost|stock|
|:---:|:---:|:--:|
|apple|300|15|
↓

```
<script>
let fruits={item:'apple',cost:300,stock:15};
console.log(fruits);
</script>
```


以上が大まかな使い分けですが
その中で条件を入れた場合、縦横に広がる表となります。

##配列とオブジェクトを組み合わせたもの
やることリストに「やること・期限・優先順位」を追加した場合

||todo|due|priority|
|:---:|:---:|:---:|:---:|
|1|美容院|6/20|1|
|2|犬のトリミング|7/1|3|
|3|ジム|6/22|2|

上記のように単なるやることリストではなく、その中で条件を入れた場合、縦横に広がる表となります。
このようなデータを管理する場合は配列とオブジェクトを組み合わせて管理します。

##終わりに
プログラミングにおいて最初に説明した通り
似たような文法があり、どのような使い分けをするのかと疑問に思うことがちらほらありますが理解できた部分だけ投稿させていただきました。
今回のような判断は経験などが必要かと思いましたので随時具体的に登校できたら良いと思います。
Dockerのコンテナー（debian）にCloudWatch-Agentをインストール

コンテナ内に「CloudWatch-Agent」をインストールし、各種ログを収集出来ます。

## Dockerfile

```Dockerfile
ENV RUN_IN_CONTAINER=True

# aws設定
RUN mkdir -p /root/.aws/ \
    && { \
      echo '[profile AmazonCloudWatchAgent]'; \
      echo 'region = ap-northeast-1'; \
    } > /root/.aws/config \
    && { \
      echo '[AmazonCloudWatchAgent]'; \
      echo 'aws_access_key_id = xxx'; \
      echo 'aws_secret_access_key = xxx'; \
    } > /root/.aws/credentials

# CloudWatch-Agentのインストール
RUN mkdir -p /usr/share/man/man1/ \
    && apt-get install -y collectd \
    && curl https://s3.amazonaws.com/amazoncloudwatch-agent/debian/amd64/latest/amazon-cloudwatch-agent.deb -o /tmp/amazon-cloudwatch-agent.deb \
    && dpkg -i -E /tmp/amazon-cloudwatch-agent.deb \
    && rm -f /tmp/amazon-cloudwatch-agent.deb

# CloudWatch-Agentの設定
# AWS Fargate？: /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
# docker: /etc/cwagentconfig
RUN { \
      echo '{'; \
      echo '  "agent": {'; \
      echo '    "metrics_collection_interval": 60,'; \
      echo '    "run_as_user": "root"'; \
      echo '  },'; \
      echo '  "logs": {'; \
      echo '    "logs_collected": {'; \
      echo '      "files": {'; \
      echo '        "collect_list": ['; \
      echo '          {'; \
      echo '            "file_path": "/var/log/test/test_*/*.log",'; \
      echo '            "log_group_name": "testgroup",'; \
      echo '            "log_stream_name": "access"'; \
      echo '          }'; \
      echo '        ]'; \
      echo '      }'; \
      echo '    }'; \
      echo '  },'; \
      echo '  "metrics": {'; \
      echo '    // いろいろと設定'; \
      echo '  }'; \
      echo '}'; \
    } > /etc/cwagentconfig

# いろいろな立ち上げ処理を行う
RUN { \
        echo '#!/bin/bash'; \
        echo "nohup /opt/aws/amazon-cloudwatch-agent/bin/start-amazon-cloudwatch-agent > /dev/null 2> /dev/null &"; \
    } > /usr/local/bin/app.sh

ENTRYPOINT ["/usr/local/bin/app.sh"]
```

* ルートユーザで動作をさせる想定ですので、AWSの認証情報を`/root/.aws/`に配置しています。
* debianで`collectd`をインストールする際、`/usr/share/man/man1/`を前もって作成しておく必要があります。  
作成をせずに実行するとインストール途中でエラーが発生します。  
エラーが発生した場合`collectd-core`を削除する必要があります。  
削除は`remove`と`purge`でライブラリを削除してください。  
※削除しないとapt-getで何もインストールが出来なくなります。
* `/etc/cwagentconfig`は`amazon-cloudwatch-agent-config-wizard`で作成できます。  
CloudWatch-Agentをインストールすると`amazon-cloudwatch-agent-config-wizard`は使用できますので、適当に調整してください。
* 収集するログの`file_path`はアスタリスクが使用可能です。
* `start-amazon-cloudwatch-agent`で実行できますのでバックグラウンドで起動します。

## docker-compose.yml
```docker-compose.yml
version: '3.7'
services:
  test:
    #
    # いろいろな設定
    #
    ports:
      # CloudWatch-Agentのデフォルトポート
      - 8125:8125
```

※ポートは変更できます
VSCode で AVR 開発のための設定

# この記事は？

VSCode には、C/C++ の文法チェックをしてくれる拡張機能があります。(Microsoft社提供)

![vscode-ext.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/830c7b48-d941-21f0-f9f0-93d81eac4033.png)

C/C++ の構文エラー等をエディター上に表示してくれます。
デフォルト設定では AVR 用ライブラリ等には対応していないので、設定してやる必要があります。
ただエラーを非表示にしたいだけであれば、拡張を一時的に無効にしてやるだけでも良いです。
が、入力補間等を愛用する自分は、AVR 用 Cプログラムでも構文チェックが欲しいです。

この記事では、自分が行ったその設定をまとめました。

# 環境

| 項目 | バージョン等 |
| --- | --- |
| OS | Linux Mint 20.1 Ulyssa 64 ビット |
| avr-gcc (GCC) | 5.4.0 |
| Visual Studio Code | 1.57.1 |
| C/C++ (VSCode 拡張) | v1.5.0-insiders2 |

この記事では、AVR チップ「ATmega 328p」に対する設定で書いていきます。

# インクルードパス

この拡張のデフォルト設定のまま AVR 用 C ソースを記述すると、このようなエラーが表示されてしまいます。

![code-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/22f0791f-9a67-d09f-06df-6f9e7de9cc83.png)

デフォルトでは標準の Cライブラリ`/usr/include`しかインクルードパスに入っておらず、AVR 用ライブラリ`/usr/lib/avr/include`は検知対象外となっています。
そこで、インクルードパスの設定に、`/usr/lib/avr/include`を追加してやります。

![conf-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/4c80f928-d5dd-5949-7210-baeebe2aedc4.png)

# コンパイラ設定

さて、`avr/io.h`等のヘッダのインクルードが検出されるようになりました。
しかし、今度はレジスタ等関連のものが定義されていないとエラーが出てしまっています。

![code-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/f4f5b433-51ed-a8cc-cb10-e7f2ef4043e6.png)

コード上で`avr/io.h`をインクルードした段階では、まだ各チップ固有のレジスタ情報等がまとめられたライブラリは読み込まれていません。
(ATmega328p の場合、`avr/iom328p.h`)
各チップ固有のライブラリの読み込みは、`avr-gcc`コンパイルの際に行われます。

```sh
avr-gcc -mmcu=atmega328p main.c -o main
```

構文チェックで`-mmcu=atmega328p`が適応されるよう、設定をしていきます。

まずはコンパイラパスの設定。
`avr-gcc`をコンパイラとしてパスを指定します。

![conf-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/97ec70ae-38c7-dc05-1d69-d2dc454cae83.png)

次に、コンパイラ引数に`-mmcu=atmega328p`を追加します。
(ここは、使用するチップによって変更してください。)

![conf-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/8af7d66a-c35f-0fbf-fd1b-f5d1c532ef41.png)

以上で設定は完了です。
AVR ライブラリが読み込まれ、レジスタ等の情報も正しく表示してくれています。

![code-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540894/10c97b2d-4349-cd4b-330d-6849f794df22.png)
Error: No formulae or casks found for "php8".

<code>brew search php8</code>

↑コマンドにてインストールできるphp8系を探そうとしたところ、掲題のエラーが、、

homebrewのバージョンが古いのかと思いupdateを試みる。。

<code>brew update</code>

が、しかし、また怒られる。。

```
Error: 
  homebrew-core is a shallow clone.
  homebrew-cask is a shallow clone.
To `brew update`, first run:
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow
These commands may take a few minutes to run due to the large size of the repositories.
This restriction has been made on GitHub's request because updating shallow
clones is an extremely expensive operation due to the tree layout and traffic of
Homebrew/homebrew-core and Homebrew/homebrew-cask. We don't do this for you
automatically to avoid repeatedly performing an expensive unshallow operation in
CI systems (which should instead be fixed to not use shallow clones). Sorry for
the inconvenience!
```

このコマンドを実行しなさいと神のお告げ。言われるままに↓を実行

```
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow
```

再度updateを試みる

<code>brew update</code>

すると、、

```
Already up-to-date.
```

やったー！！！大成功！！！

ほっと一息。

再度インストールできるphp8系を探すと

<code>brew search php8</code>

```
==> Formulae
php@8.0 ✔
```

はい、でたー！！！

そしてインストール

<code>brew install php@8.0</code>

これで無事インストール完了。
imageminで画像が圧縮されない【gulp.js】

# はじめに
gulpfile.jsを作成中、画像が想定通りに圧縮されない現象に遭遇しました。具体的には、jpgファイルのみが圧縮されるが、その他のpngやsvgは圧縮されないという状況です。解決にかなりの時間がかかってしまったので、その解決方法を共有したいと思います。

# 原因と解決
原因となったコードはこちらです。

```js：gulpfile.js
const paths = {
img: {
    src: './src/assets/img/**/*.{jpg, png, gif, svg}',
    dest: './dist/assets/img/',
  },
}
```

そして、問題が修正されたコードがこちらです。

```js：gulpfile.js
const paths = {
img: {
    src: './src/assets/img/**/*.{jpg,png,gif,svg}',
    dest: './dist/assets/img/',
  },
}
```

違いが分かりましたか？

……正解は`*.{jpg,png,gif,svg}`のカンマ直後の空白の有無です！空白があるとそれ以降は認識されないみたいです。（上のコードのままですと、jpgのみが変換されます。）勉強になりました。

# 終わりに
今回のミスはimageminに限らず、複数の拡張子を用いる際は起きる可能性があるので、癖がついてしまっている方は気をつけていきたいですね。
VSCodeでArduinoコンパイルエラー(「SD.h」に対して複数のライブラリが見つかりました)

VSCodeでArduinoスケッチファイルをコンパイルした際、
複数のライブラリが見つかりました、というエラーが出てコンパイル失敗してしまったので
その時の対応を備忘録して記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665304/420c3c04-9334-d523-c5eb-861bd43366ae.png)


コンパイル中に表示された内容を見返してみると・・・
二つのファイルをまとめてコンパイルしてしまっているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665304/b7367380-ece8-9e8b-9c6d-137aeb8d204e.png)
コンパイル失敗の原因は上記のようです。
これで二重定義のエラーになってしまったようでした。

<br>

ただ、２つのファイルはタブなどで開いておらず、何故まとめてコンパイルしようとしたのかは不明です。(同じフォルダに複数のスケッチファイルを置いておくとダメっぽいです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1665304/bdc347c7-32a8-2a10-9443-b479293806d5.png)

とりあえず1フォルダに1スケッチファイルを置くことにします。
(良い対処方法が分かれば追記するようにします)
４つのRPAを同時に動かしてみる。

# はじめに
https://qiita.com/etoootak/items/38df3f70d38b00228625
https://qiita.com/etoootak/items/2503f501c4c880dd2838
の続きです。
今度は４つのRPAを同時に動かしてみましょう。
（完全なお遊びなので、細かい所は気にしないでください）

# 操作対象
.NETのFormアプリです。数字を打ち込むと背景色が変わります。
TextBoxのIDは全てユニークでこのIDを使ってRPAから打ち込みをします。
端から順に打っていきます。
![4rpas_target.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632535/fdc51d8d-3faf-7010-4fe2-2e40462b0ad1.png)


# 使ったRPA
WinActor 7.2.1（評価版）
UiPath Comunity版 2021.2.0-beta44
Power Automate Desktop 2.7.00158.21098
Automation 360 Community版 build 9604
※ロジックは全て同じにしています。

# 実行結果
https://youtu.be/PK4ESfQayrw

# どうでもいい感想
・個々のRPA単体だと問題なくても同時に動かすと細かいところで操作ミス出る。
・特にPower Automate Desktopがよく対象を外す。ID指定なのにちょっと不思議。
・まぁ、同時に動かすというのが間違いなんだけど。
・動き重視（見た目重視）で実行方法がRPA毎に違うので実行速度は比較不可。
・でもWinActorのUI AutomationとAutomation360のキャプチャは遅く感じる。

# おまけ（各RPAの動的要素の指定方法）
##WinActor
fieldName = "textBox_0_%cnt%"
[{"id":%fieldName%, "index":"0"}]
※最近、変数指定が使えるようになったらしい。
##UiPath  
fieldName = "textBox_1_" + cnt.ToString()
ctrlname='{{fieldName}}' 
## Power Automate Desktop
fieldName=%'textBox_2_' + cnt%
edit[Id="%fieldName%"]
## Automation 360
\$fieldName\$ = textBox_3_\$cnt.Number:toString\$
ID  \$fieldName\$

似ているようで微妙に違うので同時に書いていたら、つまらないミスを多発。。。