OmniAICreator/Qiita-1.07M huggingface dataset:
【React】コンポーネント

こちらの記事は以下の書籍を参考にアウトプットとして執筆しました。
[React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで](https://www.amazon.co.jp/React%E5%85%A5%E9%96%80-React%E3%83%BBRedux%E3%81%AE%E5%B0%8E%E5%85%A5%E3%81%8B%E3%82%89%E3%82%B5%E3%83%BC%E3%83%90%E3%82%B5%E3%82%A4%E3%83%89%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AB%E3%82%88%E3%82%8BUX%E3%81%AE%E5%90%91%E4%B8%8A%E3%81%BE%E3%81%A7-%E7%A9%B4%E4%BA%95%E5%AE%8F%E5%B9%B8-ebook/dp/B078XDLL2J/ref=tmm_kin_swatch_0?_encoding=UTF8&qid=&sr=)

ただの個人的アウトプットです

#Reactコンポーネント

|種類|役割|
|---|---|
|Functional Component|関数定義による作成|
|Class Component|クラス定義による作成|

##違い
Class Componentには以下の2つができる。

- stateがある。
- ライフサイクルメソッドを定義できる。

##コンポーネントの再利用
Reactコンポーネントは設計図のようなもので、再利用可能で、同じ構造のHTMLを作れる。
##Reactエレメント
Reactコンポーネントの実体をReactエレメントと呼ぶ
###Fragmentコンポーネント
Reactコンポーネントは単一の親要素しか扱えない。
React.Fragmentを使うことで並列にできる。
#Todoアプリの作成

##stateとイベントハンドリング
|名称|意味|
|---|---|
|state|状態を管理する仕組み。何が入力されているか、何が表示するべきかなど|

##コンポーネントの準備
###Appコンポーネント
アプリの各コンポーネントをまとめ上げる役割。
データ管理を行う。
###TodoInputコンポーネント
Todo入力のためのコンポーネントを作成する。
###TodoListコンポーネント
propsとして受け取ったtodoListを一つのTodoItemに表示する。
###TodoItemコンポーネント
一つ一つのTodoを表すコンポーネント
[GitHub]「Compare & pull request」のボタンが表示されない

# 状況

リモートへgit push後、GitHubで新規のプルリクを作ろうとしたときに、「Compare & pull request」のボタンが見当たらないとき


# 対象者
* 特にエラーメッセージは出ていない
* `git log`で確認してもcommitまではうまくいっているようだ

な方向け

# 対処方法
**「Compare & pull request」ボタンを使わずにプルリクを作成します**


[手順]

* GitHubで、pull requestタブをクリック

* 下記画面右上の緑のボタン「New pull request」をクリック
![スクリーンショット 2020-08-27 15.20.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585984/43c5a3e4-410f-af88-fe33-31ddb8ac7e49.png)

* Example comparisonsから該当するブランチ名をクリック
![スクリーンショット 2020-08-27 15.22.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585984/a467ddae-8c9d-5483-62ec-59a1aadcef93.png)

これで変更内容が表示されるはずなので、
「Create pull request」をクリックしてプルリクを作成すればOK

# メモ
「プッシュ後　GitHub　消えた」「プルリク　消えた」「プルリク　表示されない」などと
検索をかけたところ、最適な記事が引っかからず、解決に時間がかかってしまいました。
慣れた方にはなんてことない操作ですがGitHub初心者が迷わないためのヘンゼルの小石として残します。
Androidのリセマラを防ぐ


# 要約

MediaDrmを使ってリセマラを防ぎます

# 概要

Androidでリセマラ（リセットマラソン）を防ぐために、一意のIDを取得します。
ここでいうリセマラとは、アプリをアンインストール/インストールを繰り返して、初回限定の利益を何度も受ける行為を指します。

## 公式のドキュメント
リセマラのように何度もアプリを再インストールしてIDを作成することをシビル攻撃というらしいです。
そして、公式にも[不正行為防止: 無料コンテンツ制限を適用し、シビル攻撃を検知する](https://developer.android.com/training/articles/user-data-ids?hl=ja#anti-fraud-free-content-limits)というドキュメントがあります。

> GUID やインスタンス ID を使用すると、ユーザーがコンテンツ上限の適用を回避するにはアプリを再インストールしなければならなくなるため、ほとんどのユーザーに思いとどまらせることができます。

しかし、これで思いとどまらないのがリセマラという行為なので、もう一つある

> APK ごとの識別子である Widevine ID を使用してコンテンツへのアクセスを制限できます。

この、Widevine IDを使ってリセマラを防いでみます。

### Widevineとは
Widevineとは、デジタル著作権管理技術プロバイダーです。
他のデジタル著作権管理技術プロバイダーとして、MicrosoftではPlayReady、AppleではFairPlayというものがあり、WidevineはGoogleのデジタル著作権管理技術プロバイダーになります。

### Widevine IDを取得する
Widevine IDを取得するために、[MediaDrm](https://developer.android.com/reference/android/media/MediaDrm#MediaDrm(java.util.UUID))というものを使います。

```kotlin
val widevineUuid = UUID.fromString("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed")
val id = MediaDrm(widevineUuid).getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
```

といった感じに、MediaDrmをWidevineのUUIDで初期化して、MediaDrm.PROPERTY_DEVICE_UNIQUE_IDを取得することにより、端末固有のIDを取得できます。

本来であれば、暗号化されたコンテンツのメタデータを取得し、それにあったデジタル著作権管理技術プロバイダーを設定する使い方をするのがMediaDrmなのですが、今回の目的はWidevine IDなので、WidevineのUUIDを直接入れています。

ちなみにUUIDの参考先はこちらのサイトです。
https://dashif.org/identifiers/content_protection/

## Widevine IDの挙動について

### アンインストールして再インストールした場合
初回取得時と同じIDになります。

### 端末ID取得処理を別アプリに実装した場合
別アプリの場合、違うIDになります。

### マルチユーザーの場合
Android同一端末でマルチユーザーの機能を利用している場合、どのユーザーに切り替えても同じIDになります。

# まとめ

つかおう！MediaDrm！
kaggle～pandas編～

#データの読み込み
![コメント 2020-08-27 224123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/aeb664b9-7af1-e256-c211-dd7ec0425ae6.png)

#フォーマットの確認
![コメント 2020-08-27 224149.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/bf158c52-e305-e41c-ee4c-f27e392ab6ea.png)

#データの表示
先頭5行の表示
![コメント 2020-08-27 224210.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/a9bc8acd-b2e3-72a3-aa96-414a31bea383.png)

#欠損値の確認
各要素が欠損していなければFalse
![コメント 2020-08-27 224344.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/87406d9b-0d14-f226-f1ca-6e435cece3cf.png)

isany()は一つでも欠損値があればTRUEになる。
![コメント 2020-08-27 224251.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/94627ac0-9e97-45a8-e0ae-1cc95daa96ed.png)

#連結
concatで2つのデータフレームが結合できる。
axis=1で横方向に結合。
keysで名称を指定できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/931d739c-73a1-9505-70ab-e57c7ccfb4af.png)

#データのカウント
```ruby:qiita.rb
#それぞれのランドマーク数をカウント
landmark_id_count = pd.DataFrame(train.groupby(['landmark_id'])['landmark_id'].count())
print(landmark_id_count)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/192b2f49-2c12-802f-34c6-31e328a5a489.png)

#名前の変更 rename()
landmark_idを各ラウンドマークの総数に変更する

```ruby:qiita.rb
#Trueだと元のDataFrameが変更される.Flaseだと変更されない
landmark_id_count.rename(columns={'landmark_id': 'Count_Images'}, inplace=True)
print(landmark_id_count)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/7bc6adb7-036f-d886-064d-8453820741eb.png)

#データのソート
多いランドマーク順にソートする

```ruby:qiita.rb
landmark_id_count.sort_values(by=['Count_Images'],ascending=False, inplace=True)
print(landmark_id_count)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/66237c29-f7be-8304-f12f-b95958dd6c6b.png)

#Indexの付けなおし
多いランドマーク順にソートする

```ruby:qiita.rb
#indexをつけなおす。drop=Trueにすると元のindexは削除される
landmark_id_count.reset_index(drop=True, inplace=True)
print(landmark_id_count)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680528/4ea6ea23-8fef-8591-c785-b7724f4855a8.png)
Nginxをリバースプロキシにして、配下にApacheウェブサーバーをおいてみたが

どうも思ったとおりに動いてくれない。
色々調べて試してみたけどあまり変わらない。
ということで回避方法を備忘録的に残しておきます。

## 環境
- VM2台
  - リバースプロキシ
    - CentOS 7
    - nginx-1.16
  - ウェブサーバー
    - CentOS 7
    - Apache 2.4

## 設定

nginxのプロキシ設定

```conf:/etc/nginx/conf.d/proxy.conf
server {
    listen 192.168.xxx.xxx:80;
    listen 192.168.xxx.xxx:443 ssl http2;

    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    server_name site1.example.com;
    server_name site2.example.com;

    client_max_body_size 128m;

    access_log "/var/log/nginx/proxy_access_log";
    error_log "/var/log/nginx/proxy_error_log";

    index index.html index.htm index.php;

    proxy_set_header Host               $host;
    proxy_set_header X-Real-IP          $remote_addr;
    proxy_set_header X-Forwarded-Host   $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    access_log on;

    location / {
        proxy_pass http://192.168.yyy.yyy:80;
    }
}
```

これの配下にApacheを置きます。
Apacheの設定は大まかに以下の通りです。

```conf:/etc/httpd/conf/httpd.conf
RemoteIPHeader X-Forwarded-For

<IfModule log_config_module>
    LogFormat "%{X-Forwarded-For}i %h %l %u [%{%a %b %d %T %Y}t] \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" mylogformat
</IfModule>
```

ApacheのアクセスログにクライアントのIPを記録させたいのですが、`%h`とかを指定していればOKだと思っていました。ところがこれがうまく行かない。`%{X-Forwarded-For}i`で記録されるようになるのですが、ちょっと違うみたい。

## 問題点

.htaccessでアクセス制限をかけたいと思っても、`REMOTE_ADDR`がリバースプロキシサーバーのIPになっているので、そのまま`Require ip zzz.zzz.zzz.zzz`とはかけない。

この場合、以下のように書くようです。

```conf:.htaccess
SetEnvIF X-Forwarded-For "^192\.168\.zzz\.zzz$" AllowIP
Require all denied
Require env AllowIP
```

## 参考
https://www.cloudn-service.com/faq/3475
Azureとクライアント端末をSSL-VPNで繋いでみた

###会社での開発は個人開発とは違い、セキュリティを考慮する必要がある
個人開発する分にはAWSだろうがAzureだろうがインターネットからアクセス（https）すれば良いが、
会社での開発となるとなかなかそうもいかない場合がある。。

ということでSSL-VPNをでAzureの上にアクセスする方法を試してみました。

##環境
windows10 Pro
※windows10であれば可能なはず

##SSL-VPNでアクセスする理由

1.ローカルIP（プライベートIP）でシステムにアクセスしたいとき
　クラウドのシステムはほぼ100％グローバルIPを持っているはずですが（Web,AP,DB系）
　グローバルIPではなく**プライベートIPを使用してアクセスしたければVPN**を使用する

2.社内だけでなく、外部からクライアント端末で（証明書持ち）アクセスしたいとき
　証明書で認証＋鍵を使って暗号化のため、安全にアクセスが可能
　**クライアント端末の認証＋暗号化でアクセスしたければVPN**を使用する
（ユーザ認証はhttpsでもAzureにログインするときに行っている）


##手順
<details><summary>1.自己証明書の発行(2と順不同)</summary>
**自己証明書とは**
クライアント端末（自分のノートPC等）自身が発行する証明書
一般的にはCA（Certificate Authority）で発行することが多い｛CAでの発行などの一連の仕組みをPKIという｝



</details>


<details><summary>2.Vnet、GatewaySubnet、VPNゲートウェイの構築</summary>
**※GatewaySubnetはVnetに一つしか構築が出来ない**
</details>
<details><summary>3.自己証明書をVPNゲートウェイに渡し、代わりにVPNcertificationを貰う</summary>
</details>
<details><summary>4.VPN接続トライ(完成)</summary>
![2019-04-30_19h01_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222041/d43c2c62-cf70-0e80-7eb5-a04bfedd7fe3.png)
もちろん既存ファイルの編集も可能
</details>

##感想
セキュリティレベルはピンキリあるので結局は要件次第となってしまうことが多い。。
今回紹介したSSL-VPN以外にもOpenSSLを利用しての接続やIPsec-VPNでの接続を行うことが出来ます。
自分の中では、一旦興味が落ち着いたので再び興味が出たら他の方法も試そうかなと思います。
（OpenSSLはかなり注目度の高いワード）
PCでpythonを使ってNetflixに自動でログインしてみる

#pythonを使ってNetflixに自動でログインしてみる

__初めに__
今回の投稿が初めてなので不足しているところが多々見受けられると思いますが、温かい目で見守って下さい。
更に、自分は初心者なので探り探りで作成したコードで分かりずらい部分も出て来ると思いますが、ご了承のほどお願い致します。

#概要
・seleniumを使ってwebを操作する


__本題へ__

pythonでwebページを操作できると言ったらseleniumである。この記事ではpythonでseleniumを介してChromeを操作するための環境構築と基本的な使い方については省略させて頂く。

#実際にコードを書いてみる

__seleniumからwebdriverをimportする__

とりあえず、必要なモジュールをimportしておきましょう

```python：netflix.py
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.action_chains import ActionChains
import time

```

Webを動かす時に、次のページに行く前に要素指定しないように、休ませるためのtimeモジュールもimportしておきます。

```python：netflix.py
driver = webdriver.Chrome("chromedriverを保存した場所を指定する")
#（例）driver = webdriver.Chrome("C:\Command\chromedriver")
driver.get('https://www.netflix.com/jp/') 
time.sleep(1)

```

これで、ひとまずNetflixのページにはアクセスできるようになります。
もちろん、応用としてhttps://www.netflix.com/jp/ の部分を変えれば他のwebページにアクセス出来ます。


__Pathを指定して文字を入力する__
次に、文字を入力するさせるコードを書きます。

```python：netflix.py
login = driver.find_element_by_css_selector('#appMountPoint > div > div > div > div > div > div.our-story-header-wrapper > div > a')
login.click()
#ログイン画面に移動
driver.find_element_by_name('userLoginId').send_keys('メールアドレス')
driver.find_element_by_name('password').send_keys('パスワード')
#メールアドレスとパスワードを入力
driver.find_element_by_css_selector('#appMountPoint > div > div.login-body > div > div > div.hybrid-login-form-main > form > button').click()
time.sleep(1)
```

ここまで来たらNetflixにログイン完了です。
しかし、ここからはNetflixの契約プランによって指定するPathが異なるので、私の場合でコードを書いていきます。実際のところ、ここまでコードがかければ、後はPathを指定してクリック、Pathを指定して入力で自動再生までもって行けます。


__ログイン完了後の再生まで__
 

ここから自分を選択して自動再生までPathの指定、クリック、入力でを繰り返していきます。

```python：netflix.py
driver.find_element_by_xpath('//*[@id="appMountPoint"]/div/div/div[1]/div[1]/div[2]/div/div/ul/li[1]/div/a/div/div').click()
time.sleep(1)
#自分を選択
driver.find_element_by_xpath('//*[@id="appMountPoint"]/div/div/div[1]/div[1]/div[1]/div/div/div/div[1]/div/button/span').click()
#検索欄をクリック
title = “ケンガンアシュラ”
driver.find_element_by_xpath('//*[@id="appMountPoint"]/div/div/div[1]/div[1]/div[1]/div/div/div/div[1]/div/div/input').send_keys(title)
time.sleep(1)
#検索欄に入力
driver.find_element_by_css_selector("#title-card-0-0 > div.ptrack-content").click()
time.sleep(2)
driver.find_element_by_css_selector("#pane-Overview > div > div > div > div.ptrack-content > div > div.jawbone-actions > a.playLink.isToolkit > button").click()
#再生をクリック
```

以上で、Netflixの自動再生のコード完成です!!

#終わりに

初心者なのでまだまだ、コードの簡略化が出来ると思いますが、初めで完成させたコードなので、ご理解の程宜しくお願い致します。
次回は、GUIをpythonで作成し、テキストボックス取得からボタンクリックで自動再生を行っていきたいと思います。

お付き合いいただきありがとうございました。
pythonでgmailアカウントを使って添付ファイルをメールで送る。

# 準備するもの
- Gmailアカウント作成
- 「安全性の低いアプリを許可する」をオンに設定
  - https://myaccount.google.com/lesssecureapps

# コード
```python
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from email.utils import formatdate
import smtplib
import ssl

if __name__ == '__main__':
    # Gmailアカウント情報(★要設定★)
    mail_username = '作成したGmailアカウント名'
    mail_password = '作成したGmailアカウントのパスワード'

    # メール情報(★必要に応じて変更★)
    body = 'ここは本文です。'
    subject = 'ここはタイトルです。'
    to_addrs = ['example@gmail.com', 'example2@gmail.com'] # 送信したアドレスリスト
    attach_files = ['aaa.zip'] # 添付したいローカルファイル名を設定
    
    _msg = MIMEMultipart()
    _msg['From'] = mail_username
    _msg['To'] = "; ".join(to_addrs)
    _msg['Subject'] = subject
    _msg['Date'] = formatdate(timeval=None, localtime=True)

    # 本文の追加
    _msg.attach(MIMEText(body, "plain"))

    # 添付ファイルの追加
    for filename in attach_files:
        with open(filename, 'rb') as _f:
            part = MIMEBase('application', 'octet-stream')
            part.set_payload(_f.read())

        # base64 encode
        encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename= {}'.format(filename))
        
        _msg.attach(part)

    # セキュアなSSL接続で送信
    context = ssl.create_default_context()

    _smtp = smtplib.SMTP_SSL(host='smtp.gmail.com', port=465, timeout=10, context=context)
    _smtp.login(user=mail_username, password=mail_password)
    _smtp.sendmail(mail_username, to_addrs, _msg.as_string())
    _smtp.close()

```
Rails 4.2.x から 5.0.x にアップグレードする際にカナリアリリースすると session が取得できなくなる不具合を回避する

## はじめに


- カナリアリリースとは複数サーバのうちの一部にだけ新しいバージョンのアプリケーションをリリースするデプロイ手法である
- Rails 4.2.x から 5.0.x にアップグレードする際にカナリアリリースをする、つまり Rails 4.2.x と 5.0.x のアプリケーションが混在した環境を作ると session が取得できなくなる不具合が発生するのでこれを回避したい

この時世に Rails 5.0.x へのアップグレード（しかもカナリアリリースを挟んで）をする方がいらっしゃるのかは不明ですが、どなたかの役に立てば幸いです :pray: 

Ruby や Rails をある程度触ってる方向けの内容なので、Rails における session や rack などの基礎的な説明は省きます。

## 解決方法

- rack gem のバージョンを `2.0.7` に固定する

## 原因を一言で

Rails 4.2.x -> 5.0.x のアップグレードに引きずられて rack gem が 2.0.8 以上にアップデートされるのが原因。（rack の 2.0.7 -> 2.0.8 で `session_id` の生成ロジックに破壊的な変更がある）

See. https://github.com/rack/rack/blob/master/CHANGELOG.md#208---2019-12-08

## 説明

[rack の 2.0.7...2.0.8 の差分](https://github.com/rack/rack/compare/2.0.7...2.0.8) の`lib/rack/session/abstract/id.rb`, `lib/rack/session/memcache.rb` 辺りを見るととても分かりやすい。

- rack 2.0.7 まではブラウザの cookie に保存されてる `_session_id`（[Rails 側で命名](https://github.com/rails/rails/blob/v5.0.7.2/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb#L31)） の値をそのままセッションストア（Redis や Memcached など）のキーにしていた
- rack 2.0.8 からは `_session_id` を `Digest::SHA256.hexdigest` した値をキーにするようになった（[コードはこの辺り](https://github.com/rack/rack/blob/2.0.8/lib/rack/session/abstract/id.rb#L15-L39)、`public_id` が `_session_id` に等しい）
- 各セッションストアの gem で `#get_session_with_fallback` という fallback 用のメソッドを用意している（[redis-rack の場合](https://github.com/redis-store/redis-rack/blob/v2.1.3/lib/rack/session/redis.rb#L87-L89), [Memcache の場合](https://github.com/rack/rack/blob/2.0.8/lib/rack/session/memcache.rb#L94-L96)）ので、新しいバージョンの rack から古いバージョンの rack で生成したセッションデータを取得することはできるが、逆はできない。つまり、後方互換性はあるが、前方互換性がない

上記より、新しいバージョンの rack gem を全サーバに一度にデプロイすれば問題ないが、各サーバで 2.0.7 以下と 2.0.8 以上の rack gem が混在する環境（カナリアリリース環境）だと `session` が取得できなくなる。
[Ruby] 詳細ページなどに直接URLを入力されても他のページに遷移させる方法

初めての投稿なのでアドバイスあればよろしくお願いいたします！

フリマアプリ作成中、詳細ページまで作成してふと、
「これって直接詳細ページ飛べちゃうやん？」
と思い調べて自分なりにまとめてみました。

```items_controller.rb
class ItemsController < ApplicationController
  
  before_action :set_item, only: [:show, :edit]
   
   def edit
#重要なのはここから@itemは、before_actionからきてます
    if @item.user == current_user
#current_userは、ログインしているユーザーのこと
#@item.userは、@itemに入ってるuserを出してます
      render "edit"
    else
#もし等しくなければ、redirect_toでroot_path(ホーム画面に戻っています)
      redirect_to root_path
    end

private

  def set_item
    @item = Item.find(params[:id])
  end

  def item_params
    params.require(:item)
          .permit(:image, :name, :explanation, :category_id, :item_status_id,
                  :shipping_fee_status_id, :area_id, :scheduled_delivery_id, :price).merge(user_id: current_user.id)
  end
end
```
【Auth0】Rulesのログをリアルタイム表示する

# はじめに
Auth0にはReal-time Webtask LogsというExtensionがあります。
これを使うとRulesのログをリアルタイムで表示することができるのでやってみました。

# Real-time Webtask Logsのインストール

Auth0の管理画面にアクセスしてExtensionのメニューに表示されているExtesionの一覧の中からReal-time Webtask Logsを選択します。

![スクリーンショット 2020-08-26 18.54.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653972/b8eed88a-1523-0d35-0540-4b6301b157b9.png)

選択するとインストール画面が表示されるのでボタンを押してインストールします。

![スクリーンショット 2020-08-26 18.55.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653972/3538b8cc-c048-5d1c-197b-9579ffb31824.png)

Installed Extensionタブ内にReal-time Webtask Logsが追加されます。
これを押すとReal-time Webtask Logsの画面が開きます。

![スクリーンショット 2020-08-26 18.55.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653972/dcd872f9-9ce3-44ed-23bf-f6690b54974c.png)

このReal-time Webtask Logsの画面にRulesから出力したログがリアルタイムに出力されます。

![スクリーンショット 2020-08-26 19.46.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653972/96389e63-5007-5c77-e8a8-249bdc0f0208.png)


# Rulesからログを出力

ログ出力を確認するためにRulesに以下のコードを登録します。
見ての通り`ABCDEFG`と表示するだけで特に処理はありません。

```javascript
function (user, context, callback) {
  console.log('ABCDEFG');
  return callback(null, user, context);
}
```

このRulesを有効にし、Real-time Webtask Logsの画面を開いた状態でログインを行うと`console.log()`で出力した内容がすぐに反映されます。

![スクリーンショット 2020-08-26 19.46.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653972/0205f92f-1a6b-34eb-e846-fd00bb548b14.png)


# おわりに
Real-time Webtask Logsを使うとRulesのデバッグがとてもやりやすくなります。
Rulesを使ってAuth0の認証時の動きをカスタマイズする機会は多いと思うのでぜひ試してみてください。
collection_selectを使って、Active_Hashに格納したデータをプルダウンにする

##概要

今回実装したいのは、商品出品機能を実装する際に、発送元や商品の状態などの変化しない情報をActive_Hashとして格納して、ビューファイルにプルダウンとして表示する機能です。

Active_Hashで都道府県などを格納する方法に関しては、別で記事を書いたのでそちらを参照してください。

[Active Hashについて](https://qiita.com/keiya_man/items/3b0f50254ceb39097f42)

##collection_selectとは
ヘルパーメソッドの一つで、セレクトボックスのなかに、キーを与えたバリューを表示できる記述です。

基本的な記述は

```ruby:xxx.html.erb
f.collection_select(:カラム名, 要素の配列, value属性の項目, テキストの項目, { prompt: "選択してください" }
```

となるようです。

##例
今回は、商品出品する際に配送料の有無をPostageTypeクラスとして実装した例を書き残しておきます。

```ruby:products/new.html.erb
<%= f.collection_select(:postage_type, PostageType.all, :id, :name, {}, {class:"select-box", id:"item-shipping-fee-status"}) %>
```

promptの部分は空にした場合、Active Hashの:id=1がプルダウンのデフォルトとして表示されるようです。

postage_typeのActive Hashの記述についても書き残しておきます。

```ruby:models/postage_type.rb
class PostageType < ActiveHash::Base
  self.data = [
    {id: 1, name: '---'},{id: 2, name: '着払い(購入者負担)'},{id: 3, name: '送料込み(出品者負担)'}
  ]
end
```

余談ですが、**クラス名はアッパーキャメルケース**で記述しましょう。
エラー起こして、一回迷子になってしまいました...
🔰Automation Anywhere 逆引き辞典メモ

Automation Anywhereの逆引き辞典をまとめてみたよ👌😘✨

* [Automation Anywhere 用語集 - scrapbox.io](https://scrapbox.io/AutomationAnywhere/) ⇒ v11
* [オートメーション道場](https://autodojo.hatenablog.com/) ⇒ A2019
    * [Excelでよく使う操作一覧](https://autodojo.hatenablog.com/entry/reverse-lookup-excel)
    * [ファイル操作でよく使う方法一覧](https://autodojo.hatenablog.com/entry/reverse-lookup-file)
    * [よく使うループ処理、条件分岐](https://autodojo.hatenablog.com/entry/reverse-lookup-loop-if)
    * [画面操作でよく使う方法一覧](https://autodojo.hatenablog.com/entry/reverse-lookup-screen)
    * [文字列/日付/変数でよく使う方法一覧](https://autodojo.hatenablog.com/entry/reverse-lookup-string)
    * [他のボットの操作/スクリプトで使う方法](https://autodojo.hatenablog.com/entry/reverse-lookup-external-program)
    * [エラー処理](https://autodojo.hatenablog.com/entry/reverse-lookup-error)
* [Automation Anywhere A2019 開発辞典](https://www.softbank.aa-portal.jp/rpa_portal/development_dictionary/) ⇒ A2019 (ソフトバンク株式会社による有償サービス)
* [Bot 構築　目的別コマンドリファレンス v11.x / A2019版 (Excelファイル)](https://apeople.automationanywhere.com/s/question/0D52t00000DCocMCAT/bot-%E6%A7%8B%E7%AF%89%E7%9B%AE%E7%9A%84%E5%88%A5%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9-v11x-a2019%E7%89%88%E3%82%92%E5%85%B1%E6%9C%89%E3%81%97%E3%81%BE%E3%81%99?language=ja) ⇒ v11/A2019
* [キノコ納豆のIT徒然草 - AutomationAnywhere](https://itmagic.hatenablog.com/archive/category/AutomationAnywhere)
* [Trendlife Automation Anywhereブログ](https://trendlife.co.jp/category/rpa/rpa-a2019/)
Chrome拡張機能で参考文献を生成する

# 制作物
訪問したページのURLとtitleタグの記述を元にMarkdown,BibTexの参考文献を生成し、クリップボードにコピーする拡張機能を作った

[https://github.com/Omochice/reference-generator](https://github.com/Omochice/reference-generator)

最低限のものしか書いていないので環境ごとの問題とかが発生するかもしれない

## 躓いたところ

1. `document.getElementsByTagName`を使おうとして詰まった

`background.js`は仮想的に裏でページを作って動作しているのでdocumentはその仮想ページのものになる(よくわかってない)ので`document.getElementsByTagName`は`undefined`になる

-> `tag.title`で取得できた

jsを全く書かないで生きてきたのでどこのリファレンスを見たらいいかわからなかった(ググり力の不足)


## 使い方
右クリックメニューから取りたい形式を選べばクリップボードにコピーされる

拡張機能の追加方法等は賢人達が書いているのを参考にしたほうが早いと思う


### 参考

[pdfの選択部分を翻訳するChrome拡張を作った - Crieit](https://crieit.net/posts/pdf-Chrome)

[Chrome拡張機能でYoutube検索を楽にする - Qiita](https://qiita.com/engabesi/items/1e9698ea779f1bd660d1)

[ブラウザの拡張機能を自作してみた - Qiita](https://qiita.com/m_hama123/items/aa3101478a8458d61689)
ヘルパーメソッドform_withについて理解する

##form_withとは
form_withとはRailsのヘルパーメソッドの一つで、ビューファイルに記述します。
このメソッドによって、情報を送信することができます。

form_withを使うと、入力フォームに必要なHTMLを作成することができます。

調べてみると、送信した情報をデータベースに保存する時と保存しない時では若干記述が変わるみたいです。

**データベースに保存しない時**

```ruby:xxx.html.erb
<%= form_with url: "パス" do |f| %>
フォーム内容
<% end %>
```

**データベースに保存する時**

```ruby:xxx.html.erb
<%= form_with(model: モデル名, local: true) do |f| %>
フォーム内容
<% end %>
```

form_withでは、データを保存しない
input typeの要素も使えるし、
データを保存するヘルパーメソッド(f.text_fieldなど)も両方使えるようです。

最後に、実際に今日実装してた内容を抜粋して記録として残しておきます。

記述例として参考になれば幸いです。

```ruby:xxx.html.erb
<div class="items-sell-main">
    <h2 class="items-sell-title">商品の情報を入力</h2>
    <%= form_with(model: @product, local: true) do |f| %>
---省略---
    <%= f.text_area :name, class:"items-text", id:"item-name", placeholder:"商品名（必須 40文字まで)", maxlength:"40" %>
      <div class="items-explain">
        <div class="weight-bold-text">
          商品の説明
          <span class="indispensable">必須</span>
        </div>
---省略---
</div>
```
log4jのdiscordとgooglechatのappender作ったからみんな使ってみてよ

ちょっと前に作りました。どんどん使ってくださいませ

## log4j discord appender
https://github.com/modeverv/log4j-discord
## log4j google chat appender
https://github.com/modeverv/log4j-googlechat
openvpnでCA認証とOPENVPNサーバを別にする


## openvpnでCA認証とOPENVPNサーバを別にする
openvpnで証明書を作成する「CA局」と、サーバ・クライアントを別サーバにしようとしたときのメモ

### 役割
- CA局 : 証明書を認証（作成）するサーバ
- openvpn-server : CA局で認証された、CA認証(ca.crt), サーバ.crt(証明書), サーバ.key(秘密鍵) を配置する
- openvpn-client(証明書認証） : サーバと同一のCA認証(ca.crt)、クライアント.crt(証明書),クライアント.key(秘密鍵)を配置する。
- openvpn-client(tls暗号化） : サーバと同一のCA認証(ca.crt)、クライアント.crt(証明書),クライアント.key(秘密鍵)を配置する。
- xx証明書 ： 証明書内に、秘密鍵を含む。


| ＃ | ファイル | CA局 | openvpn-server | openvpn-client | 作成方法 |
| :-- | :--- | :--- | :--- | :--- | :-- |
| CA証明書 | ca.crt | 作成 | copy | copy | ./easyrsa build-ca | 
| DHパラメタ | dh.pem | -- | 作成,配置 | -- | ./easyrsa gen-dh | 
| TLS認証キー | ta.key | -- | 作成,配置 | copy | openvpn --genkey --secret /etc/openvpn/ta.key |
| サーバ証明書 | サーバ.crt | 作成 | copy | -- | ./easyrsa build-server-full サーバ nopass |
| サーバ秘密鍵 | サーバ.key | 作成 | copy | -- | 同上 |
| クライアント証明書 | クライアント.crt | 作成 | -- | copy | ./easyrsa build-client-full クライアント nopass |
| クライアント秘密鍵 | クライアント.key | 作成 | -- | copy | 同上 |
🔰UIPath 逆引き辞典メモ

UIPathの逆引き辞典をまとめてみたよ👌😘✨

* [できるネット - UIPath](https://dekiru.net/category/service-software/uipath/)
* [RPA Navi](http://rpa-navi.com/)
    * [作業一覧](http://rpa-navi.com/uipath2)
    * [機能一覧](http://rpa-navi.com/uipath)
    * [機能詳細一覧](http://rpa-navi.com/uipath3)
* [逆引き UiPath](https://www.alloneslife-0to1work.jp/uipath_reverse_lookup)
    * [アクティビティ一覧](https://www.alloneslife-0to1work.jp/uipath-activities-all)
    * [アクティビティ一覧(CORE)](https://www.alloneslife-0to1work.jp/uipath/activities_core)
    * [アクティビティ一覧(EXCEL)](https://www.alloneslife-0to1work.jp/uipath/activities_excel)
    * [アクティビティ一覧(MAIL)](https://www.alloneslife-0to1work.jp/uipath/activities_mail)
    * [アクティビティ一覧(WEB)](https://www.alloneslife-0to1work.jp/uipath/activeties_web)
    * [アクティビティ一覧(WORD)](https://www.alloneslife-0to1work.jp/uipath/activities_word)
    * [アクティビティ一覧(PDF)](https://www.alloneslife-0to1work.jp/uipath/activities_pdf)
    * [アクティビティ一覧(DATABASE)](https://www.alloneslife-0to1work.jp/uipath/activities_database)
    * [アクティビティ一覧(FTP)](https://www.alloneslife-0to1work.jp/uipath/activities_ftp)
    * [アクティビティ一覧(WORKFLOW FOUNDATION)](https://www.alloneslife-0to1work.jp/uipath/workflow-foundation_activities/2)
* [逆引きUiPath ~ Automation Discovery ~](https://automation-discovery.work/)
    * [ファイル・フォルダ操作](https://automation-discovery.work/files/)
    * [文字列操作](https://automation-discovery.work/strings/)
* [UiPathでやりたことから探す逆引き辞典](https://koawaka.com/rpa-school/uipath-reverse-dictionary/)
* [Economics of TEC](https://it-rpa.hatenablog.com/archive/category/UiPath)
BFE.dev解答記録 #1 implement curry()

> https://bfe.dev/ja はFrontEnd版のLeetCode、GAFAの面接を受けるなら練習した方がいいかなと。
> 以下は自分の練習記録です。

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3gzqlf50feqgud2tuj3y.png)

第一問 [#1 implement curry()](https://bigfrontend.dev/ja/problem/implement-curry)

まずサンプルコードからinput/outputを決めていきます

```js
const  join = (a, b, c) => {
   return `${a}_${b}_${c}`
}

const curriedJoin = curry(join)

curriedJoin(1, 2, 3) // '1_2_3'

curriedJoin(1)(2, 3) // '1_2_3'

curriedJoin(1, 2)(3) // '1_2_3'
```

上から見ると`curry()`はfunctionを返し、しかも任意の数の引数を渡せることができ、function自体は以下のことをする:
1. 引数足りるなら、joinを実行して結果を返す
2. 引数足りなければ、新しいfunctionを返て、1を繰り返す

２番はすごく大事です。`curriedJoin(1,2)` で返すfunctionは、渡される引数の前に1と2をinsertする必要。これは``Function.prototype.bind()`を使えば行ける。

```js
function curry(func) {
  return function curried(...args) {
    // 1. if enough args, call func 
    // 2. if not enough, bind the args and wait for new one
    if (args.length >= func.length) {
      return func.apply(this, args)
    } else {
      return curried.bind(this, ...args)
    }
  }
}
```

よし, 通った!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/twtc3jrdf0nlu3gi17il.jpg)

第一問は簡単で、興味あれば一緒に https://bfe.dev で練習していきましょう。
浮動小数点数コードの verification をする Gappa をビルドするメモ

## 背景

数値計算コードの formal verification とか誤差がどれくらいでるかとか調べたい

gappa を使います.

http://gappa.gforge.inria.fr/

apt とかでパッケージ無いっぽいので, ソースからビルドします.

## ビルド

Ubuntu 18.04 を想定します.

mfpr, gmp(多倍長演算とかするライブラリ)と, boost が必要です.
Boost はヘッダだけあればよいです.
Coq はビルドには不要っぽいです.

mfpr, gmp は gcc に必要なので, gcc が apt で入っていればインストールされているかもです.

必要に応じて libmfpr-dev, libgmp-dev, libboost-dev あたりを apt で入れておきます.

あとは

```
$ ./configure
$ ./remake
```

するだけです! (remake がちょっと不明ですが)

## sollya

似たようなのに Sollya があります.

http://sollya.gforge.inria.fr/

こちらは Gappa よりより上位な感じでしょうか. 関数フィッティングなどもあります.
Sollya は apt で入ります!
sklearn.metrics.ConfusionMatrixDisplayを使った混合行列の可視化

scikit-learnを利用すると簡単に混合行列の可視化は可能だが、[sklearn.metrics.plot_confusion_matrix](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.plot_confusion_matrix.html) は**estimatorが引数に必要になる**。可視化するだけなのでestimatorが不要な方法はないかと調べていたら [sklearn.metrics.ConfusionMatrixDisplay](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.ConfusionMatrixDisplay.html) が見つかったので簡単にコードを書いてみた。

```python

import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
clf = SVC(random_state=0)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
cm = confusion_matrix(y_pred=y_pred, y_true=y_test)
cmp = ConfusionMatrixDisplay(cm, display_labels=data.target_names)

cmp.plot(cmap=plt.cm.Blues)

```

結果はこんな感じ。
![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40310/d565406a-b5f0-c0d0-db0a-a842be60abd3.png)



# 参考

- [sklearn.metrics.plot_confusion_matrix](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.plot_confusion_matrix.html)
- [sklearn.metrics.ConfusionMatrixDisplay](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.ConfusionMatrixDisplay.html)
- [plot_confusion_matrix sample_code](https://scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html)
外部から自宅サーバにアクセスするためMyDNS（DDNS）をためす


## 経緯
諸々の事情があってこれまでLAN内で運用していた自宅サーバに外部からアクセスできるようにしたい。
ネットワークの構成とか、セキュリティを考えるとWebサーバーや仮想化をしたほうがいいのかとか、まだまだ計画途中ではあるが、とりあえずまずは自宅のipを外部から特定するようにする必要がある。

固定IPの契約は追加費用がかかるので避けたい、ということででドメインに紐づけるIPを動的に通知、更新するDDNS（Dynamix Domain Name System)を設定する方向で行きたい。

ざっと調べたところMyDNSが定番らしいのでそれを使ってみる。

## 手順
基本的には[公式のHowTo](https://www.mydns.jp/?MENU=030)に従って設定。

会員登録とレジストラのネームサーバーの設定は省略。

## NSレコードの設定

LightsailのDNSも似たような感じだったので特に困らなかった


![ns-records.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400984/375b7df7-9113-4700-77fc-746305759a67.png)


モザイクばかりでこれだと何が何だかわからないので、まず隠しているところについて補足する。DomainとMXの入力欄には割り当てたいドメインを設定する（MXは空欄の場合自動でDomainが転記される)。
とんで後半、表。まず一番右の列にはmydnsのユーザーIDが選択できる。このIDに紐づいたIPデフォルトの値として割り振られることになる。その左のテキストボックスに入力がある場合はそちらが優先される。

具体的に設定した内容としては、今回はやり方の確認であり本番運用はまだ先ということもあって、ドメインには過去にブログで使っていたものを流用、自宅用に設定した特定のサブドメインの場合はアカウントに紐づけたipへ、それ以外のサブドメインであれば現ブログのドメインに転送という設定にした。

（実際には自宅用のサブドメインのほかに、サブドメインではないそのままのドメインへのアクセスも自宅につながっていた。ワイルドカードは＠は含まないらしい。）

## ipアドレスの通知
本題のipアドレスの更新について

最初はIPは0.0.0.0に設定されている
![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400984/3274f70c-6c22-d964-5ce7-6a5cb906805f.png)


以下を参考に自動更新用のbashを作成。
[自宅サーバーやVPSに使える無料のダイナミックDNS (Dynamic DNS) | MyDNS.JP](https://www.mydns.jp/?MENU=040)
[MyDNS(無料)のDDNS更新方法 – nosense](http://www.nosense.jp/ddns-mydns/)

公式の例では5分ずつcronをしているようだが、今回はすぐに運用するわけではなくお試しなのでデフォルトで存在している1時間ごとのcronに追加する

```bash:/etc/cron.hourly/mydns
#!/bin/bash

# 公式の例。このアドレスだと自分の環境ではIPv6のアドレスのみ設定され、IPv4のアドレスは変わらなかった
#/usr/bin/wget -O - 'https://userid:password4@www.mydns.jp/login.html'

# IPv4とIPv6を両方設定するにはそれぞれのサブドメインに個別に通知する必要があるらしい。
# IPv4
/usr/bin/wget -O - 'https://userid:password@ipv4.mydns.jp/login.html'
# IPv6
/usr/bin/wget -O - 'https://userid:password@ipv6.mydns.jp/login.html'
```

ドメインの前の`userid:password@`はBasic認証のための情報らしい。今回調べて知った。

このファイルに実行属性を付与

```
$ sudo chmod +x /etc/cron.hourly/mydns
```

試しに実行してみる

```
$ sudo /etc/cron.hourly/mydns
```

更新された模様。
![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400984/22ccb489-688e-0b72-e001-e625f1dfe8b2.png)

実際にサーバーと同じLANから別のPCでIPを確認したところ、IPV4については登録されたものと一致した。IPV6については後述
Pingも帰ってくるのでDNSはちゃんと動いている模様

## 結果
ログに表示されるのは変更時か、変わらない場合は24時間ごとなので、一日待つ

そして翌日
![log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400984/97252432-ab6f-7079-9e24-24a668338c0e.png)


自動での通知もちゃんと動作していることを確認

※IPv6とIPv4で時間が開いているのは最初に`www.mydns.jp/login.html`で通知した際にIPv6だけ通知され、その後IPv4も通知できるよう上記のように修正したため。

## 余談・気になったこと

### ルータがPingを返す
てっきりルータはデフォルトでは外部からの通信は全部シャットダウンしてると思ってたので、PINGが帰ってきたのは地味に驚いた。※ちなみにAWS Lightsailは返してくれなかった。

よく知らずに使ってるけど、一般家庭ではルータがデフォルトでファイアーウォールとして仕事しているから安全なわけで、それをこのレベルの知識でルータ様のセキュリティにあえて穴をあけようというのは改めて考えて無謀な話だとおもう。万全を期すためにはもっとネットワークの勉強しないと…サーバ公開までの道のりはまだまだ長そう。
~~やり遂げる前に飽きそうな気がしてならない。~~


## IPv6アドレスが同一LAN内でも端末によって違う

以下のサイトで確認したところ、家でPCからアクセスしたIPV６アドレスとサーバーのIPV6アドレスが前半は同じだが後半が全く違う

[あなたの IPv6 をテストしましょう。](https://test-ipv6.com/index.html.ja_JP)

ノートPCとデスクトップPCでもちがう。

IPアドレスって端末までは特定できないものだと思っていたのだが、[IPv6 - Wikipedia](https://ja.wikipedia.org/wiki/IPv6)によるとやはりIPV6だと達末ごとに割り振られるらしい。
そうなるとルーターの役割も違ってくるし、設定ややこしそう…。
VGO (UniVGO) の解説

 VGO の version 2.0 について、VGOの設計・開発者が仕様等の解説を行います。

## この記事の対象読者

- VGOの仕様を詳しく知りたい方
- UniVGOのプログラムを詳しく知りたい方


## VGO 2.0 とは

2020年8月に発表した、新しい形式の3Dデータフォーマットです。

## 特徴

- VGO 1.0 から発展させた独自のフォーマットです。
- `Node`, `Transform`, `Rigidbody`, `Collider`, `Mesh`, `BlendShape`, `Material`, `Texture` といった3Dモデルの基本情報を保存することができます。
- Unityでの使用を意識して `Human Avatar`, `Light`, `ParticleSystem`, `Skybox` の情報も保存することができます。
- アプリケーションでの使用を意識して `BlendShapePreset` の情報も保存することができます。
- ファイルフォーマットはベースにIFFチャンクを採用しています。
- 内部データとして `JSON`, `BSON`, `Binary` を使用します。
- 座標系は`右手系`、 `左手系`のどちらのデータも持つことができます。
- 独自の定義拡張（チャンク、スキーマ）が可能な仕様となっています。
- 部分暗号化に対応した仕様となっています。
- リソースはファイル内に含めることを基本としていますが、別ファイルに切り出すこともできます。
- リソースは`sparse`でデータを圧縮することができ、またより強力な`sparse`を独自に定義しています。


## 設計の経緯

VGOがバージョン2.0になる前の VGO 1.0 は、glTF 2.0 の拡張フォーマットでした。

しかし、機能を拡張し使用していくにつれ、また今後の使い方を考えた時に、glTFの仕様自体が抱える問題が私の中で顕著になってきてしまいました。

そこで、不満となっている問題点を解決した独自の3Dフォーマットを設計・実装することにしました。


## 仕様とプログラムについて

仕様とプログラムはすべてGitHubにて公開しています。

[GitHub (VGO2)](https://github.com/izayoijiichan/VGO2)

GitHubの説明は主に英語であるため、Qiitaにて日本語の説明を行います。


## 目次

解説は数回に分けて行っていく予定です。

- 1. [ファイル仕様] (https://qiita.com/izayoijiichan/items/f25acc93cf3a7c46c4bb)
  - 1-1. ファイル拡張子
  - 1-2. チャンク
     - ヘッダー チャンク
     - インデックス チャンク
     - コンポーザー チャンク
     - アセット情報 チャンク
     - レイアウト チャンク
     - リソース アクセサー チャンク
     - リソース チャンク
  - 1-3. データ フォーマット
- 2. [データ スキーマ仕様] (https://qiita.com/izayoijiichan/items/0eb1c5c2bd2b4a063fa2)
  - 2-1. アセット情報
  - 2-2. レイアウト
     - ノード
     - アニメーター
     - アバター
     - 剛体
     - コライダー
     - メッシュ
     - マテリアル
     - テクスチャー
     - ブレンドシェイプ
     - 光源
     - スカイボックス
     - パーティクル
  - 2-3. リソース
     - リソース
     - リソース アクセサー
- 3. 暗号化
   - 部分暗号化の仕組み
   - 暗号アルゴリズム
- 4. UniVGO（ツール）
   - Uni VGO の特徴
   - プログラムの解説
   - 高速化の仕組み
- 5. VGO Hub
   - VGO Hub の特徴

## 外部リンク

- [Uni VGO] (https://github.com/izayoijiichan/VGO2)
- [VGO Hub] (https://vgohub.azurewebsites.net/)
apache+Cakephpで環境変数を利用する


## やりたいこと
- DB接続先などの変数をapp.phpに直接書かずに、環境変数から読み込みたい

## 環境
- Amazon linux2
- Apache 2.4
- PHP 7.2

## Apache側

### systemdユニットファイルで環境変数リストファイルを読み込めるように設定
- `Environment`に直接定義してもいい
- `EnvironmentFile`には、環境変数リストファイルをセット

```/etc/systemd/system/multi-user.target.wants/httpd.service
[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
```

### 環境変数リストファイルをセット

- `/etc/sysconfig/httpd`に書くのが公式のよう
- たぶんどのファイルでもいい
- exportはいらない

```/etc/sysconfig/httpd
DB_HOST=testdb
```

### httpd.confへ設定

- `PassEnv` はApacheを起動するユーザーの環境変数を引き継ぐことができる
- `SetEnv` はhttpd.confに直接値を書く必要があるので、起動のたびに変わる変数などの利用には向いていない

```/etc/httpd/conf/httpd.conf
</VirtualHost>
PassEnv DB_HOST
</VirtualHost>
```


## php側

```config/app.php
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'host' => $_SERVER["DB_HOST"],
```
【each, map...】今夜、配列処理をくらべてみました【ruby】

rubyの配列の要素を操作するメソッドってたくさんありますよね。
結局、どれがどんな挙動で、どれが一番効率よくかけるかどうか比較するためにまとめてみましたよ。

#配列の操作早見表

|メソッド|戻り値|挙動
|:--|:--|:--
|each|レシーバの配列|ブロック内で配列の要素をひとつずつ、繰り返し処理
|each_slice|nil|ブロック内で配列の要素を複数単位で、繰り返し処理
|each_with_index|レシーバの配列|ブロック内で配列の要素とインデックス番号をそれぞれ繰り返し処理
|map|変化した配列|配列のまま、要素を変化させる
|select|変化した配列|配列のまま、条件式がtrueになる要素に絞る
|reject|変化した配列|配列のまま、条件式がfalseになる要素に絞る
|find|要素|要素の中で、最初にヒットした要素を取り出す

#繰り返し処理

###each
要素を単数でループさせる

```ruby

array = ["リンゴ", "ミカン", "スイカ", "メロン"]

array.each do |obj|
  p obj
end
```

```irb:返り値
リンゴ
ミカン
スイカ
メロン
=> ["リンゴ", "ミカン", "スイカ", "メロン"]
```

###each_slice
要素を複数で、ループさせる

```rb

array.each_slice(2) do |a|
  p a
  p a
end
```

```irb:返り値
["リンゴ", "ミカン"]
["スイカ", "メロン"]
=> nil
```

###each_with_index
要素と一緒にインデックスも取り出せる

```rb

array.each_with_index do |obj,index|
  p obj
  p index
end
```

```irb:返り値
"リンゴ"
0
"ミカン"
1
"スイカ"
2
"メロン"
3
=> ["リンゴ", "ミカン", "スイカ", "メロン"]
```

#要素の中身を直接、変化させる

###map
要素をいじって、配列で返す

```rb

array = [1, 2, 3, 4, 5]

array_new = array.map{ |i| i*2 }
```

```irb:返り値
[2, 4, 6, 8, 10]
```

###select
条件式の戻り値がtrueの要素を、配列で返す

```rb

array = [1, 2, 3, 4, 5]

array_new = array.select{ |i| i > 3 }
```

```irb:返り値
[4, 5]
```


###reject
条件式の戻り値がfalseの要素を、配列で返す

```rb

array = [1, 2, 3, 4, 5]

array_new = array.reject{ |i| i % 2 == 0 }
```

```irb:返り値
[1, 3, 5]
```


###find
条件式にtrueになった最初の要素を検索して取り出す

```rb

array = [1, 2, 3, 4, 5]

array_new = array.find{ |i| i > 3 }
```

```irb:返り値
4
```
TypeScriptでBranded Typeを利用してひらがな型を扱う

TypeScriptは[基本的には構造的型(Structural type system)](https://typescript-jp.gitbook.io/deep-dive/main-1/nominaltyping)なので

```typescript
type Human = {
 name: string
 age: number
}
```

と

```typescript
type Dog = {
 name: string
 age: number
}
```

を型として区別する事が[少なくとも現時点では](https://github.com/Microsoft/TypeScript/issues/202)できません。構造が同じであれば同じ型と見做すからです。なので例えばstring型と同じ構造をもった別の型としてHiragana型を扱おうとして

```typescript
type Hiragana = string

const charIsHiragana = (char: string) char is Hiragana => {
  if (char.length > 1) {
    return false
  }
  const codePoint = char.codePointAt(0)
  return (codePoint >= 12353 && codePoint <= 12435)
}

const hiraganaToKatakana = (hiragana: Hiragana): Katakana => {/* 中略 */}

const charToHiragana = (char: string) => {
  if (charIsHiragana(char)) {
    // charがHiragana型の時は許される
    return hiraganaToKatakana(char)
  } else {
    // charがHiragana型じゃない時は型検査で許されないで欲しいが、string型であれば構造が一致するので許されてしまう
    return hiraganaToKatakana(char)
  }
}
```

みたいに書いてもそもそもType AliasにしかならずHiraganaとstringを区別できないため型検査上意図した動きにはなりません。こういう時の解決方法の一つは型上の構造(実体ではなく)を変え、アサーション(やTypeGuard)を利用する事で別々の型として扱ってもらうという方法です。

```typescript
// 他に存在しないプロパティ名を型上に生やす
type Hiragana = string & { _hiraganaBrand: never }
type Katakana = string & { _katakanaBrand: never }

// 共通した名前のプロパティを持っていてもそのプロパティの型が異なれば別々の型として扱われる
// 文字列リテラル型でもok
type Hiragana2 = string & { _brand: 'Hiragana' }
type Katakana2 = string & { _brand: 'Katakana' }
```

運用でカバー的な泥臭さは感じますがこの[branding](https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698)を利用すれば、本来の意図通りひらがな(charIsHiraganaを通過したstring)以外が誤ってhiraganaToKatakanaへ渡される事を防げます。また広く使うなら

```typescript
type Brand<K, T> = K & { __brand: T }
type Hiragana = Brand<string, 'Hiragana'>
```

みたいな[Brand型](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)を用意してしまうのも良いんじゃないでしょうか。

# see also

- [How do I prevent two types from being structurally compatible?](https://github.com/microsoft/TypeScript/wiki/FAQ#how-do-i-prevent-two-types-from-being-structurally-compatible)
- [TypeScriptのStructural Subtypingの話２](https://wp.jmuk.org/2016/10/02/typescript%E3%81%AEstructural-subtyping%E3%81%AE%E8%A9%B1%EF%BC%92/)
- [TypeScript における型レベルバリデーション](https://speakerdeck.com/ytaka23/we-are-javascripters-36th)
- [How to make a marker interface, or something similar
](https://stackoverflow.com/questions/53696873/how-to-make-a-marker-interface-or-something-similar)
- [Notes on TypeScript: Phantom Types](https://dev.to/busypeoples/notes-on-typescript-phantom-types-kg9)
- [How to define an opaque type in TypeScript?
](https://stackoverflow.com/questions/56737033/how-to-define-an-opaque-type-in-typescript)
AWS Lambda を CloudWatch Events ごと一括で消す方法

# Lambedaを消すのが面倒。
最近弊社では今まで使っていた Lambda をすべて Serverless Framework を使ってデプロイできるように置き換えました。
旧 Lambda のイベントやらなんやらを serverless.yml に移行していくのも面倒だったんですが、何より面倒なのは古い方の Lambda を消すこと。

## Lambda 関数を消しただけだと関連する Event は消えない
そうです。残ってしまうんです。
コンソール上で Lambda 関数を削除しただけだと、Lambda をトリガーしていた CloudWatch Events はまるまる残ってしまいます。

数十の Lambda関数 に数個から十数個の Event が紐付いているので、これを一つずつ手で消していくとなると **相当な手間** ですし、消し忘れが発生して現実的ではありません。

## Rule を消さないと Event を消せない
CloudWatch Event は イベントが発生したタイミングで何をトリガーするかという情報、Rule をもっています。まずは Rule を消さないと CloudWatch Event は消せません。

Lambda を消したいのに、CloudWatch Events も消さなくてはならない。CloudWatch Events を消したいのに Rule を消さなくてはならない。うんざりしますね。


# AWS CLI で削除する順番。
CLI から消すには以下の手順を踏む必要があります。

1. 対象の Lambda関数 の ARN を取得
2. ARN を使って 対象の関数の Events を取得
3. Event Name から対象の Event rule 一覧を取得
4. 取得した rule をすべて削除
5. Event を削除
6. 3〜5を繰り返して Event をすべて削除
7. Lambda関数 を削除

# AWS CLI で CloudWatch Events ごと Lambda を削除するシェル
上記の手順を shell で実行できるようにしました。
**※ 対象は CloudWatch Events のみです。**

```delete_lambda.sh
#!/bin/sh

# profileを指定します。
# 特に指定なければ以下のスクリプトから 「--profile $PROFILE」をすべて削除してください。
PROFILE=sample

# 削除したいLambda関数名の配列
LAMBDA_FUNCTIONS=("SampleHandler" "SQSHandler" "S3Handler")

for function in ${LAMBDA_FUNCTIONS[@]}
do
    echo "====== START FUNCTION $function ======"
    # 対象のfunctionのarnを取得
    arn=`aws lambda get-function \
            --function-name $function \
            --query 'Configuration.FunctionArn' \
            --profile $PROFILE`
    
    # 文字列からダブルクォートを削除
    arn=`echo $arn | sed 's/"//g'`

    echo "arn = $arn"

    # functionのevent triggerを取得する
    rules=`aws events list-rule-names-by-target \
            --target-arn $arn \
            --query 'RuleNames[]' \
            --profile $PROFILE`

    rules=$(echo $rules | jq -r .[])

    for eventName in ${rules[@]}
    do
        echo "  === START $eventName ==="

        # ruleを取得する
        res=`aws events list-targets-by-rule \
            --rule $eventName \
            --query 'Targets[].Id'\
            --profile $PROFILE`
    
        echo $res

        targets=$(echo $res | jq -r .[])

        for target in ${targets[@]}
        do
            # ruleを削除する
            aws events remove-targets --rule $eventName --ids $target --profile $PROFILE
            # echo "delete target : $target" # test
        done

        # CloudWatch eventを削除する
        aws events delete-rule --name $eventName --profile $PROFILE
        echo "  ==== END $eventName ===="
    done

    # lambda functionを削除する
    aws lambda delete-function --function-name $function --profile $PROFILE
    echo "====== END FUNCTION $function ======"
done

echo "DONE"
```


# S3 Event など他の Event を消すにはこれでは足りない。
必要あればほかの Event も消せるようにシェルを改造しようと思います。
弊社では一旦 CloudWatch Events で十分だったためこれにて終了です。
Next.jsでSSGしたサイトをAmplifyでホスティングする

# 概要

Amplifyってフレームワークとかもあるんですが、ホスティングサービスとしてのみ使うこともできます。
今回はNext.jsでSSGしたサイトをAmplifyでお手軽にホスティングする方法の紹介。

# 比較対象

比較対象としては、NetlifyとかVercelと同じ感じのサービスです。
基本的にリポジトリを連携しておけば、pushごとに自動でデプロイしてくれます。
もちろん、HTTPS + 独自ドメインも（Route53も使って）ぽちぽちするだけで完了します。

差分で言うと、

- NetlifyやVercel
    - 無料枠がある。
    - 一人で作ってサクッと公開するサイトとかであれば完全無料で完結することも。
- Vercel
    - 無料枠でも、[Fair Use Policy \- Vercel Documentation](https://vercel.com/docs/platform/fair-use-policy)の範疇であればデータトランスファーが無制限。
- Amplify
    - 無料枠がない（初期の1年の無料枠はあるけど）、おなじみの従量課金。
    - 課金はデプロイ時間とデータトランスファーに対して。
        - [AWS Amplify の料金 \- アマゾン ウェブ サービス](https://aws.amazon.com/jp/amplify/pricing/)
    - 開発するだけならほとんど無料に近い金額（数十円とか）
    - 他と違ってチームメンバーに制限が無かったり、Basic認証無料でかけられたりする。

# 手順

基本的なデプロイは

1. GitHubなどリポジトリホスティングサービスとの連携
2. ビルド設定の登録

だけ。

これは本当に数分で終わります。

## 1. リポジトリ連携

1. Amplify開き、右上の「アプリを作成」押す。
2. From your existing code の中から自分の選ぶ。
3. OAuth認証する。
4. デプロイしたいリポジトリ選ぶ。
5. ブランチ選ぶ。とりあえずmasterで良い。

## 2. ビルド設定

ビルド設定というのは、コードがpushされたときに自動で走るビルドコマンドの指定です。
ありがたいことにNextを見つけるとNext用のymlをデフォルトで用意してくれるのですが、`next export`の場合はちょっと修正が必要です。


```amplify.yml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - npm ci --production=false
    build:
      commands:
        - npm run export
  artifacts:
    baseDirectory: out
    files:
      - "**/*"
  cache:
    paths:
      - node_modules/**/*
```

- `npm ci --production=false`
    - 基本的にビルドに必要なものは devDpendenciesに入れてると思うんですが、デフォルトでビルド環境のNODE_ENVがproductionなので、`--production=false`で必ず入るようにしてます。
    - ビルド時の環境変数が設定できるので、NODE_ENV=developmentも設定できますが、コード内でNODE_ENVで出しわけとかしてる場合はdevelopmentしか使えなくなって不便なので、ここは何が来てもビルドが通るように。
- `npm run export`
    - ここは各自いい感じに。
- `out`
    - これも各自の環境に合わせて。

ちなみに、これWebコンソール上で設定できますが、リポジトリルートに `amplify.yml`が置いてある場合はそっちが優先で読まれます。
Git管理したいですよね。

ちなみに、Webコンソール上のamplify.ymlにコメント（言語かかわらず）を書くとWebコンソールそのもののjsがエラーで止まって閲覧/操作できなくなるので気をつけてくださいw
近いうち直るかなー

## 完了
あとは「保存してデプロイ」押せば完了です。
これ以降、接続してあるブランチにはpushするたびに自動でデプロイが走ります。

# ブランチの追加

最初にmasterを選択しましたが、別のブランチもデプロイしたい場合は、いつでも追加できます。
アプリのトップページで「ブランチの接続」を押してブランチを選ぶだけです。

「1ブランチ = 1デプロイ」なので、一つのブランチから複数のデプロイを作ることは出来ません。

# ブランチの接続解除

「アプリの設定 > 全般」の一番下にブランチというゾーンがあるので、そこで消したいブランチを選択して「アクション > ブランチの接続解除」を押すだけ。
masterも解除できる。

解除するとデプロイが消えます。

# ロールバック

ブランチの詳細ページに飛んで、「このバージョンを再デプロイ」ボタンを押せば、いつでも好きなバージョンに戻すことができます。

# HTTPS + 独自ドメイン

独自ドメインを当てると、自動でHTTPSで繋がる用意をしてくれます。楽〜
「アプリの設定 > ドメイン管理」の右上の「カスタムドメインの追加」を押すと、Route53で管理しているドメインが出てくるので、選んで保存するだけ。
自動で証明書の設定などが進み、しばらくすると開通してます。楽〜

# 環境変数

任意の環境変数を、ブランチごとに設定できます。
「アプリの設定 > 環境変数」から。

# BASIC認証

無料でかけられるし、コードの修正も不要。
「アプリの設定 > アクセスコントロール」から。

# おしまい

これだけ！
数年前Netlifyを最初に使った時はお手軽さに感動しましたね。
それと同じ感じでホスティング出来るAmplifyでした。
【小ネタ】特定階層以下をunitypackageにまとめるためのエディタ拡張　俺々ライブラリ出力用

皆大好き俺々ライブラリ。作ってますか？
unity1weekみたいな短期間開発イベントや、ちょっとしたプロトタイピングで役立つ俺々ライブラリは皆さん作っているかと思いますが、使い回す時に俺々ライブラリだけを抽出して持っていきたい時がありますよね.
そんな時のために、特定階層以下を簡単にunitypackageにまとめるスクリプトを貼っておきます。

```cs:UnityPackageExporter.cs
using UnityEditor;
using UnityEngine;

namespace ScreenPocket
{
    public static class UnityPackageExporter
    {
        private const string titleText = "unitypackageを出力します";
        private const string defaultExtension = "unitypackage";
        private const string defaultFileName = "ScreenPocket" + "." + defaultExtension;
        private const string exportTargetPath = "Assets/ScreenPocket";
        
        [MenuItem("Tools/ScreenPocket/Export UnityPackage")]
        public static void ExportUnityPackage()
        {
            var path = EditorUtility.SaveFilePanel(titleText, 
                Application.dataPath,
                defaultFileName,
                defaultExtension);
            if (string.IsNullOrEmpty(path))
            {
                return;
            }

            AssetDatabase.ExportPackage(exportTargetPath,
                path,
                ExportPackageOptions.Recurse);
        }
    }
}
```
簡単なプログラムだけれども、ふとした時にググるのが面倒なのでメモ書き。
利用の際はScreenPocket部分を、自由に「ご自身の俺々ライブラリ識別子」に書き換えてご利用ください。

ついでに
・俺々ライブラリは1フォルダにまとめてAssets以下に置いておこう（分散させない）
・メニュー項目はTools以下に入れておいてメニューバーを散らかさないようにしよう
という主張もしておきます。
未経験からテックキャンプを利用してエンジニアに転職した話②

# はじめに
この記事は、前回の記事の続きです。
https://qiita.com/kuroron/items/ec05a02657ea00c2be86

テックキャンプについては、前回の記事に記載しているので、転職活動について感じたことを中心に書いています。

# 目次
- 未経験での転職活動の大前提
- 転職を有利に進める方法
- 面接でよく聞かれた内容
- さいごに

# 未経験での転職活動の大前提

**スクールに通ったからといって、簡単には就職できない！**
基本的に、企業は即戦力になる実務経験者を求めています。
未経験というだけで、応募できる求人は圧倒的に少なくなります。

実務経験＜＜＜＜＜＜＜＜スクール学習者・独学学習者＜全くの未経験　
体感的には、こんな感じです。

書類選考で落ちるのは当たり前。
通ったとしても、SESを中心とした「未経験歓迎」の求人ばかりです。

半年間の転職活動で、未経験での転職がどれだけ大変か思い知りました・・・


# 転職を有利に進める方法
未経験で自社開発などの優良企業に入りたい場合、他の未経験者と圧倒的な差別化を図る必要があります。

- **オリジナルアプリを２つ以上作成する**

ここでのポイントは、オリジナル性が高いものを作成することです。
スクールで学んだことの応用程度だと、評価されない可能性が高いです。

- **ポートフォリオサイトは、テンプレートを使用せずに作成する**

フロントエンドを志望する場合、テンプレートを使用していると、HTMLやCSSの技術があまりアピールできません。

- **アウトプット学習を継続する**

学習意欲・継続力がアピールできます。

- **スクール以外の言語を習得する**

Rubyの求人はあまり多くありません。
PHPやJavaど他の言語を習得し、アプリを作成することで、応募できる求人が増えると思います。

個人的には、ここまでやらないと難しいのではないかと思っています。


# 面接でよく聞かれた内容

- **前職で大変だったことや困難を乗り越えた経験**

未経験で転職する場合、技術的な質問はあまりありません。
そのため、前職での経験でどれだけアピールできるかが重要になります。

私の場合は、仕事をする上で大切にしている考えや、実際に行動した成果を中心にアピールしていました。

- **チーム開発での苦労や困難を乗り越えた経験**

スクール関係で聞かれる内容は、チーム開発のことがほとんどでした。
チーム内で自分がどれだけ活躍できたかをアピールできるようしておくと良いです。

- **コミュニケーション能力はあるか？**

エンジニアはチームで仕事をする、また顧客の意見を汲み取れるヒアリング力が必要なので、コミュニケーション能力は必須です。
コミュニケーション能力をアピールできるエピソードを用意しておくと、安心です。

- **個人アプリについて**

カリキュラムでの作成物は、ほとんど聞かれません。
聞かれるとしたら、個人アプリがほとんどです。
その為、なるべくスキルやアイデアをアピールできる個人アプリを作れると良いです。

- **スキル確認**

どの程度のスキルがあるか聞かれます。
一人でできることが多ければ多いほど、選考に有利になります。

# 最後に
未経験からエンジニアへの転職活動している方へ一言

- **あきらめずに転職活動を続けてください**

私は、スクール卒業後、約２ヶ月ほど、プログラミングも就職活動も離れていました。
そんなスキルのない私でも、希望していた地元でエンジニアに転職することができました。
諦めずに、活動を続けていれば、必要としてくださる企業は必ずあります！

もし、この記事が誰かの参考になれば幸いです。
最後までお読みいただき、ありがとうございました。
RとPythonのDocker作業環境の構築

# 前提
* WSL2でdocker環境を構築していること
（構築方法は以下で紹介）
* コマンドプロンプトを管理者権限で開いて、WSL上で操作していること

[WSLでdockerの環境構築](https://qiita.com/SeijMura/items/a61f3d8d693d7c9f4efb)

# Dockerfile

以下のコードを、ローカルの作業ディレクトリ内に"Dockerfile"という名前で配置してください。

~~~docker
FROM ubuntu:18.04

# set timezone
RUN apt-get update \
    && apt-get install tzdata \
    && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
RUN date

# install packages
RUN ["/bin/bash", "-c", "\
    apt-get update \
    && apt-get install -y \
    vim \
    build-essential \
    git curl llvm sqlite3 libssl-dev libbz2-dev \
    libreadline-dev libsqlite3-dev libncurses5-dev \
    libncursesw5-dev python-tk python3-tk tk-dev aria2 \
    lsb-release \
    python3.8 python3-pip \
    "]

# install pip package
RUN pip3 install pip --upgrade
RUN pip3 install pipenv
RUN python3 --version

RUN ["/bin/bash", "-c", "apt-get install -y software-properties-common"]
RUN apt-add-repository ppa:ansible/ansible -y
# install r
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
#RUN add-apt-repository 'deb https://cran.rstudio.com/bin/linux/ubuntu $(lsb_release -cs)-cran35/'
RUN add-apt-repository 'deb https://cran.rstudio.com/bin/linux/ubuntu bionic-cran35/'
RUN ["/bin/bash", "-c", "\
    apt-get update \
    && apt-get install -y r-base \
    "]
RUN Rscript --version
CMD ["/bin/bash", "-c"]
~~~

# ビルドと実行

Dockerfileがあるディレクトリ内に、以下のシェルスクリプトを配置します。

~~~shell
#!/bin/bash

step1="" # build
step2=do # run

CONTAINER=好きなコンテナ名
TAG=好きなタグ名
NAME=${CONTAINER}:${TAG}

if [ $step1 ]; then
    sudo docker build -t $NAME .
fi

if [ $step2 ] ; then
    sudo docker run -itv `pwd`:/mnt/work $NAME /bin/bash
fi
~~~

step1=doとするとビルドを実行し、
step2=doとするとコンテナを起動します。

CONTAINERに好きなコンテナ名を、TAGに好きなタグ名を指定してください。

以下のコマンドでシェルを実行できます。（シェルスクリプトはhoge.shとします）

~~~shell
chmod u+x hoge.sh
./hoge.sh
~~~