OmniAICreator/Qiita-1.07M huggingface dataset:
REST APIでのHTTPメソッドの考え方についてのメモ

# 目次
[1. GETメソッドでサーバからデータを取得する時](#1-getメソッドでサーバからデータを取得する時)
[1.1. GETメソッドを使う時](#11-getメソッドを使う時)
[1.2.サーバ内の取得対象データのイメージ](#12サーバ内の取得対象データのイメージ)
[2. POSTメソッドでサーバからデータを取得する時](#2-postメソッドでサーバからデータを取得する時)
[2.1. POSTメソッドを使う時](#21-postメソッドを使う時)
[2.2.サーバ内の取得対象データのイメージ](#22サーバ内の取得対象データのイメージ)

# 1. GETメソッドでサーバからデータを取得する時
## 1.1. GETメソッドを使う時
- サーバからデータを**取得**する時
- URLにクエリパラメータで、検索対象のデータの条件を付ける時
```http:クエリパラメータで、欲しい情報の条件をサーバへ送信する
http://example.html?key=hoge&id=huga
```
## 1.2.サーバ内の取得対象データのイメージ
GETメソッドを使う時は
**テーブルの中にごちゃごちゃに格納されているデータの中から、SQLで検索して取得するイメージ。**

検索対象のテーブルに、データがごちゃ混ぜに入っている。
その中から欲しいデータをSQLで条件を絞って取ってくるカンジ。


![学校テーブルにごちゃごちゃに情報が入っている.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2330584/aeeea1f9-e3f8-2891-a27e-70a5e84b6703.png "検索対象のテーブルに、データがごちゃ混ぜに入っている")



# 2. POSTメソッドでサーバからデータを取得する時
## 2.1. POSTメソッドを使う時
- サーバにデータを**保存**する時
- URLだけでデータをサーバから取って来るとき
　(HTTPリクエストのbody部に検索条件が入る)
```http:URLだけでデータをサーバから取って来るとき
http://example.html(←HTTPリクエストのbody部に検索条件が入るため、URLは変わらない)
```
- たぶんPUTやDELETEも同じ考え方

## 2.2.サーバ内の取得対象データのイメージ
POSTメソッドを使う時は
**整理されて格納されているデータの中から、バーンと決め打ちで欲しいデータを取ってくるイメージ。**

検索対象のテーブルに、データが整理されて情報が入っている。
もしくは、データを分類分けして格納したテーブルを紐づけている。
その中から「このデータが欲しい！」と決め打ちで取ってくるカンジ。

![学校テーブルに整理されて情報が入っている.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2330584/c23e0d46-8768-71c1-0b3c-8da27dc9953e.png  "検索対象のテーブルに、データが整理されて情報が入っている")


![学校テーブルにそれぞれのテーブルを紐づけることで、整理されて情報が入っている.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2330584/8255a101-2889-73f7-1a92-af36bc3ec71b.png "データを分類分けして格納したテーブルを紐づけている")
Rustでベクターを結合する

Rustで二つのベクターを結合するには、extendメソッドやappendメソッドを使うことができます。

extendメソッドは、1つのベクターに別のベクターを追加することができます。

```rust
let mut vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];
vec1.extend(vec2);
println!("{:?}", vec1); // [1, 2, 3, 4, 5, 6]
```


appendメソッドは、1つのベクターに別のベクターを追加することができます。
```rust
let mut vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];
vec1.append(&mut vec2);
println!("{:?}", vec1); // [1, 2, 3, 4, 5, 6]
```

appendメソッドを使う場合は、&mutを使って可変参照を渡す必要があります。
Docker の実験（その１１：イメージの調査）

[その１０：コンテナのレイヤ](https://qiita.com/intrajp/items/84c64c10f6f3e610920d)のつづき。

イメージの調査方法

```bash
$ docker image inspect <image name>
```

イメージ内で特定の事項を調べたい場合はどうしたらよいだろうか。
単純に grep する。

```bash
$ docker image inspect nginx | grep 'Hostname'
            "Hostname": "412e505a8437",
            "Hostname": "",
```

format オプションを利用する。

```bash
$ docker image inspect nginx --format='{{.Id}}'
sha256:3f8a00f137a0d2c8a2163a09901e28e2471999fde4efc2f9570b91f1c30acf94
```

```bash
$ docker image inspect nginx --format='{{.ContainerConfig}}'
{412e505a8437   false false false map[80/tcp:{}] false false false [PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NGINX_VERSION=1.23.3 NJS_VERSION=0.7.9 PKG_RELEASE=1~bullseye] [/bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon off;"]] <nil> false sha256:81125abfc1520356faf20748ab85157762879efb86ee42f5061fb58c7b5bbae3 map[]  [/docker-entrypoint.sh] false  [] map[maintainer:NGINX Docker Maintainers <docker-maint@nginx.com>] SIGQUIT <nil> []}
```

json 形式での表示。

```bash
$ docker image inspect nginx --format='{{json .ContainerConfig}}'
{"Hostname":"412e505a8437","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.23.3","NJS_VERSION=0.7.9","PKG_RELEASE=1~bullseye"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"nginx\" \"-g\" \"daemon off;\"]"],"Image":"sha256:81125abfc1520356faf20748ab85157762879efb86ee42f5061fb58c7b5bbae3","Volumes":null,"WorkingDir":"","Entrypoint":["/docker-entrypoint.sh"],"OnBuild":null,"Labels":{"maintainer":"NGINX Docker Maintainers <docker-maint@nginx.com>"},"StopSignal":"SIGQUIT"}
```

特定の項目の表示。

```bash
$ docker image inspect nginx --format='{{.ContainerConfig.Hostname}}'
412e505a8437
```

[その１２：イメージのクリーンアップ・検索](https://qiita.com/intrajp/items/9fc1565e723a31fd3cbc)につづく。
Rustで2の累乗を判定する

ビット論理積という考え方を使うらしい。&で記述できる。

```rust
fn is_power_of_two(n: i32) -> bool {
    n > 0 && (n & (n-1)) == 0
}
```

n > 0 は単純にゼロ以下だったらfalseになるため（2の累乗が０以下になることはない）。

2の累乗である数は、2進数表記した場合に1桁の1があり、それ以外の桁が0である数のことです。
例えば、2^3 = 8は、2進数表記すると1000で、2^4 = 16は、2進数表記すると10000です。

この性質を利用すると、ある数が2の累乗であるかどうかを判定できます。

### ビット論理積について

ビット論理積は、2つのビット列を比較して、両方のビットが1である部分だけを残した結果を返す演算です。ビット論理積の記号は & です。

たとえば、2進数表現で 1101 と 1011 のビット論理積は以下のようになります。

```
1101
&
1011
------
1001
```
上記の例では、1101 と 1011 のビット論理積は 1001 になります。これは、2進数表現で9に相当します。

ビット論理積が0になる場合、つまり2つのビット列の論理積の各ビットが0になる場合には、以下のような特徴があります。

__1.どちらかのビットが0の場合、結果のビットは必ず0になる。__
__2.両方のビットが1の場合、結果のビットは1になる。__

例えば、2進数の「1001」と「0110」のビット論理積は「0000」となります。これは、上記の特徴から以下のように計算できます。

```
1001
&
0110
-----
0000
```

つまり10進数では0になるため、trueが返ります。
Node.jsについて

# はじめに


#### <font color="red">※以下の内容には誤りが含まれる可能性があります</font>

Node.jsについて学んだことを書いていきます。



# 具体的な内容


## Node.jsとは何か


Webページ作成などの際に使われるJavaScriptをサーバー側で動作させるプラットフォームです。


# Node.jsを使うメリット



・大量のアクセスに強い。リアルタイムで処理を実行可能です。
・クライアントサイドからサーバーサイドまで同じスクリプトが使える。



# Node.jsを使うデメリット



・対応してるサーバが少ない。
・特定のイベントの処理が重たいとリクエストが待ち状態になる。


# まとめ



Node.jsを使うことでJavaScriptをサーバー側で動作させることができ、それによって色々なことが実現できることを知りました。引き続き学習を続けます。
CakePHP 会員登録、ログイン機能の実装

# はじめに
cakePHPのブログチュートリアルに沿って簡易的なブログを作った過程で、苦慮した箇所を纏めました。

    
# パスワードのハッシュ化
セキュリティ対策の為、登録されたパスワードのハッシュ化を行いました。

```php:Model / User.php

//パスワードのハッシュ化
public function beforeSave($options = array()) {
    if (isset($this->data[$this->alias]['password'])) {
        $passwordHasher = new BlowfishPasswordHasher();
        $this->data[$this->alias]['password'] = $passwordHasher->hash($this->data[$this->alias]['password']);
    }
    return true;
}
```
beforeSaveでパスワードが保存される前に、BlowfishPasswordHasherを使用してパスワードをハッシュ化を行う。



```php:Controller / AppController.php

class AppController extends Controller {
	public $components = array(
        'Auth' => array(
            'loginRedirect' => array(
                'controller' => 'posts',
                'action' => 'index'
            ),
            'logoutRedirect' => array(
                'controller' => 'posts',
                'action' => 'index'
            ),
            'authenticate' => array(
                'Form' => array(

                     //パスワードのハッシュ化処理
                    'passwordHasher' => 'Blowfish',
                )
            )
        )
    )
}
  
```
'passwordHasher'は、CakePHPがどのハッシュアルゴリズムを使用するかを指定するための設定で、'Blowfish'は、bcryptハッシュアルゴリズムを使用するための設定。他のハッシュアルゴリズムを設定することも可能らしい。。。


# 論理削除
自分が投稿したデータを削除する場合は、物理削除ではなく論理削除を行うようにしました。
今回、論理削除の実装にはCakeDC/utilsのSoftDeleteBehaviorを使用しました。

データベースに下記の論理削除用のカラムを作成

削除フラグ
カラム名：deleted
型：tinyint(1)
※デフォルト値：0

削除日
名前：deleted_date
型：datetime
※デフォルト値：NULL

```php:Config / bootstrap.php
CakePlugin::load('Utils');
```
まずはプラグインを読み込みます。

```php:Model / Post.php
<?php
class Post extends AppModel {
    public $actsAs = array('Utils.SoftDelete');
}
?>
```
SoftDeleteBehaviorを利用できるようにします。

```php:Model / AppModel.php
class AppModel extends Model {
    public function exists($id = null) {
        if ($this->Behaviors->loaded('SoftDelete')) {
            return $this->existsAndNotDeleted($id);
        } else {
            return parent::exists($id);
        }
    }

    public function delete($id = null, $cascade = true) {
        $result = parent::delete($id,$cascade);
        if ($result === false && $this->Behaviors->enabled('SoftDelete')) {
            return (bool)$this->field('deleted', array('deleted' => 1));
        }
        return $result;
    }
}
```
`$this->Behaviors->loaded('SoftDelete')` で SoftDelete ビヘイビアが適用されているか確認してます。もし適用されている場合、 `$this->existsAndNotDeleted($id)`メソッドを使用して削除されていないデータが存在するかどうかを判断し、そうでない場合は、元のexists メソッドを呼び出します。
existsAndNotDeleted メソッドは、$idで指定されたデータが存在し、削除フラグが立っていない場合に true を返します。

# 参考
[【PHP】CakePHPでパスワードをハッシュ化する](https://furudate.hatenablog.com/entry/2013/10/12/023836)
[初めての[CakePHP2.x]での開発、そして挫折するまで日記～その5 論理削除](https://www.tailtension.com/cakephp/398/)
GitHubで草を生やす方法

## 内容
・GitHubで草を生やす方法について解説していく

## 目的
・スタンプラリー感覚でコードを書くことの習慣化
・日々の記録

## 草を生やす方法・生える条件
・参考記事

https://qiita.com/mi-1109/items/006a534b26190424c499

今回は、この記事と同様にリモートリポジトリへのコミットで草を生やす。

## さっそく草を生やそう！

### 前提
・Gitインストール済
・GitHubアカウント作成済

### 手順
1　ローカルにファイル・フォルダを作成する
2　GitHubでリモートリポジトリの作成
3　ローカルリポジトリとリモートリポジトリの紐づけ

ターミナルにて以下のコマンドを実行（初回のみ）
```
git init
git add .
git commit -m "コメントの入力欄"
git remote add origin 作成したリモートリポジトリのURL
git push origin プッシュしたいbranch名 
```

2回目以降のプッシュ作業
```
git add .
git commit -m "コメントの入力欄"
git push origin プッシュしたいbranch名 
```

## おわりに
・草生やす（義務感）

### 参考記事

https://qiita.com/mi-1109/items/006a534b26190424c499
シェルソート

## 1.今日のプログラミング

前回は、セレクトソートをやりました。

が、順番を間違えたため関係はあまりないです。

今回はインサートソートに基づいて作られた**シェルソート**についてやっていこうと思います。

環境

VisualStadio 2022

C#

---

## 2.シェルソート

シェルソートとは、**一定間隔でグループ分けをして、そのグループ内で並び替えを行い、間隔を小さくしながらまた並び替えをする**、ソートアルゴリズムの一つです。

ここでいう並び替えはインサートソートの事です。

ゆえに、インサートソートをもとにして作られたソートアルゴリズムなのです。

ただし、弱点があります。

それは、すごくぐちゃぐちゃなソートはあまり得意じゃないところです。

シェルソートは、「概ねソート済みの配列に対しては高速」という性能を最大限引き出すアルゴリズムなので、とても**不安定な内部ソート**です。

---

—内部的な動き—

配列[4,6,2,5,1,3]について考えるとします。

まず、配列をABCの3パターンのグループに分けます。

A、B、C、A、B、C

||　 ||　||　||　 ||　 ||　

4、6、2、5、 1、 3

そして、同じグループ同士で並び替えをします。

この場合は、

A　:　4　＜　5　noSwap

B　:　6　＞　1　Swap

C　:　2　＜　3　noSwap

　　☟結果

[4、1、2、5、6、3]

次は、配列をABの2パターンで分けます。

A、B、A、B、A、B

||　 ||　||　||　 ||　 ||　

4、1、2、5、 6、 3

そして同じグループ同士で分けます。

A　:　2、4、6

B    :　1、3、5

　  ☟結果

[2、1、4、3、6、5]

最後に並び替えて

[1、2、3、4、5、6]

ソート完了。

やってみましょう。

---

```csharp
namespace ShellSort
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] number = {5,7,6,3,9,8,2,1,4,0};
            Shell(number);
            for(var i = 0; i < number.Length; i++)
            {
                Console.WriteLine(number[i]);
            }
            while (true) ;

        }

        public static void Shell(int[] array)
        {
            //グループ分け（array.Length / 2でグループの数を決めてる。）
            //（間隔を徐々に狭くしてるのは、group = group / 2）
            for(var group = array.Length / 2; group > 0; group = group / 2)
            {
                for(var i = group; i < array.Length; i++)
                {
                    var j = i;
                    
                    while(j - group >= 0 && array[j] < array[j - group])
                    {
                        Swap(array, j, j - group);
                        j = j - group;
                            
                    }
                }
            }
        }

        public static void Swap(int[] array , int a, int b)
        {
            array[a] = array[a] + array[b];
            array[b] = array[a] - array[b];
            array[a] = array[a] - array[b];
        }

    }
}
```

```csharp
実行結果
0
1
2
3
4
5
6
7
8
9

```

ここら辺から難しくなってきましたね。

シェルソートのメリットは、最良の場合の計算時間はインサートソートと変わらないのですが、最悪の場合の計算時間がシェルのほうが早い。というとこが良いところでしょう。

それと、インサートソート（挿入ソート）を触れてからのほうが書きやすかったかもしれないです。

なので次回はインサートソートについてやりましょう。

ちょっとした予習をした感じでみてください。

## まとめ

やはり、インサートソートの弱点がほぼ無くなったシェルソートはいいですね。

どんどん早い方を使っていきましょう！

ちなみにシェルソートの「シェル」はドナルド・シェルさんというコンピュータ科学者の名前が由来らしいヨ。

参考にするといいサイト

[https://medium-company.com/シェルソート/](https://medium-company.com/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88/)

[https://www.codereading.com/algo_and_ds/algo/shell_sort.html](https://www.codereading.com/algo_and_ds/algo/shell_sort.html)

[https://programming-place.net/ppp/contents/algorithm/sort/005.html](https://programming-place.net/ppp/contents/algorithm/sort/005.html)
NUnitでテスト対象.NETプロジェクトのConfigをDIする

# NUnitでテスト対象.NETプロジェクトのConfigをDIする

テストプロジェクトでテスト対象プロジェクトの設定をそのまま使用したい。  
でもappsettings.jsonを丸ごとコピーとか、あれこれコードを増やしたくない・・・
そんな怠惰な自分にピッタリな方法を今更ながら備忘録的に残しておく。

## IConfigurationなプロパティを作る

テストプロジェクトのSetUpでAddSingletonとかする時用にプロパティにしておく

``` csharp
public class Tests
{
  public IConfiguration Configuration
  {
    get
    {
      var configBuilder = new ConfigurationBuilder()
                .SetBasePath("参照したいjsonがあるパス")
                .AddJsonFile("appsettings.json")
                .AddJsonFile("appsettings.Development.json");
            // Buildでconfigのインスタンスが返せる
      return configBuilder.Build();
    }
  }
}
```
`.SetBasePath`でjsonのある場所を指定することと、`.AddJsonFile("appsettings.json")`と`.AddJsonFile("appsettings.Development.json")`で、分割してあるファイル分Addするのがポイント。

`.AddUserSecrets("ユーザーシークレットのID")`を足せばユーザーシークレットで管理している値も使用できる。

## 作成したプロパティでDI

SetUpなメソッドでAddSingletonなりなんなりするだけ

``` csharp
public class Tests
{
  [SetUp]
  public void Setup()
  {
    var services = new ServiceCollection();
    services.AddSingleton<IConfiguration>(Configuration);
    services.AddSingleton<IDbContext, DbContext>();
  }
}
```

　
ちなみに、DbContextの中身を・・・
``` csharp
private readonly IConfiguration _configuration;
private readonly string _connectionString;

public DbContext(IConfiguration configuration)
{
    _configuration = configuration;
    _connectionString = _configuration.GetConnectionString("key");
}

public IDbConnection CreateConnection() => new SqlConnection(_connectionString);
```
などとしておくとDapperでConnectionするのが楽
インサートソート

「目次」

## 1.　今日のプログラミング

前回は順番を前後してしまいシェルソートからしてしまったので、

今回は、シェルソートの元になっているインサートソートをやっていきたいと思います。

環境

VisualStadio 2022

C#

---

## 2.　インサートソート

インサートソートとは、**整列してある配列に追加要素を適切な場所に挿入する**、ソートアルゴリズムの一つです。

このソートアルゴリズムの強いところは、

- アルゴリズムが単純で実装が簡単
- 短い（小さい）配列に対しては高速
- 処理速度が安定したソートアルゴリズム

逆に弱いところは、

- 整列された小さい配列に「速い」ので、ぐちゃぐちゃで大きい配列には
    
    不向き。
    
- 逆順のリストを対象にした場合、めちゃくちゃ遅くなる。
- 処理の時間が並び方によって左右される。

という感じの、使える場所が限られそうなソートアルゴリズムですね。。。

---

—内部の動き—

※比較対象　　赤

　ソート済み　緑

配列[5,3,2,4,1]で考えてみましょう。

一回目

まずは5と3を比べます。

5，3，2，4，1　

5　＞　3　Insert

3、5、2、4、1

---

二回目

次に、2と5と3を比べます。

3、5、2、4、1

3　＞　2　Insert

5　＞　2   Insert

2、3、5、4、1

---

三回目

次に4と5と3と2を比べます。

2、3、5、4、1

2　＜　4　NoInsert

3　＜　4　NoInsert

5　＞　4　Insert

2、3、4、5、1

---

四回目

最後に1とすべてを比べます。

2、3、5、4、1

2　＞　1　Insert

3　＞　1　Insert

4　＞　1　Insert

5　＞　1　Insert

1、2、3、4、5

ソート完了です。

やってみましょう。

---

```csharp
namespace InsertSort
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] number = {9,0,6,8,7,3,2,5,1,4};
            InsertSort(number);

            for(var i = 0; i < number.Length; i++)
            {
                Console.WriteLine(number[i]);
            }
            while (true) ;
        }

        public static void InsertSort(int[] array)
        {
            for(var i = 0; i < array.Length; i++)
            {
                var insertNumber = array[i];
                var insertPosition = i;
                
                for(var j = insertPosition - 1; j  >= 0; j--)
                {
                    //右にシフトする
                    if(insertNumber < array[j])
                    {
                        array[j + 1] = array[j];
                        insertPosition--;
                    }
                }
                //新しい要素を挿入
                array[insertPosition] = insertNumber;

            }
        }
    }
}
```

```csharp
実行結果
0
1
2
3
4
5
6
7
8
9
```

これはイメージがつきやすいのではないでしょうか？

自分は紙に書いて要点を整理していたら簡単にかけてしまいました。

ここが理解できれば、シェルソートもできるはずです。

自分でいろいろなケースを想定して、自分なりのコードを書いてみましょう！

## まとめ

前後しましたが書いてる側としては、「あ、ここシェルソートで使った」

という点が多々ありました。

そういうとこに気付けるようになってる自分でも驚きました。

微々たる成長を感じられてモチベーションが上がりましたｗ

参考にしたサイト☟

[https://ja.wikipedia.org/wiki/挿入ソート](https://ja.wikipedia.org/wiki/%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88)

[https://www.codereading.com/algo_and_ds/algo/insertion_sort.html](https://www.codereading.com/algo_and_ds/algo/insertion_sort.html)
ページング方式

# ページング方式
ページ単位に分割してやり取りするが行われる。
仮想記憶上のページと実記憶上のページ枠はページテーブルによって対応づけられている。

### ページイン
実行に必要なページを```補助記憶装置から主記憶装置へロード```すること。

### ページアウト
ページインするときに主記憶装置上に空き領域がない場合に、ページ置き換えアルゴリズムに基づいて```不要なページを補助記憶装置へ書き戻す```こと。

### ページフォールト
実行すべきページが```主記憶装置にないとき発生する割り込み。これによってページの入れ替え（ページアウト→ページイン)が行われる。ページイン/ページアウト（主記憶から仮想記憶への退避/再ロード）が頻繁に行われることを```スラッシング```という。

### スラッシング
プロセッサが```ページイン/ページアウトに処理能力を割いてしまい、アプリケーションが実行に使うプロセッサの使用率が減少するため処理が滞る```。

# 気づき
メモリの容量は大きい方がページイン/ページアウトの回数が減るからアプリケーションの実行に使う率が上がるのか。

# 出典
https://gihyo.jp/book/2022/978-4-297-12375-8

https://gihyo.jp/book/2022/978-4-297-13178-4
Nuxt.js + GA4で自社のIPアドレスからのアクセスが除外されないという問題を解決した話

# 結論：パラメータの設定の問題

Nuxt.jsを使ったコンテンツで、自社内からアクセスしたときにGoogle Analyticsで計測されているという問題があるという相談を受けました。
Google Analyticsの設定では、自社IPからのアクセスは計測除外のフィルターを入れているということです。
ということで調査してみたました。

結論から言うと組み込んでいるGoogle Analyticsモジュールのパラメータの設定が原因でした。

Google Analyticsには`anonymize_ip`というパラメータがあり、送信時にこれを`true`にするとIPアドレスをマスキング、つまり第4オクテットなどを0にして計測するということらしいです。

[ユニバーサル アナリティクスでの IP マスキング](https://support.google.com/analytics/answer/2763052?hl=ja)

Nuxt.jsのconfigの設定が明示的に`true`になっていました。
これを`false`にしてみたところ、ちゃんと自社のIPアドレスが除外されて計測されていたとのことです。
Nuxt.jsの設定箇所は以下になります。
https://thenextbit.de/en/blog/gdpr-compliant-ga-nuxt#anonymize-ip

なぜこの設定があるのか。
これはいわゆるGDPR対応によるものだそうです。
そのため、もしかしたらサイト上でのデータ収集同意などの対応が必要かもしれません。
[GDPR対策のためにアナリティクスのIPアドレスを匿名化する方法](https://makoto-shimizu.com/news/gdpr-ip-anonymization-in-analytics/)

# 原因の特定

どうやって原因を特定したか。
問題のコンテンツは、ドメインのサブコンテンツとして運用しているものでした。
そのため、ドメイン直下のコンテンツとGoogle Analyticsの`https://www.google-analytics.com/g/collect`への通信内容を比較してみました。

するとURLの長さが違うというのと、URLの最後辺りについているパラメータに`&ep.anonymize_ip=true`というのがついていたので、名前的に明らかに怪しいと思ったわけです。
なのでそれを中心に調べてみて、試してみたらと助言しました。

ちなみに私は現時点でNuxt.jsは触ったことがない。
やらないとね。
【GAS】シート作成時同名のシートが存在している場合のエラー対策





# シートを作成するコード
まずスプレッドシートに「シート名を指定してシートを追加」する処理は以下のようになります。
```js:シート追加
// スプレッドシート指定
const ss = SpreadsheetApp.getActiveSpreadsheet();
// シートを追加
const sheet = ss.insertSheet("追加シート名");
```

# 同名のシートが存在しているとエラーになる
上記の処理は同名のシートが存在した状態で実行するとエラーになります。
```Exception: シート名「*****」はすでに存在しています。別の名前を入力してください。```

![insertSheetError.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557347/2a631a08-0389-e2d8-8963-d86c47004027.jpeg)

そこで、エラーにならないように同名のシートが存在するかを確認して
**存在していたら、そのシートを使う**
**存在しない場合は、シートを新規作成**
されるようにしていきます。

# シートの存在チェック
GASでシートの存在をチェックする方法は以下のようになります。

```js:シートの存在チェック
// シートの取得 存在しない場合はエラーにならず null になる
const sheet = ss.getSheetByName("追加シート名");

// nullの場合シートが存在しない、 nullではない場合シートが存在すると判定
if (sheet === null) {
  console.log("シートが存在しません。")
} else {
  console.log("シートが存在します。")
}
```

# 同名のシートが存在しないときだけシートを追加
以下のコードで、シートが存在しないときだけシート追加処理が実行されます。
```js:シートが存在しないときだけシートを追加
// シート名設定
const sheetName = "追加シート名";
// シート取得 存在しない場合 null になる
let sheet = ss.getSheetByName(sheetName);
// シートが存在しない場合 シートを作成
if (sheet === null) {
  sheet = ss.insertSheet(sheetName);
}

```


# 1行で書く
javascriptの仕様 「```||``` (OR) の左側が```偽```だったら、右の処理がされる」を利用して
「シートを取得してnullだったら、シート追加」を1行で書けます。
```js:シートが存在しないときだけシートを追加
// シート取得、存在しない場合新規作成
const sheet = ss.getSheetByName(sheetName) || ss.insertSheet(sheetName);

```

# 全体のコード(コピペ用)
```js:
function insertSheetTest() {
  // スプレッドシート指定
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // シート名設定
  const sheetName = "追加シート名";
  // シート取得、存在しない場合新規作成
  const sheet = ss.getSheetByName(sheetName) || ss.insertSheet(sheetName);

  // シートオブジェクトへの処理例 A1セルに"aaa"書き込み
  sheet.getRange(1,1).setValue("aaa");

}
```





# 参考

https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet?hl=ja#getsheetbynamename

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_OR
Phalcon Permission deniedで怒られた話

Phalconがどうとか以前にPHPもあんまり分からんけど使う機会があって、
フロントからファイルを送信してバックで受け取るということをした時に、
なんか分からんエラーに見舞われたのでメモ。

input type="file"で選択したファイルを
こんな感じで送信
```javascript
 $.ajax({
        url  : api_url,
        type : "POST",
        data : formdata,
        cache       : false,
        contentType : false,
        processData : false,
        enctype: 'multipart/form-data'
    })
    .done(fnc_done())
    .fail(fnc_fail());
```

Phalconの受け取り側
ドキュメント見ると
```
public function uploadAction()
{
  // Check if the user has uploaded files
  if ($this->request->hasFiles() == true) {
        // Print the real file names and their sizes
        foreach ($this->request->getUploadedFiles() as $file) {
            // $file
        }
    }
}
```
こんな感じで$fileにファイル入ってくるようなので、
はぇ〜と思いつつ、
moveTo関数で一時フォルダに置くんやで、みたいなことが書いてあるので
こんな感じにしてみて
```
$file->moveTo($file->getName());
```
fopenする必要があったので、
```
$fopenFile = fopen($file->getName(),'rb');
```
みたいにすると、普通に読み込めた。

解決や！　と思ってたら、うまくいったのはローカル環境だけで、
それをAWS上で動かした時にうまく動かなかった。

```
move_uploaded_file failed to open stream: Permission denied
```
的なことで怒られる。

ファイル、ディレクトリの権限を確認しても変なところは無い。

なんでーと思いつつ
```
$fopenFile = fopen($file->getTempName(),'rb');
```
とmoveTo飛ばして一時ファイル直接開くみたいにしたら普通に動いた。


という話でした
Rustのデータ付きenumをSwift側で使いたい

C言語と互換性があれば、rustのプログラムをSwiftのプログラムとlinkすることができます。しかし、enumの各Variantがデータを持っていた場合は、どうしますか? C言語ではenumの各Variantにデータが存在しません。この記事では、そのlink方法を紹介します。

サンプルコードはこちら

https://github.com/NiwakaDev/shared_enum_between_rust_and_swift

rustで書かれたenumを用意しましょう。

```rust
#[repr(C)]
pub enum NiwakaEnum {
    Variant1(i32, u32),
    Variant2,
}
```

これとメモリの配置が等価な表現をSwiftで用意するには、
```C
//これはヘッダーファイルです。
typedef enum NiwakaEnumTag { Variant1, Variant2} NiwakaEnumTag;
typedef struct NiwakaVariant1Fields{
    int32_t _0;
    uint32_t _1;
} NiwakaVariant1Fields;
union NiwakaEnumFields { NiwakaVariant1Fields Variant1;};
typedef struct NiwakaEnum { NiwakaEnumTag tag; union NiwakaEnumFields payload;} NiwakaEnum;
```

Swiftの`struct NiwakaEnum`が、Rustの`enum NiwakaEnum`とメモリ表現が同じです。試しに、Rust側で用意した関数に、`struct NiwakaEnum`を渡してみましょう。

```rust
#[no_mangle]
pub extern "C" fn print_niwaka_enum(niwaka_enum: NiwakaEnum) {
    match niwaka_enum {
        NiwakaEnum::Variant1(value_i32, value_u32) => println!("NiwakaEnum::Variant1({}, {})", value_i32, value_u32),
        NiwakaEnum::Variant2 => println!("NiwakaEnum::Variant2")
    }
}
```

これをSwift側で(フォーマットツール適用してません。ベタ書きですみません。)
```Swift
print_niwaka_enum(NiwakaEnum(tag: Variant1, payload: NiwakaEnumFields(Variant1: NiwakaVariant1Fields(_0: 10, _1: 100))))
```

実行結果は、

```
NiwakaEnum::Variant1(10, 100)
```

です。

## なぜ同じなのか?
詳細は、[Enums With Fields](https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields)と[Using Imported C Structs and Unions in Swift](https://developer.apple.com/documentation/swift/using-imported-c-structs-and-unions-in-swift)を読みましょう。


## 参考
[Enums With Fields](https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields)
[Using Imported C Structs and Unions in Swift](https://developer.apple.com/documentation/swift/using-imported-c-structs-and-unions-in-swift)
プラグインのアップデートでconfig.ymlを更新したい

# 苦悩
プラグインをアップデートしてconfig.ymlの項目が増えた！！
`resources/config.yml`を更新したけど`plugins/*/config.yml`が更新されない...

# 解決
```java:MainClass.java
getConfig().options().copyDefaults(true)
saveConfig()
```

コメントが消えたり消えなかったりするらしい。
並びも`resources/config.yml`と同じにはならない。
iOSのショートカットアプリで集中しすぎ防止タイマーを作ってみた

# はじめに
勉強などをしていると時間も忘れて集中してしまい、集中が切れた時には脱力感を感じてしまうことはないでしょうか。集中することは良いことなのですが、あまり長時間ぶっ続けで作業すると疲れが溜まると思うので、程よいタイミングで休憩を知らせてくれる[iOSのショートカットアプリ](https://support.apple.com/ja-jp/guide/shortcuts/welcome/ios)でのコードを紹介します。

通常のタイマーをセットするだけでも良いのですが、周りに人がいる場合だとタイマーが鳴り続けると迷惑がかかるのと、いきなり大きい音が鳴るとびっくりするので、本ショートカットを作りました。

# 方法
作成したショートカットは[こちら](https://www.icloud.com/shortcuts/8c5e922218ad4272af78fee9fb53423c)から入手できます。

全体のコード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/2b7f2e85-8e3a-d862-0c01-d10e50e14e45.png)

<details><summary>コードの説明</summary>

* まず、集中する時間の入力のため、「入力の要求」という項目を入れます。デフォルトの時間をよく使う時間を分の単位でセットします。60分に設定してます。負の数字は使わないので、チェックボックスから外します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/d493bb89-80c4-2ad9-6aa0-fa919144e524.png)

* 次に分から秒の単位に変換したいので、「計算」という項目を入れます。×60することで単位に変換します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/9cc782f4-6688-7f25-d440-27173950fd50.png)

* そして、「待機」という項目を入れて、指定した計算時間まで待機します。ここがタイマーの役割を果たしています。ここの待機に入れる時間は、秒の単位なので注意しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/b7b8bfe8-c0cd-fa54-4e07-dd0bc52a630c.png)
:::note warn
画面がロック状態では、「待機」の時間が進まず正常に動作しないので注意してください。
:::
* 「デバイスの詳細を取得」を加えて、現在の音量のパーセントを取得します。後ほどのサウンドで知らせるときに、適切な音量に調節した後に元の音量に戻すためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/ec8b72b8-2a2e-7eb9-6367-41d9561c8952.png)


* 「音量を設定」の項目から音量を調節します。この行の意図は、次のサウンドの再生が大きすぎたり小さすぎたりしないようにするためです。本記事では、25%に設定していますが、環境に合わせて適宜変えてください。ただし、**ミュート(音量0%)に設定した場合は、サウンドの調整は行われず0%になる場合があリます**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/8bdd077e-8147-7f9e-9fbb-5d52cbecb061.png)
:::note warn
ミュート(音量0%)に設定しているとサウンド音は鳴らない場合があるので注意してください。
:::


* 指定時間になったら、サウンドで知らせますがこのサウンド音は、他のアプリの通知音と同様に一度だけ「プルンッ」と鳴ります。タイマーアプリなどのように鳴り続けることもなく、この音なら周りにも迷惑がかからないので良いと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/7669b494-a99f-e5f0-99bf-571b012b52d8.png)

* 「音量を設定」の項目から音量をサウンドが鳴る前のパーセントに戻しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/73376e3c-9460-9d4d-c7ee-4fb0066ecbd9.png)


* 音だと気付けない場合もあるので、一応メッセージでも知らせるため「結果を表示」の項目を加えます。このメッセージはずっと表示されるので休憩の時間になっていることに気づくことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/d51410c2-92a1-8e6e-eae5-88ff4b16561b.png)

</details>

# 使い方
:::note warn
ミュート(音量0%)に設定しているとサウンド音は鳴らない場合があるので注意してください。
また、画面をロック状態にすると、正常に動作しないので注意してください。
:::
* ショートカットアプリを再生すると以下のような画面が出るので、時間(分)を指定します。<br><img width="70%" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/0ee904cd-9152-2f84-0af7-a3c663f0cf1c.png">

* 時間になると「プルンッ」という通知音と共に「休憩しましょう」というメッセージが出るので、作業を止めて休みましょう。休憩時間などのコードは書いていませんが、カスタマイズするともっと使い勝手が良くなると思います。<br><img width="80%" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/bf13a930-edee-39ce-0408-093e0c474a8f.png">

# おわりに
iOSのショートカットアプリを使い、**通知音で休憩を知らせてくれる簡易的なタイマー**を作りました。作業していて、普通のタイマーだと音が鳴り続けるため周りの人も気が散ってしまうけど、通知音ならあまり周りの迷惑にもならないと思い今回作りました。集中しすぎに注意して快適に作業しましょう。
プログラミング用フォント「Ricty Diminished」のススメ

プログラマーは普段からプログラムばっかり読んでいます。
ここで、どんな文字で表示されているかというのは結構大事になります。

私は普段から「Ricty Diminished」というフォントを使用しているので紹介します。
（私は10年近くこのフォントを使っていて思考停止しているので最近はもっといいフォントがあるかもしれません）

## Ricty Diminishedについて
RictyはLinux環境での研究・開発を想定したプログラミングフォントです。
- 全角文字の可視化
- 半角文字と全角文字が1:2の幅になるように調整されている
- 読みやすい（個人の感想です）

## インストール
GitHubに公式のリポジトリがあります。
普通に使う分にはソース管理しなくていいのでZIPダウンロードでOKです。
https://github.com/edihbrandon/RictyDiminished
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199145/086a4fb8-0409-e665-d189-86aad735e9c8.png)

解凍したらコンテキストメニューからインストールしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199145/612aca33-d5ec-bcb5-db42-54617b837590.png)

## エディタ(IDE)に適用
### Visual Studio
私は仕事の都合上Visual Studioを頻繁に使うので適用してみます。
ツール＞オプション＞環境＞フォントおよび色
からRicty Diminishedに変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199145/7257b8ce-6eb6-a0ae-7c6f-3c6dba3733ef.png)

Before
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199145/cdcf0fc6-1b52-9599-304e-d685c8bf6bf8.png)
After
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199145/738e4ca2-9d0b-3002-0b4b-a87b5844fd52.png)

違いがわからないですか？そんな日もあります。

### Visual Studio Code
`Font Family`で検索かけると入力欄があるのでRicty Diminishedと入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199145/759672b4-3f4e-38d2-9c62-9daa386006d0.png)

# まとめ
どうでしたか？
Rictyじゃなくても、自分が見やすいと思うフォントを探すのも大事だと思います。
「プログラミング用フォント」で検索すると相当数出てくるので色々試してみてください。
VBAにはWithEventsというものがあるらしい

久方ぶりにVBAを書くことになったのでその時の新たな発見

C#ではイベントハンドラーにラムダ式を突っ込んだりしてたが、VBAで動的に関数を切り替える方法を知らなかった。
今回、その方法を知ることができたのでまとめる

とその前にWithEventsというやつを説明するのでそのために
英字を受け付けないTextBox(疑似拡張コントロール)を作ってみる

## 作るもの
は以下
![疑似拡張コントロール動作.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70164/c3611f8e-7c7c-1608-169c-788a0e9e31e2.gif)

## 手順
1.ユーザーフォームを追加(UserForm1とする)

2.クラスモジュール(CustomCrl)に
```VB
Private pvForm As MSForms.UserForm
Private WithEvents myTextBox As MSForms.TextBox  '※１

Public Property Set SetForm(ByRef aForm As MSForms.UserForm)
    Set pvForm = aForm
    Call ControlAdd
End Property
Private Sub ControlAdd()
    'ProgIDを指定してTextBoxのインスタンス生成+追加
    Set myTextBox = pvForm.Add("Forms.TextBox.1")  '※２
    With myTextBox
        .Left = 10
        .Top = 10
        .Width = 100
        .Height = 15
    End With
End Sub
Private Sub myTextBox_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    pvForm.Caption = Chr(KeyCode) + CStr(KeyCode)
    '英字のみ不可。半角数字は可。バックスペースは可。全角も可
    If 65 <= KeyCode And KeyCode <= 90 Then
        KeyCode = 0
    End If
End Sub
```
[ProgIDはこちらを参照](https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/add-method-microsoft-forms)

3.標準モジュールに
```VB
Sub 拡張コントロール()
    Dim Custom1 As New CustomCrl
    Set Custom1.SetForm = UserForm1
    UserForm1.Show
End Sub
```
## フロー説明
初期化の過程としては
拡張コントロール()を呼び出す
↓
CustomCrlクラスを初期化し、UserForm1を格納する
↓
UserForm1を受け取ったクラスはUserForm1にTextBoxを追加する

※１
クラスモジュールに
```
Private WithEvents myTextBox As MSForms.TextBox
```
と宣言しTextBoxを格納することで、ControlCrsクラスは格納したTextBoxでハンドルされるイベント処理を記述することができるようになる

※２
```
Set myTextBox = pvForm.Add("Forms.TextBox.1")
```
この記述により、追加されたコントロールでKeyDownイベントが発生したとき
myTextBox_KeyDownが処理されるようになる

## 結語
WithEventsで宣言したコントロールを変数に持つクラスで、該当のコントロールをラップすることにより、
標準モジュールの処理次第でラップクラスの切り替え、動的にフォームの内容を変更できる

この例では標準モジュールの記述でUserFormにカスタムTextBoxを追加したが、
ユーザーフォーム内のコードでCustomCrlの生成・操作も可能
その場合、ユーザーフォームから見れば、自身と追加されるであろうコントロールをラップしたインスタンスを変数に持つことになる。
また、UserFormではなくPageを親コントロールとすることもできるし、Object型にして使いまわすことも可。

気が向いたら続きを書く
【VBA】ブックの操作(開く・閉じる・保存)

# メモ
業務でブックの操作を行うVBAを書く事があるが、良く忘れるのでメモに残しておく。

## ブックを開く
`WorkBooksコレクションのOpenメソッド`で開けます。

シンプルにフォルダパスを指定して、ブックを開く場合は下記のように指定しています。

```:フォルダパスを指定して、開く。
Sub Book_Open()
      
      Workbooks.Open("C:\Open_Target.xlsx")
    
End Sub
```

同じ配下にあるブックを開きたい場合は、`ThisWorkbook.Path`を使用しています。

```:同じ配下のブックを開く。
Sub Book_Open()

    Workbooks.Open (ThisWorkbook.Path & "\Open_Target.xlsx")

End Sub
```

上記のコードで開いたブックが、アクティブなブックになります。開いたブックで処理を行いたい場合は、このまま処理を進めて良いと思います。
違うブックで処理をしたい場合は、`WorkBooksコレクションのActivateメソッド`でアクティブ化したいブックを指定します。

```:ブックをアクティブ化
Sub Book_Activate()

    Workbooks.Open(ThisWorkbook.Path & "\Open_Target.xlsx")
    
    Workbooks("aaaaa.xlsx").Activate

End Sub
```

基本的には、ブックの操作はオブジェクト変数に格納して行う事が自分は多いです。
その方がコードの管理が個人的に楽です。


```:オブジェクト変数に格納
Sub Book_Activate()

    Dim wb1, wb2 As Workbook
    
    Set wb1 = ActiveWorkbook
    Set wb2 = Workbooks.Open(ThisWorkbook.Path & "\Open_Target.xlsx")
    
    Debug.Print wb1.Name
    Debug.Print wb2.Name

End Sub
```


## ブックを閉じる
`WorkBooksコレクションのCloseメソッド`で閉じる事が出来ます。

```:ブックを閉じる
Sub Book_Close()

    Dim wb As Workbook
    Set wb = Workbooks("Close_Target.xlsx")
    wb.Close
    
End Sub
```

ブックに変更がある場合は、下記のようなポップアップが出てきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710307/22cc939a-e0da-28f8-c952-7ca184e73da3.png)

ポップアップを出したくない場合は、SaveChangesオプションを使用しています。
Falseを指定した場合は、保存せずに閉じる。Trueを指定した場合は、保存して閉じます。
※Application.DisplayAlerts = False　を記載して、画面の更新を止める事で対応する事も可能です。(ブックは保存されないです。)

```:ブックを保存せずに閉じる。(ポップアップを出さない。)
Sub Book_Close()

    Dim wb As Workbook
    Set wb = Workbooks("Close_Target.xlsx")
    wb.Close SaveChanges:=False
    
End Sub
```

## ブックを保存

`WorkBooksコレクションのSaveメソッド`で閉じる事が出来ます。

```:ブックを保存
Sub Book_Save()

    Dim wb As Workbook

    Set wb = Workbooks("Save_Target.xlsx")
    wb.Save
    
End Sub
```

名前を付けて保存する場合は、`WorkBooksコレクションのSaveAsメソッド`を使用します。

```:名前を付けて保存
Sub Book_Save()

    Dim wb As Workbook

    Set wb = Workbooks("Save_Target.xlsx")
    wb.SaveAs (Format(Date, "yyyymmdd") & "_" & wb.Name)

End Sub
```
Golangで文字列を標準出力する

現在、業務でJava(Kotlin)とNodeを利用する機会があります。自分の主務以外の仕事としてGolangのシステムに関わることになったため、基礎を勉強しようと思います。


自分は昔少しだけGolangを業務利用していたことがありましたが、まずは基礎知識と現状を把握するために書籍を探しました。Udemyの入門講座が新しいバージョンにも対応しているとのことで、一旦これを受講します。
https://www.udemy.com/course/golang-webgosql/

Golangの最新版をインストールする
https://go.dev/dl/

自分は2020年に購入したMacBookAirでMacのM1チップなのでx86-64のgo1.20.1.darwin-amd64.pkgを選びます

```sh
$ uname -m
x86_64
$ go version
go version go1.20.1 darwin/amd64
```

早速main.goを作る

```golang:main.go
package main
import "fmt"
func main() {
	fmt.Println("hw")
}
```

コンパイルして実行する

```sh
$ go run main.go 
hw
```

まずはGolangの実行環境を整備して勉強する準備ができました。これまであまり本気で取り組んできませんでしたがGCPはもちろん、AWSやAzureでもGolangのSDKが準備されているところからも業界的にある程度地位を獲得している言語だと思います。まずは何らかのアプリを作って動かしてみることを目標に少しずつ続けていけたらと思います。
文章分割(Sentence spliter):bunkai、ネガポジ判定(Sentiment analysis):osetiのOSSを試す

### はじめに
@taishi-i さんの[awesome-japanese-nlp-resources](https://github.com/taishi-i/awesome-japanese-nlp-resources)という日本語NLPを使う際に有用なOSSをまとめているgithubを知り、幾つか試したくなりました。
こちらのgithubでは、複数のOSSがdownloads、starsが記載してされており、非常に見やすいです。

今回は、
* 文章分割:Sentence spliter
![Sentence spliter_screen.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684711/a39fe1ca-dd2b-e5c4-7793-be2229d0f6c2.png)

* ネガポジ判定:Sentiment analysis
![sentiment_screen.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684711/e08bfcbe-9d2b-25a8-2913-62749db42f80.png)


に注目することにする。
### 実験で試した環境
* Python 3.8.10 ※python 3.6では動作しなかったライブラリがあったので、バージョンは注意すること
* Sentence spliter -> [bunkai](https://github.com/megagonlabs/bunkai) 1.5.7
* Sentiment analysis -> [oseti](https://github.com/ikegami-yukino/oseti) 0.4.2

### インストール
今回紹介するOSSは非常にインストールしやすかったです。
`$ pip install -U bunkai  `
`$ pip install oseti `

### 実験で使用した文章
>これから飲み会♪旅行行きたい★でも、花粉やばい。。。。この時期涙が止まらないから大嫌いだ！！！花粉対策用の眼鏡買うかな？
>旅行のことを考え考えると楽しみでやばい。。
>と言いつつ、、、夜更かしをして明日は昼まで寝ちゃうかも(≧▽≦)最近眠いから起きれないかも、、

この文章にしたのは、
* 文末が「★」「。。。。」 「！！！」や「(≧▽≦)」のような「。」以外の記号があったとしても文章分割されるか
* 「大嫌いだ！」などの明らかにネガティブとわかる文章を「ネガティブ」と判定するか
* 「やばい」が含まれる文章が文脈によってネガポジが変化するか

この点が気になってこのような文章にしました。この文章を"sample.txt"として保存し、動作確認しました。

### ソースコード例
今回は分割した文章毎のネガポジ値を表示するスクリプトにしています。
まずはインポート。
```
from bunkai import Bunkai
import oseti
import pandas as pd
```
呼び出していきます。
```
bunkai = Bunkai()
analyzer = oseti.Analyzer()
```
"bunkai"というOSSは、
>1行は1つの文書を表します．文書中の改行は ▁ (U+2581) で与えてください．

という注意書きがgithub上にあり、以下ソースコードでは
`sentence = s.replace("\n","_")`と変換しています。

```
path = "sample.txt"
sentence_list = []
sentiment_list = []
with open(path, encoding='utf-8') as f:
    s = f.read()
    sentence = s.replace("\n","_")
    for sentence in bunkai(sentence):
        print(sentence)
        sentence_list.append(sentence)
        sentiment_list.append(analyzer.analyze(sentence)[0])

df = pd.DataFrame(columns = ["sentence", "sentiment"])
df["sentence"] = sentence_list
df["sentiment"] = sentiment_list

df.to_csv("output.csv")
```
### 結果表示と考察
||sentence|sentiment|
|:---|:---:|--:|
|0|これから飲み会♪|0|
|1|旅行行きたい★|1|
|2|でも、花粉やばい。。。。|-1|
|3|この時期涙が止まらないから大嫌いだ！！！|-1|
|4|花粉対策用の眼鏡買うかな？|0|
|5|_旅行のことを考え考えると楽しみでやばい。。|0.333333333333333|
|6|_と言いつつ、、、夜更かしをして明日は昼まで寝ちゃうかも(≧▽≦)|1|
|7|最近眠いから起きれないかも、、|0|

* 「これから飲み会♪」は、語尾の"♪"から個人的にはポジティブと判定して欲しかった。
* 「花粉やばい」の場合は「ネガティブ」と判定され、「旅行のことを考え考えると楽しみでやばい。」の場合「ポジティブ」寄りになっている（「楽しみ」という単語の影響か？）。
* 「、、、」はうまく分割できなかったが、色んな文末記号に対し、文章分割できている
* おおむねニュートラルっぽい文章をsentiment「0」と判定している

### その他
* osetiでは、github上にユーザー辞書を登録できる機能も紹介されているので、今後カスタマイズもしてみたい。
* sentimentの他のOSSでは、「大嫌い」を「ネガティブ」と判定できないものもあり、事前にサンプル文を用いて上手く評価できるか確認してから利用した方が良いと思った。

## 参考（ありがとうございます！）
* github:[awesome-japanese-nlp-resources](https://github.com/taishi-i/awesome-japanese-nlp-resources)
* github:[bunkai](https://github.com/megagonlabs/bunkai)
* github:[oseti](https://github.com/ikegami-yukino/oseti)
CUPS+Gutenprint on Raspberry pi で自宅のプリンタをLANプリンタ化して、ついでにRAMディスクにsmb共有作ってPDF自動プリントをやってみた

## 初めに
この記事は拙作「「Windows印刷鯖で、クライアントだけはドライバレスで印刷できる」ようにする (https://qiita.com/sambar_gamer/items/80f91f619b92400ef647) 」の派生ネタになります。

# 環境
1. プリンタ: Canon MP490 (Gutenprintドライバを使用)
1. Raspberry Pi 3 (部屋にあったやつ。)
    1. OSはRaspberry pi OS(arm64版)を使用

# やった事:
1. Raspberry Pi OSのインストール&初期設定(説明を省略)
1. CUPSの設定(説明省略)
1. Sambaの設定
    * 印刷用PDFファイルの仮置き場として、64MBのramddiskを作成・マウントする　[^1]
    * /etc/fstab に `tmpfs           /share          tmpfs   defaults,size=64m       0       0 ` を追記
    * `# mount -a` を実行して /share をマウント
    * /etc/samba/smb.conf に下記を追記
    ```
    [pdfprint_tmp]
    path = /share
    read only = no
    guest ok = no #guestの印刷を許可したい場合はyesにする
    writable = yes
    ```
    * `# systemctl restart smbd.service` を実行して、sambaを再起動する
    * crontab に定期的に /share 内のファイルを削除する設定を追加する (推奨。でも説明略。[^2])
1. inotifywaitを使用して、「pdfファイルが置かれたら自動でlpコマンドを実行」の設定をする [^3][^4]
    * [Linux] inotifywaitを使ってファイル更新時に任意のコマンドを実行する (https://qiita.com/sonodar/items/ddeeb98525ef4c03d48e) を参照願います。
    * pdfの自動印刷の場合、watch-file.serviceの`ExecStart` 行は、`ExecStart=/usr/local/bin/watch_pdf.sh /share '\.[pP][dD][fF]$' "lp"` になる。
    * コメントで「ファイル名に空白が含まれる場合に対応するパッチ」を投稿させていただきました。


# 参考資料
https://qiita.com/sonodar/items/ddeeb98525ef4c03d48e

# 備考
[^1]: 常識的な使い方なら、64MBも要らないと思う。
[^2]: セキュリティ上の要求があるなら、「印刷後直ちに削除」のシェルスクリプトを作って、それを実行する方が良い。
[^3]: リンク先で言及されてるけど、systemd.path の方が良いのかなぁ。
[^4]: 試し刷り用の「1枚だけ印刷」用ディレクトリと、「メンバー全員分印刷」用ディレクトリを分ける方法もあったかも。
(この場合、watch-file.sh と watch-file.service を「試し刷り用」「メンバー全員分」用の2つ作るのが簡単か?)
Python全く分からん自分のアウトプット　その1

1日目：まずはprogateの演習を3周してみてpythonの感覚を掴むのがいいっぽいので何回かに分けて一旦3周やってみる。
どこまで時間かかるか分からないけど焦らずやっていこう。


◎全然分からないけど単語だけでもメモを取る◎

文字列・・・print()「””」で囲む

数値・・・()　全部半角。「＋」「ー」「＊」「/」「％」

変数・・・name=”名前”とかnumber=”数字”とかを「print()」に代入することによって出力

変数名・・・頭に数字は持ってこない。これはルールです。2語以上は「＿」を使う

代入・・・「x+=」はよく使う。

変換型・・・str

数値型・・・int


条件分岐
if文・・・「x==y」は左右が等しい時に成り立つ、「x!=y」は左右が等しくない時に成り立つ。行末に「:（コロン）」をつける。

インデント・・・半角スペース4つ分。これがあるかないかで影響が出るから気をつけること。

真偽値・・・「True」「False」頭文字は大文字

比較演算子・・・等しい系「==（等しい時）」と「!=（等しくない時）」、大小系「<（右辺が大きい）」「<=（右辺が大きいまたは等しい）」「>（右辺の方が小さい）」「>=（右辺の方が小さいまたは等しい）」


『『『『『ここから』』』』』
else・・・if文に組み合わせて「〇〇の時は△△、そうでない時は××」と工夫ができる。

elif・・・さらに複数の「もし○○ならxxを行う、△△なら▲▲を行う、そうでなければ□□を行う」とできる。

and・・・「条件1も条件2も成り立つ」時に使う。

or・・・「条件1か条件2が成り立つ」時に使う。

not・・・条件の否定
『『『『『ここまでけっこう間違えたから要勉強』』』』』


input・・・これを使うとコンソールに入力できるようになる。「変数=input("コンソールに表示したい文字列")」でおｋ。inputは文字列型になるから数値にしたいときは型変換で「int」を使う。


ｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰｰ

一旦pythonコース1が終了。
目安が１時間45分のところ2時間ちょっとかかった。
最終応用問題3問が全然解けなくて解答見まくったから悔しい。
いや最初からできてたら凄すぎんだろ！！！！！
明日もコース1を繰り返しやろうと思ったけど、ずっと同じところをやり続けるよりもどんどん進んで行った方がいいと思うので次はpythonコース2に進んで行こう。
そして少しでも前に進むぞうおおおおお！！！！！！！
OpenFOAMの結果をblenderで可視化してみる#04 ParaviewからBlender用のファイルの書き出し

# はじめに
前回からの続き。
今回は，openFOAMの結果をBlenderで読み込める形式に出力して，可視化するまでを行う。

# Blenderで読み込み可能なファイル形式
Blenderでは，下記で標準で読み込み可能なファイル形式は下記のとおりで，このうち，**ply**と**x3d**はParaviewで出力できるようになっている。
- .dae
- .abc
- .bvh
- .svg
- **.ply**
- .stl
- .fbx
- .glb/.gltf
- .obj
- **.x3d/wrl**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601976/24ad064c-f860-2f97-7d8f-00c4d90b6347.png)
# Paraviewでデータを出力する
## x3d形式の場合
File-> Export Sceneから行う。
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601976/8bd71f58-71d4-ea07-d46c-d7276ca11c08.png" width="40%" >

::: note info
ただし，Export Sceneは表示されている状態のデータを出力するだけ。時刻ごとに出力することはできない。
:::
### pythonによる連続出力
pythonを使うと，連続出力行うことができる。
サンプルは下記のとおり。
下記では，`C:\GithubData\OpenFOAM_Vis_Blender\cal_data\iobasin\PV_x3d`に，
ファイル名`{time_step}_time={time_value}.{ext}`で出力するようになっている．
``` python
from pathlib import Path
import numpy as np

ext = ".x3d"
save_directory_path = r'C:\GithubData\OpenFOAM_Vis_Blender\cal_data\dambreak\PV_x3d'
savedir = Path(save_directory_path)
savedir.mkdir(exist_ok=True)

# time steps
tk = GetTimeKeeper()
timesteps = tk.TimestepValues
numTimesteps = len(timesteps)

animationScene1 = GetAnimationScene()
renderView1 = GetActiveViewOrCreate('RenderView')
animationScene1.PlayMode = 'Snap To TimeSteps'
animationScene1.GoToFirst()
for i, time in enumerate(timesteps):
    animationScene1.AnimationTime=time
    timeStr = f'{i:04d}_time={time:04.2f}'
    ofn = savedir/ (timeStr + ext)
    print(ofn)
    ExportView(str(ofn), view=renderView1)
```
実行すると，PythonShellにログとして出力ファイル名が出力されていく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601976/7ef1ab48-b546-3162-e9ab-5cbf8c0a2844.png)

# blender で読み込んでみる。
とりあえず読み込むことができたが，クリップした後の形状データ以外のものもデータに含まれている・・・このあたりの処理は次回に回す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601976/a4b6c69a-5f88-6604-cd95-52ec0a597e70.png)


以上。
No7_ググったこと投稿(5分)_JS系


## 目次
 [1.DOMについて](#1-DOMについて)
 [2.Nodeについて](#2-Nodeについて)
 [3.addEventListnerについて](#3-addEventListnerについて)



# 1. DOMについて（Document Object Model）
 - DOMを利用してwebページとその要素にアクセス

    - JavaScriptでHTMLの要素を操作するための仕組み
        - DOMはHTMLから作られる
        - ウェブサイトを構築するために使用される Web API の 1 つ
    - DOMは、これらのノードを抽出・追加・置換・削除するための汎用的な手段を提供するAPI
        - ノードとは
            - 文書を構成する要素や属性、テキストといったオブジェクトのこと
  
# 2. Nodeについて
|ノード|ノードタイプの定数|ノードタイプの値|インタフェース|
|---|---|---|---|
|要素ノード|ELEMENT_NODE|1|Element|
|属性ノード|ATTRIBUTE_NODE|2|Attr|
|テキストノード|TEXT_NODE|3|Text|
|コメントノード|COMMENT_NODE|8|Comment|
|文書ノード|DOCUMENT_NODE|9|Document |

参考)
https://developer.mozilla.org/ja/docs/Web/API/Node/nodeType

# 3. addEventListener() について
- 二つの引数を指定可能
    - ハンドラーを登録したいイベントの名前
    - それに反応して実行させたいコードを含んだ関数
        - 匿名関数を使って、実行したいコードを全部addEventListener() 関数の引数に書いてもOK
```
btn.addEventListener('click', function() {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
});
```

- Event Objects
    - イベントハンドラー関数内で event、evt、単に e などと名付けられた引数
    - イベントハンドラーとは
        - イベントを検出し、イベントに処理を与える
        - イベントとは
            - マウスボタンをクリックした、ページが読み込まれたなどの動作が起こった時に発生
            - 参考）https://phpjavascriptroom.com/?t=js&p=event
【GitHub】マージ済みリモートブランチを自動的に削除する方法

## 設定方法
以下の手順でマージされたリモートブランチの自動削除を設定できます。

リポジトリの「Code」の画面から「Settings」をクリックします。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323334323434332f38316634396562612d643965622d353563642d386366302d3630313433383534333935652e706e67のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/325a89a7-6a31-3f5e-438f-2ac3cbd9b923.png)

「Settings」画面の「General」をクリックし、下にスクロールします。

![スクリーンショット 2022-10-02 22.01.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/d5ef5b7d-d35c-be9d-89f4-5f95d47ff00a.png)


「Pull Requests」という項目がありその中の「Automatically delete head branches」にチェックを入れます。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323334323434332f38353431333330622d633263612d656666342d393861632d3466366231373733303362342e706e67のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/8337fb44-eabc-b402-e026-c661774562e9.png)

## コマンドで設定
コマンドで上記の設定をする場合には以下のように実行します。

```zsh
gh repo edit ユーザー名/リポジトリ名 --delete-branch-on-merge
```
ジョブカン自動打刻の環境を5分で用意する

## TL;DR
- `Cloud Scheduler`で指定の時間に繰り返し打刻してもらうようにしました
- 誰でも簡単に環境を用意できるように`terraform`にまとめました

## 対象読者
- 打刻に疲れた人
- 打刻の修正に疲れた人

## やっていること
とても原始的ですが`selenium`でポチポチやっています

```main.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from secretManager import get_secret_version
import jpholiday
import os,time,datetime


def main(event, context):

    # URL
    TOP_PAGE_URL = 'https://id.jobcan.jp/users/sign_in'

    # cloud functionの環境変数からproject_idを取得
    PROJECT_ID = os.environ.get('PROJECT_ID')

    # ログイン情報
    MAIL_ADDRESS = get_secret_version(PROJECT_ID, 'secret_mail')
    PASSWORD = get_secret_version(PROJECT_ID, 'secret_password')

    # CSSセレクタ
    MAIL_INPUT_SELECTOR = '#user_email'
    PASSWORD_INPUT_SELECTOR = '#user_password'
    LOGIN_BUTTON_SELECTOR = '#login_button'
    KINTAI_PAGE_LINK_TAB_SELECTOR = '#jbc-app-links > ul > li:nth-child(3) > a'
    DAKOKU_BUTTON_SELECTOR = '#adit-button-push'


    # 祝日だったら何もせず終了
    is_holiday = jpholiday.is_holiday(datetime.date.today()) 
    if is_holiday:
        print('Today is holiday.')
        return
    

    # seleniumの初期化
    path = os.getcwd()
    driverPath = path + '/bin/chromedriver'
    headlessPath = path + '/bin/headless-chromium'
    options = Options()
    options.binary_location = headlessPath
    driver = webdriver.Chrome(driverPath, options=options)

    # ページを開く
    driver.get(TOP_PAGE_URL)
    time.sleep(2)

    # ログイン
    WebDriverWait(driver, 15).until(EC.presence_of_element_located((By.CSS_SELECTOR, MAIL_INPUT_SELECTOR)))
    mail_input = driver.find_element_by_css_selector(MAIL_INPUT_SELECTOR)
    password_input = driver.find_element_by_css_selector(PASSWORD_INPUT_SELECTOR)
    login_button = driver.find_element_by_css_selector(LOGIN_BUTTON_SELECTOR)
    mail_input.send_keys(MAIL_ADDRESS)
    password_input.send_keys(PASSWORD)
    login_button.click()
    time.sleep(2)

    # 勤怠ページへ
    WebDriverWait(driver, 15).until(EC.presence_of_element_located((By.CSS_SELECTOR, KINTAI_PAGE_LINK_TAB_SELECTOR)))
    kintai_page_link_tab = driver.find_element_by_css_selector(KINTAI_PAGE_LINK_TAB_SELECTOR)
    kintai_page_link_tab.click()
    time.sleep(2)

    # 打刻
    driver.switch_to.window(driver.window_handles[1])
    WebDriverWait(driver, 60).until(EC.presence_of_element_located((By.CSS_SELECTOR, DAKOKU_BUTTON_SELECTOR)))
    dakoku_button = driver.find_element_by_css_selector(DAKOKU_BUTTON_SELECTOR)
    print(dakoku_button.text)
    dakoku_button.click()
    time.sleep(10)

    driver.quit()
```

## デプロイ
こちらに方法をまとめました
Google Cloud上にプロジェクトを作成して、あとは`terraform apply`してもらえれば完成です

https://github.com/centerfield77/jobcan-auto

## おわりに
ジョブカンは従業員の勤怠を管理するために導入しているものなので、自動で打刻するのはよくないと思います。
ARC156回答メモ

0.はじめに
　久々な気がするARC。
　A問題が意外に早く解けたため、好成績を収め
　レートも+95と久々にいい感じでした。
　B問題はさすがになかなか難しく断念しました・・・。

1.A - Non-Adjacent Flip
　直感的に、
　裏が偶数でないとダメで、回数は裏の枚数*２　ただし例外有
　までは思いつきました。
　問題の、例外部分について最初は
　裏が2枚の時かつ、その2枚が連続しているとき
　　1）Nが3だったら-１
　　2)それ以外だったら3回としてました。
　するとWA2だったので、ちょっと違うんだなーッと再考。

　上の2)の条件が間違っていて
　　2)”0110”だったら3回
　　3)それ以外だったら2回
　としたところACを頂けました。

　https://atcoder.jp/contests/arc156/submissions/38972452

以上
【AtCoder】ABC290 E問題 解説

# 問題

ある列のすべての部分列に対して、回文にするために必要な交換回数の総和を求める問題。

https://atcoder.jp/contests/abc290/tasks/abc290_e


# 考えていたこと
$i$番目から$j$番目までの連続部分列における$f$をdpで管理する方針でなんとかならないかと考えたけど、$O(N^2)$がどうやっても外れず却下。

制約的に重め定数倍の$O(N)$か$O(NlogN)$あたりが相場かなと思いつつ、極端なケースを考えることにして、$A_i$が全て異なるケースを考えるうちに解法が見えてきた。

$A_i$が全て異なると答えは
```math
\sum_{i=2}^N(i/2)(N-i+1)
```
で求められる。なので逆に、同じ数字すなわち交換しなくていいものについて考えることにした。

例えば$N=10$で、$4$という数字が4番目と5番目にのみ存在しているとする。この時、この複数存在する$4$によって生まれた交換しなくてよくなった回数を考える。
この2つの$4$を交換する(はずだった)ような操作を含む含むような連続部分列は{$A_i|i=4～5$},{$A_i|i=3～6$}...{$A_i|i=1～8$}の4つである。{$A_i|i=4～5$}を中心にして、両端に要素を付け加えていき、端に到達するまでその部分列が該当しているのがわかる。
つまり、$x$という数字が$p$番目と$q$番目($p<q$)のみに存在しているとき、$A_i$が全て異なるときと比較して交換しなくてよくなる回数は
```math
min(p,n-q+1)
```
回になることがわかる。


# 解答
$x$という数字が$p_1,p_2...p_l$番目に存在するような時、$A_i$が全て異なるときと比較して交換しなくてよくなる回数は、
```math
{q_i = min(p_i,n-p_i+1)}
```
のような数列$q_i$を考え、これらが昇順にソートされているものとすると、
```math
\sum_{i=1}^lq_i(l-i)
```
で求められることがわかる。$q_1$と$q_2～q_l$の存在によってそれぞれ$q_1$回交換しなくてよくなり、$q_2$と$q_3～q_l$の存在によってそれぞれ$q_2$回交換しなくてよくなり...となるからだ。
これを数列に存在する全ての種類の数字について行い、
```math
\sum_{i=2}^N(i/2)(N-i+1)
```
から減じればいい。

# コード
ソートの部分がネックになりそうだが、少し考えると最悪の場合は$A_i$が全て同じときであることが分かり、結局$O(NlogN)$となる。
```cpp
const int max_n = 200002;
vector<ll> dist[max_n];
ll n;

int main() {
	cin >> n;
	int maxi = -1;
	repl(i, n) {
		int x; (void)scanf("%d", &x); maxi = max(maxi, x);
		int d = min(i + 1, n - i);
		dist[x].push_back(d);
	}

	ll p = 0;
	repa(i, maxi) {
		if (dist[i].size()>1) {
			sort(dist[i].begin(), dist[i].end());
			rep(j, dist[i].size()) {
				p += (ll)(dist[i].size() - j - 1) * dist[i][j];
			}
		}
	}

	ll sum = 0;
	for (ll l = 2; l <= (ll)n; l += (ll)1) {
		sum += (l / 2) * ((ll)n + 1 - l);
	}

	cout << sum - p << endl;

	return 0;
}
```
Windows 起動時に WSL2 の環境への ssh 設定を自動でするやつ（改）

下記の記事に記載されているsshサーバ自動起動スクリプトの（特定環境での）バグフィックス版

https://blog.mihyaeru.com/2020/06/28/wsl2-ssh/

https://speakerdeck.com/nsaito9628/wls2-ubuntushang-defalseaws-step-functions-python-kai-fa-huan-jing-falsegou-zhu?slide=12

https://www.slideshare.net/NSaito1/wsl2dockerjupytervs-code

いずれの記事にもあるように、下記のバッチファイルを、タスクスケジューラでログイン時に自動起動するように登録している。

```
wsl -d Ubuntu -u root exec service ssh restart

for /F %%i in ('wsl -d Ubuntu exec hostname -I') do (
    set ip=%%i
    netsh interface portproxy delete v4tov4 listenport=22
    netsh interface portproxy add    v4tov4 listenport=22 connectaddress=%ip%
)
```

しかし、これを、
「wsl -d Ubuntu exec hostname -I で表示されるIPアドレスが複数ある環境」で実行すると、
* forループ（6行目）でIPアドレスを指定してsshdのlistenポートをaddして、
* その次のforループ（5行目）で全てのsshdのlistenポートをdeleteしてしまうので、

「最後にaddしたIPアドレスしかlistenできていない」ことになる。

修正版のバッチファイルが以下のもの。

```
wsl -d Ubuntu -u root exec service ssh restart

netsh interface portproxy delete v4tov4 listenport=22
for /F %%i in ('wsl -d Ubuntu exec hostname -I') do (
    set ip=%%i
    netsh interface portproxy add v4tov4 listenport=22 connectaddress=%ip%
)
```

オリジナルから5行目をforループの前に持っていっただけだが、
* forループの前（3行目）で、それまでの全てのsshdのlistenポートをdeleteし、
* forループの中（6行目）で、指定したIPアドレスに対してaddする、

としている。