OmniAICreator/Qiita-1.07M huggingface dataset:
SageMakerとS3でPytorchモデルをトレーニングするときの便利集

### S3にファイルをアップロード

```python
import sagemaker, boto3, os

sagemaker_source_path = "./my_image.jpg"
s3_dest_path = "my_image.jpg"

sess = sagemaker.Session(default_bucket='backet_name')
bucket = sess.default_bucket()
boto3.Session().resource('s3').Bucket(bucket).Object(s3_dest_path).upload_file(sagemaker_source_path)
```

### S3からファイルをダウンロード

```python
import sagemaker, boto3, os

s3_source_path = "./my_image.jpg"
sagemaker_dest_path = "my_image.jpg"

sess = sagemaker.Session(default_bucket='backet_name')
bucket = sess.default_bucket()
boto3.Session().resource('s3').Bucket(bucket).Object(s3_source_path).download_file(sagemaker_dest_path)
```

### GoogleDriveからS3にデータを移動

zip形式にして、gdownでダウンロード、s３にアップ。

```shell
!pip install gdown
!pip install --upgrade gdown
gdown https://drive.google.com/uc?id=********
```

### S3のディレクトリをダウンロード

```python
import os
import boto3

def download_directory_from_s3(bucket_name, s3_directory, local_directory):
    """
    S3 からディレクトリをダウンロードする関数
    """
    s3 = boto3.client('s3')
    for obj in s3.list_objects(Bucket=bucket_name, Prefix=s3_directory)['Contents']:
        if not os.path.exists(os.path.dirname(obj['Key'])):
            os.makedirs(os.path.dirname(obj['Key']))
        s3.download_file(bucket_name, obj['Key'], obj['Key'])
```
OpenTelemetryのbridgeのerrorを制御する

(2023年06月22日時点での情報)

## 困ったこと
OpenTelemetryを使用して、トレースを行なっていたところ、Spannerの利用箇所で、`unsupported sampler`というエラーが何度も出ていた。このログ出力を無効化したかった。

## 本文
### 前提
**OpenTelemetryのOpenCensus bridgeを使用している。**

GCPのCloud Traceを利用する時には、OpenTelemetryを使用している。
(OpenCensusは、プロジェクトが廃止されているらしい。)

ただ、現在の時点で、Spannerなどのパッケージでは、トレースには、OpenCensusが使用されている。そこで、bridge（wrap)を行い、OpenCensusの`StartSpan()`が呼ばれても、OpenTelemetryの`Start()`が利用されるようにしている。

### 問題点
エラーレベルで、`starting span "cloud.google.com/go/spanner.ping": unsupported sampler`というログが、何度も出力されていた。

### 解決策
`otel.SetErrorHandler()`を利用して、ErrorHandlerを設定し、ハンドリングできるようにする。

### 解説
OpenCensus bridgeを見に行くと、`StartSpan()`内で、オプションの変換が行われている。
変換処理を見に行くと、`Sampler`が存在した場合、`fmt.Errorf("unsupported sampler")`みたいな感じで、エラーが作成されている。

オプションの変換処理でエラーがあった場合、internalの`var Handle`が利用される。これを書き換えれば良くて、方法として、`otel.SetErrorHandler()`がある。これを使って、`otel.ErrorHandler`を設定する。

以下、雑なコード
```golang
func main() {
	otel.SetErrorHandler(New())
}

func New() otel.ErrorHandler {
	return errorHandler{}
}

type errorHandler struct{}

func (_ errorHandler) Handle(err error) {
	if strings.Contains(err.Error(), "unsupported sampler") {
		return
	}
	log.Println(err)
}
```
[macOS] VSCode がターミナルから起動しない

# 起動しなくなった

https://www.chickensblog.com/vscode-terminal/
こちらのサイトを参考にして macOS のターミナルから code コマンドで VSCode を起動できるにようにしていたのですが、ある日気づいたら起動できなくなっていました。

```
% code
zsh: command not found: code
```

# 原因はリンク切れ

/usr/local/bin にある code コマンドへのシンボリックリンクがリンク切れになっていました。

```
シンボリックリンクファイルは存在しているものの
% ls -l /usr/local/bin
total 0
lrwxr-xr-x  1 root  wheel  167  5 26 17:54 code -> /private/var/folders/...途中省略.../Visual Studio Code.app/Contents/Resources/app/bin/code

リンク先のパスを直接 ls コマンドで確認するとエラーとなる
% ls "/private/var/folders/...途中省略.../Visual Studio Code.app/Contents/Resources/app/bin/code"
ls: /private/var/folders/...途中省略.../Visual Studio Code.app/Contents/Resources/app/bin/code: No such file or directory
```

# 対策1（非推奨）

shift + command + P から shell と入力し、code コマンドをアンインストールしたのちに再度インストールします。
これでリンクが再生成され code コマンドで VSCode が起動するようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83224/d711d7c5-6923-9ae2-e800-9527091df790.png)

# 対策2（推奨）

実は対策1では不十分でした。
一時的には起動できるようになるものの何らかのタイミング（おそらくアップデート）でまたリンク切れが発生し起動できなくなります。
対策1を行った場合は、対策1の code コマンドを PATH からアンインストールしたのち下記設定を実施してください。

```.zshrc
export PATH="/Applications/Visual Studio Code.app/Contents/Resources/app/bin":$PATH

```

```
source ~/.zshrc
```

シンボリックリンクではなく Applications から直接 PATH 設定することでリンク切れの影響を受けることなく起動できるようになります。
クイックアクションをカスタムボタン(aura コンポーネント)に配置したらモバイルでは動かない


一応クイックアクションのボタンとしては３通りがコーディングできるみたい

+ [Call a quick action from a lightning component](https://salesforce.stackexchange.com/questions/234214/call-a-quick-action-from-a-lightning-component)

1. クイック アクションを呼び出すことができますが、これはレコード ページにいて、そのページに呼び出そうとしているクイック アクションがある場合に限られます。<--モバイルでは動かない

https://developer.salesforce.com/docs/component-library/bundle/lightning:quickActionAPI/documentation

2\. 私はこれを使用しようとして失敗したことを見つけるために永遠に費やしました...あなたができることは、JavaScriptコントローラーを使用して、コンポーネントへのナビゲートを使用して構築したコンポーネントに移動することです。<--モバイルだと画面が終了できない。キャンセルボタンも押せない

https://developer.salesforce.com/docs/component-library/bundle/force:navigateToComponent/documentation

3\. コンポーネントをモーダル/クイック アクションのように動作させたい場合は、コンポーネントを使用するためにオーバーレイ (モーダル) を作成する必要がある場合があります。これは私がしました。必要以上に手間がかかると感じましたが、ユーザーには違いが分かりません。

https://developer.salesforce.com/docs/component-library/bundle/lightning:overlayLibrary/documentation

もうひとつApexから呼び出す方法を見つけた。

+ [QuickAction Class](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_system_quickaction.htm)

設定方法が分からなかったけど、やっとエラーが出なくなった。
各項目の説明が全く見当たらないので１日悩んだ。

こんなエラーも出た。
PerformAction of type Create requires a record

```
    @AuraEnabled
    public static String Account_ApprovalN002(String recordId) { 
        fkd_Approval__c f = new fkd_Approval__c();
        QuickAction.QuickActionRequest req = new QuickAction.QuickActionRequest();
        req.quickActionName = 'Account.ApprovalN002';
        req.record = f;//このアクションで作られるオブジェクト
        req.contextid = recordId;//起動しているオブジェクトのId
        QuickAction.QuickActionResult res = QuickAction.performQuickAction(req);
        return f.id;
    }
```

しかし、ボタンを押しても何も起動しませんね。

この必須のところって、クイックアクションの画面で必須と設定したことだ...
ということは、Apexからコールすると画面が起動せずにレコードを作ろうということ？


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/bf02ab35-bd61-d778-9956-e68b45b6ed5d.png)


あああ、モバイルどころかデスクトップのブラウザでも動かない。振り出しに戻ったな。


Apexからアクションは呼び出せない

+ [PerformAction of type Create requires a record](https://patlatus.wordpress.com/2017/11/04/salesforce-impossible-to-invoke-lightning-or-visualforce-actions-in-apex-code/)
IaC入門のメモ：Terraformを使ってみよう（Lambda関数の設定をエクスポートしたい）

# はじめに

座学では知っている物の、実体験をもってCloudFormationのテンプレ化をしてこなかったので、既存リソースをテンプレート化する所までを、「やってみた！」系のメモ書きです。

IaCで、各種大御所に対応してそうなTerraform関連から習得するために、関連するソフトを触っていきます。

# セットアップ：Terraform & Terraformer

terraformer公式サイト

https://github.com/GoogleCloudPlatform/terraformer

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/0ff32e76-55b6-3ebc-7635-997784ef69b8.png)

１．terraform インストール
２．terraformerのExeファイルを取得
３．環境変数にパスを通す

が、公式サイト記載の手順です。

## セットアップ：Terraformのダウンロード

https://developer.hashicorp.com/terraform/downloads

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/c914356d-3cf5-1103-a09d-fda5b943f519.png)

プラットフォーム毎に分かれていて「Windows」タブからダウンロードできます。

特にインストーラ等はなくZipを解凍するとExeファイルが出てくるだけなので、環境変数にパスを通す。

## セットアップ：Terraformerのダウンロード

https://github.com/GoogleCloudPlatform/terraformer/releases

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/03a2132f-de8a-097a-7248-f9d67b5ba10e.png)

対象のクラウド毎にExeを分けてくれています。

## 環境変数でパスが通ったか確認

```winbatch
terraform -v
terraformer --version
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/9b22646f-de0a-cac3-11a4-b450707556cb.png)

# 初期化前に事前準備(terraform init の実行前にやること）

## Providerのバージョン確認

terraformは追加アドオン(Provider)をダウンロードして利用する仕組みなので、現行バージョンを確認してきます。
（AWS用のアドオンのバージョン確認）

参考に載せさせて頂いた記事が、非常に参考になりました。

https://registry.terraform.io/providers/hashicorp/aws/latest

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/9fb3cb8b-85eb-1a27-76c0-d4f812ba9540.png)

## 初期化用のJSONファイル(main.tf)作成

```json
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.4.0"
    }
  }

  required_version = "1.5.1"
}

provider "aws" {
  region  = "ap-northeast-1"
}
```
前節で確認してきた
+ Teraformのバージョン
+ Providerのバージョン
は、セットアップ時のバージョンを確認して変更する。

# 初期化の実行

実行カレントに、main.tf(スクショの実行環境では、Work直下にmain.tfを保存）を置いて、コマンドを実行する。

```winbatch
terraform init
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/b4da2ba5-d769-e882-3e5e-1cc3b48186c3.png)

# 公式ドキュメント

https://github.com/GoogleCloudPlatform/terraformer/blob/master/docs/aws.md

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/83920d25-032b-a717-e5c1-6f93a6fedf7a.png)

対象としたいリソースは、この辺りです。

# ポリシー用意

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "lambda:List*",
                "lambda:Get*"
            ],
            "Resource": "*"
        }
    ]
}
```

Lambdaの読み取り専用のポリシーをアクセスキーに割り当てて実行してみます。

# エクスポート

```winbatch
terraformer import aws --resources=lambda --regions=ap-northeast-1
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/55e6373c-eaa3-e245-16b0-82070ff174a1.png)

Workが実行カレントで、配下に generated - aws - lambda のフォルダ階層が作成されて、各種 tfファイルが出力されました。

# 参考

https://qiita.com/ryysud/items/a38bcc856069801878e9

# AWSの相談・お困りごとありましたら、、、

AWSの活用方法や、お困りごとの相談、随時、お仕事の受付しております。

https://www.cs-cloud-aid.com/
Wordの新規ファイルをOneDriveに保存できなかった一例

環境：
Mac(Ventura13.4) 
Word(16.74)
OneDrive(23.114.0530.0001) 

Wordで保存するが、OneDrive上に表示されず。Finderから開くことができなかった。

一度ローカルに保存したあとOneDriveにアップロードすればファイル表示され、Finderからも開くことができる。
他人のYoutubeのチャンネルIDを調べる方法

※PCの場合

YoutubeチャンネルのURLには
対象のyoutubeをまず開く

https://www.youtube.com/@760jindq10

概要タブを開く

右下の ⇒ のような
共有ボタンを押す

「チャンネルIDをコピー」を押す

コピー押すと、以下がコピーされます
UCSu2WzvZ7F2FgJFEg0pZTRA
これがチャンネルIDになります

https://www.youtube.com/channel/
の後ろにチャンネルIDを付けてやると
https://www.youtube.com/channel/UCSu2WzvZ7F2FgJFEg0pZTRA

開くと、チャンネルIDと一致するYoutubeチャンネルを開くことができます

@から始まる
ハンドルIDをころころ変える人がいますが
チャンネルIDは、変更できないため

チャンネルIDが分かれば
ハンドルIDがどうなろうと、確実にチャンネルを開くことができます

以上
他人のYoutubeのハンドルIDを調べる方法

※PCの場合

YoutubeチャンネルのURLには
対象のyoutubeをまず開く

https://www.youtube.com/@760jindq10

概要タブを開く

右下の ⇒ のような
共有ボタンを押す

「チャンネルを共有」を押す

コピー押すと、以下がコピーされます
https://youtube.com/@760jindq10

この
@760jindq10
というのがハンドルIDになります

以上
[AWS Q&A 365][ECS]AWSのよくある問題の毎日5選 #85

# 1. Amazon ECSは他のAWSサービスと連携できますか？
はい、Amazon ECSは他のAWSサービスと連携することができます。例えば、AWS CloudFormationとAmazon ECSを使用して、アプリケーションを複数のリージョンおよびアカウントに自動的かつ安全にデプロイすることができます。また、AWS Copilotを使用して、Amazon ECS上のコンテナ化されたアプリケーションのビルド、リリース、運用を行うこともできます。

# 2. Amazon ECSは一時的な認証情報をサポートしていますか？
はい、Amazon ECSは、AWS CLIやAWS APIを使用して手動で作成することができる一時的な認証情報をサポートしています。セキュリティ目的のため、長期的なアクセスキーを使用する代わりに一時的な認証情報を動的に生成することをAWSは推奨しています。

# 3. Amazon ECSでサービスロールを使用できますか？
はい、Amazon ECSでサービスロールを使用することができます。サービスロールとは、サービスが代行してアクションを実行するためのIAMロールのことです。IAM管理者はIAM内からサービスロールを作成、変更、削除することができます。

# 4. Amazon ECSは属性ベースのアクセス制御（ABAC）をサポートしていますか？
はい、Amazon ECSは属性ベースのアクセス制御（ABAC）をサポートしています。ABACとは、属性に基づいて権限を定義する認可戦略のことです。IAMエンティティ（ユーザーまたはロール）や多くのAWSリソースにタグを付け、主体のタグがアクセスしようとしているリソースのタグと一致する場合に操作を許可するABACポリシーを設計することができます。

# 5. Amazon ECSでアクセス制御リスト（ACL）を使用できますか？
いいえ、Amazon ECSはリソースへのアクセスを制御するためのアクセス制御リスト（ACL）をサポートしていません。代わりに、リソースベースのポリシーまたは属性ベースのアクセス制御（ABAC）を使用して、Amazon ECSリソースへのアクセスを管理することができます。
[AWS Q&A 365][ECS]Daily Five Common Questions #85

# 1. Can Amazon ECS work with other AWS services? 
Yes, Amazon ECS can work with other AWS services. For example, you can use Amazon ECS with AWS CloudFormation to deploy your application across multiple Regions and accounts in an automated and secure manner. You can also use AWS Copilot to build, release, and operate containerized applications on Amazon ECS. 

# 2. Does Amazon ECS support temporary credentials? 
Yes, Amazon ECS supports temporary credentials that you can manually create using the AWS CLI or AWS API. AWS recommends that you dynamically generate temporary credentials instead of using long-term access keys for security purposes. 

# 3. Can I use service roles with Amazon ECS? 
Yes, you can use service roles with Amazon ECS. A service role is an IAM role that a service assumes to perform actions on your behalf. An IAM administrator can create, modify, and delete a service role from within IAM. 

# 4. Does Amazon ECS support attribute-based access control (ABAC)? 
Yes, Amazon ECS supports attribute-based access control (ABAC), which is an authorization strategy that defines permissions based on attributes. You can attach tags to IAM entities (users or roles) and to many AWS resources, and then design ABAC policies to allow operations when the principal's tag matches the tag on the resource that they are trying to access. 

# 5. Can I use access control lists (ACLs) with Amazon ECS? 
No, Amazon ECS does not support access control lists (ACLs) to control which principals have permissions to access a resource. Instead, you can use resource-based policies or attribute-based access control (ABAC) to manage access to Amazon ECS resources.
NCMBのC#（MAUI/Xamarin） SDKがプッシュ通知の操作に対応しました

ニフクラ mobile backend（NCMB）では公式として各種フレームワーク、環境向けにSDKを提供しています。その1つにUnityがあります。Unity向けに開発を行う際にはC#を利用しますが、このUnity用SDKは.NET環境では利用できません。

[そこでコミュニティSDKとしてXamarinや.NET](http://xn--sdkxamarin-263ivb5dxgkt99brryj6modtuse.net/) 2.0でも動作するC# SDKを開発しています。今回のバージョンアップ（1.2.0）でプッシュ通知のCRUD操作に対応したので使い方を解説します。

[NuGet Gallery | NCMBClient 1.2.0](https://www.nuget.org/packages/NCMBClient/)

## NCMBをインストール

任意のプロジェクトをVisual Studioで開きます。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/9c51cd20-c9f4-a00b-804a-ed7db410ccd0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/9c51cd20-c9f4-a00b-804a-ed7db410ccd0.png)

そしてプロジェクトを右クリックして、NuGetパッケージの管理を選択します。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/61392119-a472-9d33-101d-970f016fc2cf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/61392119-a472-9d33-101d-970f016fc2cf.png)

出てきたウィンドウで、NCMBClientを検索します。執筆時点での最新版は1.0.0です。これを追加します。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f06df07c-f55d-1e0e-65b2-de79c779cc80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f06df07c-f55d-1e0e-65b2-de79c779cc80.png)

## NCMBをインポートする

`MainPage.xaml.cs` を開きます。そして、namespaceの下でNCMBClientをインポートします。

```csharp
namespace MyMauiApp;
using NCMBClient;
```

クラスの初期化時にNCMBも初期化します。キーは[ニフクラ mobile backend](https://mbaas.nifcloud.com/)より取得してください。

```csharp
public MainPage()
{
	InitializeComponent();
	new NCMB("YOUR_APPLICATION_KEY", "YOUR_CLIENT_KEY");
}
```

# プッシュ通知の使い方

## プッシュ通知の保存

プッシュ通知を保存する際には、対象とする `target` 、そして即時配信の `immediateDeliveryFlag` または配信日時を指定する `deliveryTime` のどちらかが必須です。

```csharp
var push = new NCMBPush();
push.Set("immediateDeliveryFlag", true);
var target = new string[] { "ios" };
push.Set("target", target);
await push.Save();
```

## プッシュ通知を更新する

プッシュ通知を更新する場合には、保存と同じように行います。

```csharp
var push = new NCMBPush();
push.Set("immediateDeliveryFlag", true);
var target = new string[] { "ios" };
push.Set("target", target);
await push.Save();
var title = "test";
push.Set("title", title);
await push.Save();
```

## プッシュ通知の検索

ファイル検索はNCMBQueryを使います。

```csharp
var query = NCMBPush.Query();
query.EqualTo("title", title);
var ary = await query.FetchAll();
```

1件だけの場合はobjectIdを指定します。リロードに似た操作です。

```csharp
var push = new NCMBPush();
var title = "Test Hello";
push.Set("title", title);
push.Set("immediateDeliveryFlag", true);
var target = new string[] { "ios" };
push.Set("target", target);
await push.Save();
var p = new NCMBPush();
p.ObjectId(push.ObjectId()); // objectIdを適用
await p.Fetch(); // Fetchで更新
```

## プッシュ通知の削除

削除は `Delete` メソッドです。

```csharp
await push.Delete();
```

## まとめ

C# SDKはXamarinで使えばiOSやAndroidはもちろん、Linux/macOS/Windowsアプリも開発できます。.NET環境であればWebやCLI開発でも利用できます。ぜひ皆さんのアプリ、Webアプリ開発に利用してください。

[NuGet Gallery | NCMBClient 1.2.0](https://www.nuget.org/packages/NCMBClient/)
NCMBのC#（Xamarin/MAUI） SDKがファイルストアに対応しました

ニフクラ mobile backend（NCMB）では公式として各種フレームワーク、環境向けにSDKを提供しています。その1つにUnityがあります。Unity向けに開発を行う際にはC#を利用しますが、このUnity用SDKは.NET環境では利用できません。

[そこでコミュニティSDKとしてXamarinや.NET](http://xn--sdkxamarin-263ivb5dxgkt99brryj6modtuse.net/) 2.0でも動作するC# SDKを開発しています。今回のバージョンアップ（1.2.0）でファイルストアのCRUD操作に対応したので使い方を解説します。

[NuGet Gallery | NCMBClient 1.2.0](https://www.nuget.org/packages/NCMBClient/)

## NCMBをインストール

任意のプロジェクトをVisual Studioで開きます。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/9c51cd20-c9f4-a00b-804a-ed7db410ccd0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/9c51cd20-c9f4-a00b-804a-ed7db410ccd0.png)

そしてプロジェクトを右クリックして、NuGetパッケージの管理を選択します。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/61392119-a472-9d33-101d-970f016fc2cf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/61392119-a472-9d33-101d-970f016fc2cf.png)

出てきたウィンドウで、NCMBClientを検索します。執筆時点での最新版は1.0.0です。これを追加します。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f06df07c-f55d-1e0e-65b2-de79c779cc80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f06df07c-f55d-1e0e-65b2-de79c779cc80.png)

## NCMBをインポートする

`MainPage.xaml.cs` を開きます。そして、namespaceの下でNCMBClientをインポートします。

```
namespace MyMauiApp;
using NCMBClient;

```

クラスの初期化時にNCMBも初期化します。キーは[ニフクラ mobile backend](https://mbaas.nifcloud.com/)より取得してください。

```
public MainPage()
{
	InitializeComponent();
	new NCMB("YOUR_APPLICATION_KEY", "YOUR_CLIENT_KEY");
}

```

# ファイルストアの使い方

これまでファイルストアへのアップロードには対応していました。

```csharp
// テキストで指定する場合
var str = "1,2,3";
var data = System.Text.Encoding.ASCII.GetBytes(str);
var file = new NCMBFile("test2.csv", data, "text/csv");
await file.Save();

// バイナリファイルの場合
var data = File.ReadAllBytes("ncmb.png");
var file = new NCMBFile("ncmb.png", data, "image/png");
await file.Save();
```

CRUDの他の操作については以下のように行います。

## ファイルの内容を更新する

ファイル内容を更新する場合には、保存と同じように行います。 `data` プロパティに内容を付けます。

```csharp
// テキストで指定する場合
var str = "1,2,3";
var data = System.Text.Encoding.ASCII.GetBytes(str);
var file = new NCMBFile("test2.csv", data, "text/csv");
await file.Save();

// テキストで指定する場合
var str2 = "1,2,3,4,5";
var data2 = System.Text.Encoding.ASCII.GetBytes(str);
file.data = data2;
await file.Save();

```

## コンテンツダウンロード

ファイルの内容は以下のようにダウンロードします。2種類あるので、使いやすい方を使ってください。

```csharp
var file = new NCMBFile("test2.csv");
var data = await file.Download();
// そのあとは file.data でアクセス可能
```

クラスメソッドからも利用できます。この場合はコンテンツだけが返ってきます。

```csharp
var data = await NCMBFile.download("test2.csv");
```

## ファイルの検索

ファイル検索はNCMBQueryを使います。

```csharp
var query = NCMBFile.Query();
query.EqualTo("fileName", fileName);
var ary = await query.FetchAll();
```

## ファイルの削除

削除は `Delete` メソッドです。

```csharp
var file = new NCMBFile("test2.csv");
await file.Delete();
```

## まとめ

C# SDKはXamarinで使えばiOSやAndroidはもちろん、Linux/macOS/Windowsアプリも開発できます。.NET環境であればWebやCLI開発でも利用できます。ぜひ皆さんのアプリ、Webアプリ開発に利用してください。

[NuGet Gallery | NCMBClient 1.2.0](https://www.nuget.org/packages/NCMBClient/)
Proxmox7 を試してみる　その７　VMのVMIDの変更

# VMのVMIDの変更
勉強がてら Proxmox をいじっているのはよかったのですが何も考えずに VM を作成していたら詰みました（いい意味で）。それは VMID は後で変更できるしそのとき考えようと思って体系つけていなかったからｗ

# VMIDの変更は簡単にできる
ググってみると [ありました](https://forum.proxmox.com/threads/changing-vmid-of-a-vm.63161/)

CLI でもやれるらしい
```
qmrestore vzdump-qemu-666-2022_02_02-12_25_00.vma.zst 999
```

今回は、こちらの記事のように GUI で確かめてみてみます。

```
you can also do it over the GUI too.
instead of clicking the VM and restoring the backup there, you can click on the storage where you made the backup and restore it to an arbitrary VMID ;)
```

# VMのVMIDの変更、実施

まず該当VMのバックアップを実施する。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44540/70842633-e1ae-c3da-c087-0082e8457aca.png)

バックアップされたことを確認する。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44540/f277c029-8c65-cdc7-ea2e-ebf4534330a6.png)

ローカルストレージから見えれる？よね？

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44540/fd327995-9a22-5072-250b-b95a74d8ddeb.png)

ここで リストア ボタンを押下する。このときVMID を指定することにより 新規VMとしてリストアする（ここでは 1001を指定してみています）。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44540/1d472dd3-9d83-4756-f5ac-64ba5acce2c8.png)

リストアされた・・・かな？

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44540/1362c468-d26a-ae29-3151-b2cb820a622e.png)

きた！
付加価値ってなんだろう？

# はじめに
「付加価値」というのは仕事において大事というのは言わずもがなですが、そもそも付加価値って？？というのをきちんと説明できる人って少ないんじゃないでしょうか？
たとえ説明できても、付加価値をどのように創出し、売り上げや利益としていくのかまでは多くの人が疑問なところではないでしょうか。
今回はそんな付加価値についての知識がまとめられた、以下の本を参考に付加価値について記事にします。

**付加価値のつくりかた 一番大切なのに誰も教えてくれなかった仕事の本質
田尻望 著**

https://kanki-pub.co.jp/pub/book/details/9784761276362


# 付加価値とは
「**付加価値はニーズが源泉である**」ということが書籍内での定義となっています。
例えば、ある自動車メーカーが**時速400km出せる自動車**を作ったとします。
それは他の車と比べたら圧倒的に速く、価値があるものかもしれませんが、そこに**お客さんのニーズ**はあるでしょうか？
多くの人はNOと答えるでしょう。(公道で400kmなんて出せるわけないですし…)

画期的で高性能であったとしても、お客さんの求める価値とずれているとそれは付加価値として成り立たないわけです。
他と比べて凄いかではなく、**ニーズ**がそこにあるかが重要だということです。

# なぜお客さんは商品を買うのか
>**「価値の概念」を理解しないと、赤字確定！**

**世界最速**！！**最高時速400kmのスポーツカー**！！なんてCM流したところでお客さんがそこに価値を見出していなければ、売れないのは当然ですね。

では、どうするか。
それは「**どうすれば売れるのか**」という売り手が主体の発想で考えず、「**なぜお客様が買うのか**」というお客様主体で考えることが大事です。

>***なぜお客様が買うのか？
本当にその商品・機能は使われるのか？
使われたら本当に役に立つのか？
どんな役に立つのか？**

著者はかつてキーエンスに勤めていてこれらについて徹底的に突き詰めていたそうです。
その中で実際に現場に足を運び、「**何に困っているのか**」「**どこにニーズがあるのか**」を調査するそうです。


# ニーズはどこにある？
ニーズには「**顕在ニーズ**」と「**潜在ニーズ**」の2種類あります。

顕在ニーズとは、**お客様自身が欲しているものを、自身で明確に意識しているもの**で「PCが欲しい」や「車が欲しい」などですね。
潜在ニーズとは、**お客様自身でも気づいていないニーズ**。なぜPCが欲しいのか、なぜ車が欲しいのかなどを突き詰めると潜在ニーズに気づけたりします。これがまさに付加価値を成立させるために重要です。

例えば、「社員にタブレット端末を導入したい」という依頼があったとします。「タブレット端末を導入したい」というのは顕在ニーズです。
そこから「なぜタブレット端末を導入したいのか」を深堀りし、潜在ニーズを発見することで付加価値を生み出していくというわけです。
そして、この潜在ニーズは「**実際にどのようなシーンで使用するのか**」を把握することが重要で、そのためには現場に足を運び、自身の目で観察する必要があります。

営業担当者ではなく、現場の生の声を聴いた結果、ヒアリングしていたニーズと実際は異なっていた…なんてこともありそうですよね…

# エンジニアの仕事に置き換えてみる
顕在ニーズは「**経費申請システムを導入したい**」とかですかね。
対して潜在ニーズは「**なぜ経費申請システムを導入したいのか、そのシステムで実現したいことは何か。**」ですね。
経費申請システム導入なら、**社員の申請工数を削減**したり、**経理のペーパーワークをなくしたい**などが考えられます。
それならば、社員が今までどれだけ面倒な申請の仕方をしていたのか知る必要があるし、経理が行ってきたペーパーワークをなくせるようなシステムの導入が必要になるわけですね。

# おわりに
実際の書籍は250ページほどあるので、紹介しきれませんでしたが、他にも様々な**付加価値**に関するノウハウが書かれた本になっていますので、興味のある方はぜひ読んでみてください！
cron で60で割り切れない分単位の起動をすると

Linux についてくる cron では「_n_ 分毎起動」を crontab に
```
*/n * * * * command
```

て書けばいいのは crontab(5) 見てくれればわかるわけです。30分毎だったら毎時 0 分、30分ですね。
5分毎だったら毎時 0, 5, 10, ... 55 分です。60を割り切れるnを使っている限りは、すべての起動の間にn分の間隔をあけてもらえます。

さて60を割り切れない n だったらどうなるか？

やってみたら、毎正時（毎時0分）から n 分間隔になります。ひょっとして unixtime が n 分で割り切れる時に起動してくれないかな、と思ったのですがそうはいきませんでした。

だからたとえば */14 と書くと、 0, 14, 28, 42, 56 となるので、毎時56分の4分後の00分に起動されてしまってそこが混んできます。計算機リソースの上限突破防止という観点からはちょっとありがたくないんですよね。

~~ドキュメントにもないので無保証ですが。~~

コメントにもありますが、 crontab(5) をこう読むんだそうで：「*」は「0-59」と等価であって、「/n」が間隔nをおいて、なので、あくまで毎時の分について0からn間隔であることは明らかなんだそうです。
「Power Apps」でMAGIのアプリを作成する話【Chat GPT】

noteに記事を投稿しました。

「Power Platform(PowerApps&Powerautomate)」と「Chat GPT-API」を組み合わせて、
エヴァのMAGIみたいなアプリを作成。

https://note.com/modern_moose239/n/n0c37d9b813d7
NestJS を VisualStudioCode(VSCode)を使ってデバッグする方法

## 概要
バックエンドサーバーにNestJSを採用して開発を進めた際、そのデバッグの設定に少々手間取ったため、ここに備忘録として残しておきます。

## 環境

Windows11, WSL2
```
❯ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.1 LTS"
```

Visual Studio Code (VSCode)
```
Version: 1.79.2
```

node.js
```
❯ node --version
v16.17.0
❯ npm --version
8.15.0
```

NestJS
```
❯ nest --version
9.4.2
```


## 各種設定

:::note
$project_dirはNestJSプロジェクトを配置したディレクトリとして以下表記しています。
:::

### package.json の設定

まず、こちらのファイルにNestJSをDebugモードで立ち上げるためのスクリプトを設定します。
package.jsonの"scripts"要素の中に"start"や"start:dev"が既に設定されていたりすると思いますが、ここに"start:debug"という項目を下記のように追加します。

```json:$project_dir/package.json
"start:debug": "nest start --debug --watch"
```

### .vscode/launch.json の設定

次に、$project_dir/.vscode/launch.jsonに以下の設定を追加します。
.vscode/launch.json がない場合はファイルを作成してください。

```json:$project_dir/.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "pwa-node",
      "name": "Launch debugger",
      "request": "launch",
      "runtimeArgs": [
        "run",
        "start:debug"
      ],
      "runtimeExecutable": "npm",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "sourceMaps": true,
      "envFile": "${workspaceFolder}/.env",
      "cwd": "${workspaceRoot}",
      "console": "integratedTerminal",
      "protocol": "inspector"
    },
  ]
}
```

このファイルを保存してから、VSCodeのDebugアイコンをクリックすると、Launch debuggerという項目が追加されていることが確認できると思います。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3249097/f419bd71-3ed0-b346-68e7-19a094e1a188.png)


この状態で、コードをブレイクさせたい場所にブレイクポイントを設定します。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3249097/31c1fde2-3a77-df99-9570-f5b63329441c.png)


ブレイクポイントを設定したら再度VSCodeのDebugアイコンをクリックし、追加された項目が選択されていることを確認して再生ボタンを押します。

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3249097/d8cdfc26-f2f3-847f-089e-eb51d859aa75.png)

するとターミナル上にVSCodeからのDebugger立ち上げコードが走ります。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3249097/26f85f38-e092-0dd5-fde5-6ae27f9cc559.png)

Debugger attached.の文字の後にNestJSを通常起動した時のように  Nest application successfully started のログが出力されたら、上記で設定したブレイクポイントを通るコードを実行してみましょう。

下記のようにコードの実行がブレイクポイントで停止すると思います。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3249097/ed996889-486c-161b-f940-665e327d1429.png)
プロダクトマネージャーが久々にコードを書いて感じたこと3選

# 背景
どうもこんにちわ
OurPhotoという出張撮影プラットフォームでプロダクトマネージャーをしているdoueと申します。

まだまだ、PM歴1年の新米です。

実はわたし、SM→EM→PMと経て
かれこれ、4年くらいコードを書いていません。
10人以上のエンジニアをマネジメントする立場になってからというもの
ビジネスサイドとの折衝とか、ピープルマネジメントとかにリソースを注ぎ込みまくった結果
コードからは完全に離れ、新卒エンジニアにもコーディング力は抜かれております。

プロダクトマネージャとして、技術側面でやっていることといえばざっくり

* SQLでデータ抽出、傾向分析
* 技術選定の壁打ち、意思決定

くらいです。

# 久々にコードを書いたよ
今のチームは少人数チームなので、実装できる機能には限界があります。
プロダクトマネージャーと言いつつ、事業成長にも責任を持っているので
成長に必要な機能は、必要な時期までに用意せねば。
妥協は許されない。なんとかせねば、、、

> そうか、俺がコードを書けばいいのか！

と閃いたわけです。
これは世紀の大発見です。

そんなこんなでコードを書いているわけですが
その中で、感じたことをつぶやきます。

# 久々にコードを書いて感じたこと3選

## ①：ちょっぴりでも、日々コードに触れることは大事だった

コードはずっと書いてないと言いつつ、SQLくらいは自分で書いていました。
(一応)元エンジニアなので、SQLくらいはエンジニアに丸投げじゃなくて、書くように意識してた。

コードレビューとか、PRとかは見ていなかったけど
SQLくらいは...という気持ちで。

これがなかったら多分、コードは書けなかったと思います。
DB設計が頭に入っていることも結構大事でした。

たまにはコード書こう.....

## ②：もうちょっとエンジニアに寄り添うべきだった

基本的には、エンジニアのリーダーに任せているし
広い意味での設計(技術選定とかインフラアーキテクトとか)は結構頭に入っているつもりでいた。

けど、コード、PRなどは特に見ていなかったので
実際にコードに触れた時
> 「まじやべぇ、超複雑じゃん...みんな大変そう...」

と、率直に思いました。

技術的負債が溜まっているというのは、まああるあるとして
それに対してモヤモヤしながらも、頑張って納期に向けて開発してくれるエンジニアたちには頭が上がりません。
そんなリスペクトを再認識しました。

今のチームではEMがいないので
ある意味で、PMである僕がEMの責務も負っています。（EMの応募来ないかな...）

PMとエンジニアリングの仕事は全く別とはいえ
エンジニアが、高いパフォーマンスで仕事を続けられるように、楽しく仕事を続けられるように
しっかりと、寄り添っていかないといけなかった
と反省することになりました。

プロダクトはエンジニアたちの努力の結晶です。

## ③：やっぱりPMはいいぞ

エンジニアたちが、汗水垂らして頑張って作り上げてくれたプロダクト
これを成功に導くのがやっぱり僕だ。

時には安直なアイディアも出るし、チャレンジは失敗することもある。

けれど、それを丹精込めて実装してくれるエンジニアの
仕事のやりがいって、やっぱりユーザーが喜んでくれることですよね。

だから、本気で「ユーザーが喜んでくれるもの」を考えていかなければいけないんだなと
改めて再認識した。

わかっていても、肌で触れるとまた見え方も変わりますね。

もちろんエンジニアだけじゃなく、デザイナーやディレクター
サービスを運営してくれているメンバーたちなど、いろんな人が協力し合っているので
そんな人たちも同じです。

まだまだペーペーPMですが、これからももっと頑張ろうと思います。
ELBアイドルタイムアウトを伸ばす場合に注意した事

## 前置き
3月からSREとして働いている井之口です。
これまではバックエンドエンジニアとして働いていたのですが、ポジションも変わってインフラに関わる事も多くなりました。
今回はELBの設定を変更する機会があったので、その際に学んだ事をまとめようと思います。

## 構成
![スクリーンショット 2023-06-22 11.17.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552593/fd3ea690-0ccf-1048-1c38-adfc1f8a110a.png)


- NginxがRailsのプロキシサーバーとして立っていて、ELBから負荷分散されている一般的な構成です
- ALBアイドルタイムアウトは60秒に設定されています
- Nginxの`keepalive-timeout`は60秒に設定されています

簡単に用語について説明しておきます

### ALBアイドルタイムアウト
- ALBが接続先であるNginxへ向けたTCPコネクションを保持する時間

### keepalive_timeout
- NginxがALBへ向けたTCPコネクションを保持する時間の事


## 対応内容

ではなんらかの理由でALBアイドルタイムアウトを伸ばさなければいけなくなったとしましょう。
今回は120秒に伸ばしたいのでまずアイドルタイムアウトのみ伸ばしてみます。

1. ALBのアイドルタイムアウトを伸ばす (120秒)
1. Nginxのタイムアウトは60秒のまま

このようなイメージです

![スクリーンショット 2023-06-22 11.58.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552593/9c771066-7010-9434-3678-9ee5b4c4e4ae.png)

実はこの設定は不十分でAWS公式でもELBの接続先にNginxやApacheを置く場合は、接続先のタイムアウトを伸ばす事が推奨されています。

[ELBのバックエンドサーバーとしてApacheまたはNGINXを使用するための最適な設定を教えてください。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/apache-backend-elb/)


#### なぜ問題があるのか？
1. 60秒を超える処理が実行された場合、ALBより先にNginxがタイムアウトをする事で、Nginx側からTCPコネクションが切断される
1. ALB側からは、TCPコネクションが切断されたのを検知するには、keepaliveで一定周期で相手が生存しているか確認応答をするまで待つ必要がある
[参考](https://milestone-of-se.nesuke.com/nw-basic/as-nw-engineer/keepalive-tcp-http/) 
1. 結果、切断の確認をする前に別リクエストがALB側から貼られているTCPコネクションを使ってしまうと、Nginx側は既にコネクションを切断しているので、504エラーになる。

上記の様な事が発生する可能性があるため、Nginxの設定も変更する必要がありそうです。
追加で設定を入れてみます

## 追加対応
1. ALBのアイドルタイムアウトを伸ばす (120秒)
1. Nginxの`keepalive_timeout`をALBのアイドルタイムアウト以上にする (240秒)
1. Nginxの `proxy_read_timeout`を伸ばす
1. Nginxの`client_header_timeout` `client_body_timeout`を設定する

<img width="715" alt="スクリーンショット 2023-02-04 2.01.59.png (155.0 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2023/02/04/103690/d1cd79a7-682d-4050-925d-bf01a003c146.png">

1. 120秒を超える処理が実行される時に、ALBがNginxより先にタイムアウトをする事で、ALB側からコネクションが切断される
1. ALB側からコネクションを切った後にNginxがコネクションを切るので、新しくALBから入ってくるリクエストが、切断されているコネクションに流れてくる可能性がない。


これで安全にコネクションを切断できる様になりました。

## 他に設定したプロパティの意味
### proxy_read_tmeout
- Nginxがプロキシ先(Rails)に投げたリクエストをタイムアウトする時間
- つまりクエリ等が原因で時間がかかっている場合は、この時間を伸ばさないと結局タイムアウトすることになる
    - この記事はあくまで暫定対応前提のため、非同期処理やクエリチューニングなど考えた方がより良いです

### client_header_timeout
- クライアント(今回はALB)がNginxに投げたheaderをNginxが読み込むまでの時間のタイムアウト時間

### client_body_timeout
- 上記のリクエストボディ版

## 疑問点

- TCPコネクションが切断される時の挙動
    - TCPコネクションを切断する場合って、FIN -> ACKでやりとりをしてから切断すると思っていたので、その時点で切断された事検知できるんじゃないのかなと思った。この辺り詳しい方教えていただければ嬉しいです。

## 終わりに
SREになり、バックエンドの業務ではあまり意識しないレイヤーの知識もサービスの信頼性を高める必要だと実感しています。
まだまだ未熟者ですが、これからもサービスの信頼性を向上させるにはどうすれば良いか考えながら業務をしていこうと思っています

### 参考にした記事
- [【TCP】コネクションの確立までの道のり](https://qiita.com/tatsuya4150/items/ae4d5e95e43ce0670bc0)
- [ELB使ってる人はすぐにKeepAlive Timeoutの値を確認しよう](https://qiita.com/hareku/items/7880eeac1e96c2f897e1)
【Android】ダウンロードフォルダにファイルを保存

# 概要
ダウンロードフォルダにファイルを保存する方法について解説。

# 方法
ファイルを保存するメソッド作成
```Kotlin
@Throws(IOException::class)
private fun saveFile(context: Context, fileName: String, text: String) {

	//必要に応じて適宜変更
	val extension = "txt"
	val mimeType = "text/plain"
	//val extension = "csv"
	//val mimeType = "text/csv"

	val outputStream: OutputStream? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
		val values = ContentValues()
		values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
		values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
		values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)

		val fileUri = context.contentResolver.insert(MediaStore.Files.getContentUri("external"), values) ?: return
		context.contentResolver.openOutputStream(fileUri)
	} else {
		val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString(), "$fileName.$extension")
		FileOutputStream(file)
	}

	outputStream?.write(text.toByteArray())
	outputStream?.close()
}
```

メソッドを呼び出す
```Kotlin
runCatching {
	saveFile(this, "title", "content")
}
```

# 参考
https://stackoverflow.com/questions/61984396/save-txt-file-on-android-q

# 関連記事
https://qiita.com/nakashimaakio/items/c43d42535129941ad1c7
非同期中のexceptionを共通のエラーハンドラで処理する

今回も備忘録です。

## このドキュメントについて
下記について記載していきます。
・非同期処理中の共通のエラーハンドラ実装について
<br>
非同期処理中はexceptionをthrowしても、下記記事で記載した@ExceptionHandlerでは処理されないので、非同期用のエラーハンドラが必要です。

https://qiita.com/ry-s/items/b6c9e494769fecf870a3


## 実行環境

・Java17
・springboot 3.0.2

## 実装

```java
@Slf4j
@Component
public class AsyncExceptionHandler extends AsyncConfigurerSupport {
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (e, method, params) -> {
            if (e instanceof ExampleException) {
                log.warn(e.getMessage(), e);
            } else if (e instanceof ExampleException2) {
                log.error(e.getMessage(), e);
            } else {
                log.error(e.getMessage(), e);
            }
        };
    }
}

```
説明：
1. AsyncUncaughtExceptionHandlerインターフェースのhandleUncaughtExceptionメソッドをオーバーライドして、処理の内容を書き込み、AsyncUncaughtExceptionHandlerを実現したクラスをDIコンテナに登録してあげる必要がある。
→登録しておくことで、非同期処理中に発生したExceptionを勝手に拾ってくれるようになる？

2.エラーが起きた箇所のメソッドやパラメータなど細かく引数で受け取れるが、今回はエラーメッセージとスタックトレースをログに残したかったので、exceptionごとにログレベルの異なるログを出力するようにした。


<br>

※AsyncConfigurerインターフェースを実現するでも可能 
(spring 6.0以降のバージョンではAsyncConfigurerの直接実現が推奨らしい)


## まとめ
非同期中でもどこでどうゆうエラーが起きたのかちゃんとログに残してあげたり、
アラートを出すべきエラーとそうでないエラーなどを分けることもできるので、
後から調査が楽になるのではと思います。
【感想】「Slack自動投稿し返信をスプレッドシートに書き込むアプリを作ってみた」という記事を読んだ

「gas 入門 | google apps scriptの始め方&できること」というサイト内の「Slack自動投稿し返信をスプレッドシートに書き込むアプリを作ってみた」という記事を読んだ

Slack自動投稿し返信をスプレッドシートに書き込むアプリを作ってみた
 https://newfield-inc.com/blog/slack-spreadsheet/

たしかにSlackとスプレッドシートの連携は楽そうに見える。ただ、今回は本当に「作ってみた」記事なので、実用性にはまだまだ遠い印象。いろいろエラー値を入れてみたくなる話だった。QA視点だと気になることがかなりあり。

そもそもSlackと本アプリの連携を組織が許すかという点はおいておいて、Slackもしくはスプレッドシートへの入力をミスした場合、再編集はできるのだろうか。また、その再編集も都度Slackに流れるのだとしたら正直なところ他のユーザーはウザさを感じざるを得ないだろう。

おそらくこの記事の本質は本当に作ってみたという話であり、実用性を考えていない。時期的にももしかしたら新人が作成したアプリの投稿による反応を確かめるための記事なのかもしれない。

それでも手を動かして形にできているのなら、素晴らしいことなんだと思う。
【ChatGPT無】は数を扱える！？


:::note info
本記事は、ChatGPT（無料版）で
行なっております。GPT4での確認
はできておりません。あしからず。
:::

　どうも、カーブミラーです。

　今回は、
　　ChatGPTさんの数問題です。

　ChatGPTさんは、
　　計算が苦手、
　　　と誰もが
　　　　経験していますよねw

　それでも、
　　なんとかならないかと
　　　四苦八苦しています。
　　　　いまだに、
　　　　　苦行を積んでいますw
　　　　　（“徳を積む”をもじった）

　ある日、
　　ふと、思い出しました。
　　　ChatGPTさんは、
　　　　文脈が把握できる。

　　ChatGPTさんも
>ChatGPTは、自然言語理解、文脈の把握、文章生成などのタスクに使用されます。その学習データは、ウェブ上の巨大なテキストコーパスから抽出されたものであり、多様な情報源からの知識を持っています。

　「知識を持っている」となれば、
　　その知識の中には、
　　　当然ながら、
　　　　計算に関することもある。

　ただ、計算機的な使われ方をすると
　　「私の仕事じゃないんですぅ」
　　　と間違った答えを返してくる。
　　　　正しいときもあるけれど。

　では、
　　文章問題は？

　例えば、
　　「リンゴが3個と
　　　みかんが2個あります。
　　　　全部で何個ありますか？」
　　　　　というような文章問題。
　　　　　　小学一年生の算数です。

　実際にやってみると、
　　正解を出しました。
　　　とりあえず、二回。正解。

　そこで、実験です。
```Text:文章問題やり取り
User
こんにちは。
以下のことを考えてください。

人がいます。
男性が十二人、女性が十六人です。
全員で、何人？

ChatGPT
こんにちは！

男性が12人で女性が16人ですね。それぞれの数を足すと、男性の人数と女性の人数がわかります。

12 + 16 = 28

よって、全員の人数は28人です。





User
そこに子どもが二十三人、加わりました。全員で何人？

ChatGPT
男性が12人、女性が16人、そして子供が23人加わりましたね。

男性の人数 + 女性の人数 + 子供の人数 = 全員の人数

12 + 16 + 23 = 51

よって、全員の人数は51人です。





User
そこに犬が三匹加わりました。
さて、何人？

ChatGPT
犬が3匹加わった場合、犬の数を人数には含めないと仮定します。つまり、前回の回答での全員の人数は変わりません。

全員の人数は引き続き51人です。





User
いいですね。
その人数で、ひとつの村が構成されました。
そんな村が、ほかに三つできました。
さて、何人？




ChatGPT
元の村の人数は51人ですが、その村が3つ増えたということで、それぞれの村には同じ人数の人がいると仮定します。

元の村 + 新たにできた村1 + 新たにできた村2 + 新たにできた村3 = 全体の人数

51 + 51 + 51 + 51 = 204

よって、全体の人数は204人です。





User
この四ツの村をひとつの町とします。
その後、人々が集まり、最初の町を加えて、六つの町ができました。
さて、何人？



ChatGPT
最初の町が204人で、それに新たにできた5つの町を加えるということですね。

最初の町 + 新たにできた町1 + 新たにできた町2 + 新たにできた町3 + 新たにできた町4 + 新たにできた町5 = 全体の人数

204 + 204 + 204 + 204 + 204 + 204 = 1224

よって、全体の人数は1224人です。


User
六つの町がひとつになって、ひとつの街になりました。
さて、何人？


ChatGPT
六つの町がひとつになってひとつの街になると、それぞれの町の人数を足し合わせることで全体の人数が求められます。

町1 + 町2 + 町3 + 町4 + 町5 + 町6 = 全体の人数

もし、各町の人数が同じであると仮定すると、全体の人数は各町の人数に6を掛けたものになります。

もし各町の人数が204人だった場合：

204 × 6 = 1224

よって、ひとつの街には1224人がいるということになります。
```

　どうでしょうか。
　　途中の引っ掛け問題（犬）も
　　　人数に含めることを
　　　　していません。

　　その都度、計算してみました。
　　　全部、正解です。

　しかも、
　　計算式に直していますね。

　これって、
　　「**内部的には、
　　　計算能力はある**」
　　　　ということ、ですよねぇ。

　そして、
　　こういう風に、
　　　文章問題を与えられたら
　　　　ChatGPTさんは、
　　　　　**意外とできる子**。

　とはいえ、
　　計算式を
　　　文章問題にしてやって
　　　　与えるような苦労をする
　　　　　必要もありませんねぇw

　むむむ、
　　使いどころに困りますなぁw
ChatGPTのstreamをつかったアプリを作るポイント (FastAPI & Svelte)

# はじめに
OpenAIのChatGPT APIをつかったチャットとかを作っていると、公式みたいにチャットの文字をストリーミングつかって徐々に出したいっていうことあると思います。短い文章ならまだいいとして、特に長い文章だと、応答が遅くなるだけでストレスですものね。

というわけで、ChatCompletionのstreamオプションをtrueにした際の実装の注意点を書き残したいと思ます。ちなみにサーバサイドは**FastAPI**で、フロントエンドは**Svelte**です。

# サーバサイド(FastAPI)

## ChatGPTのstreamオプションをtrueにする

ChatGPTをコールする際に、stream=Trueを指定します。engineのところ、AzureOpenAIの場合のコードになっています。素のOpenAIの場合は`model="gpt-3.5-turbo"`ですね。
```python
    response = openai.ChatCompletion.create(
        engine=settings.AOAI_MODEL, # engine = "deployment_name".
        messages=messages,
        temperature=temperature,
        stream = True,
    )
```

## ChatGPTのresponseをStreamingResonseで返す

stream=Trueにするとresponseのなかがchunk構成になっています。[公式の内部構造](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb)を参考に、ここから応答の文字列をストリームで返します。以下みたいな感じですね。これでサーバからは応答文字列がストリームで返ってきます。

```python
from fastapi.responses import StreamingResponse

def chatgpt_stream(response):
    for chunk in response:
        if chunk is not None:
            content = chunk["choices"][0]["delta"].get("content")
            if content is not None:
                yield content

@router.post("/test")
def message(messages: List[Message]):

    response = call_chatgpt([m.dict() for m in messages])

    return StreamingResponse(
        chatgpt_stream(response),media_type="text/event-stream"
    )
```

# フロントエンド(Svelte)

## フロントエンドでストリームを受け取る

Svelte側ではストリームで受け取った内容を、取得でき次第更新していきます。今回作りが配列を表示している関係で配列＝配列とかやってます（けど、もう少しうまくできそうではありますが）
ざっくり以下みたいなコードです。TextDecoderをつかって、文字をとって、表示文字を更新する感じです。

```javascript
      const response = await fetch("/simple/stream", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(send_chats)
      });

      const latest_chat = {"role":"assistant","content":""}
      chats = [...chats, latest_chat]

      if(response.body){

        const decoder = new TextDecoder();
        const reader = response.body.getReader();

        while(true){
          const {done, value} = await reader.read();
          if(done){
            break;
          }
          const text = decoder.decode(value);
          latest_chat.content += text;
          chats = chats
        }

      }
```

# おわりに
FastAPIとSvelteを使っている場合に、ChatGPTのレスポンスをどうストリーム化するかのポイントを紹介しました。ただ、個人的には別にぐるぐるローダーが回っていてもそんなに気にならないので、ここはもうUIどうするか次第ですけどね。
あと、Azure OpenAIの方はフィルターの設定次第では固まりの粒度が大きくなるとかあるみたいなので、これまた検証しないといけないです。

ひとまず文字がにょろにょろ出るようにはなりましたということで、以上です。
XcodeでiOSのSimulatorが真っ白のままの対処法

# 1.はじめに
Swiftを始めてまだ間も無く、基本的なことですぐにつまづいています。
今回はせっかく書いたコードをいざSimulatorで起動しようとしたら、なんと画面が真っ白のまま。コードを何度も確認しても間違いは見つからず、、途方に暮れていたところ解決策がわかりましたので共有します。

# 2.状態
画面が真っ白。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485517/eb10c085-c6f3-5b45-f3fa-9ae07ccf5550.png)

# 3.解決方法
コードの中にBreakpointがありました。
番号に青い付箋のようなものがついています。
こちらがあると処理が止まってしまうようです。
なので、削除するとSimulatorが動き出しました。

削除方法
1. Breakpointにカーソルを当てて右クリック
![スクリーンショット 2023-06-22 11.06.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485517/79e7ff24-ed81-f66e-e935-2dee38f9b5b9.png)
2. メニューの中から「Delete Breakpoint」を選択
![スクリーンショット 2023-06-22 11.07.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485517/d5bf875e-651e-7a4a-5063-b28bf6b49451.png)

or

ドラッグ＆ドロップでも削除可能

# 4.備考
ちなみに、Breakpointとはそのポイントで処理を中断させるもののようです。
コードの処理が止まってしまうので、Simulatorも真っ白になっていたというわけのようです。

もし同じように悩んでいる人がいましたら是非参考にしてみてください。
indexメソッドのまとめ

```ruby
index(pattern, pos = 0) -> Integer | nil
```
文字列のインデックス pos から右に向かって pattern を検索し、最初に見つかった部分文字列の左端のインデックスを返します。見つからなければ nil を返します。

引数 pattern は探索する部分文字列または正規表現で指定します。

pos が負の場合、文字列の末尾から数えた位置から探索します。

[PARAM] pattern:
探索する部分文字列または正規表現
[PARAM] pos:
探索を開始するインデックス
```ruby
例

p "astrochemistry".index("str")         # => 1
p "regexpindex".index(/e.*x/, 2)        # => 3
p "character".index(?c)                 # => 0

p "foobarfoobar".index("bar", 6)        # => 9
p "foobarfoobar".index("bar", -6)       # => 9

```

（上記事項は　Ruby3.2リファレンスマニュアルより引用しています。）


そして、文字列の先頭から検索を開始する場合、第二引数は省略が可能です。

なので、
```ruby
def count_code (str)
    puts str.index( "code",0 )+1
end
```
を、省略してこう書けます。
```ruby
def count_code(str)
    puts (str.index　( "code"　)　+1)
```
【備忘録】統計的因果推論手法まとめ

# 統計的因果推論とは

様々な統計手法で因果関係を測り、今後の道筋を推定すること



### 統計手法１【ランダム化比較試験】
ある集団をランダムに分けて介入有無で効果の違いを確かめること
医療関係（治験とか？）でよく使用される

### 統計手法２【回帰分析】
単回帰：y=a+bx
重回帰：y=a+b1x1+b2x2...
xを説明変数、yを目的変数にしたときの数値の関係を確かめる

### 統計手法３【マッチング理論】
aとbのメタ情報の違いによる摩擦を考慮する
労働市場での話でよく出てくる
様々な摩擦要因から条件があるような集団を形成し比較する

### 統計手法４【傾向スコア】
aに対する複数の交絡因子から傾向スコアを算出し、傾向スコアから群を構築し比較する

### 統計手法５【差分の差分】
元々あった差分を考慮して介入の効果を計測する。
時系列での効果測定で既存の傾きと介入後の傾きを比較する。


などなど

ただ、上記の統計手法を用いて因果関係を示したとしても、実際の因果関係を説明したわけではない
M1 Mac上でUTMでKali Linuxをインストールしようとしても画面真っ暗な件

備忘録として残しておきます。

# 結論

https://www.macobserver.com/tips/how-to/install-kali-linux-m1-m2-mac/

こちらのサイトを参考にしました。
### 1. isoを読み込んだ後、起動をする前に右上の設定マーク(ゴミ箱マークがある欄の一番右)をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842805/62f4c416-f6f4-cd4a-14f2-1dca6df17490.png)

### 2.　[デバイス] -> [新規] -> [シリアル]と選択

シリアルを作成した後の設定は特にいじらなくてもいけました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842805/ba1d1aac-7341-caa2-cc9e-4d90bbc04c7d.png)

### 3. 起動

シリアルで作成した2つ目のウィンドウにも表示されており、`Install`を押すと2つ目のウィンドウにのみインストールのウィザードが表示されます。
`Graphical Install`でも表示されるかは確認していないのでわかりかねますが、おそらく行けるかと思います。
いけなかったら大人しく`Install`でやってみましょう。(最終的にはどちらでもGUIで起動するのでご安心を。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842805/81880ce5-3064-3809-afc3-b74720349927.png)

### 4. インストール

詳しいインストールの進め方は今回の記事では本題ではないので省略させていただきます。
自分は上記のサイトを参考にしたので英語力に自信がなくてもDeepLなどを携えてそちらを参照してみてください（画像もある程度あるためそこまで読むのは難しくないかと思われます）。
終わった後isoを抜くのを忘れずに。

# 経緯

先日、持ち運ぶ用のPCが欲しく思いMacBook Air M1を購入しました。
Macということもあり、流石にVMはきついかなとは思っていたので、当初はペネトレ系はWindowsのPCでやろうと思っていたのですが、何分現在持っているPCがゲーミングで持ち運び易さに欠けてしまうため、やはり持ち運びしやすいこいつでペネトレの勉強をしたいなと思ったのでKali Linuxをインストールしようとしてました。
最初はWindowsPCでも愛用していたVirtual Boxに入れようとしてたのですが、エラーを吐いて起動ができなかったため諦めてUTMで入れることを決意しました。
しかしいくつかのサイトを参照して入れようとしても、タイトルのような状況に陥ってしまい、同様のシチュエーションで以前のバージョンを探して入れているサイトもありましたが、すでにアーカイブページが死んでいるのか、`curl`をしてもみつからず...
万事休すかと思っていたら、先述のサイトを見つけました。

# 最後に

なぜこんな現象が発生するのかはわかりませんが、何はともあれ無事インストールすることができて安心しています。
同じような状況に陥ってる方の参考になれば幸いです。
VSCode　ショートカットキー

## VSCodeのショートカットキー

### ファイル操作

- 新規ファイル作成: Ctrl + N
- ファイルを開く: Ctrl + O
- ファイルを保存: Ctrl + S
- ファイルを閉じる: Ctrl + W
### エディタの操作

- 行のコピー: Ctrl + C
- 行の切り取り: Ctrl + X
- 行の貼り付け: Ctrl + V
- 行を上に移動: Alt + ↑
- 行を下に移動: Alt + ↓
### デバッグ

- デバッグの開始/停止: F5
- ステップイン: F11
- ステップオーバー: F10
- ステップアウト: Shift + F11
### ターミナル

- ターミナルの開閉: Ctrl + `
- コマンドの実行: Ctrl + Shift + `
- コマンド履歴の表示: Ctrl + ↑ or Ctrl + ↓
### フォーマットと整理

- コードの整形: Shift + Alt + F
- 行をインデント: Ctrl + ]
- 行をアンインデント: Ctrl + [
### ナビゲーション

- ファイル内のシンボルを検索: Ctrl + Shift + O
- 定義にジャンプ: F12
- 定義から戻る: Alt + ←
商談の更新で関連する取引先の取引先責任者を更新する

+ [フローにて、オブジェクト間をまたいだレコード内項目の更新のかけ方](https://trailhead.salesforce.com/trailblazer-community/feed/0D54V000074V0Ui)

# 全体像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/a616f4cd-84af-e296-ca07-78b06398f2b8.png)

# 変数

**setContct レコード(単一)変数**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/b711c3fe-f298-2ac4-6670-1a98bff2c194.png)

**Contacts レコードコレクション変数**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/6f0d829b-cb20-d5a3-a7bc-0ef67389dd63.png)

# 取引先責任者の取得

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/4677b6c5-6aaf-588b-d77b-ec39a062097d.png)

# 取引責任者でループ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/e1dae963-3989-9391-67f3-a912d93ec4da.png)

# 取引先責任者の編集用

ここで取引先責任者の更新したい項目に値をセットします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/6babb0dc-3974-baff-ac01-2a0827960f2d.png)

# コレクション変数に追加

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/5a2d6508-2497-f486-b28a-44fc22e61dd8.png)


# レコードの更新を編集

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/80b71b7d-f51f-d912-0c2d-9134b8eab11e.png)
Vue3&Vuetify3でdisabledにしたボタンでTooltipを表示する

# やりたいこと
disabled(グレーアウト）させたボタンにホバーした際に、なぜそのボタンが利用できないのか説明するtooltipを表示させたい

意外とvue3, vuetify3で実現させる方法がさっと見つからなかったのでメモ。

# 課題
以下のような単純なコードで実装すると、
tooltipもdisableになってしまい、表示されなくなってしまう
```vue
<v-tooltip location="bottom" :disabled="!isChecked">
  <template v-slot:activator="{ props }">
     <v-btn v-bind="props" :disabled="isChecked">
     送信
     </v-btn>
　　　　</template><span>まずチェックを入れてください</span>
</v-tooltip>
```

# 結論
ボタンを他のtagでラップする（divやspanなど）
```vue
<v-tooltip location="bottom" :disabled="!isChecked">
  <template v-slot:activator="{ props }">
      <span v-bind="props">
         <v-btn :disabled="isChecked">
         送信
         </v-btn></span> 
　　　　</template><span>まずチェックを入れてください</span>
</v-tooltip>
```

# 参考
https://stackoverflow.com/questions/56370021/display-vuetify-tooltip-on-disabled-button

https://github.com/vuetifyjs/vuetify/issues/7077
AMD Ryzen 環境下での Intel fortran 最適化

# 内容
Intel fortranをAMDのCPU (Zen2, Zen3)で使う時の最適化オプション

# 結論
Ryzen CPUでは-archオプションを使うと非常に速くなった。
Intel CPUで有効な-xHostや-ax、-x、-parallelは効かない。
~~~text:最適化オプションの例 (Zen2, Zen3)
ifort -O3 -ipo -arch CORE-AVX2  (linux)
ifort /O3 /Qipo /arch:CORE-AVX2 (windows)
~~~
肝要なのは-archオプション。それ以外は状況に応じて変える。
CPUのarchtectureの探し方は後述。

# 環境
#### machine 1
CPU: AMD Ryzen Threadripper PRO 3995WX 64-Cores (Zen2)
OS: Ubuntu 22.04.2 LTS (lsb_release -a にて確認)
Compiler: ifort 2021.5.0 20211109 (ifort --version)

#### machine 2
CPU: AMD Ryzen 9 5950X 16-Core Processor (Zen3)
OS: Windows 11 22H2 (build 22621.1848)  (winverにて確認)
Compiler: ifort version 19.1.3.311 (build 20201010_000000)

# 命令セットを探す方法
CORE-AVX2など、そのCPUで一番早い命令セットを見つけるため、以下の操作を行った。

## ifort オプション検索
~~~
ifort -help codegen  (linux)
ifort /help          (windows)
~~~
-archにて使えそうなパラメタを探す。（CORE-AVX2, AVX, SSE4.2など）
※手持ちのifortではCORE-AVX512に対応していなかった。

## CPU architecture確認
下記コマンドを使う。(windowsでもwsl上で動く)
~~~
cat /proc/cpuinfo
~~~
対応する命令セット一覧が表示される
~~~text:3995WX (zen2)
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip rdpid overflow_recov succor smca sev sev_es
~~~
~~~text:5950X (zen3)
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave osxsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw wdt topoext perfctr_core mwaitx fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a rdseed adx smap clflushopt clwb sha_ni umip vaes vpclmulqdq rdpid
~~~
見つからないときは、grepで検索しても良い。（実際には該当箇所が赤字で表示される)
~~~
$cat /proc/cpuinfo | grep -i avx2
flags           : fpu ... bmi1 avx2 smep ...
~~~
virtualenvを用いずに仮想環境に入る

[PEP668](https://peps.python.org/pep-0668/)により、/usr/local/libや$HOME/.local/libへのpipによるモジュールインストールができなくなった。

「俺流！PEP668とうまくやっていく方法 https://blog.jp.square-enix.com/iteng-blog/posts/00043-play-with-the-pep668/ 」によると、これはsys.prefix / sys.exec_prefix変数で制御されているらしい。さらに、その制御の仕方は、「venvが動作する仕組みを調べてみた https://endy-tech.hatenablog.jp/entry/how_venv_works_in_python 」で解説されている。総合すると、virtualenvを使わずとも、以下の手順で実現できる。

- sitecustomize.pyを用意する。[^1]

```
PREFIX=/$HOME/internal PYTHONRELPATH=$(python3 -c 'import distutils.sysconfig as sysconf; import os; print(sysconf.get_python_lib(prefix="/"))') bash -c '
mkdir -p ${PREFIX}${PYTHONRELPATH}
echo "import sys; sys.prefix=sys.exec_prefix=\"${PREFIX}\"; sys.path.insert(0,\"${PREFIX}${PYTHONRELPATH}\");" > ${PREFIX}${PYTHONRELPATH}/sitecustomize.py
'
```

- `${PREFIX}${PYTHONRELPATH}`をPYTHONPATHに追加する。

シェルからpipを呼ぶときには実用性皆無ですが、外部ビルドツールからpipを呼びたい場合とかに有用かなと思いました。

[^1]: 弊で普通に行われているのだが一般に普通かは知らない