Node.js の preload 用の引数
preload するスクリプト用のコマンドライン引数という珍しいものを見ました

node --import pre.js main.js foo=bar

みたいなもので foo=bar は pre.js 用という扱いでした
pre.js で処理した process.argv は main.js でも共通なので pre.js で先に引数を処理してしまうみたいです



[pre.js]
const args = process.argv.splice(2, 2)

console.log("pre", args)

[main.js]
console.log("main", process.argv)

root@fcb8f9998337:/mnt/t/1# node --import ./pre.js main.js a b c
pre [ 'a', 'b' ]
main [ '/usr/local/bin/node', '/mnt/tmp/1/main.js', 'c' ]

pre.js で splice を使って引数の最初の 2 個を取り出して main.js ではそれ以外が残ってる感じです
順番だけでなく pre.js 側で特定のプレフィックスのみを処理するとかもあると思います

preload をあまり使わないので気にしてませんでしたが 結構ありな考え方ですね
Fastify の printPlugins で表示される名前
Fastify では printRoutes や printPlugins で登録したルートやプラグインをわかりやすく表示できます
printPlugins ではプラグインの名前が表示されます

import Fastify from "fastify"
import sensible from "@fastify/sensible"

const fastify = Fastify()

await fastify.register(sensible)

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 8 ms
├── @fastify/sensible 1 ms
└── bound _after 0 ms

ただ register のところで import を使うと問題が出ます

import Fastify from "fastify"

const fastify = Fastify()

await fastify.register(import("@fastify/sensible"))

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 6 ms
├── [object Promise] 1 ms
└── bound _after 1 ms

import が返すのは Promise オブジェクトなので [object Promise] という表示になってました
register の引数のところで import を書くのは Fastify 側でサポートされていて ドキュメントでも見かける方法なのに対応できてないのは残念です

ただ import が終わって Promise が解決されないと中身の情報がわからないです
printPlugins が呼び出されるときに Promise が解決済みとも限らないですし仕方ないものなのかもしれません

名前をつけてみたら動くかなと試したら動きました

import Fastify from "fastify"

const importWithName = (specifier, name) =>
Object.assign(import(specifier), { name: name || specifier })

const fastify = Fastify()

await fastify.register(importWithName("@fastify/sensible", "sensible"))

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 8 ms
├── sensible 1 ms
└── bound _after 0 ms

考えてみたら .name を参照してますが Promise だと .name はないはずです
どうやって作ってるのか気になったのでソースを見てみました

https://github.com/fastify/avvio/blob/v8.3.0/lib/get-plugin-name.js

いくつかパターンがあるみたいです
Promise のプロパティを追加しなくても単純にオプションとして name を渡すで良さそうです

import Fastify from "fastify"

const fastify = Fastify()

await fastify.register(import("@fastify/sensible"), { name: "sensible" })

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 4 ms
├── sensible 1 ms
└── bound _after 0 ms

デバッグなど開発時向け機能の printPlugins のためだけに名前をつける必要があるのかはわかりませんけど 一時的にどれなのかわからないからつけるというときには良さそうです
過去バージョンの Chrome の動作を確認する
npx @puppeteer/browsers install chrome@115

みたいな感じで 指定バージョンの Chromium をインストールできる
現時点では 113 以降がインストールできる
112 や 111 は 404 エラー

Puppeteer が使用してるデータソースがこの辺で 113 以降しかデータがない
https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone.json
https://googlechromelabs.github.io/chrome-for-testing/latest-patch-versions-per-build.json

最新が 123 だし 最新+過去 10 件だけ残して古いのは消されてるのかも

それ以前なら この辺からダウンロード
https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html

Windows 用
https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Win_x64/

ここでダウンロードする場合は Chrome のバージョンじゃなくて通しのビルド番号で指定する必要あり
バージョンとの対応の参考

{
"113": {
"milestone": "113",
"version": "113.0.5672.63",
"revision": "1121455"
},
"114": {
"milestone": "114",
"version": "114.0.5735.133",
"revision": "1135570"
},
"115": {
"milestone": "115",
"version": "115.0.5790.170",
"revision": "1148114"
},
"116": {
"milestone": "116",
"version": "116.0.5845.96",
"revision": "1160321"
},
"117": {
"milestone": "117",
"version": "117.0.5938.149",
"revision": "1181205"
},
"118": {
"milestone": "118",
"version": "118.0.5993.70",
"revision": "1192594"
},
"119": {
"milestone": "119",
"version": "119.0.6045.105",
"revision": "1204232"
},
"120": {
"milestone": "120",
"version": "120.0.6099.109",
"revision": "1217362"
},
"121": {
"milestone": "121",
"version": "121.0.6167.85",
"revision": "1233107"
},
"122": {
"milestone": "122",
"version": "122.0.6261.6",
"revision": "1250580"
},
"123": {
"milestone": "123",
"version": "123.0.6265.0",
"revision": "1252026"
}
}

これの revision の数値を使う

Chromium だと開発中バージョンで機能が全部入ってないケースがあるので どの Chrome バージョンで変更があったかを確認するのに注意が必要
例えば 110 で動かなくて 111 で動いたら 111 で追加された機能かと思うけど 試した 110 のバージョンより後の 110 のリリースで追加される場合もありえる
マイルストーンに記載されてる version と revision は その Chrome バージョンの最後のビルドになってるのでこのバージョンなら大丈夫かも
ただこの情報が取れない古いバージョンだと そのバージョンの最後のビルドの revision を探すのが大変
イベントリスナに設定した関数を簡単に置き換えたい
elem.addEventListener("event", (event) => {
//
})

で登録した関数を置き換えたいことがあります
事前に処理内容がわかってるなら関数はそのままで中の if 文で分岐でもいいのですが そうでない場合は関数ごと置き換えたいです
しかしイベントリスナって付け外しが少し面倒です

楽にしたいなと思って考えると リスナとして設定した関数は外部の関数を呼び出すだけにして 外部の関数を置き換えるという方法が考えられます

let fn = () => {}
elem.addEventListener("event", (event) => {
fn(event)
})

const changeHandler = (new_fn) => {
fn = new_fn
}

こうすると登録した関数はそのままで fn を置き換えればいいです
ローカル変数なのでそれを更新する関数 changeHandler を作って それを外部に公開することでリスナの関数を置き換えできます

もう少し扱いやすくしたいので オブジェクトにして関数を作る部分も共通処理としてみます

const createHandler = (ref) => (event) => ref.handler(event) 

const ref = { handler: () => {} }
elem.addEventListener("event", createHandler(ref))

これで ref オブジェクトを公開するか ref.handler を更新する関数を公開すれば良いです

しかし 実はこれとほぼ同じ機能が標準で用意されています
addEventListener にハンドラーとして渡すものは関数がほとんどですがオブジェクトを渡すことができます
その場合は handleEvent というプロパティの関数が実行されます
なので

const handler = { handleEvent: () => {} }
elem.addEventListener("event", handler)

とだけ書けば 上のと同じことができます
handler オブジェクトを公開したり handler.handleEvent を更新する関数を公開すれば良いです

あまり知られてないマイナー機能ですね
ただのプロパティの書き換えで済んで removeEventListener して addEventListener するよりも高速なので 再レンダリングするようなライブラリの内部処理で使われていたりします
systemd の StandardOutput と StandardError
以前の記事で書いたように systemd の機能で標準出力をファイルに追記するようにしてロガーはシンプルに標準出力に書き込むようにしています
console.log でいい感じに整形してくれるので楽なんですよね
depth 問題があって省略されて必要な部分が残らないこともありますけど

ひさびさにその設定を見ていたら ふと思ったのですが標準出力しか設定していないです
StandardOutput は指定しているのに StandardError は未指定です

自分が書き込むのはすべて標準出力に統一してるのですがライブラリや Node.js など実行する環境側で出力されるものは標準エラー出力になることもありそうです

そういう特殊なものはむしろ systemd 標準の journal に送ったほうがいいのかもと思うところもあったりしますが 時系列を考えると同じログファイルに混ざっていて欲しいところもあります

とりあえず同じファイルに出力しようとしたのですが シェルの 「2>&1」 相当の記法がわかりません
シェルを通して本来のプログラムを実行して 標準エラー出力をすべて標準出力にまとめるようにすることもできますが あまりスッキリしないのでできれば避けたいです

ドキュメントを見てると fd:stdout のような記法があるみたいなので 「StandardError=fd:stdout」 でいいのかなと思ったのですが 構文エラーのようで起動ができなくなりました

もう少し読んでみると StandardError では inherit を使えば 標準出力のファイルディスクリプタが複製されると書いています
https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#StandardError=

また StandardError のデフォルト値 DefaultStandardError は inherit になっています
ということで何も設定してないこの状態で期待通りの動きになっていました

StandardOutput=file:/tmp/output

とだけ書けば標準出力も標準エラー出力も /tmp/output に書き込まれます
一応動かしてみたところ期待通りに動作しました



ちなみに fd を試していたときに同名指定も試しました

StandardOutput=file:/tmp/output
StandardError=file:/tmp/output

append なら問題なさそうでも通常の書き込みだと問題が出そうに思います
例えばこういうの

node index.js > out 2> out

[index.js]
console.log("out1")
console.error("err1")
console.log("out2")
console.error("err2")
console.log("out3")
console.error("err3")

[out]
err1
err2
err3

append モードじゃないとファイルの書き込み位置が更新されないので同じ場所に書き込んで上書きされます

同じ文字数だと分かりづらいですが こうするとそれぞれが持ってる書き込み位置に書き込んでることがわかりやすいと思います

[index.js]
console.error("AB")
console.log("a")
console.log("bcdefghijk")
console.error("CDEFG")
a
bCDEFG
ijk

自動で追加される改行があるので少しわかりづらさはありますけど AB が a と改行で上書きされて cd.. は CD.. で上書きされます
G の次の改行で h が上書きされてそれ以降は上書きされないので ijk が残ります

同じ名前を指定すると こうなるんだろうなと思ったのですが StandardOutput と StandardError に同じものを指定してもこうはならず両方が出力されていました
中でうまく管理されているようですね
ただ基本は同じのを 2 回書かずに inherit でいいと思います
全体を display:none にしておくとページ遷移のときにちらつきが減る
最近は CSR のものばかりで最初から HTML があるものはあまり作らないので気にしてなかったのですが 久々にそういうページを作ったら module のロード中に初期化前の状態が見えていまいちなものになりました
CSS のロードも ESM でやると適用されるのが後からになりますからね
また CSS を同期的にしても JavaScript で初期状態のクラスを設定する場合 module が全部ロードされてから処理が行われるので それまでに違う状態の画面が表示されてしまいます
ほぼ一瞬ですが チラつきになりますし あまり気持ちのいいものではないです

一瞬関係ない画面が映るよりは display: none をつけて何も表示されないほうがマシかなと思って display: none にしてみると 思ってた以上に良い動作になりました
一瞬の真っ白な画面が挟まりません
前のページの画面が維持されて display: none が消えて表示される状態になって始めて画面が更新されました

ただ 500ms くらいが境目のようで 600ms とか 1s ほどの遅延になると一瞬真っ白な画面が挟まります
それ未満の 300ms などだと 真っ白な画面は発生しません
display: none を外してみると 一瞬関係ない画面は出るので ブラウザがいい感じにしてくれてるようです

注意点として body の中身が全く表示されない状態の必要があります
メインのコンテンツの div を display: none にしてもその外に表示される要素があったら それだけが表示されます
body ごと display: none にしておくのがいいかもですね

試せるページ↓

遅延300ms+display:noneあり
遅延300ms+display:noneなし
遅延800ms+display:noneあり
遅延800ms+display:noneなし

下の方にあるリンクでメインとサブのページを交互に移動できます
URL の delay と hide で遅延させる時間や display: none の有無を変更できます
詳細はページ内のクエリパラメータの説明参照です
インストールしたいコマンドのパッケージ名がわからないとき
ps コマンドってデフォルトで入ってないこともあって 追加でインストールしたいことがあります
ps コマンドをインストールするのに必要なパッケージは RPM だと

procps-ng

です

多くのコマンドはコマンド名のままなのに特殊過ぎます
覚えられません

そういうときはインストールするコマンドのパスを指定すればいいです

dnf install /usr/bin/ps

これで procps-ng がインストールできます
大抵のコマンドは /usr/bin にインストールされるので困ったときはやってみるといいです
Git 自身の更新履歴を見たい
Git の性質もあって ググっても Git を使って変更履歴を管理するとかそういうのばかり出てくる
公式ドキュメントのサイトを探しても変更履歴ぽいのが見つからない
Github のプロジェクトの Releases では変更履歴が管理されてない
リポジトリのトップレベルのファイルを見ても ChangeLog や History 的なものがない

諦めかけたところで発見
リポジトリの Documentation/RelNotes というフォルダの中にまとまってた
https://github.com/git/git/tree/master/Documentation/RelNotes

ググったときに一応見かけたけど一番下が最新版じゃないのでもう古いやつだろうとスルーしてた
数字と文字列ソートの都合で最新版が一番下じゃないだけで今でも更新されてた
Windows で拡張子なしのファイルを規定のプログラムで開く
https://superuser.com/questions/13653/how-to-set-the-default-program-for-opening-files-without-an-extension-in-windows

メモ帳で開く場合
管理者権限コマンドプロンプトで以下コマンドを実行

assoc .="No_Extension"
ftype "No_Extension"="C:\Windows\System32\notepad.exe" "%1"

notepad.exe のパスのところを好きなエディタに変える
サクラエディタなら

C:\Program Files (x86)\sakura\sakura.exe

拡張子なしは基本的に設定ファイルなどテキストファイル
とりあえずテキストエディタで問題ないはず



assoc コマンドは拡張子とファイルタイプを関連付けるもの
拡張子に対して 適当に名前をつけれる
引数無しで assoc コマンドだけを実行すると登録済みの一覧が見れる


.htm=htmlfile
.html=htmlfile
.jpe=jpegfile
.jpeg=jpegfile
.txt=txtfile

ftype コマンドはファイルタイプとそれを開くデフォルトのコマンドを関連付けるもの
assoc でつけた名前に対してコマンドを指定できる
引数無しで ftype コマンドだけを実行すると登録済みの一覧が見れる


htmlfile="C:\Program Files\Internet Explorer\iexplore.exe" %1
txtfile=%SystemRoot%\system32\NOTEPAD.EXE %1

ただ .html は Chrome で .txt はサクラエディタになってる PC でも↑
ファイルのプロパティから関連付けたものはここに反映されなくて そっちが優先されるようになってるみたい
拡張子なしの場合はプロパティで設定できないのでこれで設定するしかないみたい

基本的にファイルを右クリックすれば◯◯で開くがメニューにあるのでわざわざ設定する必要性は薄いけど zip の中のファイルとなると右クリックメニューで◯◯を開くを選べないのでそういうときに便利
HTML 要素の innerText
HTML から見た目通りの改行のテキストを取り出したいです
innerText を使うと見た目に合わせて文字列が受け取れます
例えばこういう HTML があるとき

foo
bar<br/>baz
<div>div</div>
<p>p</p>
text

受け取る文字列はこうなります

foo bar
baz
div

p

text

HTML 中の改行はスペースになるので スペースに変換されています
br タグは改行に変換されています
また div タグでも改行され p タグでは前後に空行が追加されます

この変換を大きめのファイルにまとめて行いたいです
Node.js でやろうかと思って jsdom を使ってみたのですが innerText はサポートされていませんでした
レンダリングエンジンありきみたいな機能なので実装されてないそうです
issue で要望は出てましたが 未だに実装されていないのであまり期待はできません

happy-dom ならどうかなと試してみたらこっちはサポートされていました
しかし 思ったような出力になりません

foo
barbaz
div
ptext

br タグは単純に無視され 改行がそのまま反映されています
また div は前後で改行されていますが p は改行されず直後の改行も無視されて text とくっついてしまっています

しかたなくブラウザを使うことにしました
ただブラウザを使っても期待する結果を得るにはドキュメントと接続されて画面に表示されている必要があるようでした
document.createElement を使って div を作った直後に innerHTML を設定して innerText を取り出してもこうなります

foo
barbaz
div
p
text

HTML の改行がそのまま反映されて br タグは無視されています
happy-dom と同じ感じです
p タグのあとに改行ができるところだけ違っています

ブラウザでも一旦表示させないといけないのが面倒ですね
速度にも影響出そうですし

一応簡単に調べてみたところ 8 ~ 10 倍くらいの時間がかかるようになってました
適当なページから持ってきた HTML で文字数は 6500 くらいでタグ数は 150 くらいのもので確認しました
開発者ツールで要素を確認しようとしたら要素が消えるときの対処方法
ポップアップで表示される部分の HTML 構造を確認したいとかありますよね
でも右クリックの「検証」を押したり devtools にフォーカスするとポップアップが閉じてしまうということがあります

原因は blur や focusout イベントを使って 要素がフォーカスを失ったらポップアップが閉じるようになっているから
devtools にフォーカスをあてるとページからはフォーカスが失われて このイベントが起きてポップアップが消えてしまいます

このためにけっこうな力技で対処していました

devtools の Elements タブですべての blur と focusout イベントのリスナを削除してから ポップアップを開きます
ポップアップの要素を消す処理が行われないので ポップアップは開いたままになります
本来の動作が行われなくなるので 確認後にリロードが必要です

別の手段では setTimeout で遅延して debugger を実行します
3 秒後に debugger を実行するようコンソールで実行してから 3 秒以内にポップアップを開きます
debugger が実行されると JavaScript の処理は実行されず固まるので ポップアップが開いたまま devtools を操作できます

こんな方法を使ってましたが 正当な方法がありました

devtools で Rendering タブを開きます
出てないならメニューの More tools から選択します
リストから「Emulate a focused page」を探してチェックを入れます
これでページにフォーカスが残るので devtools にフォーカスを当ててもポップアップは消えずに残ります

デフォルトで有効になってて欲しいくらいの機能です
Node.js でイベントループに残ってるタスク数を確認する
process.getActiveResourcesInfo() を使うとイベントループに残ってる一覧を文字列で見れる

Welcome to Node.js v18.17.1.
Type ".help" for more information.
> console.log(process.getActiveResourcesInfo())
[ 'TTYWrap', 'TTYWrap', 'TTYWrap' ]
> setTimeout(() => {}, 10000)
> console.log(process.getActiveResourcesInfo())
[ 'TTYWrap', 'TTYWrap', 'TTYWrap', 'Timeout' ]

setTimeout を登録すると Timeout が追加された
setTimeout を複数登録すると 配列の中の Timeout が複数になる
残ってる非同期処理の数がわかるのでどれくらい負荷があるかの参考にできる

console.log(0, process.getActiveResourcesInfo())
console.log(1, process.getActiveResourcesInfo())

setTimeout(() => {}, 100)

console.log(2, process.getActiveResourcesInfo())

const timeout = setTimeout(() => {}, 100)

console.log(3, process.getActiveResourcesInfo())

timeout.unref()

console.log(4, process.getActiveResourcesInfo())

require("fs/promises").readFile(__filename)

console.log(5, process.getActiveResourcesInfo())
0 []
1 [ 'TTYWrap', 'TTYWrap' ]
2 [ 'TTYWrap', 'TTYWrap', 'Timeout' ]
3 [ 'TTYWrap', 'TTYWrap', 'Timeout', 'Timeout' ]
4 [ 'TTYWrap', 'TTYWrap', 'Timeout' ]
5 [ 'FSReqPromise', 'TTYWrap', 'TTYWrap', 'Timeout' ]

ファイル指定で実行すると最初は何も入ってなくて 2 回目からデフォルトのものが含まれる
unref すると active 扱いされなくなって一覧から消える

最初から入ってるものは OS や CJS/ESM でも違うみたい

console.log(0, process.getActiveResourcesInfo())
console.log(1, process.getActiveResourcesInfo())

これを実行すると

Windows の場合

D:\t12>node a.cjs
0 []
1 [ 'TTYWrap', 'TTYWrap' ]

D:\t12>node a.mjs
0 [ 'CloseReq' ]
1 [ 'CloseReq', 'TTYWrap', 'TTYWrap' ]

Linux の場合

root@426b4d5c3e42:/opt# node a.cjs
0 []
1 [ 'TTYWrap' ]

root@426b4d5c3e42:/opt# node a.mjs
0 [ 'CloseReq' ]
1 [ 'CloseReq', 'TTYWrap' ]

バージョンはどっちも Node.js 18

Windows だと TTYWrap が 2 つ入ってる
Linux だと 1 つ

REPL だと Windows も Linux も TTYWrap がもう一つ増える

ESM だと CloseReq が最初から入ってる
CJS だと入ってない
PostgreSQL の SELECT は列が空でもよかった
以前書いたコードを見てたとき

SELECT FROM table;

と言う感じで SELECT のあとに列名が抜けてるのがあった
「*」 が抜けてて動いてなさそう……と思ったけど実行されてるはずのコード
psql で試しに実行してみたらエラーにならず成功した

列名は省略可能みたい
昔からそうだっけと思って調べたら 9.4 からの変更だった

https://www.postgresql.jp/document/9.3/html/sql-select.html
https://www.postgresql.jp/document/9.4/html/sql-select.html

リリースノートには この変更で 0 個の列のビューのダンプ・リストアが正常に出来るようになるって書いてるから このために許可したみたい
https://www.postgresql.jp/document/9.4/html/release-9-4.html

列なしの SELECT なんてしないと思ったけど実際に使われていたわけで EXISTS の中で使う SELECT 文には 「*」 書かなくていいからありなのかも

ちなみに SQL 標準ではないみたいで mysql だと構文エラーでした
Windows ってファイルを開いてると消せないと思ってたけど
Windows だとファイルを開いたままにしてると そのファイルを消せないと思ってた
なにかのプロセスで開いたままになってて消せなくて そのプロセスを見つけて停止することってよくあるし

でも普通に消せるみたい
Node.js で stream で書き込み中にエクスプローラーからファイルを消してみる

const stream = require("fs").createWriteStream("foo.txt")

stream.on("error", err => console.log({ err }))

setInterval(() => {
stream.write(
Date.now() + "\n",
(err, result) => console.log({ err, result })
)
}, 1000)

↑で毎秒書き込むので適当なところでファイルを消す
例のエラーはなく 普通に消せる

消したあとに ファイルを開いたプロセスが書きこむとどうなるか気になったけど 何も起きないみたい
書き込みは成功扱いで特にエラーは起きない
書き込み時に新規にファイルが作られるわけではなく どこにも出力されない
Linux での扱いと同じみたい

ということは消せないエラーが出てくるものはわざわざ追加でロックするような処理をしてる?
言語によってはデフォルトの挙動がそうなってるのかも

Windows だと OS やファイルシステムの都合で開いてるファイルは消せないものだと思ってたけど できるんだったらこの動きをデフォルトにしてほしい
どこかで開いてるからってエラー出されても面倒なだけで このエラーを見るたび Linux のほうがこういうところは便利だな―と思ってるくらいなので
Node.js で事前処理をした状態で REPL を開く
REPL でデータを確認したりフィルタしたり変換したりと色々したいことがあります
そこで使うデータは別のファイルを読み取って変換したデータになるのですが REPL の中で毎回その準備をするのは大変です
1 回の REPL プロセス内で必要なデータの確認まで全てやって終わりならともかく変数等をクリアしたくてプロセスを終了して再度実行はけっこうあります
あれを見たいと思うたびに REPL のプロセスを起動してデータを読み取って準備しては手間です

「↑」キーで履歴呼び出しができるのですが 1 実行ごとの呼び出しなので初回準備が何行にも及ぶと再実行が面倒です
スクリプトにまとめておいてそれを require するだけにすれば 1 行になりますが REPL 操作部分が長くなると履歴からその require を探すのも一苦労だったりします
ということでいい方法を探したところ -r オプションでモジュールをロードするのが良さそうでした

仮にデータは 1 行 1 JSON 形式で保存されたテキストファイルで それをパースしてオブジェクトの配列として扱うとします

> cat data.txt
{"x":1,"y":2}
{"x":4,"y":0}
{"x":2,"y":10}

> cat pre.js
globalThis.items = require("fs").readFileSync("./data.txt").toString()
.split("\n").filter(x => x.trim()).map(x => JSON.parse(x))

この pre.js を -r で読み込むと REPL が実行される前にロードされるのでグローバル変数にデータを入れておけば参照できます

> node -r "./pre.js"
Welcome to Node.js v18.12.1.
Type ".help" for more information.
> items.filter(item => item.x === 1)
[ { x: 1, y: 2 } ]

データのファイルが複数あり ファイルを選択できるようにしたい場合はコマンドライン引数で渡すようにもできます

> cat pre.js
globalThis.items = require("fs").readFileSync(process.argv[2]).toString()
.split("\n").filter(x => x.trim()).map(x => JSON.parse(x))

> node -r "./pre.js" - data.txt
Welcome to Node.js v18.12.1.
Type ".help" for more information.
> items
[ { x: 1, y: 2 }, { x: 4, y: 0 }, { x: 2, y: 10 } ]

プロセスを終了して再実行するときには node コマンド内の REPL で実行したコマンドは関係なくシェル側の履歴から参照するので 1 回 「↑」 を押して再実行するだけです
PostgreSQL でデータベース・ロール固有の設定を表示する
alter database や alter role で設定した内容を表示したいです
show を使えば現在有効な設定を確認できますが alter database などで変更したものかはわかりません

pg_db_role_setting を見ればその情報が取れます
https://www.postgresql.jp/document/14/html/catalog-pg-db-role-setting.html

データベースとロールを用意します

postgres=# create database db1;
CREATE DATABASE
postgres=# create role foo;
CREATE ROLE

データベースとロールの設定を変更します

postgres=# alter database db1 set timezone to 'asia/tokyo';
ALTER DATABASE
postgres=# alter role foo set search_path to a, b, c;
ALTER ROLE
postgres=# alter role foo in database db1 set search_path to public;
ALTER ROLE

一応変更されてることの確認です

postgres=# show timezone;
TimeZone
----------
Etc/UTC
(1 row)

postgres=# \c db1
You are now connected to database "db1" as user "postgres".
db1=# show timezone;
TimeZone
------------
Asia/Tokyo
(1 row)

pg_db_role_setting の中身を表示します

db1=# select * from pg_db_role_setting;
setdatabase | setrole | setconfig
-------------+---------+-------------------------
16388 | 0 | {TimeZone=asia/tokyo}
0 | 16391 | {"search_path=a, b, c"}
16388 | 16391 | {search_path=public}
(3 rows)

設定した内容が表示されています
片方のみだと未設定の部分は 0 です
両方の組み合わせに対して設定すると両方に oid が入ります
setdatabase と setrole の数値は pg_database の oid と pg_roles の oid になっています

db1=# select oid, datname from pg_database;
oid | datname
-------+-----------
5 | postgres
16388 | db1
1 | template1
4 | template0
(4 rows)

db1=# select oid, rolname from pg_roles;
oid | rolname
-------+---------------------------
6171 | pg_database_owner
6181 | pg_read_all_data
6182 | pg_write_all_data
3373 | pg_monitor
3374 | pg_read_all_settings
3375 | pg_read_all_stats
3377 | pg_stat_scan_tables
4569 | pg_read_server_files
4570 | pg_write_server_files
4571 | pg_execute_server_program
4200 | pg_signal_backend
4544 | pg_checkpoint
10 | postgres
16391 | foo
(14 rows)
VSCode で上下を簡単に揃える
ソースコード上で = や : の上下位置を揃えようとする人がいます
個人的にはムダでしかないので不要だと思いますし フォーマッターがやるにしても差分が増えてしまうのでやってほしくないものです

しかし ものによっては視覚的に縦に揃えて見たい ということもあります
例としてはこういう感じで揃えて見たいです

const one = 100001
const two = 100002
const three = 100003
const four = 100004
const five = 100005
const six = 100006
const seven = 100007
const eight = 100008
const nine = 100009
const ten = 100010
const eleven = 100011
const twelve = 100012
const thirteen = 100013
const fourteen = 100014
const fifteen = 100015
const sixteen = 100016
const seventeen = 100017
const eighteen = 100018
const nineteen = 100019
const twenty = 100020



const one       = 100001
const two = 100002
const three = 100003
const four = 100004
const five = 100005
const six = 100006
const seven = 100007
const eight = 100008
const nine = 100009
const ten = 100010
const eleven = 100011
const twelve = 100012
const thirteen = 100013
const fourteen = 100014
const fifteen = 100015
const sixteen = 100016
const seventeen = 100017
const eighteen = 100018
const nineteen = 100019
const twenty = 100020

対応してるフォーマッターがあればいいですが こういうことをするのは少数だと思います
それに準備が面倒です
たまにしか使わないので 件数が少ないなら手作業でしたほうが早いです

とは言え ある程度の件数になると手作業でやるのも嫌になってきます
方法のひとつは / *= */ を \t に置換してエクセルにコピペです
ソースコード自体が揃う必要はなく確認用なのでこれでもいいです

しかしエクセルって結構開くのに時間がかかりますし 入れてない PC も多いです
で いい感じにできないものかと考えていたら VSCode 標準機能だけで工夫すれば簡単にできました
エクセルを起動する間に終わってしまいます

こういうデータがあるとします

const obj = {
short1: "a",
middle1: "b",
longlong1: "c",
short2: "A",
middle2: "B",
longlong2: "C",
}

マルチカーソルで全部の「:」を選択して 右にスペースを多めに確保します
最初の「:」を選択して Ctrl-D を繰り返し押せば次の「:」も追加で選択できます

const obj = {
short1: "a",
middle1: "b",
longlong1: "c",
short2: "A",
middle2: "B",
longlong2: "C",
}

こんな感じで一番長いキーの「:」よりも一番短いキーのバリュー位置が右になったら十分です

次はスペースを幅 0 の矩形選択で縦に選択します
「|」が選択位置を表しています
マウスのミドルクリックのドラッグや Ctrl-Shift-Alt-←↑↓→ で矩形選択できます

const obj = {
short1: | "a",
middle1: | "b",
longlong1: | "c",
short2: | "A",
middle2: | "B",
longlong2: | "C",
}

Ctrl-Shift-→ で各行を次の区切りまで範囲選択します
その後に Shift-← や Ctrl-Shift-← などでスペースのみを選択するように調整してから選択範囲を削除します
こうなりました

const obj = {
short1: "a",
middle1: "b",
longlong1: "c",
short2: "A",
middle2: "B",
longlong2: "C",
}

揃ってますね
慣れるとササッと操作できるので置換してエクセルを起動してる間には VSCode 上で揃えてしまえます
エクスプローラーのサイドバーに 「Linux」 が増えてた
いつのまにかエクスプローラーのサイドバーに 「Linux」 という項目が増えてました
普段 「クイックアクセス」 や 「PC」 の部分が見えていて 下の方にスクロールしないので全然気づいてませんでした

この 「Linux」 を開くと WSL のディストリビューション一覧が共有という形で見れます
ディストリビューションを選ぶとそのディストリビューションのルートディレクトリが表示されます

これまでアドレスバーに 「\\wsl$」 と入力してから開いていた場所と同じような動きです
「Linux」 からフォルダを開いてからアドレスバーでパスを取得するとこうなってました

\\wsl.localhost\Ubuntu-22.04

wsl.localhost というサーバー名になってるようです
自分で 「\\wsl$」 と入力したり そこへのショートカットをつくる手間が省けるので便利です
CSS で infinity が使えた
CSS で絶対表示されないような位置に移動するためにすごく大きな数値を設定するなど とても大きな値を設定したい ということが稀にあります

そういうときに適当に大きな値を指定していたのですが infinity が使えました
infinity だけだと単位を書けないので calc と合わせて使います

.foo {
width: calc(infinity * 1px);
}
.bar {
position: absolute;
top: calc(infinity * -1px);
}
.baz {
margin-left: calc(infinity * 1px);
}
.qux {
z-index: calc(infinity * 1);
}
.quux {
animation: anime1 calc(infinity * 1s) linear 0s;
}

px 以外に 時間等にも使えます
% や em や vw などもありますが 長さ系は infinity にするなら px と同じです
z-index は単位なしの数値なのでそのまま書ける気もしましたが calc が必要でした
仕様的には calc 用の定数みたいですね
-infinity, e, pi, nan などもあります
back/forward cache が効かない理由を調べる
以前 bfcache が Chrome に実装されたときにいくつかのページを試すと bfcache が使われてるページとそうでないページがあって なぜ使われたり使われなかったりするのか はっきりとした原因はわかりませんでした

その後いつのころからか devtools の Application タブに 「Back/forward cache」 という項目が増えていました
「Test back/forward cache」 というボタンがあるので確認したいページを開いて押します
ページがリロードされて bfcache が有効化どうかを表示してくれます
無効の場合は原因も教えてくれます
ときどき原因が表示されないこともありますけど

このブログだと成功になって bfcache が有効みたいですが メインの方のブログでは成功が出たかと思ったらすぐ失敗に切り替わってます

失敗になる原因のひとつは unload ハンドラが設定されてることで 結構見かけます
他にも拡張機能で JavaScript や CSS を挿入してる場合も無効になるようです
ページのカスタムや広告ブロック系の拡張機能とは相性が悪いですね

あと HTTP のレスポンスのヘッダーに 「Cache-Control: no-store」 が付いてる場合も bfcache が無効になるようです
自分で作ったページで unload などを使ってないのに bfcache が動いてなくて調べるとこれでした
Cache-Control なんてつけた覚えはなかったのですが PHP のレスポンスではデフォルトでついてるようでした
つく場所とつかない場所があって少し困りましたが session を使うと追加されるようです

戻るボタンで戻ったときでもサーバにアクセスしてログイン状態を確認したいのならいいのかもですが 戻るだけならログインチェックなどはしなくていいと思うんです
これまで見えていた画面ですし 新規に検索したり画面を移動するとそこでログインチェックが入りますし

PHP では

session_cache_limiter('');

で自動で Cache-Control ヘッダーを出力するのを防げるようです
freeze 済みオブジェクトの Proxy が本来と異なる値を返せない
freeze したオブジェクトの Proxy を作る
get でそのままプロパティを返すだけだと特に問題ない

const frozen = Object.freeze({x: 1})

const proxy = new Proxy(frozen, {
get(target, name) {
return target[name]
}
})

console.log(proxy.x)
// 1

同じ値を返せばリテラルでも

const frozen = Object.freeze({x: 1})

const proxy = new Proxy(frozen, {
get(target, name) {
return 1
}
})

console.log(proxy.x)
// 1

異なる値を返そうとすると

const frozen = Object.freeze({x: 1})

const proxy = new Proxy(frozen, {
get(target, name) {
return 2
}
})

console.log(proxy.x)
// Uncaught TypeError: 'get' on proxy: property 'x' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '1' but got '2')
// at <anonymous>:9:19

Proxy 済みの別オブジェクトなのに元の freeze を引き継がせようとしてるみたい
そんな機能いらないけど
でも Proxy にわたすオブジェクトをダミーにすればいいので回避は簡単

本来の取得値に 1 足したものを返したい場合

const frozen = Object.freeze({x: 1})

const proxy = new Proxy(frozen, {
get(target, name) {
return target[name] + 1
}
})

とする代わりに

const frozen = Object.freeze({x: 1})

const proxy = new Proxy({}, {
get(target, name) {
return frozen[name] + 1
}
})

console.log(proxy.x)
// 2

とすればエラーにはならない
VSCode の検索のフォルダ指定
Ctrl-Shift-F を押したり サイドバーの虫眼鏡アイコンから出てくる検索画面
「files to include」 というところに 「*.js」 や 「src/**/*.js」 といった形で指定することで対象のファイルを制限できる

「src/**/*.js」 だと src フォルダの全部の .js ファイルだけど src フォルダが複数あれば全部が対象になる
「app1/src」 と 「app2/src」 があれば両方から探される
どちらかに制限したい場合は親も指定すればできる 「app1/src/**/*.js」

親がなくて 開いたフォルダの直下に 「src」 があって 「subapp/src」 もある場合
探したいのは直下の 「src」
ルートからの検索になるからと思って 「/src/**/*.js」 と書いたら見つかるはずのものも見つからなかった

「/」 は開いたフォルダのルートではなくファイルシステムのルートになるみたい
Windows の場合は 「/src」 は 「C:\src」 フォルダが対象になってる
開いたフォルダのルートから検索したい場合は 「./」 にすればできた
カレントディレクトリが開いたフォルダのルートという扱いみたい
「./src/**/*.js」

開いたフォルダ内のみって思ってたけどパス指定すればフォルダ外も検索できるのは結構便利そう
TortoiseGit で OpenSSH の鍵を使う
久々に TortoiseGit が入ってる PC でリポジトリを追加しようとしたのですが TortoiseGit は Putty の鍵を要求するので OpenSSH の鍵を変換する必要があり面倒です 
OpenSSH をクライアントに使えないのか調べると設定できました

右クリックメニューから Settings を選んで設定ウィンドウを出して サイドバーから Network を選択
パネル内の 「SSH client」 に OpenSSH の ssh.exe のパスを設定する
最近の Windows10 では標準で OpenSSH クライアントが入ってるのでこれを設定

C:\Windows\System32\OpenSSH\ssh.exe

これでクライアントの切り替えができました

次は鍵の設定ですが 「Git > Remote」 にあるリモートの設定は 「Putty Key」 となっていて OpenSSH 用のキーのパスを入れてもダメでした
個別設定はできないみたいなので デフォルトの鍵の配置場所に配置します
ホームフォルダ内の .ssh フォルダ内に id_rsa という名前で配置します
これで問題なく通信できるようになりました

ただ この設定はリポジトリごとではなく全体設定なので 既存リポジトリではこれまでのクライアントで個別に設定した鍵を使いたいという場合には使えなさそうです
window が load 済みか知りたい
HTML のパース時に実行されるスクリプトを埋め込めるなら

window.loaded = false
window.addEventListener("load", () => {
window.loaded = true
})

というスクリプトを実行して window.loaded 変数を見るようにできる
ただ いつ実行されるかわからない場合には上のスクリプトを実行してもすでに load イベントが起きたあとだと検出できない
いつ実行されるかわからない状況で すでに load イベントが起きたのかまだなのかを判断したい
window の load イベントじゃなくて document の DOMContentLoaded なら document.readyState でわかるけど window の load だとそれらしいプロパティがない

直接的なプロパティはなさそうだけど load などの各イベントがいつ起きたかを保持するプロパティがどこかにあった気がするので探すと performance.timing にあった
load イベントが発生する前は performance.timing.loadEventEnd は 0 だけど load イベントが起きるとそのタイムスタンプが入ってる

const windowIsLoaded = () => !!performance.timing.loadEndEvent

という感じで判定すれば良さそう
PHP を Nginx で動かすとき index.php を公開フォルダにおかなくてもよかった
Apache の感覚で URL を rewrite して公開フォルダに配置した index.php を呼び出してそこから公開フォルダの外にある PHP ファイルを実行するものだと思ってた
だけど fastcgi_param の設定で実行するスクリプトファイルのパスを指定してる
URL のパスに応じた PHP ファイルを実行する場合は URL に応じたファイルのパスを設定するけど rewrite するような場合は URL 問わず常に同じ PHP ファイルになる
それなら直接設定ファイルにファイルのパスを書いておける
この指定は公開フォルダの内側にする必要はなし
/opt/app/static が公開フォルダだとして

fastcgi_param  SCRIPT_FILENAME  /opt/app/main.php;

と設定すれば公開フォルダ内に index.php みたいなエントリポイント PHP ファイルは要らなくなる

Apache の rewrite させる設定は何度見てもどうなってるのかよくわからないから こういうシンプルな方法でできるのはいいところ
Chrome のコンソールに文字列を出力する時
最初が文字列じゃないと 以降の文字列にクオートがつく

console.log("1", "1")
console.log("1", 1)
console.log(1, "1")
console.log(1, 1)
console.log("1", 1, "1")
console.log(1, "1", 1, "1")
1 1
1 1
1 '1'
1 1
1 1 1
1 '1' 1 '1'

Node.js だとそんなことない

これでも基本は困らないけど間に文字列を挟みたい時

console.log(1, "(", 2, ")")
// 1 '(' 2 ')'

これは嫌
この場合なら `` を使えば

console.log(`${1} (${2})`)
// 1 (2)

にできるけど 値部分がオブジェクトかもしれない

console.log(`${{x: 1}} (${{x: 2}})`)
// [object Object] ([object Object])

こうなると中身が見れない
こういうときに普段使わないフォーマット文字列が役に立つ

console.log("%o (%o)", {x: 1}, {x: 2})
// {x: 1} ({x: 2})
WeasyPrint でドキュメントタイトルをヘッダに表示する
WeasyPrint は PDF 作成に特化してるのでブラウザでは使えない CSS も使える
結構古い Working Draft だけどこの辺
https://www.w3.org/TR/2014/WD-css-gcpm-3-20140513/

string-set プロパティと string 関数が使えるので セクションタイトルをページの右上に常に表示したいなら

<!doctype html>
<meta charset="utf-8" />

<title>title</title>

<style>
@page {
size: A4;

@top-right {
content: string(heading);
}
}

h1 {
string-set: heading content();
}
</style>

<h1>h1</h1>
<p>p</p>

という感じで h1 のスタイルで string-set を指定して heading に h1 タグ内の content を設定
右上のヘッダ部分の content で heading を参照
これでヘッダにセクションタイトルを設定できる

それならドキュメントタイトルを左上に表示にしたければ title タグに同じようなことをすればできるはず

<style>
@page {
size: A4;

@top-left {
content: string(title);
}

@top-right {
content: string(heading);
}
}

title {
string-set: title content();
}

h1 {
string-set: heading content();
}
</style>

これで表示されるかと思ったけど表示されない

title タグは head タグの中だからスタイルの対象外?
調べると head タグの中ではなく display: none の要素は対象外らしい
https://github.com/Kozea/WeasyPrint/issues/473

回避策は head と title タグを display: none じゃなくすることらしい
だけど head タグを block にすると表示対象になるのでその中も表示される
head タグ内の他のタグ(style タグなど)は display: none だから 問題は title タグ自身も表示されること
これを避けるために visibility を hidden にする
それだけだと見えないだけでレイアウトに影響するのでサイズを 0 にする
これで左上にドキュメントタイトルが表示できるようになった

	head, head title {
display: block;
visibility: hidden;
width: 0;
height: 0;
}

結構前からある問題みたいだし そろそろ修正されて欲しいけど
Knex が 1.0 になってた
久々にみたら 1.0 になってた
https://github.com/knex/knex/blob/master/CHANGELOG.md

0.21 だったり 0.95 だったりでずっと 1 を超えなくて 0.X 系バージョニングを採用してるものとして取り上げられてたりもしたけど とうとう 1.0
大きく変わったことを期待したけど変化はそれほどなさそう
0.95 のときのほうが大きかった気がする
そういえば forever のときも大したことなかった気がするし 0.X 系プロジェクトの 1.0 にそこまで大きな意味はないのかも

影響しそうな破壊的変更はこれ
「Changed data structure from RETURNING operation to be consistent with SELECT」

以前書いたこともある returning を使った時の返り値の仕様

select メソッドだと .select("col1", "col2") のように可変長引数
returning は第 2 引数がオプションなので列名の指定は .returning(["col1", "col2"]) のように第 1 引数で配列形式
配列じゃなくて文字列単体で .returning("col1") とも書ける
この場合は ひとつだけなので返り値の配列の中の要素がオブジェクトではなく列の値になってる

await pg("table").insert([{ value: "a" }, { value: "b" }]).returning(["id"])
// [{ id: 1 }, { id: 2 }]

await pg("table").insert([{ value: "a" }, { value: "b" }]).returning("id")
// [1, 2]

慣れないと分かりづらいところはあるけど 引数の型が違うし 一つしか無いのにオブジェクトにする必要もなく合理的と思ってた
returning に id だけを指定してから id の配列にするために

const ids = rows.map(row => row.id)

というのはよく必要になるわけでこの手間をなくせる
だけど文字列でも "*" みたいなのが来ればオブジェクトになるし 気をつけないといけないポイントみたいになってた
それが 1.0 では変更されて常にオブジェクトになるようになった

await pg("table").insert([{ value: "a" }, { value: "b" }]).returning(["id"])
// [{ id: 1 }, { id: 2 }]

await pg("table").insert([{ value: "a" }, { value: "b" }]).returning("id")
// [{ id: 1 }, { id: 2 }]

id だけ指定して id の配列を受け取るようにしてる人は割といるんじゃないかと思うし 影響の大きな変更といえるかも
mysql/mariadb はこの機能がそもそもなかったと思うから影響受けるのは PostgreSQL/Oracle/MSSQL ユーザだけのはず
VSCode の言語の自動判定を無効にする
VSCode で新規ファイルを開いて文字を入力してると自動で言語が判定されて色がついたりします
HTML みたいなわかりやすいものだと精度が高いですが そうでもないものだと間違いが多いです
特に完全なコードではない部分的なものを書いてるときには違う言語が選択されることが多いです
また メモ用途でプレーンテキストとして書いていてもコードとして扱おうとして誤った選択をされます
ただ間違ったシンタックスハイライトをされるだけならまだしも 間違ったファイルタイプを基に拡張機能のインストールを勧めてきたりしてとても邪魔です

オフにしようと設定を探すと Language Detection というものがありました
これのチェックを外します

これで新規ファイルは Plain Text のままになります
勝手に判断して言語モードを切り替えられることがなくなります
PostgreSQL の正規表現が速い
前に SQL のクエリでわざと少し遅くしようとしたことがあり 遅くなるものといえば正規表現かなと試してみましたが 想像以上に速かったです
普通の正規表現だと遅いと言ってもそんなに変わらないので 遅くなるような正規表現を探したところシンプルなのだとこういうのが見つかりました

^(\d+)*$
.+a

まずは Node.js でどれくらいかかるのか調べると

/^(\d+)*$/.test("1234567890123456789012345678a")

が 17 秒ほど
最適化されるのか 3 回目くらいから 1 秒程度になりましたけど

/.+a/.test("1".repeat(100000))

が 9.8 秒ほど
repeat で 10 万文字もありますが 「.+a」 ではなく 「a.+」 だと 1 ミリ秒未満なので 文字数が多いせいというわけではないです

これを PostgreSQL で試したのですが

postgres=# \timing
Timing is on.
postgres=# select '1234567890123456789012345678a' ~ '^(\d+)*$';
?column?
----------
f
(1 row)

Time: 2.068 ms
postgres=# select '1234567890123456789012345678a' ~ '^(\d+)*$';
?column?
----------
f
(1 row)

Time: 0.538 ms
postgres=# select '1234567890123456789012345678a' ~ '^(\d+)*$';
?column?
----------
f
(1 row)

Time: 0.675 ms
postgres=# \timing
Timing is on.
postgres=# select repeat('1', 100000) ~ '.+a';
?column?
----------
f
(1 row)

Time: 5.444 ms
postgres=# select repeat('1', 100000) ~ '.+a';
?column?
----------
f
(1 row)

Time: 5.588 ms
postgres=# select repeat('1', 100000) ~ '.+a';
?column?
----------
f
(1 row)

Time: 5.080 ms

速すぎです
数ミリ秒程度です

遅い正規表現はエンジンの実装次第なところがあるらしいですが パフォーマンスには力を入れているはずの V8 でも遅いものをこれだけの速度で実行できるというのはすごいです
ユーザ入力で遅くなりうる正規表現を実行するなら SQL として PostgreSQL 側で実行するのもありかも?
Docker コンテナでの入力・出力がすべてログに保存されてた
docker コマンドを見ていたら logs というのがあって何が見えるんだろうとためしてみたら 操作していたログが全部見れました
fedora コンテナを起動して適当にコマンドをいくつか打ってみます

user@PC005:/mnt/c/WINDOWS/system32$ sudo docker run -it fedora
[root@b50fa270db7e /]# ls
bin boot dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@b50fa270db7e /]# cat /etc/fedora-release
Fedora release 35 (Thirty Five)
[root@b50fa270db7e /]# python3
Python 3.10.0 (default, Oct 4 2021, 00:00:00) [GCC 11.2.1 20210728 (Red Hat 11.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 1+1
2
>>> exit()
[root@b50fa270db7e /]# exit
exit

単純に出力する ls や cat だけでなく python3 のインタラクティブモードで処理を実行したりもしてます
logs サブコマンドでこのコンテナを指定すると

user@PC005:/mnt/c/WINDOWS/system32$ sudo docker logs b50 -t
2022-02-02T08:27:57.299750500Z [root@b50fa270db7e /]# ls
bin boot dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
2022-02-02T08:28:04.372611200Z [root@b50fa270db7e /]# cat /etc/fedora-release
Fedora release 35 (Thirty Five)
2022-02-02T08:28:14.276586600Z [root@b50fa270db7e /]# python3
Python 3.10.0 (default, Oct 4 2021, 00:00:00) [GCC 11.2.1 20210728 (Red Hat 11.2.1-1)] on linux
2022-02-02T08:28:14.303025200Z Type "help", "copyright", "credits" or "license" for more information.
2022-02-02T08:28:18.316116000Z >>> 1+1
2022-02-02T08:28:18.316329100Z 2
2022-02-02T08:28:20.756528500Z >>> exit()
2022-02-02T08:28:29.796579000Z [root@b50fa270db7e /]# exit
exit-02-02T08:28:29.796598700Z

自分の入力したコマンドも出力もすべて表示できました
-t オプションでタイムスタンプを表示です
-t をなくせばタイムスタンプなしでコンソール上の見た目と一緒です

ログデータはただのテキストではなく シェルのエスケープ文字等も含まれるようでタイムスタンプを表示すると一部おかしくなったりします
最後の行で 2022 が exit に置き換わっていますが こういうのがたまに発生します

ログの実体のファイルは↓にあります
/var/lib/docker/containers/{id}/{id}-json.log
id にコンテナ id が入ります

user@PC005:/mnt/c/WINDOWS/system32$ sudo ls /var/lib/docker/containers
b50fa270db7ea5ee9eb14aadbe09fe9803aeae6f54ba342600f0079d4c43aef8

user@PC005:/mnt/c/WINDOWS/system32$ sudo cat /var/lib/docker/containers/b50fa270db7ea5ee9eb14aadbe09fe9803aeae6f54ba342600f0079d4c43aef8/b50fa270db7ea5ee9eb14aadbe09fe9803aeae6f54ba342600f0079d4c43aef8-json.log
{"log":"\u001b]0;@b50fa270db7e:/\u0007\u001b[?2004h[root@b50fa270db7e /]# ls\r\n","stream":"stdout","time":"2022-02-02T08:27:57.2997505Z"}
{"log":"\u001b[?2004l\r\u001b[0m\u001b[01;36mbin\u001b[0m \u001b[01;34mboot\u001b[0m \u001b[01;34mdev\u001b[0m \u001b[01;34metc\u001b[0m \u001b[01;34mhome\u001b[0m \u001b[01;36mlib\u001b[0m \u001b[01;36mlib64\u001b[0m \u001b[01;34mlost+found\u001b[0m \u001b[01;34mmedia\u001b[0m \u001b[01;34mmnt\u001b[0m \u001b[01;34mopt\u001b[0m \u001b[01;34mproc\u001b[0m \u001b[01;34mroot\u001b[0m \u001b[01;34mrun\u001b[0m \u001b[01;36msbin\u001b[0m \u001b[01;34msrv\u001b[0m \u001b[01;34msys\u001b[0m \u001b[30;42mtmp\u001b[0m \u001b[01;34musr\u001b[0m \u001b[01;34mvar\u001b[0m\r\n","stream":"stdout","time":"2022-02-02T08:27:57.30205Z"}
{"log":"\u001b]0;@b50fa270db7e:/\u0007\u001b[?2004h[root@b50fa270db7e /]# cat /etc-o\u0008\u001b[K\u0008\u001b[K/fedora-release \r\n","stream":"stdout","time":"2022-02-02T08:28:04.3726112Z"}
{"log":"\u001b[?2004l\rFedora release 35 (Thirty Five)\r\n","stream":"stdout","time":"2022-02-02T08:28:04.3758671Z"}
{"log":"\u001b]0;@b50fa270db7e:/\u0007\u001b[?2004h[root@b50fa270db7e /]# python3\r\n","stream":"stdout","time":"2022-02-02T08:28:14.2765866Z"}
{"log":"\u001b[?2004l\rPython 3.10.0 (default, Oct 4 2021, 00:00:00) [GCC 11.2.1 20210728 (Red Hat 11.2.1-1)] on linux\r\n","stream":"stdout","time":"2022-02-02T08:28:14.3029901Z"}
{"log":"Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\r\n","stream":"stdout","time":"2022-02-02T08:28:14.3030252Z"}
{"log":"\u003e\u003e\u003e 1+1\r\n","stream":"stdout","time":"2022-02-02T08:28:18.316116Z"}
{"log":"2\r\n","stream":"stdout","time":"2022-02-02T08:28:18.3163291Z"}
{"log":"\u003e\u003e\u003e exit()\r\n","stream":"stdout","time":"2022-02-02T08:28:20.7565285Z"}
{"log":"\u001b]0;@b50fa270db7e:/\u0007\u001b[?2004h[root@b50fa270db7e /]# exit\r\n","stream":"stdout","time":"2022-02-02T08:28:29.796579Z"}
{"log":"\u001b[?2004l\rexit\r\n","stream":"stdout","time":"2022-02-02T08:28:29.7965987Z"}

一つ一つの行が JSON 形式で 日付や本文が入っています

Docker は試しにあれこれやるときによく使うので その時のログをコンソールだけじゃなくてファイルにもログを残しておきたいってときに便利そうです
本来は シェルで自分が操作したログではなく データベースやウェブサーバのサービスを起動させたときのログを見る用途だと思いますけど
Node.js の http サーバで keep-alive を無効にする
リクエストは API のみで HTML のあとに CSS とか JavaScript とかをロードすることはないサーバ
同じクライアントからのリクエスト頻度はたまにあるくらい
keep-alive のメリットが特にないし keep-alive することで前書いたようなサーバを即時止めるために少し手間が必要になる
これは keep-alive を無効でいいかと思ったけどドキュメントを見ても無効化する方法が載ってない

server.keepAliveTimeout というのがあるけどこれはタイムアウトの設定だから 0 にしてもタイムアウトしなくなるだけ
たぶんクライアントが切断するまでつないだままになる

調べると レスポンスの HTTP ヘッダで 「Connection: close」 を送ればいいというのがあったけど 直接ヘッダ操作はあまりしたくない
ソースコードを見てると shouldKeepAlive というプロパティがあって これを見てヘッダの Connection を追加してる
https://github.com/nodejs/node/blob/v14.17.6/lib/_http_outgoing.js#L436

HTTP 1.0 だと false になってて HTTP バージョンによって自動で判断されてる
このプロパティを false にすれば無効にできそう

http.createServer((req, res) => {
res.shouldKeepAlive = false
res.end("ok")
})

これで試してみるとレスポンスは 「Connection: close」 になって ちゃんと無効化できてた
C ドライブ全体のフォルダ共有
デフォルトで C ドライブなどドライブルートはフォルダ共有されています

\\172.23.116.130\c$\

みたいに共有名にドライブレターと $ を入れます
隠されているのでコンピュータ名だけを指定して共有一覧を見ても表示されていません

PowerShell の Get-SmbShare コマンドで共有リストを取得するとちゃんと含まれています

PS C:\Users\nexpr> Get-SmbShare

Name ScopeName Path Description
---- --------- ---- -----------
ADMIN$ * C:\WINDOWS Remote Admin
C$ * C:\ Default share
IPC$ * Remote IPC

C$ へのアクセスで使うユーザ情報は Administrator の必要があります
普段使いの管理者権限ユーザの情報ではログインできません

Administrator は無効になってることが多いので 使う場合は

コンピュータの管理 > ローカル ユーザとグループ > ユーザー

から Administrator を選んで

◯ プロパティを開いてアカウントを無効にするのチェックをはずす
◯ 右クリックからパスワードの設定

が必要です

設定すると Administrator と設定したパスワードで C$ を開けます

セキュリティ的にはあまり良くないと思いますが VM やサンドボックスのファイルすべてをホスト側のエクスプローラで操作したいときに使えます
ユーザエージェントの Shadow DOM を見る
devtools の設定を開いて (F1 キーを押すかヘッダ右の歯車アイコン) Preferences ページの Elements セクションにある「Show user agent shadow DOM」のチェックを付ける

適当なページの video タグなどを見ると自作の ShadowDOM と同じように ShadowDOM の中が見える
Windows10 で自動スリープされない原因を調べる
いつの頃からか設定している時間が経っても自動でスリープしなくなりました
イベントビューアーを見ても 自動で再開されてるわけではなくスリープ自体が行われていないようです
設定を再確認しても Windows Update でリセットされたとかはなく ちゃんと設定されています
インストールしたソフトか Windows Update あたりがブロックしてそうです

調べてみると 「powercfg -requests」 でブロックしているソフトが見れるそうです
「実行:」のところに MoUsoCoreWorker.exe があったので Windows Update 関係のようです
アップデートを全て終えて再起動後に試してみましたが スリープしません

アップデート後の powercfg の結果はすべて「なし」になっています

PS C:\WINDOWS\system32> powercfg -requests
DISPLAY:
なし。

SYSTEM:
なし。

AWAYMODE:
なし。

実行:
なし。

PERFBOOST:
なし。

ACTIVELOCKSCREEN:
なし。

powercfg のヘルプを眺めていると分析・解析してくれるコマンドがいくつかあるようでした
試しにやってみたところ 「-energy」 で出力されるレポートに有用な情報がありました

PS C:\WINDOWS\system32> powercfg -energy
トレースを 60 秒間有効にしています...
システムの動作を監視しています...
トレース データを分析しています...
分析が完了しました。

エネルギー効率の問題が見つかりました。

5 個のエラー
6 個の警告
29 個の情報

詳細については、C:\WINDOWS\system32\energy-report.html を参照してください。

という感じで エラーや警告が表示されます
出力は HTML ファイルなのでレポートはブラウザで見れます

レポートのエラーの中にはこういうものがありました

システムの利用可能性の要求:システムが必要な要求
プログラムによって、システムが自動的にスリープ状態にならないようにする要求が行われました。
プロセスを要求しています \Device\HarddiskVolume2\Program Files\Buffalo\RakUpdate\RakUpdate.exe

バッファローのアップデートツールがスリープを妨害してるようです
特に使ってるものでもなかったので終了させてみると 無事に自動スリープができるようになりました

何がスリープを妨害しているかわからないときには「powercfg -energy」 のレポートを見てみるのがよさそうです
リモートデスクトップの上のバーが邪魔
リモートデスクトップを使ってるときに上にあるバーが邪魔です
自動で隠す事もできるのですが リモートの画面でブラウザを最大化していたりするとタブエリアを操作しようとしたときにバーが表示されて困ります

調べてみると 接続時の設定で「オプションの表示」から「画面」タブを開いて 「全画面表示の使用時に接続バーを表示する」 のチェックを外すとバーが非表示になるようです
やってみたところ 接続直後は表示されますが 数秒おいて自動で非表示になるとそれ以降は出て来なくなりました

便利だと思ってたのですが バーがないと最小化したり全画面化を解除してウィンドウモードに戻せません
切断だけならリモートのスタートメニューからできますが 最小化したいときには困ります

またも調べてみると ショートカットキーから接続中でもバーの表示状態を切り替えられるそうです
ただ ちょっと複雑なキー入力で Home キーか Break キーが必要みたいです
ノート PC など最近のコンパクトなキーボードだとこのあたりのキーはないんですよね
そういうときにどうすればいいのかを調べるとタスクマネージャからプロセスを終了するとかいう荒業を紹介してるところもありました

あれこれ試していたところ 2 つの方法を見つけました
どちらもタスクバーから操作する方法です
全画面アプリの使用中にタスクバーを出す方法は基本的には Windows キーを押すか別アプリをアクティブにすることです
ですがリモートデスクトップの場合は Windows キーはリモートに送信されますし ローカルの別アプリを起動するのも簡単には行かないです

1 つは Ctrl-Alt-Delete キーの入力からタスクマネージャの表示を行います
こうしてタスクマネージャをアクティブにするとタスクバーが表示されます
後は他のアプリのときと同じように最小化します

2 つめは ちょっと裏技ぽい方法です
Windows10 のデスクトップ切り替えを使います
別のデスクトップに切り替えて Windows キーを押してスタートメニューを出します
このメニューが表示されてる状態でリモートデスクトップが全画面のデスクトップに戻します
すると スタートメニューが出たままになり タスクバーも表示されています
Home キーが無いような環境はだいたいノート PC やタブレット PC でタッチパッドがあるので デスクトップ切り替えは 3 本か 4 本指で左右にスライドするだけなので簡単です

(追記)
タッチパッドなら 3 本指で下にスワイプなどにデスクトップの表示を割り当てておくと これはリモートに送信されずローカルで行われるのでリモートデスクトップを最小化できました
他のウィンドウも最小化されますけど……
全画面表示を一時解除したいだけならこれが一番簡単かもですね
Edge の devtools を英語に戻す
Chrome が入ってない環境を使ったとき Edge があるし これでいいかと使ってました
なんとなく devtools を開くと……

edgedevtools01

日本語
違和感しかないです
そういえば Edge では devtools もローカライズするとか言う話ありましたっけ

日本語である必要がないですし 特にタブのところみたいなカタカナで並ぶようならもう英語でいいよって思うんです
devtools みたいなところの詳しい情報は基本英語で書かれてるので 自分の画面が日本語になってると対応するところを探す余計な手間が入って困ります
ググるときにも日本語名でググっても大した情報が出ないですし

英語に戻そうと思ったもののこれまで devtools の言語設定なんて見たことがないです
どこから戻せるのか調べてみました

Edge では設定画面に言語関係の設定が追加されていました
devtools 上で F1 キーを押して設定画面を出すと一番最初にあります

edgedevtools02

「ブラウザーの言語に合わせる」のチェックを外します
すると自動で devtools が自動で再起動して 再起動後にはいつもの英語版に戻っています
.gitignore でトップレベルのみ除外したい
これまで build とかのフォルダを除外するときは階層を気にしなかったので単純に .gitignore に「build/」のように書くだけでした
これだとどの階層の build も除外されるのでトップレベルのみ除外してそれ以外は含めたいというときに困りました

mercurial の .hgignore だと「syntax: regexp」で正規表現モードに切り替えてから「^」で始めるみたいな工夫が必要だったので git でもそういうのが必要かなと思ったのですが git では単純に「/」から書き始めるだけでトップレベルを表せました
簡単で助かります

「/」から始めるとトップレベルになるなら「/foo/bar」のようにして特定フォルダ内の 2 階層目を除外もできるので書きやすいです
深い階層を指定するならリポジトリルートのフォルダに全部書くよりも .gitignore を foo フォルダに置いて「/bar」と書くような方法のほうが良いかもです
変数を設定して文字列の JavaScript コードを実行
const code = `console.log(a + b)`

こういう文字列に入った JavaScript コードを実行したくて そのコード内で使われる変数は

const obj = { a: 1, b: 2}

みたいなオブジェクトで設定したいとき

シンプルでライブラリなどで見かけるものは with + eval

with(obj) { eval(code) }
// 3

with は推奨されないけど deprecated というわけではないのでこういうときには使うのは問題ないみたい
MDN によればパフォーマンス的にも遅くなるどころか優れてるらしい

with を使いたくない場合は こういうこともできる

Function(
...Object.keys(obj),
code
)(...Object.values(obj))
// 3

下のコードを実行するのと同じ

(function(a, b) {
console.log(a + b)
})(1, 2)
// 3
fs.appendFile は順番保証されなかった
書き込みを待たずに次の処理に行きたかったのでこんなのを書いたら

const fs = require("fs")

const w = text => {
fs.appendFile("./log.txt", text + "\n", () => {})
}

w("A")
w("B")
w("C")
w("D")
w("E")
w("F")
w("G")
w("H")
w("I")
w("J")

順番がバラバラだった

D
A
C
E
F
H
B
I
G
J

非同期とは言え書き込み順は保証されてると思ったのにそんなことなかった

キューに入れて書き込み順は保証するようにした

const fs = require("fs")

const w = (() => {
const q = []
let running = false
const run = async () => {
running = true
let text
while(text = q.shift()) {
await fs.promises.appendFile("./log.txt", text + "\n")
}
running = false
}
return text => {
q.push(text)
if (!running) run()
}
})()

w("A")
w("B")
w("C")
w("D")
w("E")
w("F")
w("G")
w("H")
w("I")
w("J")
VSCode で一旦閉じたファイルの Ctrl-Z や Ctrl-Y が使える
VSCode で編集していて 一旦閉じたファイルだけどちょっと前の状態に戻したいなと思ってダメ元でもう一度開いて Ctrl-Z を押したら戻せた
VSCode を閉じるまではファイルを閉じても保存されてるみたい

助かる機能だけど 間違って Ctrl-Z しすぎないように一旦確定のためにファイルを閉じて開き直すってやってたのが意味無いのはそれはそれで怖いかも
Python のパッケージの場所
site パッケージを使う

WSL の例

>>> import site
>>> site.getsitepackages()
['/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages',
'/usr/lib/python3.6/dist-packages']
>>> site.getuserbase()
'/home/wsluser/.local'
>>> site.getusersitepackages()
'/home/wsluser/.local/lib/python3.6/site-packages'

実際にインポートされるのは sys.path の場所から

>>> import sys
>>> sys.path
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload',
'/home/wsluser/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages',
'/usr/lib/python3/dist-packages']

-m site を使うとまとめて見れる

~> python3 -m site
sys.path = [
'/home/wsluser',
'/usr/lib/python36.zip',
'/usr/lib/python3.6',
'/usr/lib/python3.6/lib-dynload',
'/home/wsluser/.local/lib/python3.6/site-packages',
'/usr/local/lib/python3.6/dist-packages',
'/usr/lib/python3/dist-packages',
]
USER_BASE: '/home/wsluser/.local' (exists)
USER_SITE: '/home/wsluser/.local/lib/python3.6/site-packages' (exists)
ENABLE_USER_SITE: True

msys2 の場合

$ python -m site
sys.path = [
'/home/msysuser',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/msysuser/.local' (exists)
USER_SITE: '/home/msysuser/.local/lib/python3.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

msys2 (mingw64) の場合

$ python -m site
sys.path = [
'C:/softwares/msys2-x86_64/home/mingwuser',
'C:/softwares/msys2-x86_64/mingw64/lib/python38.zip',
'C:/softwares/msys2-x86_64/mingw64/lib/python3.8',
'C:/softwares/msys2-x86_64/mingw64/lib/python3.8/lib-dynload',
'C:/softwares/msys2-x86_64/mingw64/lib/python3.8/site-packages',
]
USER_BASE: 'C:\\Users\\mingwuser/.local' (doesn't exist)
USER_SITE: 'C:\\Users\\mingwuser/.local/lib/python3.8/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

Windows (Anaconda3) の場合

>py -m site
sys.path = [
'C:\\Users\\winuser',
'C:\\Users\\winuser\\Anaconda3\\python37.zip',
'C:\\Users\\winuser\\Anaconda3\\DLLs',
'C:\\Users\\winuser\\Anaconda3\\lib',
'C:\\Users\\winuser\\Anaconda3',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages\\win32',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages\\win32\\lib',
'C:\\Users\\winuser\\Anaconda3\\lib\\site-packages\\Pythonwin',
]
USER_BASE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python' (doesn't exist)
USER_SITE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python\\Python37\\site-packages' (doesn't exist)
ENABLE_USER_SITE: True

Windows (CPython インストーラでユーザインストールの場合)

>py -m site
sys.path = [
'C:\\Users\\winuser',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\python38.zip',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\DLLs',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\lib',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32',
'C:\\Users\\winuser\\AppData\\Local\\Programs\\Python\\Python38-32\\lib\\site-packages',
]
USER_BASE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python' (doesn't exist)
USER_SITE: 'C:\\Users\\winuser\\AppData\\Roaming\\Python\\Python38\\site-packages' (doesn't exist)
ENABLE_USER_SITE: True

fedora 31 の場合

~> python -m site
sys.path = [
'/home/fedorauser',
'/usr/lib64/python37.zip',
'/usr/lib64/python3.7',
'/usr/lib64/python3.7/lib-dynload',
'/home/fedorauser/.local/lib/python3.7/site-packages',
'/usr/lib64/python3.7/site-packages',
'/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/fedorauser/.local' (exists)
USER_SITE: '/home/fedorauser/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True


Windows だと DLLs に .pyd ファイル
Linux は lib-dynload に .so ファイル (dyn は dynamic)

ここのフォルダは _ から始まるファイルばかり
.py ファイルからインポートされる内部モジュールばかりでユーザが直接 import するのはなさそう

Windows
>>> import site
>>> site
<module 'site' from 'C:\\Users\\winuser\\Anaconda3\\lib\\site.py'>
>>> import _tkinter
>>> _tkinter
<module '_tkinter' from 'C:\\Users\\winuser\\Anaconda3\\DLLs\\_tkinter.pyd'>

Linux
>>> import _sqlite3
>>> _sqlite3
<module '_sqlite3' from '/usr/lib64/python3.7/lib-dynload/_sqlite3.cpython-37m-x86_64-linux-gnu.so'>
systemctl 使わず Apache を操作する
動かしたい PHP のページがあるけど ちょうど使える環境がない
Docker に CentOS のイメージ入ってたしこれでいいか

みたいな感じで試してみたら systemctl が動かない!


Docker だと PID 1 が systemd じゃなくて systemd が起動していません
そのせいで systemctl コマンドは使えません

Apache の場合 httpd コマンドを実行するだけでバックグラウンドで動作します
再起動や停止するには httpd -k に stop などのコマンドを指定します

httpd -k stop
httpd -k restart

また systemd が使えないと apachectl も同様に使えません

System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Node.js でも console.table できる
罫線をうまく使って表が表示される
さすがにブラウザみたいにヘッダクリックでソートは無理

Linux だと問題なくキレイに表示されてるけど Windows だとずれて表示された
フォントの問題みたい
横線が半角でいいのに全角表示されるのが原因
Linux での表示もコピペしてエディタで見るとずれる

console.table([{x: 1, y: 2}, {x: 3, y: 0}, {x: 4, y: 4}])
┌─────────┬───┬───┐
│ (index) │ x │ y │
├─────────┼───┼───┤
│ 0 │ 1 │ 2 │
│ 1 │ 3 │ 0 │
│ 2 │ 4 │ 4 │
└─────────┴───┴───┘

半角ハイフンに置換してみると

┌---------┬---┬---┐
│ (index) │ x │ y │
├---------┼---┼---┤
│ 0 │ 1 │ 2 │
│ 1 │ 3 │ 0 │
│ 2 │ 4 │ 4 │
└---------┴---┴---┘
モバイルのマウスイベント
タップで click イベントが起きる

◯ Android
長押しで contextmenu イベントが起きる
ダブルタップしても dblclick イベントは起きない

◯ iOS
長押ししても contextmenu イベントは起きない
ダブルタップで dblclick イベントが起きる
コマンドラインで JSON 表示 (Node.js 編)
これの Node.js 版

node -p "require('./dump.json')"

node コマンドを普通に実行して REPL で require すればいいだけで Node.js インストール済み環境ならそもそも困らないと思う
Python のと違って .foo みたいなプロパティアクセスでほしいとこだけ取れるのが使いやすい
コマンドライン環境で JSON フォーマットしたい
自動生成された圧縮された JSON をみたいのですが インデントもない状態だと読むのが辛いです
短ければコピペして手元の Windows でフォーマットすればいいのですがちょっと長めです
ローカルまでコピーしてくるのも面倒ですし フォーマットするツールをインストールするのもできれば避けたいです

いい方法ないものかと探してみると Python でできました

python3 -m json.tool min.json

これで min.json をフォーマットして表示できます
デフォルトでいろいろな機能が揃ってるのでやっぱり Python は便利ですね
CentOS8 はデフォルトで入ってないのが残念ですが これくらいなら platform-python 使ってしまって良いかも

注意したいのは 複数指定できないところです

python3 -m json.tool a.json b.json

とすると 2 つを見れるのではなく b.json へ出力となって もとの b.json は消えてしまいます

使い方

user@ubuntu ~> python3 -m json.tool --help
usage: python -m json.tool [-h] [--sort-keys] [infile] [outfile]

A simple command line interface for json module to validate and pretty-print
JSON objects.

positional arguments:
infile a JSON file to be validated or pretty-printed
outfile write the output of infile to outfile

optional arguments:
-h, --help show this help message and exit
--sort-keys sort the output of dictionaries alphabetically by key
systemctl status が less で開かれる
久々に systemctl status でデーモンの状態を見たら less で開かれるようになってました
これまでは普通に status のテキストが出力されていましたが less で status のテキスト部分だけが開かれた挙動になってました

less なので検索できたり 画面右端ではみ出た部分も右にスクロールさせて読めます
これまでは詳細みるなら -l (?) みたいなオプションを追加してとか言われて面倒だったのが不要になったようです
ただ一々画面が切り替わったり 上の方にスクロールしてログとしてあとから確認できないなどのデメリットもあります

変わったのは fedora を 31 にアップデートしてからの気がします
less で見たいならパイプすればよくて 基本はこれまでのように単純に出力でいいかなと思って戻す方法がないか調べたらこんなページがありました

https://unix.stackexchange.com/questions/343168/can-i-prevent-service-foo-status-from-paging-its-output-through-less

--no-pager オプションを使えばいいようです