tag:crieit.net,2005:https://crieit.net/users/shikiyura/feed
しきゆらの投稿 - Crieit
Crieitでユーザーしきゆらによる最近の投稿
2024-04-27T13:37:45+09:00
https://crieit.net/users/shikiyura/feed
tag:crieit.net,2005:PublicArticle/18824
2024-04-27T13:37:04+09:00
2024-04-27T13:37:45+09:00
https://crieit.net/posts/truncate-string-by-javascript
【JS】テキストを一定の文字数で切り捨てる
<p>こんにちは、しきゆらです。<br />
今回は、SNSなどで時々見るような「一定数を超えたテキストを切り捨てる」処理を作ったのでメモしておきます。</p>
<h2 id="結論: 以下のコードでいい感じに端折れる"><a href="#%E7%B5%90%E8%AB%96%3A+%E4%BB%A5%E4%B8%8B%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A7%E3%81%84%E3%81%84%E6%84%9F%E3%81%98%E3%81%AB%E7%AB%AF%E6%8A%98%E3%82%8C%E3%82%8B">結論: 以下のコードでいい感じに端折れる</a></h2>
<pre><code class="js">const truncate = (str, max) => {
if (str.length < max) return str;
return `${str.slice(0, max - 1)}…`;
};
const truncateStringExceptURL = (str, max) => {
if (!str) return "";
// URLの正規表現
const urlRegexp = /https?:\/\/\S+/g;
// マッチしたURLリスト
const urls = [];
// 文字列からURLにマークして除外する
const markedString = str.replace(urlRegexp, (matchStr) => {
urls.push(matchStr);
return `>${urls.length - 1}<`;
});
// テキストを切り捨てる
const truncatedString = truncate(markedString, max);
// マークしたURL箇所の正規表現
const replaceUrlRegexp = />\d+</;
// マークをURLに置換
return truncatedString.replace(replaceUrlRegexp, (matchStr) => {
const urlIndex = parseInt(matchStr.replace(/>|</, ""), 10);
return urls[urlIndex];
});
};
</code></pre>
<p>細かく見ていきます。</p>
<h2 id="テキストを切り捨てる"><a href="#%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E3%82%8B">テキストを切り捨てる</a></h2>
<p>単純に一定の文字数で切り捨てるだけであれば、以下だけで実現できます。<br />
なお、ここでは文字数をString.lengthで簡易的に取得していますが、文字コード等によっては意図しない数になりそうです。<br />
正確な文字数に関しては、以下が参考になると思います。<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/suin/items/3da4fb016728c024eaca">JavaScript: 文字数を正確にカウントするには? #JavaScript - Qiita https://qiita.com/suin/items/3da4fb016728c024eaca</a></p>
<pre><code class="js">const truncate = (str, max) => {
if (str.length < max) return str;
return `${str.slice(0, max - 1)}…`;
};
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice">String.prototype.slice() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice</a></p>
<p>この中で、URLが入っている場合はURLの途中で切り捨てられてしまいます。<br />
それが問題にならなければよいのですが、私の場合はURLはそのまま出した方が便利だったのでURL以外の部分で切り捨てるようにしてみた結果が、最初のコードでした。</p>
<p>処理の流れとしては、以下の通り。</p>
<ul>
<li>文字列に含まれるURLを特定のマーカに置換</li>
<li>マーカに置換した文字列を指定された文字数に切り捨て</li>
<li>切り捨てられた文字列にあるURLマーカをURLに戻す</li>
</ul>
<p>それぞれ見てみます。</p>
<h3 id="文字列に含まれるURLを特定のマーカに置換"><a href="#%E6%96%87%E5%AD%97%E5%88%97%E3%81%AB%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8BURL%E3%82%92%E7%89%B9%E5%AE%9A%E3%81%AE%E3%83%9E%E3%83%BC%E3%82%AB%E3%81%AB%E7%BD%AE%E6%8F%9B">文字列に含まれるURLを特定のマーカに置換</a></h3>
<p>該当部分を抜き出します。</p>
<pre><code class="js">const truncateStringExceptURL = (str, max) => {
...
// URLの正規表現
const urlRegexp = /https?:\/\/\S+/g;
// マッチしたURLリスト
const urls = [];
// 文字列からURLにマークして除外する
const markedString = str.replace(urlRegexp, (matchStr) => {
urls.push(matchStr);
return `>${urls.length - 1}<`;
});
...
}
</code></pre>
<p>URLを表現する正規表現を定義し、文字列内にあれば配列に格納しつつマーカに保管しています。</p>
<p><code>String.prototype.replace()</code>は第1引数に検知するパターン、第2引数に置換するものを渡しますが、第2引数は文字列以外にも関数も渡すことができます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace">String.prototype.replace() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace</a></p>
<p>なお、この処理自体は過去記載した「YAMLに環境変数を埋め込む」や「YAMLにローカル変数を埋め込む」と同じ発想です。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2021/03/embed_env_value_in_yaml/">【Ruby】YAMLに環境変数を埋め込む | しきゆらの備忘録 https://shikiyura.com/2021/03/embed_env_value_in_yaml/</a></p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2021/10/embed_local_variable_in_yaml/">【Ruby】YAMLにローカル変数を埋め込む | しきゆらの備忘録 https://shikiyura.com/2021/10/embed_local_variable_in_yaml/</a></p>
<h3 id="マーカに置換した文字列を指定された文字数に切り捨て"><a href="#%E3%83%9E%E3%83%BC%E3%82%AB%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%97%E3%81%9F%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%95%E3%82%8C%E3%81%9F%E6%96%87%E5%AD%97%E6%95%B0%E3%81%AB%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6">マーカに置換した文字列を指定された文字数に切り捨て</a></h3>
<p>該当部分を抜き出します。</p>
<pre><code class="js">const truncate = (str, max) => {
if (str.length < max) return str;
return `${str.slice(0, max - 1)}…`;
};
const truncateStringExceptURL = (str, max) => {
...
// テキストを切り捨てる
const truncatedString = truncate(markedString, max);
...
}
</code></pre>
<p>URLをマーカに置換した文字列に対して、指定数以上の文字列を切り捨てる処理をかませます。</p>
<p>単純に<code>String.prototype.slice()</code>で先頭から最大数-1までの文字列を作り、末尾に…を入れてます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice">String.prototype.slice() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice</a></p>
<h3 id="切り捨てられた文字列にあるURLマーカをURLに戻す"><a href="#%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E3%82%89%E3%82%8C%E3%81%9F%E6%96%87%E5%AD%97%E5%88%97%E3%81%AB%E3%81%82%E3%82%8BURL%E3%83%9E%E3%83%BC%E3%82%AB%E3%82%92URL%E3%81%AB%E6%88%BB%E3%81%99">切り捨てられた文字列にあるURLマーカをURLに戻す</a></h3>
<p>該当箇所を抜き出します。</p>
<pre><code class="js">const truncateStringExceptURL = (str, max) => {
...
// マークしたURL箇所の正規表現
const replaceUrlRegexp = />\d+</;
// マークをURLに置換
return truncatedString.replace(replaceUrlRegexp, (matchStr) => {
const urlIndex = parseInt(matchStr.replace(/>|</, ""), 10);
return urls[urlIndex];
});
...
}
</code></pre>
<p>ここでは、マーカを正規表現で表現し、切り捨てた文字列からマーカ部分をURLに置換しています。<br />
処理的にはマーカに置き換える処理の反対なので特に複雑なことはないかと。</p>
<p>マーカ部分をもう少し綺麗に書けないかな、と思っていますがいい方法思いついてません。<br />
URLをマーカに置換する部分と、マーカからURLに置換するが分かれているので何とかしたい気持ち。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、SNSなどでよく見るような一定数を超えた文字列を端折る処理を書いたのでメモしました。</p>
<p>改めて全体を記載しておきます。</p>
<pre><code class="js">const truncate = (str, max) => {
if (str.length < max) return str;
return `${str.slice(0, max - 1)}…`;
};
const truncateStringExceptURL = (str, max) => {
if (!str) return "";
// URLの正規表現
const urlRegexp = /https?:\/\/\S+/g;
// マッチしたURLリスト
const urls = [];
// 文字列からURLにマークして除外する
const markedString = str.replace(urlRegexp, (matchStr) => {
urls.push(matchStr);
return `>${urls.length - 1}<`;
});
// テキストを切り捨てる
const truncatedString = truncate(markedString, max);
// マークしたURL箇所の正規表現
const replaceUrlRegexp = />\d+</;
// マークをURLに置換
return truncatedString.replace(replaceUrlRegexp, (matchStr) => {
const urlIndex = parseInt(matchStr.replace(/>|</, ""), 10);
return urls[urlIndex];
});
};
</code></pre>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18812
2024-04-16T23:38:37+09:00
2024-04-16T23:38:37+09:00
https://crieit.net/posts/JS-Google-Apps-Script-vite
【JS】Google Apps Scriptのコードをviteでバンドルする
<p>こんにちは、しきゆらです。<br />
今回は、Google Apps Script(GAS)をViteでビルドしてclaspでプッシュする環境を作っていきます。<br />
・・・久々のプログラム関連の記事な気がする。</p>
<p>個人的に、GASのコードをローカルで書き、JSのバンドラー?ビルダー?を使ってビルドしてclaspでアップロードする、というのがGASを書く上でのお作法だと思っています。</p>
<p>claspに関しては、こちらをご覧ください。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/google/clasp">GitHub - google/clasp: 🔗 Command Line Apps Script Projects https://github.com/google/clasp</a></p>
<p>ビルドツールも様々あります。<br />
Webpackやparcel、esbuildなどいろいろあるうえに、時代によって流行り廃りが速いのであまり追い切れてません。<br />
ビルドツールについてはいかがいろいろまとまっててよさそう。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/nakaakist/articles/86457bf2908379">JavaScriptビルドツールの整理 各ツールの機能と依存関係</a><br />
<a target="_blank" rel="nofollow noopener" href="https://zenn.dev/mizchi/articles/native-esm-age">Native ESM 時代のフロントエンドビルドツールの動向</a></p>
<p>また、過去のツールの流行り廃りは以下あたりが参考になるかと。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://stateofjs.com/en-US">State of JavaScript</a></p>
<p>ということで、いろいろあるわけですが<br />
今回は、tauriでちょっと使って動作が速かったviteを使ってGASをビルドする環境を作ってみようと思います。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://ja.vitejs.dev/">Vite | 次世代フロントエンドツール</a></p>
<p>Tauriを触ったときの記事はこの辺をご覧ください。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/08/create_simple_app_with_tauri/">【Rust】Tauriで簡易的なアプリを作ってみる</a></p>
<p>今回、調べてみるとviteを使ってGASを書く記事はHTMLを作ってWebアプリとしての環境構築ばかりでした。<br />
欲しいのはJSやTSをビルドするだけの環境です。<br />
色々調べているとviteの裏側にあるRollupのプラグインでGAS向けのものがあったのでこれを使ってみます。</p>
<h2 id="環境構築"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89">環境構築</a></h2>
<p>適当なフォルダで以下のツールをインストール。<br />
ここではyarn(v4.1.1)を使ってインストールしていますが、他のツールを使っている場合も同じような感じかと思うので読み替えていただければと。</p>
<p><code>yarn add clasp vite rollup-plugin-google-apps-script</code></p>
<p>それぞれのリポジトリをいかにおいておきます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/google/clasp">GitHub - google/clasp: 🔗 Command Line Apps Script Projects</a></p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/vitejs/vite">GitHub - vitejs/vite: Next generation frontend tooling. It's fast!</a></p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/mato533/rollup-plugin-gas#readme">GitHub - mato533/rollup-plugin-gas: Rollup plugin for Google Apps Script. This plugin supports local development of applications that run on Google Apps Script.</a></p>
<h2 id="vite/rollupの設定"><a href="#vite%2Frollup%E3%81%AE%E8%A8%AD%E5%AE%9A">vite/rollupの設定</a></h2>
<p>viteは設定ファイルが必要です。<br />
しかし、必要なものは以下に参考としてあるので、これをもとにちょっと手直しします。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/mato533/rollup-plugin-gas?tab=readme-ov-file#vite">GitHub - mato533/rollup-plugin-gas: Rollup plugin for Google Apps Script. This plugin supports local development of applications that run on Google Apps Script.</a></p>
<pre><code class="javascript">// vite.config.js
import { defineConfig } from "vite";
import rollupPluginGas from "rollup-plugin-google-apps-script";
import path from "path";
export default defineConfig({
plugins: [rollupPluginGas()],
build: {
rollupOptions: {
input: "src/main.js",
output: {
dir: "dist",
entryFileNames: "main.js",
},
},
minify: false, // trueにすると関数名が消えるのでfalse必須
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
</code></pre>
<p>ここでは<code>src/main.js</code>というファイルを作り、これを<code>dist/main.js</code>へビルドする形を作っていきます。</p>
<h2 id="GASのコードを書く"><a href="#GAS%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F">GASのコードを書く</a></h2>
<p>では、<code>src/main.js</code>へ適当なコードを書きます。<br />
単一ファイルだけでなく、せっかくなので分割してimportする形で書いてみます。</p>
<pre><code class="javascript">// src/main.js
import parts from "./parts";
const greet = () => {
Logger.log("hello");
};
global.greet = greet;
global.parts = parts;
</code></pre>
<pre><code class="javascript">// src/parts.js
export default parts = () => {
Logger.log("parts");
};
</code></pre>
<p>では、ビルドします。<br />
ビルドは以下のコマンドでできます。</p>
<p><code>yarn run vite build</code></p>
<p>ビルドすると以下のようなコードが吐き出されるはず。</p>
<pre><code class="javascript">// dist/main.js
var global = this;
function greet() {
}
;
function parts() {
}
;
(function(factory) {
typeof define === "function" && define.amd ? define(factory) : factory();
})(function() {
"use strict";
const parts$1 = parts = () => {
Logger.log("parts");
};
const greet2 = () => {
Logger.log("hello");
};
global.greet = greet2;
global.parts = parts$1;
});
</code></pre>
<p>では、コードをGASへアップしましょう。</p>
<h2 id="Google Apps Scriptへプッシュする"><a href="#Google+Apps+Script%E3%81%B8%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%81%99%E3%82%8B">Google Apps Scriptへプッシュする</a></h2>
<p>まずは、claspからログインしましょう。<br />
以下のコマンドを実行すると、ブラウザが立ち上がるので、画面に従ってGoogleアカウントへログインしましょう。</p>
<p><code>clasp login</code></p>
<p>ログイン後、以下のコマンドでApps Scriptを作成します。 なお、すでに作成済みの場合は場合はcloneでもよいです。</p>
<pre><code class="bash">> clasp create
? Create which script? standalon
reated new standalone script: <https://script.google.com/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit>
Warning: files in subfolder are not accounted for unless you set a '/path/to/project/.claspignore' file.
Cloned 1 file.
└─ /path/to/project/appsscript.json
</code></pre>
<p><code>clasp create</code>を実行するとどんなスクリプトか聞かれるので、今回はstandaloneを指定してます。</p>
<p>完了すると、URLが表示されます。<br />
これを開くと、作成されたApps Scriptへアクセスできます。<br />
また、<code>clasp open</code>でも開けます。</p>
<p>そして、<code>appsscript.json</code>、<code>.clasp.json</code>の2ファイルが生成されます。<br />
<code>.clasp.json</code>ファイルを開き、rootDirを<code>vite.config.js</code>で設定したdistフォルダを指定します。</p>
<p><code>// .clasp.json
{
"scriptId":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"rootDir":"/path/to/project/dist"
}</code></p>
<p>そして、<code>appsscript.json</code>ファイルをdistフォルダへコピーします。</p>
<p>では、<code>clasp push</code>でコードをApps Scriptへアップロードします。</p>
<p><code>clasp push</code></p>
<p>Apps Scriptを開くと、以下のようにコードが反映されているはずです。<br />
では、関数名を指定して実行ボタンを押して動作確認しましょう。<br />
<a href="https://crieit.now.sh/upload_images/7a5da27598b0ac7c7bde3fb86338c228661e8d0c5f2ec.webp" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a5da27598b0ac7c7bde3fb86338c228661e8d0c5f2ec.webp?mw=700" alt="Untitled_result.webp" /></a></p>
<p>greetを実行すると以下のように表示されるはずです。<br />
<a href="https://crieit.now.sh/upload_images/c069e96e53f499d91294f7f327e3c24f661e8d1ce2c95.webp" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c069e96e53f499d91294f7f327e3c24f661e8d1ce2c95.webp?mw=700" alt="Untitled2_result.webp" /></a></p>
<p>はい、これにてviteでGASを書くための環境ができました。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回はローカルでGASを書くためviteでビルドする環境を作ってみました。<br />
viteは裏側がrollupであり、プラグインもrollupのものが使えるようなので、プラグインを見てから判断してもよいかもしれません。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://ja.vitejs.dev/plugins/">プラグイン | Vite https://ja.vitejs.dev/plugins/</a></p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18679
2023-12-17T10:50:09+09:00
2023-12-17T10:55:37+09:00
https://crieit.net/posts/avoid-processing-limit-on-gas
【GAS】処理時間の制限を回避する
<p>こんにちは、しきゆらです。<br />
急な出社が続いてあまり記事を書けていない今日この頃です。<br />
今回は、以前投稿したGASでファイル一覧を取得するコードを載せましたが、実際の環境で処理回したら処理時間の制限に引っかかったので、回避するように直したのでその旨をメモしておきます。<br />
前回の記事はこちら。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/12/avoid_processing_limit_on_gas/">【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する | しきゆらの備忘録 https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/</a></p>
<p>改めて、GASの制限については以下を参照いただければと思います。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/guides/services/quotas?hl=ja#current_limitations">Google サービスの割り当て | Apps Script | Google for Developers https://developers.google.com/apps-script/guides/services/quotas?hl=ja#current_limitations</a></p>
<p>上記を見ると、スクリプトの制限としては1回の処理で6分までとなっています。 今回は、ここを回避していきます。</p>
<h2 id="回避の方針"><a href="#%E5%9B%9E%E9%81%BF%E3%81%AE%E6%96%B9%E9%87%9D">回避の方針</a></h2>
<p>処理時間の制限としては、処理時間が一定時間を超えたらそこでいったん処理を止める、というだけ。<br />
ただし、それだけだと処理しきれなかった部分が出てくるので、そこもケアしてあげましょう。</p>
<p>ざっくり手順としては以下の通り。</p>
<ul>
<li>定期的に処理時間の確認し、一定時間を超えていたら処理を止める</li>
<li>処理途中のデータを保存する</li>
<li>処理を続きから実行するためのトリガーを定義する</li>
<li>一時停止した場合に前回の処理から再開する</li>
</ul>
<p>それぞれ見ていきましょう。</p>
<p>なお、スクリプトのよって内容が変わると思いますが、ここでの例として前回のファイル・フォルダの一覧を取得する、というものに対してコードを書いていきます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/">【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する | しきゆらの備忘録 https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/</a></p>
<p>ファイル・フォルダ一覧を取得するコードはこんな感じ。</p>
<pre><code class="javascript">function allFiles(){
const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
const files = []; // ファイルオブジェクトを保持する配列
const subFolders = [targetDir.getFolders()];
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolders.push(folder.getFolders());
folders.push(...allFilesBy(folder));
}
}
return files;
}
function allFilesBy(folder) {
const files = []; // 対象となるフォルダにあるファイルオブジェクトを保持する配列
const fileIterator = folder.getFiles();
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
return files;
}
</code></pre>
<h2 id="処理時間を回避するコード"><a href="#%E5%87%A6%E7%90%86%E6%99%82%E9%96%93%E3%82%92%E5%9B%9E%E9%81%BF%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89">処理時間を回避するコード</a></h2>
<h3 id="処理時間の確認"><a href="#%E5%87%A6%E7%90%86%E6%99%82%E9%96%93%E3%81%AE%E7%A2%BA%E8%AA%8D">処理時間の確認</a></h3>
<p>処理時間を取得する機能はなさそうなので、単純に実行タイミングで<code>Date</code>オブジェクトを生成しておき、定期的に現在時刻との差分を求める形で簡易的に確認します。</p>
<p>// 処理の開始時点の日付・時刻を取得する</p>
<pre><code class="javascript">const startDate = new Date();
...
</code></pre>
<p>後は、定期的に処理の中で<code>startDate</code>と現在時刻との差分を求めて処理時間を計算しましょう。</p>
<p><code>Date</code>オブジェクトの差分だったり、<code>Date.getTime()</code>を使っても同じように取得できます。 なお、値はミリ秒なので注意が必要です。</p>
<pre><code class="javascript">...
// ミリ秒で計測する場合
const processingMilliSec = (new Date() - startDate);
// 秒に直す場合
const processingSec = (new Date() - startDate) / 1000;
...
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime">Date.prototype.getTime() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime</a></p>
<p>雑に関数にしておきます。 一定時間(ここでは300秒(= 5分)が経過したら処理を止めるようにしています。</p>
<pre><code class="javascript">function hoge() {
// 処理の開始時点の日付・時刻を取得する
const startDate = new Date();
// いろいろな処理
if (limitCheck(startDate)) return ;
}
function limitCheck(startDate: Date): boolean => {
const processingSec = (new Date().getTime() - startDate.getTime()) / 1000;
return processingSec >= 300; // 処理時間が300秒より大きい場合はtrueを返す
}
</code></pre>
<p>これにて、一定時間が経過した時点で処理を止める機能は完了です。</p>
<h3 id="処理途中のデータを保存する"><a href="#%E5%87%A6%E7%90%86%E9%80%94%E4%B8%AD%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B">処理途中のデータを保存する</a></h3>
<p>処理を止めるだけでは、次に実行したときに再開することはできません。 処理を止めたときに、次回実行時に処理途中から再開できるように</p>
<p>前述の通り、前回のファイル・フォルダ一覧を取得するコードを例として書いていきます。 前回の記事はこちら。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/">【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する | しきゆらの備忘録 https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/</a></p>
<p>データを保持する先は、以前に紹介したPropertiesServiceを使います。 PropertiesServiceに関しては、過去記事を書いているのでこちらもご覧ください。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2021/07/how_to_use_properties_service_at_gas/">【GAS】パスワードやトークンをコードに直書きしない方法 | しきゆらの備忘録 https://shikiyura.com/2021/07/how_to_use_properties_service_at_gas/</a></p>
<p>ファイル・フォルダの一覧を取得する場合、保持しなければいけないのは主に以下の3点ですね。</p>
<ul>
<li>取得したファイル・フォルダのリスト</li>
<li>処理途中のファイル・フォルダイテレータ</li>
<li>参照予定のフォルダリスト</li>
</ul>
<p>この3点の中で、最初の「取得したファイル・フォルダのリスト」については、おそらく取得した後で最終的にはスプレッドシートにまとめたりするはずなので、あえて<code>PropertiesService</code>に置いておかなくてもよいかもしれません。</p>
<p>一方で、「処理途中のファイル・フォルダイテレータ」や「参照予定のフォルダリスト」は処理途中で終了しなければいけない場合は保持しておかなければ続きから再開ができません。<br />
ということで、この2点を保持する形を作っていきます。</p>
<p>イメージ図を置いておきます。<br />
赤フォルダ配下にあるフォルダを取得するとき、<code>getFolders()</code>で青フォルダたち<code>FolderIterator</code>として取得できます。</p>
<p><a href="https://crieit.now.sh/upload_images/b3b529b8f6a5073642d0882136913d91657e55096d865.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b3b529b8f6a5073642d0882136913d91657e55096d865.png?mw=700" alt="image" /></a></p>
<p>青フォルダたちを<code>FolderIterator</code>で取得している間に下図の線の位置で時間切れとなった場合、残り2つのフォルダは次回に回さないといけません。<br />
合わせて、青フォルダ1の配下にある紫フォルダたちを取得する<code>FolderIterator</code>も取得済みなので、こいつらも次回処理するときに見る必要がありますよね。</p>
<p><a href="https://crieit.now.sh/upload_images/c6c8e59753fab560266a366bc25aeb18657e55155a660.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c6c8e59753fab560266a366bc25aeb18657e55155a660.png?mw=700" alt="image" /></a></p>
<p>ということで上記3点のうち、青フォルダの<code>FolderIterator</code>が「処理途中のファイル・フォルダイテレータ」、紫フォルダの<code>FolderIterator</code>が「参照予定のフォルダリスト」となります。<br />
では、それぞれの保持の仕方を見ていきます。</p>
<h3 id="イテレータの保持"><a href="#%E3%82%A4%E3%83%86%E3%83%AC%E3%83%BC%E3%82%BF%E3%81%AE%E4%BF%9D%E6%8C%81">イテレータの保持</a></h3>
<p><code>FolderIterator</code>、<code>FileIterator</code>のどちらも<code>getContinuationToken()</code>メソッドが定義されており、イテレータ処理で時間がかかる場合に途中から再開することができるようになっています。<br />
再開するには、DriveAppクラスに定義されている<code>continueFolderIterator</code>、<code>continueFileIterator</code>にトークンを渡せばよい。</p>
<pre><code class="javascript">// トークンの取得
const iteratorToken = fileIterator.getContinuationToken();
// イテレータの再開
const resumeIterator = DriveApp.continueFileIterator(iteratorToken);
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja#getContinuationToken%28%29">Class FolderIterator | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja#getContinuationToken()</a></p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/file-iterator?hl=ja#getContinuationToken%28%29">Class FileIterator | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/file-iterator?hl=ja#getContinuationToken()</a></p>
<p>このトークンをPropertiesServiceなどで保持しておけば再開できますね。</p>
<h3 id="参照予定のフォルダリストの保持"><a href="#%E5%8F%82%E7%85%A7%E4%BA%88%E5%AE%9A%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E4%BF%9D%E6%8C%81">参照予定のフォルダリストの保持</a></h3>
<p>前述の通り、フォルダやファイル一覧についてはPropertiesServiceを使って保持します。 なお、参照予定のフォルダ一覧はFolderIteratorが複数ある形なので、これらも上記の通りトークンに変換して保持します。</p>
<pre><code class="javascript">const subFolders = []; // 参照予定のFolderIteratorリストを保持する配列
...
// スクリプトプロパティを取得
const scriptProperty = PropertiesService.getScriptProperties();
// 参照予定のFolderIteratorをトークンに変換する
const subFolderTokens = subFolders.map(subFolder => subFolder.getContinuationToken());
// 参照予定のリストをJSONに変換して保持
scriptProperty.setProperty("folders", JSON.stringify(subFolderTokens));
</code></pre>
<p>処理途中のイテレータも同じく<code>getContinuationToken</code>メソッドでトークンを取得できるので 取得しつつ<code>subFolders</code>の先頭に置いておけば再開できそうですね。</p>
<p>ここまでで、一時停止のためのデータ保持が完了です。</p>
<h3 id="処理の続きを実行するトリガーを定義する"><a href="#%E5%87%A6%E7%90%86%E3%81%AE%E7%B6%9A%E3%81%8D%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%83%88%E3%83%AA%E3%82%AC%E3%83%BC%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B">処理の続きを実行するトリガーを定義する</a></h3>
<p>GASにはトリガーという機能があり、特定の時間やタイミングなどになったら処理を始める、ということを指定できます。<br />
トリガークラスについてはこの辺を参照ください。</p>
<p>トリガーの作成は<code>ScriptAppのnewTrigger()</code>で作成できます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/script/script-app?hl=ja#getProjectTriggers%28%29">Class ScriptApp | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/script/script-app?hl=ja#getProjectTriggers()</a></p>
<p>トリガー作成時に、トリガー起動時に呼び出す関数名を文字列で指定します。 ここでは、トリガーとして1分後に起動するトリガーを作成します。</p>
<pre><code class="javascript">ScriptApp.newTrigger(functionName).timeBased().after(1000 * 60).create();
</code></pre>
<p><code>newTrigger()</code>が返してくるのは<code>TriggerBuilder</code>クラスになっています。 詳しくはこちら。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/script/trigger-builder?hl=ja">Class TriggerBuilder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/script/trigger-builder?hl=ja</a></p>
<p><code>timeBased()</code>はトリガーの種類として時間を基準として動くタイプとして指定しています。 <code>after()</code>でトリガーが起動する時間をミリ秒で指定しています。 <code>create()</code>で指定したトリガーを作っているだけ。見ればわかる感じですね。</p>
<h3 id="一時停止した場合に前回の処理から再開する"><a href="#%E4%B8%80%E6%99%82%E5%81%9C%E6%AD%A2%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AB%E5%89%8D%E5%9B%9E%E3%81%AE%E5%87%A6%E7%90%86%E3%81%8B%E3%82%89%E5%86%8D%E9%96%8B%E3%81%99%E3%82%8B">一時停止した場合に前回の処理から再開する</a></h3>
<p>前回の処理がある場合は、前回の処理から再開してあげる必要があります。</p>
<p>先ほど<code>PropertiesService</code>に保持したデータですが、ここから取得してできるかどうかで判別したり、関数の引数の有無で判別したり、方法はいくつかありそうですがここでは<code>PropertiesService</code>にデータがあるかどうかで判別してみます。</p>
<pre><code class="javascript">// スクリプトプロパティを取得
const scriptProperty = PropertiesService.getScriptProperties();
// 前回の処理からの続きとなるデータを取得
const resumeData = scriptProperty.getProperty("folders");
if(resumeData === null) {
// 前回のデータがないので、ターゲットとなるフォルダを取得
} else {
// 前回のデータがあるので、再開する
}
</code></pre>
<p>再開する場合は、トークンを使ってイテレータの続きを取得します。 上記サンプルのelse部分は以下のような感じになります。</p>
<pre><code class="javascript">const tokenJson = JSON.parse(resumeData);
const subFolders = tokenJson.map(token => DriveApp.continueFolderIterator(token));
</code></pre>
<p>あとは、このトークンから復元したイテレータをひとつづつ取り出して処理を進めればよいですね。</p>
<p>まとめてコードを載せると以下のような感じ。</p>
<pre><code class="javascript">// 処理時間の制限を超えたかどうかのフラグ
let limitFlag = false;
function allFiles(){
const functionName = "allFiles";
const startDate = new Date();
let subFolders = []; // 配下にあるサブフォルダを保持する配列
let files = [];
// 中断データを取得する
const resume = getResume(functionName);
if (resume === null) {
// 中断データがない場合は、初期フォルダから処理を開始する
const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
subFolders.push(targetDir.getFolders());
files.push(...Array.from(allFilesBy(targetDir, startDate)));
} else {
// 中断データがある場合は、トークンからFolderIteratorに変換する
const tokensJson = JSON.parse(resume);
subFolders = tokensJson.map(token => DriveApp.continueFolderIterator(token));
}
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolders.push(folder.getFolders());
files.push(...allFilesBy(folder));
if(limitFlag || checkLimit(startDate)) break;
}
// 一定の処理時間を超えた場合、処理途中のFolderIteratorをトークンに変換して保存する
if(limitFlag || checkLimit(startDate)){
const tokens = [subFolder.getContinuationToken()];
const subFolderTokens = subFolders.map(subFolder => subFolder.getContinuationToken());
setResume(functionName, JSON.stringify(tokens.concat(subFolderTokens)));
break;
}
}
// 今回処理した結果を返す
return files;
}
function allFilesBy(folder) {
const files = []; // 対象となるフォルダにあるファイルオブジェクトを保持する配列
const fileIterator = folder.getFiles();
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
return files;
}
function checkLimit(startDate) {
const processingSec = (new Date().getTime() - startDate.getTime()) / 1000;
limitFlag = processingSec >= 300
return limitFlag;
}
// 中断データを取得する
function getResume(functionName) {
const scriptProperty = PropertiesService.getScriptProperties();
const properties = scriptProperty.getProperty(functionName);
scriptProperty.deleteProperty(functionName);
return properties;
}
// 中断データを保持し、トリガーを設定する
function setResume(functionName, data) {
const scriptProperty = PropertiesService.getScriptProperties();
scriptProperty.setProperty(functionName, data);
setTrigger(functionName);
}
// トリガーを設定する
function setTrigger(functionName) {
let triggers = ScriptApp.getProjectTriggers();
for(let trigger of triggers) {
if(trigger.getHandlerFunction() === functionName){
ScriptApp.deleteTrigger(trigger);
}
}
ScriptApp.newTrigger(functionName).timeBased().after(1000 * 60).create();
}
</code></pre>
<p>これで、処理時間の制限に引っかかるような長時間の処理が必要な場合、これを回避して処理させることができるようになりました。<br />
処理時間の制限があるのでGASを使うのはきびしいな、というような場合に参考にしていただければ幸いです。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、GASの処理時間の制限を回避するため、以下の項目を組み合わせて回避してみました。</p>
<ul>
<li>処理時間の計測</li>
<li>処理途中のデータをPropertiesServiceへ保存</li>
<li>処理再開のためのトリガー設定</li>
<li>前回の処理途中からの再開</li>
</ul>
<p>私の場合は、とあるフォルダ配下にあるファイルたちのオーナー一覧を取得してほしい、というお題が来たので上記のようなコードを書いていました。 RubyなどからAPI経由でデータ取得するよりも、GAS上で書く方がシンプルでしたが、ざっと書いたところ処理時間の制限に阻まれたのでリファレンス等を読みつつ変更した結果が今回の時期の内容になります。</p>
<p>GASでコードを書いた方がシンプルだが、処理にどの程度時間がかかるかわからない というような場合でもGASを使って処理させることができるようになるので、参考になればうれしいです。</p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18641
2023-11-04T21:30:36+09:00
2023-11-04T21:30:36+09:00
https://crieit.net/posts/how-to-get-file-list-in-google-drive
【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する
<p>こんにちは、しきゆらです。<br />
今回は、GASでGoogleドライブにあるファイル・フォルダの一覧を取得する方法をメモしておきます。</p>
<h2 id="ファイル・フォルダの一覧を取得するコード"><a href="#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%BB%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%AE%E4%B8%80%E8%A6%A7%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89">ファイル・フォルダの一覧を取得するコード</a></h2>
<h3 id="すべてのフォルダを取得する"><a href="#%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">すべてのフォルダを取得する</a></h3>
<p>すべてのフォルダを取得する場合、以下だけです。</p>
<pre><code class="JS">const folders = DriveApp.getFolders();
</code></pre>
<p><code>DriverApp.getFolders()</code>でGoogleドライブにあるフォルダのイテレータを取得できます。<br />
ドライブ内にあるすべてのファイルが欲しければ、このイテレータ内でファイル一覧を取得していけば可能ですね。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/drive-app?hl=ja#getfolders">Class DriveApp | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/drive-app?hl=ja#getfolders</a></p>
<p>なお、<code>Folder</code>クラスにも直下にあるフォルダ一覧を取得できる<code>getFolders()</code>メソッドが生えているので、同じように使えます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfolders">Class Folder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfolders</a></p>
<h3 id="フォルダ直下にあるファイル一覧を取得する"><a href="#%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E7%9B%B4%E4%B8%8B%E3%81%AB%E3%81%82%E3%82%8B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%B8%80%E8%A6%A7%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">フォルダ直下にあるファイル一覧を取得する</a></h3>
<p>上記のような<code>FolderIterator</code>から<code>Folder</code>クラスのインスタンスを取得します。<br />
<code>Folder</code>クラスには、直下にあるファイルのイテレータを取得するメソッドがあるのでこれを使います。</p>
<pre><code>const folder = folders.next(); // FolderIteratorからFolderオブジェクトを取得
const fileIterator = folder.getFiles();
const files = []; // ファイルオブジェクトを格納する配列
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfiles">Class Folder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfiles</a></p>
<p>これらを組み合わせれば、特定のフォルダ配下のすべてのファイル一覧を取得することができますね。 やってみましょう。</p>
<h2 id="特定のフォルダ配下にあるすべてのファイルを取得する"><a href="#%E7%89%B9%E5%AE%9A%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E9%85%8D%E4%B8%8B%E3%81%AB%E3%81%82%E3%82%8B%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">特定のフォルダ配下にあるすべてのファイルを取得する</a></h2>
<h3 id="ターゲットとなるフォルダを取得する"><a href="#%E3%82%BF%E3%83%BC%E3%82%B2%E3%83%83%E3%83%88%E3%81%A8%E3%81%AA%E3%82%8B%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">ターゲットとなるフォルダを取得する</a></h3>
<p>まずは、GASからターゲットとなるフォルダを取得します。</p>
<pre><code>// https://drive.google.com/drive/u/0/folders/xxxxxxのxxxxxxx部分
const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
</code></pre>
<p><code>targetDir</code>はGASの<code>Folder</code>クラスのインスタンスが入っています。</p>
<p>Folderクラスについては以下を参照。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder?hl=ja">Class Folder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder?hl=ja</a></p>
<h3 id="フォルダ一覧を取得する"><a href="#%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E4%B8%80%E8%A6%A7%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">フォルダ一覧を取得する</a></h3>
<p>前述の通り、<code>Folder</code>クラスには配下にあるフォルダ一覧を取得するメソッドが定義されているのでそれを使います。</p>
<pre><code>const folderIterator = targetDir.getFolders(); // Folderのイテレータオブジェクトが返ってくる
while (folderIterator.hasNext()) { // 参照していないFolderがあればループする
folders.push(folderIterator.next()); // .next()でFolderを取得
}
</code></pre>
<p><code>FolderIterator</code>クラスは以下を参照。<br />
よくあるイテレータで、<code>hasNext()</code>で次の要素があるかどうかを確認しつつ、次の要素があれば<code>next()</code>で取得する形です。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja">Class FolderIterator | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja</a></p>
<p>ただし、ここで取得できるのは<code>targetDir</code>直下にあるフォルダのみです。<br />
<code>targetDir</code>配下すべてを取得するにはちょっと工夫してあげる必要がります。</p>
<pre><code>const subFolders = [targetDir.getFolders()];
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolder.push(folder.getFolders()); // フォルダ一覧を取得し、次に参照する
}
}
</code></pre>
<p>配列に破壊的に追加しつつ<code>for</code>でループ処理するので、だいぶお行儀がよくない印象ですがいい方法が思いつきませんでした。<br />
<code>forEach</code>を使うと、追加された要素を参照してくれなかったので<code>for</code>で回しています。</p>
<p>ファイルの一覧を取得するコードの全体像はこんな感じ。<br />
最終的にはfilesの配列にファイルオブジェクトが集まるので、ここに対してあれこれすればよい。<br />
3重ループになっているので、ファイル取得部分を関数に分けるなどすればもう少し見やすくなるかなと思います。</p>
<pre><code>const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
const files = []; // ファイルオブジェクトを保持する配列
const subFolders = [targetDir.getFolders()];
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolders.push(folder.getFolders());
const fileIterator = folder.getFiles();
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
}
}
</code></pre>
<p>フォルダの一覧の場合は、<code>fileIterator</code>のループが不要で、<code>subFolder.next()</code>を配列に詰めればOK。<br />
もっとこう書けばええやん、というご指摘等あればコメントいただければ嬉しいです。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、GASでGoogleドライブにあるとあるフォルダ配下にあるすべてのファイルを取得する方法をメモしました。<br />
GASは結構機能が用意されているので、Googleサービスに対してやろうと思ったことは気軽に実装できそうな印象です。</p>
<p>ただし、GASには実行時間の制限などもあるので、この辺に気をつけて使いましょう。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/guides/services/quotas?hl=ja">Google サービスの割り当て | Apps Script | Google for Developers https://developers.google.com/apps-script/guides/services/quotas?hl=ja</a></p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18631
2023-10-29T22:01:49+09:00
2023-10-29T22:05:54+09:00
https://crieit.net/posts/how-to-fix-login-user-on-wsl
【WSL】ユーザアカウントにログインできなくなった場合の確認と対処方法
<p>こんにちは、しきゆらです。<br />
今回は、WSL起動時に初期設定したユーザでログインできなくなったので、調べたり対応したことをメモしておきます。</p>
<p>WSLのインストール時にユーザアカウントを作成しますが、いつの間にかユーザアカウントへログインできずにrootとしてログインしてしまう状況になっていました。<br />
何が起こっているのかわからなかったので、諸々も調べてユーザアカウントとしてログインできるよう修正したのでメモしておきます。</p>
<p>WSLはWindows Subsystem for Linuxという通り、動いているのはLinuxになります。<br />
なのでLinuxコマンドや各ファイルをもとに確認していきます。</p>
<h2 id="作業の流れ"><a href="#%E4%BD%9C%E6%A5%AD%E3%81%AE%E6%B5%81%E3%82%8C">作業の流れ</a></h2>
<p>ざっと流れを記載しておくと以下の通りです。</p>
<ul>
<li>現在ログインしているユーザを確認する</li>
<li>ユーザアカウントの有無を確認する</li>
<li>デフォルトのログインユーザを指定する</li>
<li>指定したユーザでログインできるか確認する</li>
</ul>
<p>では、それぞれを見ていきます。</p>
<h3 id="現在ログインしているユーザを確認する"><a href="#%E7%8F%BE%E5%9C%A8%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">現在ログインしているユーザを確認する</a></h3>
<p>ほぼログイン時のパスなどでわかると思いますが、念のため書いておきます。<br />
<code>whoami</code>コマンドで確認すると以下のように表示されます。</p>
<pre><code class="bash">> whoami
root
</code></pre>
<p>コマンド名の通り、現在のユーザを確認するコマンドになります。 rootとなっていれば大本の管理アカウントなので、普段使っているユーザアカウントとは異なっていることがわかります。</p>
<p>ユーザアカウントの有無を確認する</p>
<p>続いて、これまで使っていたユーザが残っているのかどうかを確認してみます。<br />
これはコマンドではなく<code>/etc/passwd</code>ファイルにまとまっているので、中身を確認してみます。<br />
このファイルには、ユーザ以外にもデーモンなど裏側で作られているものも含まれているので、grepコマンドでほしい情報があるかを絞ったほうが見やすいです。</p>
<pre><code class="bash"># 全体の確認
> cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
### これまでのアカウントがあるかどうかを確認する場合
> cat /etc/passwd | grep <ユーザ名>
# ファイルの中にユーザ名が含まれていればその行が表示され、含まれていなければ何も表示されません
</code></pre>
<p>grepしてユーザ名が書かれた行がなければ、<code>/home/<ユーザ名></code>のディレクトリを確認し必要なファイルのバックアップを取ったうえで初期化したほうが良いかもしれません。</p>
<p>私の場合は、このファイルにユーザ名の行があったので以下の手順で復帰しました。</p>
<h3 id="ログインするユーザを指定する"><a href="#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%99%E3%82%8B%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B">ログインするユーザを指定する</a></h3>
<p>上記でユーザが残っていることはわかったので、ログインするユーザを明示してあげます。<br />
記載するファイルは<code>/etc/wsl.conf</code>で以下のように記載します。</p>
<pre><code># 以下を追記
[user]
default=<ユーザ名>
# ログイン時にWindowsのパス情報を追加したくない場合は以下も記載
[interop]
appendWindowsPath = false
</code></pre>
<p>上記を記載したら、念のためWSLの再起動をしてあげた後にログインしてみましょう。<br />
PowerShellからWSLを止めます。</p>
<pre><code class="powershell"># PowerShellからWSLをシャットダウンする
> wsl --shutdown
</code></pre>
<h3 id="指定したユーザでログインできるか確認する"><a href="#%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E3%83%A6%E3%83%BC%E3%82%B6%E3%81%A7%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%8B%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">指定したユーザでログインできるか確認する</a></h3>
<p>WSLを一度シャットダウンしたので、該当するディストリビューションへログインましょう。</p>
<pre><code class="PoqweShell"># wslを立ち上げてログインする
> wsl -d <ディストリビューション>
</code></pre>
<pre><code class="bash"># whoamiコマンドで意図したユーザとしてログインできているかを確認する
> whoami
<ユーザ名>
</code></pre>
<p>無事に指定したユーザでログインできれば、修正完了です。</p>
<p>rootのままだった場合は、ユーザの指定を間違えていないかを確認してみてください。<br />
解消しなければ「ユーザアカウントの有無を確認する」のところにも記載していますが、バックアップを取ったうえで初期化したほうが安全かと思います。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、WSLのログインがrootになってしまったので修正方法を調べつつまとめました。<br />
私の場合は、たまたま今回はアカウントが残っていたので無事に修正できましたが、アカウントそのものが残っていない場合は復旧が難しいかもしれません。<br />
その場合は、できるだけ必要なファイルをバックアップしたうえで初期化するしかないかと思います。<br />
幸い、WSLの初期化は簡単なので使える状態にするには時間がかからないかと思います。</p>
<p>参考情報としてLinuxディストリビューションの登録解除の方法を置いておきます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://learn.microsoft.com/ja-jp/windows/wsl/basic-commands#unregister-or-uninstall-a-linux-distribution">WSL の基本的なコマンド | Microsoft Learn https://learn.microsoft.com/ja-jp/windows/wsl/basic-commands#unregister-or-uninstall-a-linux-distribution</a></p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18579
2023-09-27T12:17:24+09:00
2023-09-27T12:17:24+09:00
https://crieit.net/posts/get-recording-time-using-ruby-and-rust
【Rust/Ruby】OBS Studioの録画時間をスクリプトから取得してみる
<p>こんにちは、しきゆらです。<br />
連休は出かけていてブログ書けませんでした。</p>
<p>今回は、最近ちょっと触っているOBSをプログラムから操作したり情報を取得できるということを知ったので、サンプルを見ながら情報を取得してみようと思います。</p>
<p>OBSのドキュメントは以下になります。 詳しく知りたい方はこの辺を参考にするとよいかなと思います。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://docs.obsproject.com/">Welcome to OBS Studio’s documentation! — OBS Studio 29.1.3 documentation https://docs.obsproject.com/</a></p>
<p>上記を見るとC系での話が主なようですが、今回はよく使う言語であるRubyと、最近ちょっと触っているRustで書けることを目的として調べてみました。<br />
どうやらOBSはWebSocket経由で操作することができるようです。</p>
<p>公式のリポジトリにもOBSのWebSocketプラグインがあるようで、v28以降のバージョンに対応しています。 手元の環境では、初期状態で入っていてちょっと設定してあげればすぐ使えるようになったので、初めはこの辺を触ってみるのが良いかなと思います。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/obsproject/obs-websocket">GitHub - obsproject/obs-websocket: Remote-control of OBS Studio through WebSocket https://github.com/obsproject/obs-websocket</a></p>
<h2 id="Rubyから録画時間を取得する"><a href="#Ruby%E3%81%8B%E3%82%89%E9%8C%B2%E7%94%BB%E6%99%82%E9%96%93%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">Rubyから録画時間を取得する</a></h2>
<p>上記を受けてRubyから操作するgemがないかな、と思って調べてみると非公式ながらありました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/onyx-and-iris/obsws-ruby">GitHub - onyx-and-iris/obsws-ruby: Ruby clients for OBS Studio WebSocket v5.0 https://github.com/onyx-and-iris/obsws-ruby</a></p>
<p>まずは、RubyからOBSを操作するためにサンプルを見たり内部のスクリプトを見ながら録画時間を取得するコードを書いてみました。</p>
<pre><code class="ruby">require "obsws"
client = OBSWS::Requests::Client.new(
host: "IPアドレスなど",
port: ポート番号,
password: "PWの文字列"
)
loop do
p client.get_record_status.output_timecode
sleep 1
end
</code></pre>
<p>上記コードはシンプルに1秒ごとに録画時間を取得してターミナルへ表示するコードです。</p>
<p>IPアドレスやポート番号などは、OBSの「ツール」>「WebSocketサーバ設定」から確認・設定可能です。</p>
<p>実際にスクリプトを実行しつつOBSで録画すると、録画時間が表示されました。</p>
<h2 id="Rustから録画時間を取得する"><a href="#Rust%E3%81%8B%E3%82%89%E9%8C%B2%E7%94%BB%E6%99%82%E9%96%93%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">Rustから録画時間を取得する</a></h2>
<p>では、同様のことをRustでやってみます。<br />
RustからOBSのWebSocketを操作するcrateは、obs-websocketにリンクがあったこちらを使ってみました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/dnaka91/obws">GitHub - dnaka91/obws: The obws (obvious) remote control library for OBS https://github.com/dnaka91/obws</a></p>
<p>サンプルや内部コード、エラーを修正しつつできたのがこちら。</p>
<pre><code class="rust">use std::{thread, time};
use anyhow::Result;
use obws::{Client, client, requests::EventSubscription};
#[tokio::main]
async fn main() -> Result<()> {
let sleep_time = time::Duration::from_secs(1);
let config = client::ConnectConfig {
host: "IPアドレス",
port: ポート番号,
password: Some("PW文字列"),
event_subscriptions: Some(EventSubscription::all()),
broadcast_capacity: Some(100),
};
let client = Client::connect_with_config(config).await?;
loop {
let outputs = client.recording();
let status = outputs.status().await?;
println!("outputs: {}", status.timecode);
thread::sleep(sleep_time)
};
Ok(())
}
</code></pre>
<p>サンプルコードから動かしてみたのですが、諸々エラーだったりcrateが足りなかったりと動かず悪戦苦闘しながらここまで持っていきました。<br />
Rustのお作法をきちんと理解できていなので躓いただけかもですが、動く状態まで持っていけてよかったなと。</p>
<p>これにて、Rustでも同様にターミナルに録画時間を表示することができました。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、OBS Studioの操作をスクリプトからできることを知ったのでRuby/Rustで簡単なサンプルを作ってみました。<br />
どちらもライブラリが用意されているので、そこまで難しくはありませんでした。<br />
また、Rustの勉強にもなったのでちょっと自信が付いたかもしれません。</p>
<p>今回はここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18545
2023-07-29T23:01:28+09:00
2023-07-31T23:48:38+09:00
https://crieit.net/posts/fix-chromedriver-v115-distribution-change
【Ruby/Selenium】v115からChromeDriverの配布元が変わったようなので対応した話
<p>こんにちは、しきゆらです。<br />
数年ごとに時たま書いているSeleniumネタ、 今回は久々に大きく動かなくなったので調べつつ対応した記録を残しておきます。</p>
<h2 id="webdriversが動かなくなった"><a href="#webdrivers%E3%81%8C%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%8F%E3%81%AA%E3%81%A3%E3%81%9F">webdriversが動かなくなった</a></h2>
<p>これまでは<code>webdrivers</code>というgemを使い、インストールされているChromeのバージョンにあったChromeDriver良しなに取得するようにしていました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/titusfortner/webdrivers">GitHub - titusfortner/webdrivers: Keep your Selenium WebDrivers updated automatically</a></p>
<p>さっくり<code>webdrivers</code>の中身を見てみましたが、インストールしているChromeのバージョンを確認し、必要なDriverがなければ以下のサイトから該当バージョンのものを取得・配置するようです。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://chromedriver.storage.googleapis.com/"></a></p>
<p>「https://chromedriver.storage.googleapis.com/LATEST_RELEASE_114」のようにChromeのバージョン値を指定するとその最新版となるChromeDriverのバージョンを取得できるので、これをもとに取得する感じでした。</p>
<p>ところが、先日Seleniumを動かしたらChromeDriverがない、とエラーが出て動かなくなっていました。<br />
何事かと思って調べてみました。</p>
<h3 id="ChromeDriverの配布先が変わった"><a href="#ChromeDriver%E3%81%AE%E9%85%8D%E5%B8%83%E5%85%88%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%9F">ChromeDriverの配布先が変わった</a></h3>
<p>ChromeDriverのサイトを見てみると、配布先が変更されたようです。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://chromedriver.chromium.org/downloads">ChromeDriver - WebDriver for Chrome - Downloads</a></p>
<blockquote>
<p><strong>Latest ChromeDriver Binaries</strong></p>
<ul>
<li>Starting with M115 the latest Chrome + ChromeDriver releases per release channel (Stable, Beta, Dev, Canary) are available at <a target="_blank" rel="nofollow noopener" href="https://googlechromelabs.github.io/chrome-for-testing/">the Chrome for Testing availability dashboard</a>. For automated version downloading one can use the convenient <a target="_blank" rel="nofollow noopener" href="https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json">JSON endpoints</a>.</li>
<li>The older releases can be found at the <a target="_blank" rel="nofollow noopener" href="https://chromedriver.chromium.org/downloads">Downloads</a> page.</li>
</ul>
</blockquote>
<p>ということで<code>webdrivers</code>が参照していたサイトとは別のところで配布するようになったようです。</p>
<p>ついでに調べていると、Chrome for Testingなるものが出てきました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developer.chrome.com/blog/chrome-for-testing/">Chrome for Testing: reliable downloads for browser automation - Chrome Developers</a></p>
<p>以前も何かのタイミングでちらっと見た気がするんですが、あまり詳しく見てはいなかったので今回読んでみました。</p>
<p>どうやら、Chromeは自動更新なので開発者はテストをするタイミングによって意図しないバージョンになっていたりしてつらい、そうだテスト用に自動更新がないChromeを作ろう、ということのようです。</p>
<p>ということでテスト用Chromeとそのバージョン向けのChromeDriverが以下のサイトで一緒に配布されるようになっていました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://googlechromelabs.github.io/chrome-for-testing/#stable">Chrome for Testing availability</a></p>
<p>自前でChromeDriverを取得するスクリプトを組んでいる場合は、この辺の対応が必要になりそう。</p>
<h2 id="対応方法"><a href="#%E5%AF%BE%E5%BF%9C%E6%96%B9%E6%B3%95">対応方法</a></h2>
<h3 id="暫定対応"><a href="#%E6%9A%AB%E5%AE%9A%E5%AF%BE%E5%BF%9C">暫定対応</a></h3>
<p><code>webdrivers</code>でも対応するPRが上がってますが、記載時点(2023/07/29)ではまだマージされてません。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/titusfortner/webdrivers/pull/249">Fixed Webdrivers::VersionError with chrome version greater than 115 by sadahiro-ono · Pull Request #249 · titusfortner/webdrivers</a></p>
<p>こちらのissueで対応方法が記載されていました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/titusfortner/webdrivers/issues/247">Webdrivers trying to load a Chrome version that doesn't exist · Issue #247 · titusfortner/webdrivers</a></p>
<p>現在はv114向けのChromeDriverでも動くようなので、コード内でChromeDriverのバージョンを決め打ちすることでその場しのぎをすることができます。<br />
ただ、これもこのバージョンで動作しなくなるのは時間の問題なのでいつまで使えるかは不明です。</p>
<pre><code class="ruby">Webdrivers::Chromedriver.required_version = "114.0.5735.90"
</code></pre>
<h3 id="Selenium Manager"><a href="#Selenium+Manager">Selenium Manager</a></h3>
<p>issueを見ていくと、Selenium Managerなるものが出てきました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://www.selenium.dev/documentation/selenium_manager/">Selenium Manager (Beta)</a></p>
<p>まだBetaのようですが、Selenium自体に含まれる各WebDriverの取得などをしてくれるツールのようです。</p>
<p>これまでは、手動で配布サイトから取得したり上記<code>webdrivers</code>のような別ツールでWebDriverを取得して使う必要がありましたが、Selenium Managerを使えば外部ツールを使わずにSeleniumだけで完結するようになりますね。</p>
<p>使い方も、以下の3点を満たしていれば勝手に動いてくれるようです。</p>
<ul>
<li><code>Service</code>クラスでWebDriverのパスを指定していない</li>
<li><code>webdrivers</code>のような外部のWebDriver管理ツールを使っていない</li>
<li>環境変数<code>PATH</code>の中にWebDriverがない</li>
</ul>
<p>ということで、<code>webdrivers</code>が動かないのでこっちを使ってみることにしました。</p>
<p>ここでは、タイトルの通りRuby環境である前提で記載しますが<br />
<code>selenium-webdriver</code>のgemを最新にしたうえで、上記3点を満たすように環境を整えるだけで勝手に使えるようになります。</p>
<p>なお、<code>selenium-webdriver</code>のCHANGELOGを見てみると、4.6.0のころからSelenium Managerへ対応が入っているようです。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES"></a></p>
<p>手元では、<code>webdrivers</code>のみしか使っていないのでこれを削除するだけで利用できました。</p>
<p><code>webdrivers</code>がない状態でSeleniumを使ってブラウザを立ち上げようとするとターミナル上に以下のように記載されてChromeが立ち上がるようになりました。</p>
<pre><code class="ruby">2023-07-29 20:53:08 WARN Selenium applicable driver not found; attempting to install with Selenium Manager
</code></pre>
<p>Selenium Managerが良しなに動いてくれているようです。</p>
<p>これで、対応はおしまいです。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回はSeleniumを使っていたらChromeDriverを取得できずに死んでしまったので、原因を調べつつ対応してみました。</p>
<p>結果としては、外部ツールに頼ることなくSeleniumが擁してくれているSelenium Managerを使うことで無事動くようになりました。</p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18432
2023-05-11T23:28:12+09:00
2023-05-11T23:28:12+09:00
https://crieit.net/posts/resolve-404-error-at-apt-update
【Ubuntu】apt updateで404エラーがでたので解消する
<p>こんにちは、しきゆらです。<br />
今回は、Ubuntuで<code>apt update</code>を実行したときに404エラーが発生していたので<br />
解消するために色々調べつつ対応したのでメモしておきます。</p>
<p>今回利用していたUbuntuのバージョンは以下の通り。</p>
<pre><code>$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.10"
NAME="Ubuntu"
VERSION_ID="22.10"
VERSION="22.10 (Kinetic Kudu)"
...
</code></pre>
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/05/resolve_key_is_stored_in_legacy_trusted-gpg-keyring_issue/">前回のPostgreSQLのインストール</a>のように、あれこれリポジトリを追加したりOSを更新していると<code>apt update</code>を実行するとリポジトリの一部で404エラーが出るようになっていました。<br />
出ていたエラーは以下の通り。</p>
<pre><code>$ sudo apt update
...
Err:10 <https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu> kinetic Release
404 Not Found [IP: 185.125.190.52 443]
</code></pre>
<p>エラーが出ていたのは「<code>ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu</code>」でした。<br />
何を入れたときのものかすでに覚えていないですが、404であれば不要だろう、ということで解消していきます。</p>
<h2 id="解消方法"><a href="#%E8%A7%A3%E6%B6%88%E6%96%B9%E6%B3%95">解消方法</a></h2>
<p>ざっくりと手順を記載すると以下の通りです。</p>
<ol>
<li>対象リポジトリを管理しているファイルを探す</li>
<li>リポジトリを無効化する</li>
<li>解消されたかを確認する</li>
</ol>
<p>それぞれに分けてみていきます。</p>
<h3 id="対象リポジトリを管理しているファイルを探す"><a href="#%E5%AF%BE%E8%B1%A1%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%82%92%E7%AE%A1%E7%90%86%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%8E%A2%E3%81%99">対象リポジトリを管理しているファイルを探す</a></h3>
<p>調べてみると、apt updateで参照するリポジトリは/etc/apt配下にあるxxx.listというファイルで管理されているようです。<br />
<a target="_blank" rel="nofollow noopener" href="https://help.ubuntu.com/community/Repositories/CommandLine">Repositories/CommandLine - Community Help Wiki</a></p>
<p>ということで、この中にあるファイルの中に該当するURLが書かれているファイルを探しましょう。 まずは、該当するリポジトリが書かれたファイルの一覧を取得してみます。<br />
なお、出力結果は環境によって異なるので適宜ご自身の環境に合わせて読み替えてください。</p>
<pre><code>$ find /etc/apt -name "*.list"
/etc/apt/sources.list
/etc/apt/sources.list.d/amdgpu.list
/etc/apt/sources.list.d/rocm.list
/etc/apt/sources.list.d/deadsnakes-ubuntu-ppa-kinetic.list
/etc/apt/sources.list.d/amdgpu-proprietary.list
/etc/apt/sources.list.d/docker.list
/etc/apt/sources.list.d/pgdg.list
</code></pre>
<p>取得できたそれぞれのファイルの中を確認していきます。<br />
とはいえ、catとかエディタとかでひとつづつ見ていくのは手間。<br />
この辺もコマンドでやってしまいましょう。</p>
<pre><code>$ find /etc/apt -name "*.list" | xargs grep ppa
/etc/apt/sources.list.d/deadsnakes-ubuntu-ppa-kinetic.list:deb <https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/> kinetic main
/etc/apt/sources.list.d/deadsnakes-ubuntu-ppa-kinetic.list:# deb-src <https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/> kinetic main
</code></pre>
<p>なお、xargsは標準入力やファイルなどからリストを受け取って、それを別コマンドの引数とするコマンドのようです。<br />
<a target="_blank" rel="nofollow noopener" href="https://atmarkit.itmedia.co.jp/ait/articles/1801/19/news014.html">【 xargs 】コマンド――コマンドラインを作成して実行する:Linux基本コマンドTips(176) - @IT</a></p>
<p>これで、該当するファイルのパスと内容を確認できました。</p>
<h3 id="リポジトリを無効化する"><a href="#%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%82%92%E7%84%A1%E5%8A%B9%E5%8C%96%E3%81%99%E3%82%8B">リポジトリを無効化する</a></h3>
<p>無効化するには、単純にコメントアウトすればよいだけなので以下のように先頭に#を追加。</p>
<pre><code>cat /etc/apt/sources.list.d/deadsnakes-ubuntu-ppa-kinetic.list
# deb <https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/> kinetic main
# deb-src <https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/> kinetic main
</code></pre>
<p>これにて対応は完了。</p>
<h3 id="解消されたかを確認する"><a href="#%E8%A7%A3%E6%B6%88%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">解消されたかを確認する</a></h3>
<p>apt updateを実行して404エラーが出ないことを確認できれば完了です。</p>
<p>私の環境では特に不要としてコメントアウトしてしまっていますが、リポジトリを追加したということは何らかのアプリ等をインストールするために追加しているはずなので<br />
利用しているパッケージが度のリポジトリで管理されているかを確認してあげる必要があるかと思います。<br />
未確認ですが、この辺で確認できそうです。<br />
<a target="_blank" rel="nofollow noopener" href="https://kazuhira-r.hatenablog.com/entry/2022/08/13/202239">現在認識しているaptリポジトリーの一覧や、リポジトリーに含まれているパッケージを調べたい - CLOVER🍀</a></p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回はUbuntuでapt update時に404エラーが発生していたので、解消するために原因や解消方法を調べました。</p>
<p>何気なく使っているパッケージマネージャの裏側をなんとなくチラ見できた気がします。<br />
調べてみると、よく使うものでもよくわからずに使っているんだなぁというのと、もう少し深く知りたくなります。</p>
<p>今回はここまで。<br />
おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18427
2023-05-07T08:47:15+09:00
2023-05-07T08:55:07+09:00
https://crieit.net/posts/resolve-key-is-stored-in-legacy-truste-gpg-keyring-issue
【Ubuntu】apt updateでKey is stored in legacy trusted.gpg keyringと注意されたので対応する
<p>こんにちは、しきゆらです。<br />
今回は公式のドキュメントを見ながら対応したのに、<code>apt update</code>で怒られるようになったので<br />
なぜなのか調べつつ、対応方法を見つけて対応したのでメモしておきます。</p>
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>今回起こったのはPostgreSQLのインストール時のこと。<br />
PostgreSQLをインストールするためにリポジトリ追加を行った。 手順はこれ。<br />
<a target="_blank" rel="nofollow noopener" href="https://www.postgresql.org/download/linux/ubuntu/">PostgreSQL: Linux downloads (Ubuntu) </a></p>
<p>該当箇所は以下の通り。</p>
<pre><code class="zsh"># Create the file repository configuration:
$ sudo sh -c 'echo "deb <http://apt.postgresql.org/pub/repos/apt> $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
# Import the repository signing key:
$ wget --quiet -O - <https://www.postgresql.org/media/keys/ACCC4CF8.asc> | sudo apt-key add -
# Update the package lists:
$ sudo apt-get update # <= ここでWarningが出た
...
Key is stored in legacy trusted.gpg keyring
</code></pre>
<p>ということで、記載されているが、そもそも何を怒っているのかを調べてみました。</p>
<h2 id="何が起きているのか"><a href="#%E4%BD%95%E3%81%8C%E8%B5%B7%E3%81%8D%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B">何が起きているのか</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://gihyo.jp/admin/serial/01/ubuntu-recipe/0675">apt-keyはなぜ廃止予定となったのか</a><br />
<a target="_blank" rel="nofollow noopener" href="https://salsa.debian.org/apt-team/apt/-/commit/ee284d5917d09649b68ff1632d44e892f290c52f">Fully deprecate apt-key, schedule removal for Q2/2022 (ee284d59) · Commits · APT Developers / apt · GitLab</a></p>
<p>apt-keyコマンドがだいぶ前の2020年8月ころから廃止予定で、2022年Q2に廃止となっていたようです。<br />
手元の環境はWSL2上のUbuntu 22.10でしたが、まだ残っているようでした。<br />
今後はなくなるんでしょう。</p>
<blockquote>
<p>セキュリティ上の懸念点からくるもので、簡単にまとめると次の2点が理由です。</p>
<ol>
<li>apt-key addは単一ファイル(/etc/apt/trusted.gpg)に鍵を追加していくため、複数のリスクの異なるリポジトリの鍵を同じ権限で管理しなくてはならない。</li>
<li>リポジトリ鍵として追加した鍵は、すべてのリポジトリに対して適用される。</li>
</ol>
<p>引用元: https://gihyo.jp/admin/serial/01/ubuntu-recipe/0675</p>
</blockquote>
<p>ということで、リポジトリごとにリスクが異なるカギを全体で使うのよくいないよね、ということでこれをやめた、という感じなのかなと。<br />
やめたはいいが、この記事時点では代替となるものが用意されていないようです。 今後は、各リポジトリで利用するカギを/usr/share/keyrings/配下に設置して個別に指定する運用が良いだろう、として締めています。</p>
<h2 id="対応方法"><a href="#%E5%AF%BE%E5%BF%9C%E6%96%B9%E6%B3%95">対応方法</a></h2>
<p>大きく分けると以下の通り。</p>
<ol>
<li>既存のカギを削除する</li>
<li>別途カギを取得し設定しなおす</li>
<li>リポジトリとカギを紐づける</li>
</ol>
<h3 id="1. 既存のカギを削除する"><a href="#1.+%E6%97%A2%E5%AD%98%E3%81%AE%E3%82%AB%E3%82%AE%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B">1. 既存のカギを削除する</a></h3>
<pre><code class="zsh">$ apt-key list
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
/etc/apt/trusted.gpg.d/apt.postgresql.org.gpg # <= これを削除する
---------------------------------------------
pub rsa4096 2011-10-13 [SC]
B97B 0AFC AA1A 47F0 44F2 44A0 7FCC 7D46 ACCC 4CF8
uid [ unknown] PostgreSQL Debian Repository
...
# 鍵の削除はfinger printの最後の8文字のようです
$ sudo apt-key del ACCC4CF8
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
OK
# 消えたことを確認
$ apt-key list
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
...
</code></pre>
<h3 id="2. 別途カギを取得し設定しなおす"><a href="#2.+%E5%88%A5%E9%80%94%E3%82%AB%E3%82%AE%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%AA%E3%81%8A%E3%81%99">2. 別途カギを取得し設定しなおす</a></h3>
<p>カギについては、ここに記載されているので取得して入れなおします。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://www.postgresql.org/download/linux/ubuntu/">PostgreSQL: Linux downloads (Ubuntu) </a></p>
<pre><code class="zsh"># カギの取得
$ wget <https://www.postgresql.org/media/keys/ACCC4CF8.asc>
# カギをgpgコマンドを使って変換
$ gpg --no-default-keyring -o postgresql-keyring.gpg --dearmor ACCC4CF8.asc
# カギを確認
$ file postgres-keyring.gpg
postgres-keyring.gpg: GPG keybox database version 1 ...
# 変換したカギを/usr/share/keyrings/へ設置する
# 手元ではフォルダがなかったので手動で作成
$ sudo mkdir -p /usr/share/keyrings
$ sudo cp postgres-keyring.gpg /usr/local/share/keyrings
</code></pre>
<h3 id="3. リポジトリとカギを紐づける"><a href="#3.+%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%A8%E3%82%AB%E3%82%AE%E3%82%92%E7%B4%90%E3%81%A5%E3%81%91%E3%82%8B">3. リポジトリとカギを紐づける</a></h3>
<p>リポジトリは/etc/apt/sources.list.d/pgdg.listに作成しているので、このファイルを以下のように変更。</p>
<pre><code class="zsh"># deb [signed-by=/path/to/gpg_key] URLの形式でカギの場所を記載
deb [signed-by=/usr/local/share/keyrings/postgresql-keyring.gpg] <http://apt.postgresql.org/pub/repos/apt> kinetic-pgdg main
</code></pre>
<p>これにて対応完了です。<br />
Warningが出ないことを確認しましょう。</p>
<pre><code class="zsh"># warningが出ないことを確認
$ sudo apt update
...
Hit:7 <http://apt.postgresql.org/pub/repos/apt> kinetic-pgdg InRelease
</code></pre>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、apt-keyコマンドが廃止となっているために、ドキュメント通りに対応してもWarningが出てしまっていたので、諸々調べつつ対応してみました。 そもそもの廃止から知らなかったので勉強になりました。</p>
<p>なお、DockerやGithub CLIでは /etc/apt/keyrings 配下に設置しているようです。<br />
<a target="_blank" rel="nofollow noopener" href="https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository">Install Docker Engine on Ubuntu | Docker Documentation</a><br />
<a target="_blank" rel="nofollow noopener" href="https://github.com/cli/cli/issues/5122">Linux installation instructions recommend storing GPG keyring in the wrong location · Issue #5122 · cli/cli · GitHub</a></p>
<p>管理をユーザ側でやる、という形であれば1か所にまとめたほうが良いので どこか1か所を決めて、リポジトリ追加時にそこへ追加していく方法をとったほうが良いかもしれません。<br />
DebianのWikiにも /etc/apt/keyrings 配下に設置しよう、となっているのでこっちに合わせたほうが良いかもしれません。<br />
<a target="_blank" rel="nofollow noopener" href="https://wiki.debian.org/DebianRepository/UseThirdParty">DebianRepository/UseThirdParty - Debian Wiki</a></p>
<p>今回はここまで。<br />
おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18318
2022-11-19T08:32:00+09:00
2022-11-19T08:33:44+09:00
https://crieit.net/posts/systemd-supported-in-wsl
【WSL2】systemdがサポートされたようなので試してみた
<p>ここ数年はWindowsの環境をクリーンインストールするたびにWSL2の環境を作り直し、その度にgenieを使ってsystemdをPID 1 にするための方法をメモしてきました。<br />
<a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2020/06/execute_systemctl_on_wsl2/">【WSL2】systemctlが動かない問題をきちんと解決する</a><br />
<a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2020/08/run_systemd_as_pid_1_on_wsl2/">【WSL2】Ubuntu20.04でPID1をSystemdにする</a><br />
<a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2021/03/run_systemd_as_pid_1_on_wsl2_at_202103/">【WSL2/Ubuntu】systemdをPID1で動かす 2021年3月版</a></p>
<p>しかし、気が付いたらWSL自体がsystemdをサポートしたようなので、WSLだけでsystemdをPID 1にしてみたのでメモしておきます。</p>
<p>詳しいことは公式ブログを参照ください。<br />
<a target="_blank" rel="nofollow noopener" href="https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/">Systemd support is now available in WSL!</a></p>
<h2 id="設定手順"><a href="#%E8%A8%AD%E5%AE%9A%E6%89%8B%E9%A0%86">設定手順</a></h2>
<p>公式ブログによると、WSLのバージョンが0.67.6以上で使えるとのこと。<br />
なお、手元のWSLのバージョンを確認するとすでに1.0になっていたのでだいぶ前から使えたんだなぁともっと早く気づきたかった気持ちでいっぱいです。<br />
確認するためのコマンドは以下の通り。</p>
<pre><code class="powershell">powershell > wsl --version
WSL バージョン: 1.0.0.0
カーネル バージョン: 5.15.74.2
WSLg バージョン: 1.0.47
MSRDC バージョン: 1.2.3575
Direct3D バージョン: 1.606.4
DXCore バージョン: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windowsバージョン: 10.0.22621.819
</code></pre>
<p>なお、WSLの更新もwslコマンドでできるようになったようなので、バージョンが古い場合は以下のコマンドで更新すればよい。</p>
<pre><code class="powershell">powershell > wsl --update
</code></pre>
<p>次に、WSL環境にwsl.confというファイルを作成し、以下を書き込む。</p>
<pre><code class="bash">wsl(Ubuntu) > sudo emacs /etc/wsl.conf
# 以下を追記
[boot]
systemd=true
</code></pre>
<p>書き込んだらWSL環境を再起動。<br />
これにて準備完了です。</p>
<pre><code class="powershell">powershell > wsl --shutdown
</code></pre>
<p>この後、WSL環境に入りsystemctlを動かしてみます。<br />
以下のようにエラーが起きずにつらつら表示されたらOK。</p>
<pre><code class="bash">wsl(Ubuntu) > systemctl
UNIT LOAD ACTIVE SUB DESCRIPTION >
sys-devices-LNXSYSTM:00-LNXSYBUS:00-ACPI0004:00-VMBUS:00-0fd20160\x2d7ac7\x2d4158\x2d9e38\x2d8685d280c3a6-net-eth0.device loaded active plugged /sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:>
sys-devices-LNXSYSTM:00-LNXSYBUS:00-ACPI0004:00-VMBUS:00-ba31a026\x2d5b46\x2d4a8d\x2da6dc\x2df00324cf1e90-pci5b46:00-5b46:00:00.0-virtio0-virtio\x2dports-vport0p0.device loaded active plugged /sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:>
sys-devices-LNXSYSTM:00-LNXSYBUS:00-ACPI0004:00-VMBUS:00-fd1d2cbd\x2dce7c\x2d535c\x2d966b\x2deb5f811c95f0-host0-target0:0:0-0:0:0:0-block-sda.device loaded active plugged Virtual_Disk
sys-devices-LNXSYSTM:00-LNXSYBUS:00-ACPI0004:00-VMBUS:00-fd1d2cbd\x2dce7c\x2d535c\x2d966b\x2deb5f811c95f0-host0-target0:0:0-0:0:0:1-block-sdb.device loaded active plugged Virtual_Disk
sys-devices-LNXSYSTM:00-LNXSYBUS:00-ACPI0004:00-VMBUS:00-fd1d2cbd\x2dce7c\x2d535c\x2d966b\x2deb5f811c95f0-host0-target0:0:0-0:0:0:2-block-sdc.device loaded active plugged Virtual_Disk
sys-devices-platform-serial8250-tty-ttyS0.device loaded active plugged /sys/devices/platform/serial8250/tty/ttyS0
sys-devices-platform-serial8250-tty-ttyS1.device loaded active plugged /sys/devices/platform/serial8250/tty/ttyS1
sys-devices-platform-serial8250-tty-ttyS2.device loaded active plugged /sys/devices/platform/serial8250/tty/ttyS2
sys-devices-platform-serial8250-tty-ttyS3.device loaded active plugged /sys/devices/platform/serial8250/tty/ttyS3
...
</code></pre>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、WSLがsystemdをサポートしたということで設定してみました。<br />
今後はgenieを入れずともWSLだけでsystemdを使えるので、より手軽に環境を使えるようになりました。</p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18287
2022-08-24T23:45:46+09:00
2022-08-24T23:53:31+09:00
https://crieit.net/posts/clean-dist-folder-by-webpack
【JS/Webpack】ビルドしたフォルダの不要ファイルを削除する
<p>こんにちは、しきゆらです。今回は、Webpackでビルドしたものが出力されるフォルダにできる不要ファイルを削除する方法を知ったのでメモしておきます。</p>
<p>WebpackでJSのコードをビルドしていると、結構な頻度で不要なファイルができたりします。例えば、デバッグ用に生成したソースマップファイルがそのまま残っていたり、過去に設定を変えてあるところから出力しなくなったファイルが残っていたり等、ビルド先のdistフォルダの中はごちゃごちゃしがちです。これを何とかしたいなぁ、と思って調べたら簡単に解決できました。</p>
<p>Webpackでこういうことできないかなぁと調べると大抵プラグインが出てきて追加しないといけないことが多く、今回もそのパターンでよくわからないものを追加しないといけないのかなぁ・・・と思っていたのですが、どうも標準機能として用意されているようでした。ということで結論。</p>
<h2 id="結論: outputの中に[clean: true]を追加すればよい"><a href="#%E7%B5%90%E8%AB%96%3A+output%E3%81%AE%E4%B8%AD%E3%81%AB%5Bclean%3A+true%5D%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8C%E3%81%B0%E3%82%88%E3%81%84">結論: outputの中に[clean: true]を追加すればよい</a></h2>
<p>ドキュメントを読むことは大事ですね。しっかり記載がありました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder">https://webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder</a></p>
<p>こちらによると、ビルドしたものを置くdistフォルダの中身をビルドのたびにきれいにしたい場合はoutput.cleanオプションを使え、とあります。ただし、この方法が使えるのはWebpack5以降のみなので注意してください。</p>
<pre><code class="javascript">// webpack.config.jsの一部分
~~~~~~
output: {
filename: "output.js",
path: path.resolve(__dirname, "dist"),
clean: true // この1行を追加すればよい
}
</code></pre>
<p>はい、たったこれだけ。1行追加するだけで、ビルドのたびにdistフォルダの中身をきれいにしてくれるので、過去にビルドした残骸が残ることはなくなりました。</p>
<p>また、こちらのページには細かい設定方法もありました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://v4.webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder">https://v4.webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder</a></p>
<p>そして、前述の通りこの方法が使えるのはWebpack5以降のみとのことでした。Webpack4などを使っている方は、プラグインでの対応となるようです。</p>
<h2 id="Webpack4を使っている方: clean-webpack-pluginを使う"><a href="#Webpack4%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E6%96%B9%3A+clean-webpack-plugin%E3%82%92%E4%BD%BF%E3%81%86">Webpack4を使っている方: clean-webpack-pluginを使う</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://v4.webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder">https://v4.webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder</a></p>
<p>Webpack4版のドキュメントを見ると、同様の項目ですが対応方法が異なっていました。「clean-webpack-plugin」というプラグインを使えば削除できるので、導入・設定しましょうとのことでした。以下にドキュメントにあったサンプルを載せておきます。</p>
<pre><code class="bash"># プラグインのインストール
npm install --save-dev clean-webpack-plugin
</code></pre>
<p>webpack.config.jsはこんな感じ。</p>
<pre><code class="javascript">// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// プラグインを読み込む
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js',
},
plugins: [
new CleanWebpackPlugin(), // この行を追加する
new HtmlWebpackPlugin({
title: 'Output Management',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
</code></pre>
<p>これにより、output.pathで指定したフォルダにある不要ファイルを削除したうえでビルドしてくれるとのことでした。さらに、Webpack5では細かい設定ができましたが、clean-webpack-pluginでも似たような設定はできそうです。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/clean-webpack-plugin">https://www.npmjs.com/package/clean-webpack-plugin</a></p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、Webpackで過去にビルドした残骸をきれいに削除する方法をまとめました。不必要なプラグインを導入せずに、たった1行の設定追加だけでやりたかったことが実現できてとてもハッピーです。</p>
<p>そして、Webpack5ではデフォルトでこの機能が入っていることを知りませんでした。過去に調べたときはプラグイン対応だったよなぁと思いつつ調べると、やはりプラグインを導入しろ、という記事が多く出てきました。とりあえずググるのは大事ですが、ドキュメントをさっくりと眺めておく、というのも必要ですね。</p>
<p></p>
しきゆら
tag:crieit.net,2005:PublicArticle/18281
2022-08-14T18:34:08+09:00
2022-08-14T20:57:55+09:00
https://crieit.net/posts/js-filtermap-can-remove-element
【JS】flatMapで不要なものを削除したい
<p>こんにちは、しきゆらです。今回は、flatMapの処理の中で不要な要素が出てきた場合にそれを排除する方法を知ったのでメモしておきます。</p>
<p><a target="_blank" rel="nofollow noopener" rel="noreferrer noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap" target="_blank">https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap</a>こちらのページの「<a target="_blank" rel="nofollow noopener" rel="noreferrer noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap" target="_blank">map()のアイテムの追加と削除</a>」の項目にある通り、処理の中で空配列([])を返すと、その要素の処理を削除することができるようです。</p>
<pre><code class="javascript">let array = [1,2,3,4,5,6,7,8];
array.flatMap( (item) => {
if (item % 2 === 0) {
return item * 2;
} else {
return [];
}
})
// => [4, 8, 12, 16]
</code></pre>
<p>なお、同様の処理をmapで行うと、そのまま要素が空配列に置き換わってしまいました。(それはそう)</p>
<pre><code class="javascript">let array = [1,2,3,4,5,6,7,8];
array.map( (item) => {
if (item % 2 === 0) {
return item * 2;
} else {
return [];
}
})
// =>[Array(0), 4, Array(0), 8, Array(0), 12, Array(0), 16]
</code></pre>
<p>ということで、flatMapの場合は処理の中で不要な要素を削除ができるようです。</p>
<p>なぜ、flatMapで空配列を返すと要素を削除することができるのかというのは</p>
<blockquote>
<p>flatMap()はmap()の後にflat()を行うのと同じ<br />
参照元: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap</p>
</blockquote>
<p>とある通り、処理の中でflat()をかけるので空配列は消えてしまいます。ということで、flatMapの中で空配列を返してあげれば要素を削除することができるよ、という話でした。</p>
<p>・・・で、これで終わるとしょうもないので、Rubyで同様のことができるのかやってみました。<a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/collect_concat.html" target="_blank" rel="noreferrer noopener">https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/collect_concat.html</a></p>
<pre><code class="ruby">irb(main):001:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> b = [-1, 0, 9]
=> [-1, 0, 9]
irb(main):003:0> list = [a, b]
=> [[1, 2, 3], [-1, 0, 9]]
irb(main):004:0> list.flat_map{|array| array.select{|i| i < 0<span>}</span><span>}</span>
=> [-1]
irb(main):005:0> list.flat_map{|array| array.include?(-1)? []: array}
=> [1, 2, 3]
</code></pre>
<p>こちらも同様で、空配列を返すと削除される動きをしています。ただ、リファレンスにはそのような記載はないので、当たり前だよね、として書かれていないのかなと。</p>
<h2 id="不要な要素をはじく場合、flatMapとmap+αはどっちが速いのか"><a href="#%E4%B8%8D%E8%A6%81%E3%81%AA%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%AF%E3%81%98%E3%81%8F%E5%A0%B4%E5%90%88%E3%80%81flatMap%E3%81%A8map%2B%CE%B1%E3%81%AF%E3%81%A9%E3%81%A3%E3%81%A1%E3%81%8C%E9%80%9F%E3%81%84%E3%81%AE%E3%81%8B">不要な要素をはじく場合、flatMapとmap+αはどっちが速いのか</a></h2>
<p>この手の処理はmap()メソッドの前後で特定の値をはじいたりしていましたが、どちらが速いのでしょう。気になったので、ついでに調べてみました。ここでもJSとRubyで測ってみました。</p>
<p>例としては微妙ですが、-5から5の間の乱数の中で0以上の場合に3倍する処理をflatMapと他の処理で処理時間を計測してみます。</p>
<h3 id="JSの場合"><a href="#JS%E3%81%AE%E5%A0%B4%E5%90%88">JSの場合</a></h3>
<p>こんな感じの雑さで試してみます。</p>
<p>なお、処理の中で不要なコードが混じってますが一応2つの処理で同じ結果が返ってくるかを確認したかったのでequalArrayという雑チェック関数を作ってます。</p>
<pre><code class="javascript">import * as Benchmark from "benchmark";
const filterMapTest = (array) => {
return array.filter((item) => {
return item >= 0;
}).map(item => item*3)
}
const flatMapTest = (array) => {
return array.flatMap((item) => {
if(item >= 0) {
return item * 3;
} else {
return [];
}
})
}
const equalArray = (a, b) => {
if(!Array.isArray(a)) return false;
if(!Array.isArray(b)) return false;
if(a.length !== b.length) return false;
for(let i = 0; i < a.length; i += 1) {
if(a[i] !== b[i]) return false;
}
return true;
}
// initialize
const minNum = -5;
const maxNum = 5;
const array = [...Array(99*99)].map(_ =>; Math.floor(Math.random() * 10) - 5);
let suite = new Benchmark.Suite
// benchmark
suite
.add("filter + map", () => {
filterMapTest(array)
})
.add("flatMap", () => {
flatMapTest(array)
})
.on("cycle", (event) => {
console.log(String(event.target))
})
.on("complete", function() {
console.log("Fastest is " + this.filter("fastest").map("name"));
})
.run({async: true})
const filterTest = filterMapTest(array);
const flatTest = flatMapTest(array);
console.log(equalArray(filterTest, flatTest))
</code></pre>
<p>実行結果はこんな感じでした。</p>
<pre><code class="zsh">$ > node dist/bench.js
true
filter + map x 21,538 ops/sec ±18.58% (87 runs sampled)
flatMap x 1,673 ops/sec ±0.93% (96 runs sampled)
Fastest is filter + map
</code></pre>
<p>大きな差はなさそうですが、flatMapよりもfilter+mapのほうが速そうです。</p>
<h3 id="Rubyの場合"><a href="#Ruby%E3%81%AE%E5%A0%B4%E5%90%88">Rubyの場合</a></h3>
<p>こんな感じの雑さで試してみました。</p>
<pre><code class="ruby">require "benchmark"
def map_uniq(array)
array.map { |item| item.negative? ? next : item * 3}.compact
end
def flatmap(array)
array.flat_map{|item| item.negative? ? []: item * 3 }
end
array = Array.new(99*99){ rand(-5...5) }
Benchmark.bmbm do |r|
r.report("flatMap") { flatmap(array) }
r.report("map_uniq") { map_uniq(array) }
end
</code></pre>
<p>実行結果はこんな感じでした。</p>
<pre><code class="zsh">Rehearsal --------------------------------------------
flatMap 0.001012 0.000000 0.001012 ( 0.001012)
map_uniq 0.000562 0.000000 0.000562 ( 0.000562)
-----------------------------------total: 0.001574sec
user system total real
flatMap 0.000815 0.000000 0.000815 ( 0.000814)
map_uniq 0.000564 0.000000 0.000564 ( 0.000563)
</code></pre>
<p>よく考えなくても、flatMapのほうは無駄に配列生成して処理スキップさせているので遅いよなぁ、という印象でした。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、JSのflatMapで不要な要素をはじいて処理することができる、ということを知ったのでメモしました。また、気になったので、同様な処理がRubyでもできるか確認したり、flatMapで不要要素を弾く場合とfilterなどをかけた場合の処理時間の差を見てみました。</p>
<p>今回はここまで。おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18278
2022-08-13T18:55:09+09:00
2022-08-13T18:55:09+09:00
https://crieit.net/posts/install-nodejs-using-asdf
【node/yarn】asdfでnodeをインストールし、Yarnもつかえるようにする
<p>こんにちは、しきゆらです。</p>
<p>今回は、<a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2022/08/install_the_multiple-runtime-versions_management_tool__asdf/">こちらで紹介したasdf</a>を使ってNode.jsをインストールし、個人的によく使っているYarnも使えるように環境を作っていきます。</p>
<p>なお、Node.jsのインストールはすぐ終わったのですがYarnのインストールとか諸々に至るまでに紆余曲折ありました。まずは、インストールして環境構築までの流れをまとめます。</p>
<p>その後に、紆余曲折部分をメモしておきます。<br />
必要なコマンドだけよこせ!という方は、以下の「環境構築の流れ」を参考にしてみてください。</p>
<h2 id="環境構築の流れ"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%AE%E6%B5%81%E3%82%8C">環境構築の流れ</a></h2>
<p>コマンド自体の使い方等はそれぞれのツールのサイト等で確認してください。</p>
<pre><code class="zsh"># node用のプラグインを追加
asdf plugin add nodejs
# nodeをインストール
asdf install nodejs latest
# システム全体で使うバージョンを指定
asdf global nodejs latest
# yarnを使う準備
corepack enable
# asdfの再構築
asdf reshim nodejs
</code></pre>
<p>これにて、環境構築は終了です。</p>
<p>yarnの手順が以前とだいぶ変わりましたが、npmで個別にインストールせずにパッケージマネージャを管理する仕組みが入ったようです。<br />
そのおかげで手順がさっぱりしました。</p>
<p>では、前述の通り環境構築の紆余曲折や各ドキュメントを見ながらの細かい話は以下にまとめていきます。</p>
<h2 id="環境構築の紆余曲折"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%AE%E7%B4%86%E4%BD%99%E6%9B%B2%E6%8A%98">環境構築の紆余曲折</a></h2>
<h3 id="Nodeのインストール"><a href="#Node%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Nodeのインストール</a></h3>
<p>こちらは、asdfの仕組みにのっとって進めるだけなので、<a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2022/08/install_the_multiple-runtime-versions_management_tool__asdf/">前回の記事</a>と同じ手順を踏むだけです。</p>
<pre><code class="zsh"># node用のプラグインを追加
asdf plugin add nodejs
# nodeをインストール
asdf install nodejs latest
# システム全体で使うバージョンを指定
asdf global nodejs latest
# 確認
node -v
</code></pre>
<h3 id="Yarnのインストール"><a href="#Yarn%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Yarnのインストール</a></h3>
<p>手間取ったのはこちらのほう。<br />
インストールするために、公式サイトを見てみると記述がガラッと変わっていました。<br />
<a target="_blank" rel="nofollow noopener" href="https://yarnpkg.com/getting-started/install">https://yarnpkg.com/getting-started/install</a></p>
<p>Node.js 16.10以上だと以下のコマンドだけでよいとのこと。</p>
<pre><code class="zsh">corepack enable
</code></pre>
<p>corepackとは何ぞや。<br />
と思って調べると、Node.jsのパッケージマネージャを管理するための仕組みのようです。<br />
<a target="_blank" rel="nofollow noopener" href="https://nodejs.org/api/corepack.html">https://nodejs.org/api/corepack.html</a></p>
<p>いい感じにまとめてくれている方がいました。先人に感謝です。<br />
<a target="_blank" rel="nofollow noopener" href="https://zenn.dev/teppeis/articles/2021-05-corepack">https://zenn.dev/teppeis/articles/2021-05-corepack</a></p>
<p>ということで、公式の手順に従ってコマンド実行してみました。</p>
<pre><code class="zsh"># corepackを有効化
corepack enable
# yarnは入った・・・?
yarn
zsh: command not found: yarn
</code></pre>
<p>ということで、実行してもyarnは使えませんでした。</p>
<h3 id="corepackについてもう少し調べてみる"><a href="#corepack%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%82%82%E3%81%86%E5%B0%91%E3%81%97%E8%AA%BF%E3%81%B9%E3%81%A6%E3%81%BF%E3%82%8B">corepackについてもう少し調べてみる</a></h3>
<p>なんでじゃ、と思ってこれまた調べてみると、プロジェクトごとに使うパッケージマネージャを指定するような使い方を想定しているようです。</p>
<p>そして、事前に使うパッケージマネージャを指定することもできるようです。<br />
ということで、以下のコマンドで明示的にyarnを使うようにしてみます。</p>
<pre><code class="zsh"># 明示的にyarnを使う
corepack prepare --activate [email protected]
# yarnは入った・・・?
yarn
zsh: command not found: yarn
</code></pre>
<p>同じくコマンドがない、と怒られたのでいろいろ考えてみました。<br />
ここで思い出したのが、rbenvではrehashというコマンドを使っていたこと。<br />
gemや新しいバージョンのRubyをインストールしたときはとりあえずrehashしておけ、<br />
という程度の認識でしたが、このような仕組みがasdfにもあるのでは、と思い調べてみました。</p>
<h3 id="asdf reshim"><a href="#asdf+reshim">asdf reshim</a></h3>
<p>調べてみると、ドンピシャな質問と回答が出てきました。<br />
<a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/70082424/command-not-found-corepack-when-installing-yarn-on-node-v17-0-1">https://stackoverflow.com/questions/70082424/command-not-found-corepack-when-installing-yarn-on-node-v17-0-1</a></p>
<p>asdf reshim <name>というコマンドでrbenv rehashのようなことができるとのこと。<br />
<a target="_blank" rel="nofollow noopener" href="https://asdf-vm.com/manage/core.html#reshim">https://asdf-vm.com/manage/core.html#reshim</a></p>
<p>上記によると、インストール時に~/.asdf/shimsへコマンドとして使えるようにファイルが自動的に生成されるが、それ以外ではファイルが生成されないのこと。今回の場合は、nodeインストール後に手動でcorepack enableやcorepack prepare --activate [email protected]を実行しているのでasdf的には存在を知らない。そこで、asdfに追加があったことを教えてあげて、ファイルを作ってもらう必要があったようだ。</p>
<pre><code class="zsh"># asdfにファイル生成をしてもらう
asdf reshim nodejs
# 確認
yarn -v
# => 3.2.2
</code></pre>
<p>ようやく使えるようになりました。<br />
ただ、未確認ではありますが、asdf reshimの動きを見るとcorepack prepareは不要な気がします。<br />
ということで、「環境構築の流れ」では省いてます。</p>
<h3 id="おまけ: rbenv rehashについて改めて確認してみる"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%3A+rbenv+rehash%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E6%94%B9%E3%82%81%E3%81%A6%E7%A2%BA%E8%AA%8D%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">おまけ: rbenv rehashについて改めて確認してみる</a></h3>
<p>ここで、改めてrbenv rehashを調べてみました。<br />
<a target="_blank" rel="nofollow noopener" href="https://github.com/rbenv/rbenv#rbenv-rehash">https://github.com/rbenv/rbenv#rbenv-rehash</a></p>
<p>御幣を恐れずにざっくり書けば、新しいgemやRubyをインストールした場合は「~/.rbenv/versions/*/bin/」にインストールされるが、PATHが通っているのは「~/.rbenv/shims」なのでそのままでは使うことはできません。<br />
そこで、バージョン切り替えやgemのコマンドなどを追加する場合は新たに「~/.rbenv/shims」へコピーしてあげる必要があるわけです。そのコマンドがrbenv rehashのようです。</p>
<p>・・・とはいえ、いつの間にかrbenv installなどのタイミングで良しなにrbenv rehashが実行されるようになり明示的に実行する必要はなくなったようです。</p>
<p>ということで、asdfも正式版がリリースされる頃にはもしかしたら今回のようなごたごたはなくなるかもしれないなぁ・・・ということを思いながら、諸々調べる機会を与えてくれて感謝しつつ紆余曲折のメモを終わります。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、asdfを使ってNode.jsをインストールし、パッケージマネージャのYarnを使えるように環境構築をしました。<br />
いつの間にかYarnのインストール方法が変わっていたり、asdfの仕組みに躓いたりしましたが、いろいろなことを調べたり知ることができたので満足です。</p>
<p>というわけで、今回はここまで。<br />
おしまい</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18221
2022-06-12T20:06:21+09:00
2022-06-12T20:06:50+09:00
https://crieit.net/posts/WSL2-clasp-WSL2-clasp-push-Error-Looks-like-you-are-offline
【WSL2/clasp】WSL2上からclasp pushするとError: Looks like you are offline.と怒られる問題を回避する
<p>こんにちは、しきゆらです。<br />
今回は、タイトルの通りWSL2からclaspを使おうとするとofflineと怒られる問題が起こったのでこれを回避する方法をメモしておきます。</p>
<p>気が付くとWSL2からclasp pushをしようとすると、以下のようなエラーが出てしまってプッシュできない状態になっていました。<br />
よくわからないですが、たまにプッシュできたりするので何が悪いのかよくわかりません。<br />
WSLをインストールしなおしたりしても解決しませんでした。<br />
また、調べてもそれっぽい記事が出てこないので「おま環」ぽい気がしています。</p>
<p>さて、これではWSL内で書いたGASをプッシュできません。<br />
これを回避する方法をメモしておきます。</p>
<p>回避方法は単純で、Docker上からclasp pushをするだけ。<br />
調べてみると、結構DockerでGASを管理したい人がいるようで、情報がそこそこありました。<br />
ということで、プロジェクト諸々をDocker上にマウントしてコマンド実行できるようにしていきます。<br />
なお、ここではGASをTypeScriptで書いたのちWebpackでビルドしたものをclaspコマンドでプッシュする流れです。<br />
参考: <a target="_blank" rel="nofollow noopener" href="https://qiita.com/rei-ta/items/61b3fde6a069b77d335d">https://qiita.com/rei-ta/items/61b3fde6a069b77d335d</a></p>
<h1 id="Dockerの準備"><a href="#Docker%E3%81%AE%E6%BA%96%E5%82%99">Dockerの準備</a></h1>
<p>まずは、Dockerの準備をしましょう。<br />
Dockerのインストールは、WSLのホストであるWindows側に行えば良いようです。<br />
Dockerfileを以下のように作成します。</p>
<pre><code>FROM node:slim
RUN npm install @google/clasp -g
</code></pre>
<p>claspを実行する環境が必要なので、イメージとしてnodeを使います。<br />
サイズが小さいほうが何かと便利なのでnode:slimを使っていますが、nodeが動けばなんでもよいかと思います。<br />
また、claspを使えるようにインストールしておきます。</p>
<p>続いて、docker-compose.ymlを作成します。</p>
<pre><code>version: "3"
services:
clasp:
build: .
tty: true
stdin_open: true
volumes:
- "/home/:/usr/src"
working_dir: /usr/src/${user}
</code></pre>
<p>clasp loginしたのち、認証情報がhome直下にできるため/home/${user}をマウントしています。<br />
これで準備は完了。</p>
<h1 id="offlineと怒られる問題を回避する"><a href="#offline%E3%81%A8%E6%80%92%E3%82%89%E3%82%8C%E3%82%8B%E5%95%8F%E9%A1%8C%E3%82%92%E5%9B%9E%E9%81%BF%E3%81%99%E3%82%8B">offlineと怒られる問題を回避する</a></h1>
<p>Dockerを立ち上げていきます。<br />
<code>docker-compose run --rm -u ${id -u $usr} clasp /bin/bash</code><br />
シェルが立ち上がったら以下のコマンドでログインします。<br />
<code>clasp login --no-localhost</code><br />
これにて準備は完了です。<br />
あとは、コードを書いてWebapckでビルドしたものをclasp pushしましょう。</p>
<pre><code>node@ded2ca180394:$ clasp push
└─ /usr/src/path/to/project/appsscript.json
└─ /usr/src/path/to/project/bundle.js
Pushed 2 files.
</code></pre>
<p>問題なくプッシュできていますね。<br />
これにて回避完了です。</p>
<h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1>
<p>手元の環境では、これにてofflineと怒られる問題を回避できました。<br />
ちょっと手間ですが、ひとまずは回避できました。<br />
同じような問題が起こっている場合は試して見てください。</p>
<p>今回は、ここまで。<br />
おしまい</p>
しきゆら