OmniAICreator/Qiita-1.07M huggingface dataset:
C#でコンソール画面でのカレンダー作ってみた

```Test.cs
using System.Collections.Generic;
using static System.Linq.Enumerable;
using static System.Console;
class Sub : object
{
    const int WEEK_MAX = 7;//マジックナンバーは禁止

    public Sub()
    {
    }

    public int Week()
    {
        WriteLine("1/1の曜日を入力");
        switch (ReadLine())
        {
            case "日":
            case "日曜日":
                return 1;
            case "月":
            case "月曜日":
                return 2;
            case "火":
            case "火曜日":
                return 3;
            case "水":
            case "水曜日":
                return 4;
            case "木":
            case "木曜日":
                return 5;
            case "金":
            case "金曜日":
                return 6;
            case "土":
            case "土曜日":
                return 7;
            default:
                return 0;
        }
    }

    public string Calendar(int year,int you)
    {
        if (year <= 0 || you == 0)
        {
            return null;
        }
        WriteLine($"{year}年カレンダー\n");
        var cld = "";
        int max;
        foreach (var y in Range(1, 12))
        {
            cld += $"{y.ToString()}月のカレンダー\n\n"; 
            if (y == 4 || y == 6 || y == 9 || y == 11)
            {
                max = 30;
            }
            else if (y == 2)
            {
                if (year % 4 == 0 && !(year % 100 == 0 && year % 400 != 0))
                {
                    max = 29;
                }
                else
                {
                    max = 28;
                }
            }
            else
            {
                max = 31;
            }
            cld += "日月火水木金土\n";
            foreach (var x in Range(1, max))
            {
                if (x < 10)
                {
                    cld += " ";
                }
                if (x == 1)
                {
                    if (you == 0)
                    {
                        you += 1;
                    }
                    foreach(var z in Range(1, you-1))
                    {
                        cld += "  ";
                    }
                }
                if (you % WEEK_MAX == 0)
                {
                    you %= WEEK_MAX;
                    cld += x.ToString() + "\n";
                }
                else
                {
                    cld += x.ToString();
                }
                you++;
            }
            cld += "\n\n";
        }
        return cld;
    }

    public void End()
    {
        WriteLine("何かを入力してください");
        ReadLine().ToString();
    }

}

class Test
{
    static void Main(string[] args)
    {
        var sub = new Sub();
        Write("調べたい年を入力");
        var year = int.Parse(ReadLine());
        var cld = sub.Calendar(year,sub.Week());
        WriteLine(cld is null? "\n存在しないです\n":cld);
        sub.End();
    }
}
```
再帰を使ったマージソート

#再帰を使ったマージソートのやり方

※備忘録的な感じ (見返す時に忘れている・コードだけでは理解ができない等があると思うので）

配列data = [3, 1, 9, 4, 2, 5, 7, 6, 8, 10]
1. 配列の真ん中でleft(左）・right(右）に分ける    => 左[3,1,9,4,2] 右[5,7,6,8,10] 真ん中: 5
2. left・rightでそれぞれでさらに分けていく(再帰を使う） => 左[3,1,9,4,2] -> 左[3,1] 右[9,4,2] 真ん中: 2 
 右[5,7,6,8,10] -> 左[5,7] 右[6,8,10]....のように分けていく

3.最終的に[3],[1],[9],[4],[2],[5],[7],[6],[8],[10]　　となる（下準備完了！）

4.今度は、二分割した値を左（０から始まるため）から順番新しい変数（result_sortに並び替え結果を代入していく(結合)。[3,1],[4,2] => それぞれソートを行う [1,3][2,4]
                  
※ [9],[6]がない！なぜ？ => [9,4,2]を分けるとき 真ん中が「１」のため 右:[9] 左: [4,2]となってしまうため先に[4,2]から処理をしてしまう。（再帰は、1->2->3->4->5 と進めた後, 5->4->3->2->1と巻き戻しをしながら処理を戻していく） =>　**より理解を深めるためには、再帰の勉強をする**


5.[9]と[4,2]は ->[9]と[4,2]で並び替え -> result_sort => [2,4,9]となる
6.[3,1]と[9,4,2]は -> [3,1]と[9,4,2]で並び替え ->  result_sort => [1,2,3,4,9]
7.右が完了。次は左から　4~6　を行う
8 [3,1,9,4,2]　と　右[5,7,6,8,10] を比較して行う
9 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]　となる


#コードでの解説(python)

```sample.py
data = [3, 1, 9, 4, 2, 5, 7, 6, 8, 10]
```

データを変数に代入


```sample.py

def merge_sort(data):
    if len(data) <= 1: #配列の中身が1以下になったらtrue
        return data
    center = len(data) // 2 #配列の中心
    left = merge_sort(data[:center])#左側に分断 [3,1,9,4,2] => [3,1] => [3]
    right = merge_sort(data[center:]) #右側に分断 [5,7,6,8,10] => [9,4,2] => [1]
    return left, right #=> [3], [1]が渡される
```

1 ~ 3 をこのコードで行う事ができる。



```sample.py
def merge_sort(data):
    if len(data) <= 1: #配列の中身が1以下になったらtrue
        return data
    center = len(data) // 2 #配列の中心
    left = merge_sort(data[:center])#左側に分断 [3,1,9,4,2] => [3,1] => [3]
    right = merge_sort(data[center:]) #右側に分断 [5,7,6,8,10] => [9,4,2] => [1]
    return merge(left, right)
````

`return merge(left, right)`を加えただけ


```sample.py
def merge(left, right):
    result_sort = [] #結合結果
    left_i, right_j = 0, 0
    print(left + right) #=> 並び替えをしようとする配列がわかる
    while(left_i < len(left)) and (right_j < len(right)):
        if left[left_i] <= right[right_j]:
            result_sort.append(left[left_i]) #ここで結合をしようとしている rightとleftを比較しながらresultに当てていく
            left_i += 1
        else:
            result_sort.append(right[right_j]) #ここで結合をしようとしている rightとleftを比較しながらresultに当てていく
            right_j += 1
    if len(left[left_i:]) != 0: #=> 並び替えをしたが余ってしまったleftの配列内の数字が0で無ければTrue(left_iで確認）
        result_sort.extend(left[left_i:])
    if len(right[right_j:]) != 0:
        result_sort.extend(right[right_j:]) #並び替えをしたが余ってしまったrightの配列内の数字が0で無ければTrue（right_jで確認)
    print(result_sort) #=> 並び替えた配列がわかる
    return result #最終的な返り値
```

4 ~ 8 を行っている


#引用（ありがとうございます）

- https://medium-company.com/%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88/
- https://qiita.com/woolon/items/4cff50de7e697cb2664f
フォルダをgithubリポジトリにpushする流れ

#githubにローカルリポジトリをpushする方法

###あまり下調べせずにgithubを触っていたらエラーが頻出したのでメモ書き

.githubにリモートリポジトリを作成。（この際に HTTPSを選んでおくと簡単に設定できる

↓

pushしたいディレクトリにターミナルから移動（cd~を使う

↓　

1.git init　　と入力して隠しフォルダを作成（これは一度だけ打てば良い
　　　　　　   隠しフォルダを確認する際は　⌘ + shift + .(ピリオド）で表示を切り替えることができる。
             この際　.git というディレクトリが出来ていればOK。

2.git add . と入力（ステージング

3.git commit -m "（コミットメッセージ）"

4.git remote add origin ~指定したいリモートリポジトリ先

5.git push -u origin master

これが1連の流れ
Karabiner-Elementsで独自ルールを作る(バックスペースをctrl+n)

# 概要
* `ctrl + hjkl`は[公開されているルール](https://ke-complex-modifications.pqrs.org/#vi_mode_arrow)を利用したが、Macのデフォルトのバックスペースショートカット`ctrl + h`が右カーソルに割り当てられてしまった。
* 別途、`Karabiner-Elements`に独自ルールを作成してバックスペースをで`ctrl + n`にした。

# 公式ドキュメント
* いくつか典型的な設定例がサンプルとして載せられており助かった
    * https://karabiner-elements.pqrs.org/docs/json/typical-complex-modifications-examples/#change-control-h-to-delete

# 設定ファイルの置き場

```
/Users/ユーザー名/.config/karabiner/assets/complex_modifications/
```

# 作成
* 適当なファイル名でjsonファイルを作成する

```json:backspace_ctrl_n.json
{
  "title": "俺のルール",
  "rules": [
    {
      "description": "Ctrl + n = backspace",
      "manipulators": [
         {
            "type": "basic",
            "from": {
                "key_code": "n",
                "modifiers": {
                    "mandatory": ["control"],
                    "optional": ["caps_lock", "option"]
                }
            },
            "to": [
                {
                    "key_code": "delete_or_backspace"
                }
            ]
         }
      ]
    }
  ]
}
```

# Karabiner-Elements で追加
* complex modifications > `+ Add rule`ボタンを押すと先程の独自ルールがいるので`+ Enable`する

![スクリーンショット 2020-03-28 22.00.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87716/ae5d10f7-6f41-acdf-cc8b-d0124ceab2eb.png)

以上
Ubuntuにpip/pip3をインストールする

# はじめに
UbuntuにPythonの実行環境を整える際、同時にpipを入れる人は少なくないだろう。
そして、Googleさんに聞けばaptでインストールができるという回答が得られるだろう。ただ、私の記憶が正しければ、この方法で入れてしまうと**後々pip自体のアップデートがうまく行かないなどの不具合が発生する可能性がある**。
そこで、不具合が生じない方法を備忘録的に簡潔に残しておく

#pipの導入
Python2系。もういらない子...

```bash
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo python2 get-pip.py
```
pip自体のアップデート

```bash
pip install -U pip
```

#pip3の導入
多分よく使うのがこっち。

```bash
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo python3 get-pip.py
```
pip3自体のアップデート

```bash
pip3 install -U pip
```
「Raspberry Piでスーパーコンピュータをつくろう！」のマウントでハマる

# Raspberry Pi 4 のマウントが出来ない

[Raspberry Piでスーパーコンピュータをつくろう！](https://www.kyoritsu-pub.co.jp/bookdetail/9784320124370)
の、6章「マスターノード上にマウント可能なディレクトリを作る」で、ハマりました。

# 環境
1. Raspberry Pi 4B
2. OS：2020-02-13-raspbian-buster-full.zip
[Raspbian](https://www.raspberrypi.org/downloads/raspbian/) よりダウンロード

# 結論
マウントがうまく行かなかったのは、
「/etc/rc.local」に書いた処理が、自動実行されなかったためでした。


[rc.localを使えない環境でマシン起動時にコマンドを実行する](http://atomiyama.com/linux/page/rc-local-systemd/)
の通り、サービス化して解決しました。

**2020-03-29 追記 ここから**
解決していませんでした。

マスタとスレーブの全マシンを、シャットダウンしてから起動すると、
スレーブからマスタのディレクトリのマウントが外れていました。

その後、マスタで「sudo systemctl restart nfs-kernel-server」すると直ります。

という事で、スレーブが全て立ち上がってから、
マスタで「sudo systemctl restart nfs-kernel-server」します。

本当は、サーバー再起動時に nfs-kernel-server リスタートしたかったのですが、
うまく行かなかったので、
マスタのログイン時に nfs-kernel-server リスタートします。

[本当に正しい .bashrc と .bash_profile の使ひ分け](https://qiita.com/magicant/items/d3bb7ea1192e63fba850)
をみて、「.bash_profile」に入れました。

```./.bash_profile
sudo systemctl restart nfs-kernel-server

test -r ~/.bashrc && . ~/.bashrc
```

マスタでログインするまで、
スレーブ側からマウントしたディレクトリが見えない、
というのがイマイチですが、
マスタから操作することを徹底すれば、問題にはならないはず。。。

根本的な対応するなら、
マスタとは別にファイルサーバーを立てれば、行けるはず。
LAN接続のHDDを用意しないと（使命感）。

**2020-03-29 追記 ここまで**


# 参考文献・サイト
[Raspberry Piでスーパーコンピュータをつくろう！](https://www.kyoritsu-pub.co.jp/bookdetail/9784320124370)

[rc.localを使えない環境でマシン起動時にコマンドを実行する](http://atomiyama.com/linux/page/rc-local-systemd/)

[本当に正しい .bashrc と .bash_profile の使ひ分け](https://qiita.com/magicant/items/d3bb7ea1192e63fba850)
psycopg2 で Pandas DataFrame を Bulk Insert する

Pandas DataFrame を PostgreSQL に Bulk Insert したいときは SQLAlchemy を入れて `.to_sql()` を使うとできるのだけど、PostgreSQL クライアントとして psycopg2 を使っている状況だと「そのためだけに SQLAlchemy 入れたくねぇ〜」という気持ちになってしまう。

そこで「DataFrame を `.to_csv()` してそれを psycopg2 の `.copy_from()` で読み込めば Bulk Insert できるのではないか」と考えてやってみたらできた。

``` python
from io import StringIO
import pandas as pd
import psycopg2

def df2db(conn: psycopg2.extensions.connection, df: pd.DataFrame, table: str):
    buf = StringIO()
    df.to_csv(buf, sep='\t', na_rep='\\N', index=False, header=False)
    buf.seek(0)
    columns = [f'"{col}"' for col in df.columns]
    with conn.cursor() as cur:
        cur.copy_from(buf, table, columns=columns)
```

こういう関数を作っておいて、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/675d8b0c-5cf1-7088-9a7a-925eb24c428f.png)

例えばこんな DataFrame を PostgreSQL の `logs` というテーブルに Bulk Insert したいときは、


``` python
with psycopg2.connect('postgresql://...') as conn:
    df2db(conn, df.reset_index(), 'logs')
    conn.commit()
```

これでできる。

index は出力しないようにしているので、index もテーブルに入れたい場合は `.reset_index()` しておく必要がある。

## [2020.08 追記] 特殊なカラム名をもつテーブルに Bulk Insert できない場合の対処方法
- 大文字の英字を含むカラム名
- `.` などの記号を含むカラム名

など、いくつかのケースでは Bulk Insert が失敗することがある。

どうやら psycopg2 側ではカラム名のエスケープなどは特に行わずに単純に `,` で連結するだけのようなので、特殊なカラム名を持つテーブルの場合はうまくいかない。

この場合は `.copy_from()` に渡すカラム名を CSV フォーマットに準じた方法でエスケープすればよい。
※ 冒頭のコードはカラム名を `"` で囲むように修正済み。

## [2020.08 追記] 欠損値を含む整数型のデータ列を扱いたい場合
欠損値を含む整数型のデータ列は、そのままだと Pandas が float にキャストしてしまい、copy_from でエラーが出てしまう。
とはいえ int に変換しようとしても Pandas の int 型はそのままだと欠損値を扱えないためエラーになってしまう。

この場合は、Pandas v0.24.0 で追加された Nullable integer data type を利用するとうまくいく。
https://qiita.com/hoto17296/items/b6c90db4b9bcdb7b6d78


## [2021.02 追記] 別の方法
COPY FROM 使わなくてもこれでいいのでは？

``` python
def bulk_insert(conn: psycopg2.extensions.connection, df: pd.DataFrame, table: str):
    query = f"""
        INSERT INTO {table} ({", ".join(df.columns)})
        VALUES {", ".join(["(" + ", ".join(["%s"] * len(df.columns)) + ")"] * len(df))}
        """
    with conn.cursor() as cur:
        cur.execute(query, df.values.flatten())
```

この方法だと、psycopg2 が扱えないデータ型が含まれている場合に、何らかの方法で変換してやる必要はある。(`numpy.ndarray` → `list` とか)

参考： psycopg2 が扱える型 https://www.psycopg.org/docs/usage.html#adaptation-of-python-values-to-sql-types
Jupyter Notebookの使用方法

最近、データ分析のためにJupyter Notebookを使うことが多くなりましたので、使用方法をまとめました。本記事は随時更新してまいります。


#使い始める前に
Anacondaでインストールすれば、Jupyter Notebook自体はすぐに使えるようになります。
が、便利に使うために以下の対策等をしておいた方が良いです。

###①matplotlibの文字化け対策
何もしないと日本語が文字化けするため、日本語フォントをインストールし、Jupyter Notebookに適用します。
詳しくは以下の記事をご参照ください。
[【Python】matplotlibを日本語化する](https://qiita.com/maroKanatani/items/3b080c639395bba7795a)

###②拡張機能「Table of Contents(2)」の有効化
ノートが長くなると、目的の箇所を探すのが大変になります。
なのでjupyter_contrib_nbextensionsをインストールし、拡張機能「Table of Contents(2)」を有効にしておくと良いです。
詳しくは以下の記事をご参照ください。
[Jupyter notebookのextensionを使ってオレオレPythonチートシートを作る](https://qiita.com/hanon/items/1d00a1eac026af0389fb)


#基本操作
頻繁に使うもののみ抜粋しました。

###編集モード／コマンドモード共通
- セルの実行：Ctrl-Enter
- セルの実行 → 下のセルを選択：Shift-Enter
- セルの実行 → 下にセルを挿入：Alt-Enter
- 保存：Ctrl-s

###編集モード
- コマンドモードに移行：Esc
- 実行中のコードの中断：Ctrl-C
- タブ補完：変数名・メソッド名などを途中まで入力した状態でタブ
- オブジェクトイントロスペクション（オブジェクトの一般情報やdocstringを見る）：変数の前または後に？をつけてセルを実行

###コマンドモード
- 編集モードに移行：Enter
- ショートカットキーの一覧表示：（コマンドモードで）h
- セルをコードに変更：y
- セルをマークダウンに変更：m
- マークダウンで見出し作成：1～6
- セルの追加（選択中のセルの上）：a
- セルの追加（選択中のセルの下）：b
- セルのコピー：c
- セルの切り取り：x
- セルの貼り付け（下）：v
- セルの貼り付け（上）：Shift-v
- セルの削除（選択中のセル）：dd
- 検索と置換：f


#マジックコマンド
Jupyter Notebook独自のコマンドです。

- %magic：全マジックコマンドの詳細記述を表示
- %time 命令（メソッドなど）：命令実行時間の計測
- %timeit 命令（メソッドなど）：命令実行時間の計測（複数回）※実行時間が極短い命令の計測に有効
- %debug：直前に発生した例外に対して、トレースバックの最下部から開始
- %run Pythonファイル名 コマンドライン引数（オプション）：Pythonプログラムの起動
- %who, %who_ls, %whos 型名・変数名など：利用中の名前空間内で定義された、その種類の変数リストを返す（%whoは一覧で、%who_lsはリストで、%whosは整形した一覧で返す）
- %reset：名前空間から変数を含めた名前を除去
- %matplotlib inline：Matplotlibをインライン表示する


#Pandas関連
- display関数：print関数ではなくこちらを使えば罫線つき表示ができる


#その他
###スライド化
`pip install rise`でRISEをインストールすると使える。


#参考文献
- Wes McKinney(2018)「Pythonによるデータ分析入門 第2版 -NumPy, pandasを使ったデータ処理」,オライリー・ジャパン
CDKでS3バケットポリシーを設定する

CDKでS3のリソースを作成する際にバケットリソースを設定する実装コードのメモ
今回はFirebaseで作成したHostingアプリと特定のIPからのみAWSのS3バケットのオブジェクトを取得できるようにポリシーを設定します。

```typescript:cdk-stack.ts
import cdk = require('@aws-cdk/core')
import iam = require('@aws-cdk/aws-iam')
import s3 = require('@aws-cdk/aws-s3')

export class TestStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    // S3
    const testBucket = new s3.Bucket(this, 'test-bucket-id', {
      bucketName: 'test-bucket'
    })

    const testBucketPolicy = new iam.PolicyStatement({
      effect: iam.Effect.DENY,
      actions: ['s3:GetObject'],
      principals: [new iam.ArnPrincipal('*')],
      resources: [
        testBucket.bucketArn + '/*'
      ],
      conditions: {
        // リクエストのヘッダーに含まれるrefererが下記に含まれないものをDENY
        'StringNotLike': {
          'aws:Referer': [
            'https://app-name.firebaseapp.com/*',
          ]
        },
        // リクエスト元のIPが下記に含まれないものをDENY
        'NotIpAddress': {
          'aws:SourceIp': [
            '123.456.78.90',
          ]
        }
      }
    })
    testBucket.addToResourcePolicy(testBucketPolicy)

```

この設定で特に注意すべき点としては`Conditions`で設定されているものは`AND`条件ではなく`OR`条件であるということ。  
上記のような設定でオブジェクトの取得を自宅のIPからのアクセス（検証用）とfirebaseのhostingアプリからのアクセスだけを許可するように設定できました。
[fukabori fm ep27より] 論理削除とその解法メモ

## 削除フラグ(論理削除)について

[fukabori fm ep27](https://fukabori.fm/episode/27) から文字起こしとちょっと要約
API開発や設計に携わる人は必聴だと思う素晴らしい回だったのでとったメモを公開しておきます。権利とかで消したほうがよければ削除します。一回しか聞いてないのでよく理解したい人はリンクから聞いてください。。

### 論理削除の定義

rdb において、データの削除時に実際の削除ではなく、カラムにdeleted_atなどフラグを持たせて削除したことにする仕様
sqlのdelete 文を使わない。update 文を使う

### 反対とされる物理削除とは

delete 文を使ってデータを削除する

### 論理削除はなぜ生まれたか

delete 文を使ってデータが見れなくなるという恐怖
削除したデータを検索したい
データをログとして残しておきたい(サービスの保全期間など)
ユーザの誤った操作で削除したとき、元に戻したい

### 論理削除はアンチパターン?

アンチパターン。よかれと思ってやったことが悪い結果になってしまうこと。
sql アンチパターンより
とりあえず削除フラグ。-> テーブル全てに削除フラグをつける。

なぜかというと、where句に必ずdeleted_atが入る。
join した場合も必ずdeleted_at が必要。可読性も悪い。
削除フラグも含めたユニーク制約も必要になってくる。できてないとセレクトしたときに2件引っかかるパターンがでてくる。
そうなると、limit 1 が必要になってくる。

### テーブルにフラグが増えていく

状態を判断するためのカラムがどんどん増えるとき

### 解法

論理削除。世の中にはない概念、
システム屋特有の言葉。顧客には関係ない。顧客から要望が出ることもない。
顧客の本当の要求を深掘りするべき。システム屋に都合のいい概念に持ち込まない

社員の退職を論理削除ではなく、退職という状態として議論する。
復職した場合どうするかとか。紐づくデータをどうしたいとか。

システムにはどうも可視性制御が必要な場面がある。状態遷移など。
例えば記事の公開非公開。削除。タイマー投稿。下書き。
これらを考えることなしにとりあえず削除フラグは思考停止につながる。
アンチパターンにならないために要求・概念を捉えて設計をしましょう

大体のORMには論理削除の実装があるから楽に実装できてしまう。
過去はis_deleted: bool だった
それは情報量少ないということで、deleted_at: datetime が利用されることになった。

対象の状態遷移を考え、設計に反映させる
SoftDelete
HardDelete

Doctrine php のormapper
v1 から v2 でsoftDeleteサポートやめた
state pattern を使いなさい
status カラムをいれて、enum なり varchar で値をいれる。

state pattern が使えないケースって?
特にないと思われる

論理削除のプラグイン探すんじゃなくてstate machine のプラグインさがそう
不正な状態遷移を防げる
状態遷移の向きを捉えておく。向きを定義する。遷移が飛ばないように。
status が一気にpublish にならないとか。draft -> 審査 -> publish のstateで
draft -> publish にならないように

rails で aasm というstate machine plugin がある
transition とか定義できる。

### 履歴テーブルに移す

ユーザからは見せたくないけど、データとして残したいテーブル

users テーブルにトリガつくってdelete したら deleted_users に入れるようにする
archive_**　テーブル

どちらをとるかだけど、削除のデータが多くなるテーブルでは、こちらが向いている
検索パフォーマンスが落ちてしまう。インデックスが無駄に消費される。
データの比率の見通しも重要

### 更新も削除も何もしない

退職って削除じゃないよね。情報の追加だよね。
操作の事実を消さずに残していく
特にエンタープライズで重要
更新も削除もデータの改ざん、データを全て残すのが正しいのでは。
なので、insert と select しかしない
何が起こるかというと、改ざんがない。最新のdate time のレコード引っ張ってくれば
必ずそれが正だとわかる
イベントソーシングの考えかな
データが増えるなら金を積む

現役のデータは必ず最新のデータ、order by しないといけなくなる
web 側でやると辛い。ユーザ数がエンプラと比べるとはるかに多いので。

### T字型のテーブル設計

### イミュータブルデータモデル

update_at のカラムがおかしい
更新を許すものと許さないものをしっかり判断しましょう。

### 解決できていない問題

誤った操作をすぐに元に戻したい
-> 間違えにくいUIを作りましょう。guide の削除は guide 名いれさせるとか確認画面とか

10分遅れのセカンダリデータベース用意するとか。遅延レプリケーションでサルベージできる。
誤操作の物理削除に対する一つの解。(ただし、t_wadaさんが調べて出てきたやつで、実際に運用したことはない)

### まとめ

論理削除は現実世界にない解法のため、それがありふれているのは思考停止になっているので注意
現実世界の言葉(顧客)に耳を傾け、設計に落とし込む。状態を捉える、定義する。

自分も論理削除には懐疑的で、他のテーブルに移したりしたほうがいいのかなと思ってたが実際にはやったことがなかった。ただ、上で挙げられていたデメリットの他に、ORMマッパー以外でクエリするときにdeleted_at をつけ忘れてこの結果正しいんだっけと混乱することが多々あったりした。status で管理されていれば、頭の中でこの情報をとりたいという風に自然とクエリが思いつくのでstateパターンによせてくのがいいのかなと。顧客の声や、実現したい機能の要求をしっかり掘り下げていくことでフラグで埋め尽くされるテーブルがなくなり、データモデルがわかりやすくなるので議論を行なっていくのが結局大事だなと思った。
Azure Storage Explorer を使って Azure Stack Hub のストレージアカウントにアクセスする

# はじめに

Azure Stack Hub でも Storage Explorer が使える事を確認します。

# 環境

- Azure Stack Hub Integrated System (1907)
- Azure Storage Explorer 1.12.0


# 準備

Storage Explorer で Azure Stack Hub に接続するには、次の2つの作業を行う必要があります。

- 証明書のインポート
- Azure Stack API の有効化

#### 証明書のインポート

Azure CLI や Powershell for Azure Stack Hub の構成と同様に、アクセスには SSL 証明書が必要です。
Storage Explorer を起動し、メニューの「編集」からSSL証明書をインポートします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/b28f6634-9ffc-2b33-8191-23382ebb57c4.png)


#### Azure Stack API を有効にする

Azure Stack Hub の API バージョンは Azure のそれとは異なります。
メニューの「編集」から「Azure Stack API を対象にする」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/20decc54-5f12-de43-5a12-14ce4e805827.png)


# Azure Storage への接続

「アカウントの管理」から接続先を追加します。
複数の接続方法がありますが、ここでは『接続文字列』と、『ストレージアカウント名およびキー』を使用して接続します。


#### 接続文字列を使用する

接続ダイヤログで「接続文字列を使用します」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/1d505e85-a14b-1bdb-5cbe-7f09c362cf7f.png)

「表示名」は任意の名称を、「接続文字列」にはストレージアカウントの接続文字列をコピペします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/e71541e8-4f49-e412-769f-9129a8db32bd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/c57832f2-ad7f-aaa8-3e09-fc10ec50712e.png)

「接続」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/d2188e0c-b613-2596-ed44-282243101e46.png)

接続されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/9e2c0eb1-c00d-28db-3809-647b935c1b0a.png)


#### ストレージアカウント名とキーを使用する

接続ダイヤログで「ストレージ アカウント名とキーを使用する」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/51ff953c-ebd1-d2e8-e2ec-e047e1d1326a.png)


ストレージアカウント名とキーをコピーし、次の画面に張り付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/9805abe2-043d-e62d-c9e7-2b27081b627f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/bab33c17-b61b-d427-e7bc-0a2ebd0fdae7.png)

ストレージのドメインは「その他」を選択し、ストレージのエンドポイント※を指定します。
※Azure CLI を利用する際に `az cloud register` コマンドで指定する `suffix_storage_account` パラメータの値です。

「接続」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/a5651de6-6b97-95d7-b7eb-e5af46c4dc26.png)

接続されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/822d5a96-3416-9aa1-d213-16a4cdc05d21.png)



# Storage Explorer の利用

ファイルのアップロードや参照など、あとは普通に利用できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64309/9c71daf8-15da-f5e8-11a0-839b098fe333.png)



# おわりに

Azure Stack Hub でも Storage EXplorer が使える事を確認しました。
Azure CLI や Visual Studio, Visual Studio Code と同様に、ポイントは Azure Stack Hub への接続です。

Storage Explorer は Azure Stack Hub 対応が直感的に判り易いので良いですね。



# 参考

- https://docs.microsoft.com/en-us/azure-stack/user/azure-stack-storage-connect-se?view=azs-2002
VisualStudioCodeのRubyアウトラインが一部表示されないときは繰り返し処理でdoをつけているせいかもしれない

VSCodeのアウトラインは便利ですね、ショートカットでアウトラインにフォーカス当たるようにしたりしています。

```
def hoge
end

def fuga
end

def piyo
end
```

とすると。
◇ hoge
◇ fuga
◇ piyo
みたいに表示されます。ところが

```
def hoge
end

def fuga
  while cond1 do
  end
  while cond2 do
  end
end

def piyo
end
```
とすると、
◇ hoge
◇ fuga
になってしまうのです。piyo以下が表示されなくなってしまいます。しかし、繰り返し処理の後ろのdoは省略できます

```
def hoge
end

def fuga
  while cond1
  end
  while cond2
  end
end

def piyo
end
```
とすると、ちゃんと表示されるのです。
◇ hoge
◇ fuga
◇ fuga

一つの関数の中に繰り返し処理が複数出てくるようなコードはよくないのかもれないけれど、ひとまず解決。
コロナウイルス感染症対策サイトの開発環境構築[Cloud9]

# はじめに
コロナウイルス感染症対策サイトに貢献したかったのですが、
この機会にAWSのCloud9を使ってみようかなと思ったので備忘録です。
ご意見、改善点あれば教えて下さい。

また、内容の殆どは @FPC_COMMUNITY さんの
[東京都 新型コロナウイルス対策サイトへの貢献方法を解説](https://qiita.com/FPC_COMMUNITY/items/b9cc072813dc2231b2b2#comments)
の記事を参考にさせていただいています。

#事前準備
準備が必要なのはAWS　Cloud9 の会員登録です。
以下の記事を参考にさせていただきました。
[Cloud9の使い方と便利機能！最強プログラミング開発環境（IDE）](https://www.sejuku.net/blog/385)
まずはワークスペースの作成までできたらOKです。
**EC2のインスタンスはメモリが大きいものを選ぶと無難です。**
デフォルトのt2.microではメモリ不足で今後作業に影響が出る可能性があります。


あとは[Git hub](https://github.com/)は登録しておいて下さい。

#環境構築
##Gitからプロジェクトをフォークする
Cloud9 の準備ができたら今度はGitからソースを持ってきます。

東京都 新型コロナウイルス対策サイトのGitHubページを開いて下さい。
https://github.com/tokyo-metropolitan-gov/covid19

<img width="1131" alt="Covid19-Fork.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/741832df-b4dc-b91e-3a1b-37eae16d7181.png">
↑の画面が表示されたら、画面右上の赤枠で囲んである[Fork]ボタンを押下します。


<img width="1069" alt="スクリーンショット 2020-03-28 15.32.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/7c2e4e44-6b44-7950-54f6-b1a759a70fe1.png">
↑自身のアカウントにForkしてきたプロジェクトが表示されます。


##Cloud9にCloneしてくる

<img width="1047" alt="スクリーンショット 2020-03-28 15.41.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/7c9e2366-bab5-c1cb-2568-487901285315.png">
Forkしたきたプロジェクトの[Clone or Download]ボタンを押下します。(↑画面右側赤枠部分)



<img width="465" alt="スクリーンショット 2020-03-28 15.45.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/fbb98375-5221-2f27-74bc-49b91a3ce855.png">
↑の画面が出たら赤枠で囲んだボタンを押下してリンクをコピーします。


Cloud９に移動して自身のワークスペースを開きます。

<img width="1679" alt="スクリーンショット 2020-03-28 15.56.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/f8d2dc43-ff9d-a1c2-6f00-f81540be64bd.png">
↑画面のターミナル(赤枠部分)に以下のコマンドを入力します。

```
$ git clone コピーしたGithubリポジトリのURL
```

すると↓のようにGithubからソースがコピーされます(赤枠)。
<img width="1680" alt="スクリーンショット 2020-03-28 16.03.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/0da46860-97c4-0cc9-bb93-e74a39fed763.png">


##yarn 導入
**Cloneしたディレクトリに移動し、**ターミナルで以下のコマンドを入力します。

```
$ npm install -g yarn
```

```
$ yarn install
```
<img width="1425" alt="スクリーンショット 2020-03-28 16.16.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/335348ff-eaf7-2926-6cbc-18eb6755fbe7.png">



最後にサーバを起動します。
ターミナルで以下のコマンドを入力します。

```
$ yarn dev
```
<img width="889" alt="スクリーンショット 2020-03-28 20.57.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/ce49d0a7-61d2-5839-092c-3945dbf47b70.png">
ターミナルに↑の赤枠のような記述が出れば準備はOK


最後にCloud9画面上部の[Preview]→[Preview Running Application]を押下します
<img width="1678" alt="スクリーンショット 2020-03-28 21.02.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/131ef38c-1d59-5719-306a-ef90921c120e.png">

<img width="1680" alt="スクリーンショット 2020-03-28 21.03.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508592/14971f05-0a6c-eb6d-9436-6e9bf0d2464b.png">
プレビューが表示されれば環境構築は完了です👏
後は煮るなり焼くなり好きにやって下さい！
今日はここ迄。
WSLのディレクトリにあるhtmlをブラウザで開く

意外と分からなかったので、メモ。

```bash:terminal
explorer.exe foo.html
```
explorer.exeを使用すればhtmlファイルを開くことが出来る。

```bash:.bashrc
export BROWSER="explorer.exe"
```
開けなければ、.bashrcに設定する。

```bash:.bashrc
alias start="explorer.exe"
```
楽に開けるようにエイリアスを.bashrcに設定する。

```bash:terminal
start foo.html
```
startコマンドでhtmlを開けるようになる。


##参考
[Is there a way to open a file in a browser from WSL?](https://www.reddit.com/r/bashonubuntuonwindows/comments/8teo9i/is_there_a_way_to_open_a_file_in_a_browser_from/)
CentOS8のAnsible管理対象サーバ接続

# 管理サーバからSSH鍵交換

## 管理サーバの公開鍵を送付

<pre>
[ansible@mng053 ~]$ ssh-copy-id root@192.168.0.XXX
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/ansible/.ssh/id_rsa.pub"
The authenticity of host '192.168.0.XXX (192.168.0.XXX)' can't be established.
ECDSA key fingerprint is SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes     ⇒初回接続時に聞かれる。「yes」回答
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.0.XXX's password:     ⇒ログイン先のパスワード

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@192.168.0.XXX'"
and check to make sure that only the key(s) you wanted were added.
</pre>

## 接続確認

作業したら必ず確認。
hostnameコマンドで意図したホストに接続していることを確認します。

<pre>
[ansible@mng053 ~]$ ssh root@192.168.0.XXX
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sat Mar 28 08:05:04 2020 from 192.168.0.XXX
[root@dbpg054 ~]# hostname
dbpg054.localdomain
</pre>

# 管理サーバ側でのAnsibleインベントリ登録

Ansibleはインベントリ登録が必要なため、今回登録する192.168.0.XXXを登録します。
書き方は以下を見よう見真似。
https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#hosts-in-multiple-groups

## 登録したインベントリ
<pre>
[ansible@mng053 ~]$ cat /etc/ansible/hosts
all:
  children:
    centos8:
      hosts:
        192.168.0.XXX:
    postgre10:
      hosts:
        192.168.0.XXX:
</pre>

OS製品用のグループとDB製品用のグループを構成してみました。

## 疎通確認
とりあえず、ホスト名でやってみる。
管理サーバから管理対象サーバへ接続します。
<pre>
[ansible@mng053 ~]$ ansible 192.168.0.XXX -m ping
192.168.0.XXX | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ansible@192.168.0.XXX: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
    "unreachable": true
}
</pre>

ユーザ名を指定しないと、管理サーバ側と同じユーザ（ansible）で管理対象サーバに接続しようとする模様。
rootでログインしないと・・・ダメなんですよね。

ユーザ指定は「-m」オプションで行ける模様。

<pre>
[ansible@mng053 ~]$ ansible 192.168.0.XXX -u root-m ping
192.168.0.XXX | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
</pre>

うまくいきました。

「all」と「centos8」のグループでも確認

<pre>
[ansible@mng053 ~]$ ansible all -u root -m ping
192.168.0.XXX | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
[ansible@mng053 ~]$ ansible centos8 -u root -m ping
192.168.0.XXX | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
</pre>

**接続完了！！**
DockerComposeでWSLからdockerを使用するときのVolumeについて

#WSLにdockerをインストール
この記事ではDocker for Windowsをインストールし、Hyper-V上で
Dockerが起動することを前提としています。

WSL2の場合は、上記のインストールがいらないそうですが、
まだ、Insider Previewでしか使用できないようなので、
Docker for Windowsのインストールを前提とし、
expose deamon on~~のチェックを入れてDockerが起動していることを前提にしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102725/52c39072-de0a-02d8-c479-92c292b159a1.png)



dockerをWSLにインストールする方法は
https://docs.docker.com/install/linux/docker-ce/ubuntu/
に記載している為、省略します。

だいたいこのあたりは他の方もブログがQittaに記載しているので、
すぐにインストールができると思います。

#問題はVolumeの設定
まず最初にdocker-compose.ymlやDockerfile
dockerにマウントするソースなどは
/home/{{username}}
のフォルダではなく、
/c/Users/{{username}}
などのWindows上のフォルダに保存しましょう。

**※注意**
通常ならWindowsのCドライブに格納したファイルは/mnt/c/・・・・
となるのですが、

WSLで/etc/wsl.confを新規作成（編集）し、以下の内容を追記します。

```/etc/wsl.conf
[automount]
root = /
options = "metadata"
```
そうすると、/mnt/c/・・・・・ではなく/c/・・・・となります。
この設定と、/c/Users/{{username}}などのWindows上のフォルダに保存することによって
docker-compose.ymlに記載した
Volumeを相対パスにすることが可能です。

docker-compose.ymlやDockerfileをソースと一緒に
gitで管理している場合は、基本で記載されていると思いので、この点を知っておくと便利です。

#なぜWSLからDockerを使うのか？

**答えは、EDIがVScodeだから!!**

最近のVSCodeにはRemote-WSLという機能があるのはご存知ですか？

RailsやPHPなどの開発の際、VScodeの機能を十分に活かそうとすると

- PHPやRubyなどをWindows上にインストールが必要 
- ファイルを新規作成すると、改行コードがCRLF
- gitでcore.autocrlf=falseにする必要がある

開発環境Windows⇔実行環境Linux間の弊害が発生します。
PHPやRubyなどをWindows上にインストールが必要は
結局WSL上にインストールが必要にはなりますが、
windowsとは違いバージョンの切り替えも容易ににできるのと、
コマンドベースでインストールもできるため、開発環境手順書を作らずとも
shellを容易しておけば、新しく開発者が増えた場合も容易に開発環境構築ができます。
Visual Studio 201x コメントアウト ショートカット

忘れるからメモ。

ちょっと信じられないけど、VisualStudioってコメントアウトとコメントアウトの解除のショートカットが面倒。
VisulaStudioの経験長い人も意外と知らなかったりする・・。
どうしてこうなった？
※ちなみにVisual Studio Codeの方は「Ctrl + /」でのコメントアウト／解除もできるし以下の方法でもできる。

# コメントアウト

「Ctrl + K」を一度押してさらに「Ctrl + C」

# コメントアウト解除

「Ctrl + K」を一度押してさらに「Ctrl + U」
Splunkでカスタムサーチコマンドを作成する（その２）

前回（その１）はカスタムサーチコマンドを作成するための基礎的な方法を記載しました。今回はもう少し機能を紹介していきたいと思います。この投稿は、前回の投稿を見ていることを前提としており、**前回使用したプログラム`test.py`を書き換えて使用する**内容になってますので、まだ見ていない方は下のリンク先の投稿を先に見ることをおすすめします。

[Splunkでカスタムサーチコマンドを作成する（その１）](https://qiita.com/uneyamauneko/items/9d199d007f899928e813)

#使用するデータ

前回と同じデータを使用します。

```:/dummy/sample.csv
"time","number","value"
"2020/01/01 12:00:00","1","a"
"2020/01/01 12:00:00","2","b"
"2020/01/01 12:00:00","3","c"
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593076/68fe44a9-761b-813a-b2ed-c44e61ec86ee.png)

#クエリに書かれているパラメータを読み込む

カスタムサーチコマンドでクエリのパラメータを読み込む機能を追加します。下のプログラムは、パラメータ１（下の例では`value`）をフィールド名とするフィールドに、パラメータ２（下の例では`zzz`）の文字を入れるものです。

```python:/opt/splunk/etc/apps/search/bin/test.py
import splunk.Intersplunk
import sys

data,dummy1,dummy2 = splunk.Intersplunk.getOrganizedResults(input_str=None)
for tmp in data:
    tmp[sys.argv[1]] = sys.argv[2]
splunk.Intersplunk.outputResults(data)
```

```:SPL
host = sample.csv | hoge value zzz | table number value
```
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593076/1a6891b3-ded0-60c5-a432-07bf65748db5.png)

`value`の値が、`a`,`b`,`c`から`zzz`に変わりましたね。`sys.argv[1]`でパラメータ１を`sys.argv[2]`でパラメータ２を取り出すことができます。

#エラー表示を自作する

プログラムでエラーが発生すると下のメッセージが出ます。このエラーメッセージは常に同じであまり使い勝手がよくありません。そこで、エラーメッセージを自由に書ける方法を記載します。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593076/a3b802ec-6712-a72f-83fd-8ce59c005609.png)
下のプログラムはパラメータが無い場合にエラーメッセージ`parameter error`を出力するものです。

```python:/opt/splunk/etc/apps/search/bin/test.py
import splunk.Intersplunk
import sys

data,dummy1,dummy2 = splunk.Intersplunk.getOrganizedResults(input_str=None)
if len(sys.argv) < 2:
    splunk.Intersplunk.generateErrorResults('parameter error')
    sys.exit(0)
splunk.Intersplunk.outputResults(data)
```

```:SPL
host = sample.csv | hoge
```
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593076/ce6f1221-28ad-f216-78d9-50d879327c18.png)

`splunk.Intersplunk.generateErrorResults`がエラー表示の部分です。エラーを出す時は`splunk.Intersplunk.outputResults`を実行させないように終了する必要があります。

#サーチアシスタントを表示する

サーチアシスタントを使いカスタムサーチコマンドの使い方を表示する方法を記載します。設定すると下の図のように表示されます。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593076/dbdfce9c-7de5-495a-c2d1-bc564d816527.png)

カスタムサーチコマンドのサーチアシスタントを設定するには、`/opt/splunk/etc/system/local/searchbnf.conf`に以下の文を追加します。なお、Splunkのインストール直後の場合は`searchbnf.conf`がないので作成する必要があります。また、`searchbnf.conf`を変更した後はSplunkの再起動が必要です。

```:/opt/splunk/etc/system/local/searchbnf.conf
[hoge-command]
shortdesc = 短い説明を書きます
description = 長い説明を書くことができます
syntax = hoge <field>
comment = exampleの説明を書きます
example = | hoge abc
usage = public
```

#おわりに

これで様々なカスタムサーチコマンドを作ることができますね。

---

動作環境
Ubuntu 18.04.4 LTS
Splunk 8.0.2.1
Docker + create-react-app v3.4.1でサーバーが立ち上がらない

# 起きたバグ
react-scripts startでサーバーを立ち上げようとしても

```
node_1  | yarn run v1.22.4
node_1  | $ react-scripts start
node_1  | ℹ ｢wds｣: Project is running at http://172.26.0.2/
node_1  | ℹ ｢wds｣: webpack output is served from 
node_1  | ℹ ｢wds｣: Content not from webpack is served from /usr/src/app/react-sample/public
node_1  | ℹ ｢wds｣: 404s will fallback to /
node_1  | Starting the development server...
node_1  | 
node_1  | Done in 25.46s.
docker-react-app_node_1 exited with code 0
```

というメッセージが表示されサーバーが終了する。

# 原因

create-react-app v3.4.1で取り込まれた下記の修正

https://github.com/facebook/create-react-app/commit/7e6d6cd05f3054723c8b015c813e13761659759e

process.stdout.isTTYかprocess.env.CIがtrueじゃないと開発用サーバーが停止するらしい。

# 解決方法
上のコミットのメッセージに

```
// Gracefully exit when stdin ends
(標準入力が終了したら正常終了するよ)
```
とあるので標準入力を終わらせないオプションをdocker-compose.ymlに追加。
具体的には下記のオプション

```
stdin_open: true
```
と追加するだけ。
MacBookPro に CentOS7 をインストール

# MacBookPro に CentOS7 をインストール

- 最新の macos がインストールできない MacBookPro に CentOS をクリーンインストールする
- USBメモリのディスクイメージからインストールする

## 手順

### インストール用USBメモリ作成

#### 1. ディスクイメージをダウンロードする

- CentOS-7-x86_64-DVD-1908.iso

* 古いバージョンisoディスクイメージ
  - https://archive.kernel.org/centos-vault/

#### 2. USBメモリの disk number を確認する

```
$ diskutil list
```

#### 3. USBメモリをアンマウントする

```
$ diskutil unmountDisk /dev/[disk number]
```

#### 4. centos のISOイメージを書き込む

```
$ sudo dd if=/path/to/image.iso of=/dev/[rdisk number] bs=1m
```

```例:
sudo dd if=/Users/hoge/Downloads/CentOS-7-x86_64-DVD-1908.iso of=/dev/rdisk2 bs=1m
```

---

### インストール

#### 1. インストール用USBメモリをMacbookProに接続する

#### 2. option キーを押しながらMacbookProを起動

#### 3. 起動ボリュームの選択画面が表示されるので、EFI boot を選択する

#### 4. Install CentOS 7を選択

#### 5. GUIインストーラー で下記を設定

  * キーボードレイアウト：英語（Macintosh） ※USキーボードの場合
  * ネットワーク設定：固定IPアドレス
  * 日付と時刻：
    - アジア-東京
    - ネットワーク時刻: on
    - ntpサーバを別サーバへ変更
  * インストール先ディスクの設定

  - インストール中にrootパスワードを設定

#### 6. インストールが完了したら再起動

---

### インストール後の設定

* バッテリー状態を確認

    ```
    # cat /sys/class/power_supply/BAT0/uevent
    POWER_SUPPLY_NAME=BAT0
    POWER_SUPPLY_STATUS=Charging
    POWER_SUPPLY_PRESENT=1
    POWER_SUPPLY_TECHNOLOGY=Li-ion
    POWER_SUPPLY_CYCLE_COUNT=8
    POWER_SUPPLY_VOLTAGE_MIN_DESIGN=10950000
    POWER_SUPPLY_VOLTAGE_NOW=12613000
    POWER_SUPPLY_CURRENT_NOW=1272000
    POWER_SUPPLY_CURRENT_AVG=1272000
    POWER_SUPPLY_CAPACITY=76
    POWER_SUPPLY_CHARGE_FULL_DESIGN=6000000
    POWER_SUPPLY_CHARGE_FULL=5795000
    POWER_SUPPLY_CHARGE_NOW=4584000
    POWER_SUPPLY_TEMP=266
    POWER_SUPPLY_MODEL_NAME=bq20z451
    POWER_SUPPLY_MANUFACTURER=ANSANOR
    ```

* MacbookProのモニタを閉じてもスリープさせない

    1. 編集するconfファイルをバックアップ
        ```
        # cp -p /etc/systemd/logind.conf{,.org}
        ```

    2. logind.conf(/etc/systemd/logind.conf)を下記のように編集
        ```
        # MacbookProのモニタを閉じてもスリープさせない
        HandleLidSwitch=ignore
        # パワーキーを押した場合の動作
        HandlePowerKey=ignore
        # サスペンドキーを押した場合の動作
        HandleSuspendKey=ignore
        # ハイバネートキーを押した場合の動作
        HandleHibernateKey=ignore
        ```

    3. systemd-logind サービスを再起動

        ```
        # systemctl restart systemd-logind.service
        ```

---

### 参考
https://lizefieldwp.azurewebsites.net/index.php/2015/08/22/古いmacbook-proにcentos7をクリーンインストール/
https://docs.centos.org/en-US/centos/install-guide/Making_Media_USB_Mac/
https://mseeeen.msen.jp/how-to-avoid-sleep-on-pc-closing-with-centos-7/
Route53+MyDNSでサーバを公開する(複数サブドメイン/自動IP報知/Let's Encrypt対応)

SaaSいっぱいの時代に個人でサーバを生やしている人がどれほどいるのか謎ですが、お遊び用途で固定IPのISP契約とかElasticIPとか使ってられないよみたいな人に向けたDDNSの設定記事です。

MyDNS、便利に使わせていただいているのですが、複数ドメインを管理しようとすると最初は設定がわからないところが結構あったので、書き残しておきます。

# 前提
Route 53でドメインを取得し、管理ができる状態になっていること。
とりあえずMyDNSのIDは発行されていてログインできること。

# Route53の設定
### NSレコード追加
↓のようにサブドメインのゾーン情報にmydnsのネームサーバを指定します。
mydnsに管理させたいサブドメインを同様に全て放りこんでいきます。

```:値
ns0.mydns.jp
ns1.mydns.jp
ns2.mydns.jp
```
![download1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565369/c9f53a42-a1c8-2e13-ca29-435f11b72dfb.png)

### CAAレコード追加
Let's encrypt(letsencrypt.org)でのみ証明書を利用する場合は下記のような形でOK。
各サブドメイン側に設定をするのが面倒なので、ドメイン全体に対して証明書発行を許可しています。
指定したサブドメインにのみ設定する場合、mydns側で設定しましょう。

```:値
0 issuewild "letsencrypt.org"
0 iodef "あなたのメールアドレス"
0 issuewild ";"
```
![download2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565369/f71f2f14-f506-936d-4a84-cb58b970b1be.png)


# MyDNSの設定

### 子ID発行
今回は複数のサブドメインを管理する想定なので、管理メニューの「USER INFO」で子IDを追加します。
管理したい数を考えて適当な個数を指定して子IDを発行します。完了したらメールでIDが一杯飛んできます。
![download3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565369/b4ef98bc-d07f-32e4-e237-080c8f915c7f.png)

### 親IDのドメイン設定
親IDには取得したドメインを設定します。
レコード入力欄にサブドメインをDELEGATEで登録し、最右のの項目でそのサブドメインを管理する子IDを指定します。
![download4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565369/e91d99da-73db-8bf3-b3e4-d3d6bdf8358c.png)

### 子IDのドメイン設定
子IDは「*」をAレコードに設定しましょう。
![download6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565369/71965b0a-a8b5-d989-016a-5c1613b2681b.png)

# サーバの設定
最後に到達したいサーバのIPをmydnsに報知してやります。
以下のシェルスクリプトを作成し、IDとパスワードを入力して保存します。(パスワード直書きなんてありえない！って方は別の方法を教えてください)
私はcronで定期的に報知したいので、cron.hourlyに置きましたが、そこは要件やディストリに合わせましょう。

```bash:/etc/cron.hourly/regist-ip.sh
#!/bin/bash
mydnsid=mydnsxxxxxx #yourid
mydnspass=xxxxxxxxx #yourpassword

while true
do
    status=`curl -o /dev/null -w %{http_code} -s --user $mydnsid:$mydnspass https://ipv4.mydns.jp/login.html`
    if [ $status -eq 200 ]; then
        logger "$mydnsid ip-regist success"
        echo "success"
    break
    fi
    sleep 2
done
```

実行確認

```
sh /etc/cron.hourly/regist-ip.sh
```
successが表示されたら成功です。
同サーバから別IDも報知したい場合、利用する子IDの分だけスクリプトを作るとか処理を追加するとかで対応しましょう。

# 確認
報知されていることはmydns子IDポータルの「LOG INFO」で確認できます。

```
2020/03/26 06:01:11 JST 	IPv4 UPDATE : mydnsXXXXXX XX.XX.XX.XXX (yourhost), http, jpn 
2020/03/26 06:01:11 JST 	DNSINFO UPDATE : test1.example.co.jp
``` 

名前解決できているか、CAAレコードが登録されているか、digで確認してやりましょう。

```bash:
dig test1.example.co.jp
dig example.co.jp CAA
```

これで固定IPじゃなくてもサーバに到達できますね。
Rubyでナノ秒、PostgreSQLだとマイクロ秒

環境: circleci/ruby:2.4.2-stretch-node-browsers, circleci/postgres:10.12

とあるアプリケーションで、新しくCircleCIを設定してRspecを動かしてみたら、エラーが大量に出ました。

```
expected: 2020-03-25 12:36:20.477148202 +0900
got: 2020-03-25 12:36:20.477148000 +0900
```

Rubyはナノ秒（小数点以下9桁）まで時刻を保持し、PostgreSQLはマイクロ秒（小数点以下6桁）までしか保持しないので、比較が失敗しまくっていると判明。ひえー、どうすんのこれ。助けて！

## 用語

参照: [Orders_of_magnitude_(time)](https://en.wikipedia.org/wiki/Orders_of_magnitude_(time))

| 名前      | 単位 | 例 |
|:---------|:---------------|:------|
| ミリ秒    |10<sup>-3</sup> 秒|0.123 |
| マイクロ秒 |10<sup>-6</sup> 秒|0.123456 |
| ナノ秒    |10<sup>-9</sup> 秒|0.123456789 |

以下、3桁ずつピコ 10<sup>-12</sup>、フェムト 10<sup>-15</sup>、アット 10<sup>-18</sup>、ゼプト 10<sup>-21</sup>、ヨクト 10<sup>-24</sup>と続きます。

## Macでは

Mac（rbenvでインストール）では、RubyのTime#nsecはナノ秒（9桁）を表しますが、実際はマイクロ秒（6桁）で切り取られるので、Postgresとの違いに気づきませんでした。

```ruby
t = Time.now
t.to_f #=> 1585394326.856343
t.nsec #=> 856343000
```

Ubuntuだとナノ秒までしっかり入ります。

```ruby
t = Time.now
t.to_f #=> 1585394640.8082483
t.nsec #=> 808248257
```

## 対策

Time#<=> を上書きしてミリ秒で比較することでエラーを減らすことができました。<=> を上書きするだけで、== も != も < も対策できます。Time#<=> を上書きすると、ActiveSupport::TimeWithZone の比較にも使われます。

マイクロ秒で比較（floor(6)）ではうまく行きませんでした。

```ruby:spec/supports/time_patch_helper.rb
if ENV['CIRCLECI']
  class Time
    alias_method :old_cmp, :<=>
    def <=>(val)
      if val.try(:acts_like_time?)
        self.to_f.floor(3) <=> val.to_f.floor(3)
      else
        old_cmp(val)
      end
    end
  end
end
```

ちなみにRubyには時刻の小数点以下を丸めるメソッド [Time#round](https://ruby-doc.org/core-2.4.3/Time.html#method-i-round) があり、Ruby 2.7 では切り落としを行う [Time#floor](https://ruby-doc.org/core-2.7.0/Time.html#method-i-floor)が追加されています。

## 今後やりたいこと

- 上記の対策よりもうまい方法を考える。
- Rubyのソースコードをチェックしたり、Linuxでコンパイルしてみたりする（たぶんC言語の#defineに違いがある）。
Objective-Cでフーリエ変換によるローパスフィルタ

iOS向けアプリの開発で、
指定Hz以下の波形をカットするローパスフィルタを作成したかった訳だが
それをする為には
フーリエ変換→周波数カット→逆フーリエ変換
の流れで処理する必要がある。

ググってもObjective-Cによる
これらの処理を一貫してやってくれるもの無かった為、自作した。

コードは[こちら](https://ellery459.web.fc2.com/text/DFT.c)で紹介されていた
C言語のものを参考にして書き起こした。

```Filter.m
const float pi = 3.14;
const float T = 0.01;

@implementation Filter : NSObject


- (float*)fftLawPassFilter:(float*)srcData dstData:(float*)dstData dataLength:(int)length filterHz:(float)filterHz
{
    // 初期化
    float tmpRefOut;
    float tmpImfOut;
    float imfOut[length];
    int size = length;
    float ReF[size];
    float ImF[size];
    
    //フーリエ変換
    for(int n=0;n<size;n++){
        float Ref = 0.0f;
        float Imf = 0.0f;
        
        for(int k=0;k<size;k++) {
            Ref+=srcData[k]*cos(2*pi*k*n/size);
            Imf+=-srcData[k]*sin(2*pi*k*n/size);
        }
        
        ReF[n]=Ref;
        ImF[n]=Imf;
        
        if(n%1000==0)printf("%d\n",n);
    }
    
    // フィルタをかける
    for(int n=0;n<size;n++){
        float hz = (n+1) / (size*T);
        NSLog(@"a: %f", hz);

        if(hz > filterHz){
            ReF[n]=0;
            ImF[n]=0;
        }
    }
    
    // フーリエ逆変換
    for(int k=0;k<size;k++) {
        tmpRefOut=0.0;
        tmpImfOut=0.0;
        for(int n=0;n<size;n++) {
            tmpRefOut+=(ReF[n]*cos(2*pi*k*n/size)-ImF[n]*sin(2*pi*k*n/size))/size;
            tmpImfOut+=(ReF[n]*sin(2*pi*k*n/size)+ImF[n]*sin(2*pi*k*n/size))/size;
        }

        dstData[k]=tmpRefOut;
        imfOut[k]=tmpImfOut;
        if(k%1000==0)printf("%d\n",k);
    }
       
    return dstData;
}
```

引数にフィルタしたいデータの配列、そのサイズ、周波数カット数(Hz)を指定してやれば良い。
【XML】気象庁から地震情報を取得する①

#気象庁XMLを取得して解析までの道のり
① XMLのダウンロード（今回）
② XMLファイルの解析（次回）

#気象庁からXMLファイルを取得する
ダウンロードを実行するクラスを作成する。

```java:XMLDownloader.java
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class EarthquakeXMLDownloader implements Runnable {

	// 気象庁のリンク（http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml）
	String link;

	// ダウンロードする場所
	File out;

	public EarthquakeXMLDownloader (String link, File out) {
		this.link = link;
		this.out = out;
	}

	@Override
	public void run() {

		try {
			URL url = new URL(link);
			HttpURLConnection http = (HttpURLConnection) url.openConnection();
			double fileSize = (double)http.getContentLengthLong();
			BufferedInputStream in = new BufferedInputStream(http.getInputStream());
			FileOutputStream fos = new FileOutputStream(this.out);
			BufferedOutputStream bout = new BufferedOutputStream(fos, 1024);
			byte[] buffer = new byte[1024];
			double downloaded = 0.00;
			int read = 0;
			double percentDownloaded = 0.00;

			while((read = in.read(buffer, 0, 1024))>= 0) {
				bout.write(buffer, 0, read);
				downloaded += read;
				percentDownloaded = (downloaded*100)/fileSize;
				String percent = String.format("%.4f", percentDownloaded);
				System.out.println("Downloaded " + percent + "of a file.");
			}

			bout.close();
			in.close();
			System.out.println("Download complete.");
		}catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

```

↑のファイルを実行する。

```java:Main.java
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Main {

	public static void main(String[] args) {

		// アウトプットするファイルパスを作成
		// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
		LocalDateTime now = LocalDateTime.now();
		DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm");
		String fileName = "earthquake_" + now.format(dateTimeFormatter);
		String link = "http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml";

		File out = new File("C:\\pleiades\\workspace\\SAX\\resource\\" + fileName + ".xml");
		// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

		// 実行する。
		new Thread(new EarthquakeXMLDownloader(link,out)).start();

	}

}

```
sys.path.append('..')しても親ディレクトリにあるモジュールをimportできない

## 問い
1. Pythonでmain.pyからmodule1.pyを使いたい。
2. projectディレクトリで`python scripts/main.py`を実行。

``` text:ディレクトリ構成
project/
      ├ module/
      │       └ module1.py
      └ scripts/
              └ main.py
```

```python:main.py
import sys
sys.path.append('..')
from module import module1
```
このままでは`ModuleNotFoundError`が出る。

## 答え
`..`が示すのは、**作業ディレクトリ**から見た親ディレクトリ。**実行ファイルのあるディレクトリではない**。つまり、projectディレクトリに対する親ディレクトリを参照している。
解決策としては、モジュールとして実行することが考えられる。

```shell
python -m scripts.main
```
Android Studio SDKToolsでHAXMがインストールできない

#対象読者
・これからAndroidアプリの開発を行う人
・Android Studioのデバッグで環境構築が止まっている人

#Tips
HAXMを実際にインストールしようとすると以下のようなLogが出てくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537785/c9eb3d7c-78e3-ff42-8a30-78582c7ec34f.png)

HAXMをインストールするにはHyper-Vを無効化する、もしくは仮想化機能を有効にする必要がある。しかしWindows10ProではないためHyper-Vは存在しない、しかも仮想化機能も有効になっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537785/901a6af7-f335-5139-0e7d-c56e643371bf.png)

全く分らんとなったのですが、とりあえず以下の方法でデバッグは出来るようになった。
以下の図の枠線の箇所をインストール。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537785/f9367b98-d604-8686-1406-3eac680019fb.png)

もしかしたら、開発を進めるうえで障害があるかもですが、とりあえず前には進めそう。
指定したエリアだけを印刷する処理

# こんな場面で使えます。
　例えば、ユーザーに何らかのアンケートに回答をしてもらうサイトで
**ユーザーが自分の回答結果の部分だけを印刷したい場合**などに使えます。
普通の印刷では余計な箇所も入ってくるので、必要なエリアだけを印刷できるようにします。
※実際の挙動が確認できるwebアプリのURLを一番下に載せてますので、そちらもご確認ください。
![11printImg1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533713/69200c0f-8c51-2c4c-d8fc-3a4af6868b15.jpeg)

# 実装の流れ
　１：印刷ボタンを作成
　２：プリントしたいエリアの取得の指定
　３：２のエリア全体のコピーを作成
　４：3以外を非表示にする処理を作成
　５：印刷ダイアログの呼び出し➡︎印刷する
　６：３で作成した印刷用エリアを削除 ・ ４で非表示にした要素を表示する

# 実装スタート
## １：印刷ボタンを作成

```html:print.html
<button class="print-btn">
　<span>結果だけを印刷する</span>
</button>
```

## ２：プリントしたいエリアの取得
　プリントしたい範囲を以下の「divタグ」で囲む。

```html:print.html
<div class="print-area">
　<!--　この中に要素を入れる。印刷しないものは、後で非表示にする処理で消します。 -->
</div>
```
## 3：２のエリア全体のコピーを作成
　１：プリントしたい範囲を取得
　２：プリント用の要素「#print」を作成。
　３：２の子要素に１を入れる。

```Javascript:print.js
$(function(){
//印刷ボタンをクリックした時の処理
$('.print-btn').on('click', function(){

//プリントしたいエリアの取得
var printArea = document.getElementsByClassName("print-area");
 
//プリント用の要素「#print」を作成し、上で取得したprintAreaをその子要素に入れる。
$('body').append('<div id="print" class="printBc"></div>');
$(printArea).clone().appendTo('#print');

//この下に、以降の処理が入ります。

 });
});
```

## ４：3以外を非表示にする処理を作成
    
```Javascript:print.js
//プリントしたいエリア意外に、非表示のcssが付与されたclassを追加
$('body > :not(#print)').addClass('print-off');
```

```css:print.css
.print-off {
　display: none;
}
```

## ５：印刷ダイアログの呼び出し➡︎印刷する

```Javascript:print.js
window.print();
```

## ６：３で作成した印刷用エリアを削除 ・ ４で非表示にした要素を表示して終了です。

```Javascript:print.js
//window.print()を実行した後、作成した「#print」と、非表示用のclass「print-off」を削除
$('#print').remove();
$('.print-off').removeClass('print-off');
```

# 終わりに。
いかがでしょうか。
印刷したいエリアだけを取り出せるのでユーザーにとって不要なエリアが無くなり使い勝手の良いページが作成できると思います。
自作の家庭用備蓄をチェックできるアプリでも、チェックした備蓄情報を印刷できるようにしてありますので
良かったら作業の合間にそちらを見ていただけると具体的な挙動を確認していただけます。
https://saku2stockcheck.herokuapp.com/

では、最後まで見ていただいてありがとうございました！
Python3でメール等の返信のための引用符「>」をつける

#この記事について
この記事ではメール等で返信を行う際に、相手の文面の文頭に引用符をつけるコードを紹介します。筆者は素人です。色々教えていただけると幸いです。

筆者はPython2のことをよく知りませんが、自分がPython3を使ってるっぽいことだけはわかっています(Python3.6.0かな？)。そのため、記事のタイトルではPython3としました。

#やりたいこと
下記のように送られてきたメール(相手の文面)に対して、先頭に「>」をつけた返信用の文面を出力したいです。ただし、空行はそのままにしておきたいです。

###送られてきたメール(相手の文面)
佐藤さん

こんにちは

来週の飲み会のお店の候補です。

店A
うまい。少し高い。

店B
味はまあまあ。安い。

店C
飲み物が沢山ある。雰囲気が良い。

鈴木

###返信用の文面

\>佐藤さん

\>こんにちは

\>来週の飲み会のお店の候補です。

\>店A
\>うまい。少し高い。

\>店B
\>味はまあまあ。安い。

\>店C
\>飲み物が沢山ある。雰囲気が良い。

\>鈴木

#実装コードと実行結果
実装したコードと使い方、実行例を以下に示します。

###コード
```python:reply_mark.py
path_r = "sent_text.txt"
path_w = "reply_text.txt"
with open(path_r) as f_r, open(path_w, mode='w') as f_w:
    for line in f_r:
        if len(line) > 1:
            f_w.write(">" + line)
        else:
            f_w.write("\n")
```

- with openなんたら〜の書き方でファイルオープンすると、ブロックの終了時にファイルクローズしてくれるらしいです。
(参考： https://note.nkmk.me/python-file-io-open-with/ )

- if では空行かどうかの判定をしています。

- 用途に合わせて f_w.write(">" + line) の > を他の記号等に変えるなどもできます。

###使い方
1. reply_mark.py があるディレクトリに sent_text.txtというファイルを作成し、送られてきたメール(相手の文面)をコピペする。
2. 下記のように、ターミナルで reply_mark.py を実行する。

    ```bash:
    $ python reply_mark.py
    ```
3. reply_text.txt というファイルが作成され(※)、相手の文面の先頭に「>」をつけた返信用の文面が出力される。使うときはここからコピペする。

※reply_text.txt というファイルが既に存在している場合は上書きされます。

###入力例
```:sent_text.txt
佐藤さん

こんにちは

来週の飲み会のお店の候補です。

店A
うまい。少し高い。

店B
味はまあまあ。安い。

店C
飲み物が沢山ある。雰囲気が良い。

鈴木

```

###出力例
```:reply_text.txt
>佐藤さん

>こんにちは

>来週の飲み会のお店の候補です。

>店A
>うまい。少し高い。

>店B
>味はまあまあ。安い。

>店C
>飲み物が沢山ある。雰囲気が良い。

>鈴木

```

#終わりに
もう少し便利にしたいですね。理想で言えば、DeepLのアプリの「command + C」を2回押したら結果出るみたいなの良いなあ。

あと、シェルスクリプトで同じことできる気がしますが、Pythonの勉強を兼ねてるのでまあいっかということで。

気づいた点などあればご指摘、ご質問いただければと思います。特にコードの書き方や仕組みの作り方の改善案などあれば勉強になるなと思います。