PID(+R)コントローラの離散化のテンプレ

古典制御手法の離散化

組み込みフィードバック制御をお前に教える
俺はなるべく双一次変換を使いたいけど貴様は?

変数

  •  T_s は周期時間
  •  T_u は緩和時間
    •  K_u は緩和時間 =\dfrac{T_u}{T_s}

z変換の実装

sドメインからzドメインに変換する時の双一次変換は

 \displaystyle
    s \to \dfrac{2}{T_s} \dfrac{1-\frac{1}{z}}{1+\frac{1}{z}}

で定義される

離散化制御器の実装

上で得た式をそのまんまテンプレート

 \displaystyle
    H_z = \dfrac{\Sigma_{p=0}^{m}\frac{a_p}{z^p}}{1-\Sigma_{q=0}^{n}\frac{b_q}{z^q}}

に対応させてIIRにぶち込めばおk
 aフィードフォワード bはフィードバック, zの次数に対応させて遅延をかければヨシ

一覧

比例(P)コントローラ

まぁそれはそう

 \displaystyle
    K_P \to K_P

積分(I)コントローラ

1次遅れ系の定常偏差を消去する時に使うやつ

 \displaystyle
    \dfrac{K_I}{s} \to \dfrac{\frac{T_sK_I}{2}+\frac{T_sK_I}{2z}}{1-\frac{1}{z}}

微分(D)コントローラ

分野的に使ったことないからよくわからん
が,擬似微分の実装は大事なので書いておく

 \displaystyle
    \dfrac{\rm{d}}{\rm{d}t} \to \dfrac{s}{1+T_us}
    \to \dfrac{\frac{2}{T_s(1+2K_u)}+\frac{-2}{T_s(1+2K_u)z}}{1-\frac{2K_u-1}{(2K_u+1)z}} \\
    \therefore K_Ds \simeq \dfrac{K_Ds}{1+T_us} \\
    \to \dfrac{\frac{2K_D}{T_s(1+2K_u)}+\frac{-2K_D}{T_s(1+2K_u)z}}{1-\frac{2K_u-1}{(2K_u+1)z}}

共鳴(R)コントローラ

あんま聞かないかもしれんが,特定の周波数成分だけゲインを爆上げできる
通過させる周波数 \omega_cと通過させる帯域 \omega_bと帯域ゲイン K_Rの設定が必要になる
負ループ組んだ時の安定性は勝手に確認してくれ,ボード線図が必要かも

 \displaystyle
    \dfrac{K_R\omega_bs}{s^2+\omega_bs+\omega_c^2}
    \to \dfrac{ \frac{2T_sK_R\omega_b}{4+2T_s\omega_b+T_s^2\omega_c^2} + \frac{0}{z} + \frac{-2T_sK_R\omega_b}{(4+2T_s\omega_b+T_s^2\omega_c^2)z^2} }{ 1 - \frac{8-2T_s^2\omega_c^2}{(4+2T_s\omega_b+T_s^2\omega_c^2)z} - \frac{2T_s\omega_b-4-T_s^2\omega_c^2}{(4+2T_s\omega_b+T_s^2\omega_c^2)z^2} }

matlabで並列処理を伴う高次方程式の解計算とグラフ作成

なにがしたいか

  • 5次以上の高次方程式の数値解を網羅したい
    • 一般解の表現が存在しない以上は数値的に求めるしかない
    • パラメータを弄るとどんな感じに変化するかを確認したい
    • でもちょっと面倒なので簡単な2次方程式で実装してみるよ
  • 国の金で買った多コアCPUなんだから使い潰したいね
    • 簡単な並列処理の導入と通信手法のテンプレート化を図る

注意

  • matlabの並列化にはParallel Computing Toolboxのライセンスが必要だぞ
    • 対応していないと並列化は(多分)できない,設定も表示されないかも

jp.mathworks.com

方程式を定義しよう

.mlxって知ってる?
ライブスクリプトを使うと方程式の作成と編集が楽になるよ

clear;

syms a b x;
symfx = x^2 + a*x + b;

save("testEquation.mat");

.mファイルに保存すれば同じ方程式をいつでも呼び出せる

並列処理の環境設定

ライセンスがあれば左下のアイコンが表示されるので,並列基本設定に進む

手元のPCだけならlocalを選ぶ(普通はそれ以外は出てこないはず),ワーカー数を指定する(物理コアよりも少なめで設定)

OKを押したら並列プールを起動してみる,謎の模様が青く光ってればOKだ

実装と実験

簡単にルールを説明すると,

  • parforforループの体をしながらも処理を1つずつ並列ワーカーに送信する
  • parforの中にparforを入れてはいけない
  • parforの内から外に変数をダイレクトに持ち出してはいけない
    • 外から内だとメモリ空間を共用できるけど内から外だとできるわけないからね
    • 代わりにparforの中からparallel.pool.DataQueuesendすることはできる
  • 以上の条件から実装したい処理は以下の通りになる
    • 親プロセスで方程式,パラメータ,グラフ,キュー,ちょっとした関数などの環境を整える
    • 子プロセスにターゲットの方程式とそれに適用するパラメータを与えて解かせる
    • 解を出したらデータセットを整形して子プロセスがキューを更新(事実上のプッシュ)する
    • 親プロセスはキューを監視してデータセットを元にグラフを(リアルタイムで)反映させる
  • 2種類のパラメータの総当たりを1つのparforで振り分けるので,そのへんの工夫は必要になる
    • 1次元の配列に2次元のデータをマッピングする競プロ感のある実装になってる,しゃーない
  • 実数解は青十字,虚数解は赤正円で表示を分けてみる
    • このビジュアライズとても便利なのでちょっと布教したい

コード

clear;
load('testEquation.mat');

brStr = convertCharsToStrings(char(10));


arrayA = -10:1:10;
lengtA = [-10,10];
arrayB = -10:1:10;
lengtB = [-10,10];


stDateTime = datetime();
messg = "Launched Analysis: testEquation"+brStr+"Start at: "+datestr(stDateTime);
disp(messg);
sendSlackTxt('simulinkMonitor',messg);



try
    image = imread(title+".png");
    clear("image");
    messg = "Already Analysed"+brStr+"Terminate Situation";
    disp(messg);
    sendSlackTxt('simulinkMonitor',messg);
catch


    title = "testEquation";
    disp(title);

    index = "testEquationSolver"+brStr+"with Parallel Computing";
    graph = initGraph(index);
    graph.WindowState="maximized";


    titleSolve = "Solutions Mapping";
    tableSolve = initTable(titleSolve,1,2,1);
    xlabel(tableSolve,"$\rm{Re}$",'Interpreter','latex');
    ylabel(tableSolve,"$\rm{Im}$",'Interpreter','latex');
    xticks(tableSolve,-10:1:10);
    xlim(tableSolve,[-10,10]);
    yticks(tableSolve,-10:1:10);
    ylim(tableSolve,[-10,10]);
    tableSolve.XAxis.Exponent = 0;
    tableSolve.YAxis.Exponent = 0;

    titleParam = "Parameter Mapping";
    tableParam = initTable(titleParam,1,2,2);
    xlabel(tableParam,"$a$",'Interpreter','latex')
    ylabel(tableParam,"$b$",'Interpreter','latex')
    xticks(tableParam,arrayA);
    xlim(tableParam,lengtA);
    yticks(tableParam,arrayB);
    ylim(tableParam,lengtB);
    tableParam.XAxis.Exponent = 0;
    tableParam.YAxis.Exponent = 0;


    pListSolve = parallel.pool.DataQueue;
    pListSolve.afterEach(@(data) addPlot(tableSolve,data));

    pListParam = parallel.pool.DataQueue;
    pListParam.afterEach(@(data) addPlot(tableParam,data));



    parfor countPattr = 1:(length(arrayA)*length(arrayB))


        [countA,countB] = addAxis(length(arrayA),length(arrayB),countPattr);
        paramA = arrayA(countA);
        paramB = arrayB(countB);


        numfx = subs(symfx,a,paramA);
        numfx = subs(numfx,b,paramB);

        poles = vpasolve(numfx,x);
        reals = real(poles);
        imgns = imag(poles);
        color = 'b';
        shape = '+';
        diamt = 8;
        for check=imgns.'
            if (check~=0)
                color='r';
                shape='o';
                diamt=16;
            else
            end
        end


        send(pListSolve,{reals,imgns,diamt,shape,color});
        send(pListParam,{paramA,paramB,diamt,shape,color});


    end

    %saveas(graph,title+".png");
    %close(graph);

    edDateTime = datetime();
    messg = "Finished Analysis: testEquation"+brStr+"End at: "+datestr(edDateTime);
    disp(messg);
    sendSlackTxt('simulinkMonitor',messg);


end



function [] = addPlot(table,data)
scatter(table,data{1},data{2},data{3}^2,data{4},data{5});
hold on;
drawnow('limitrate');
end



function [countA,countB] = addAxis(lengthA,lengthB,countPattr)
ii = countPattr;
jj = 0;
while ii>0
    ii = ii-lengthA;
    jj = jj+1;
end
countA = ii+lengthA;
countB = jj;
end

ちなみにグラフのプリセットはこれで生成している

function graph = initGraph(index)

    graph = figure;
    set(graph,'color','white');
    
    if index==""
    else
        sgtitle(graph,index,'FontName','Times New Roman','FontSize',48,'interpreter','latex');
    end

end
function [table] = initTable(index,heigt,width,order)

    table = subplot(heigt,width,order);
    set(table,'FontName','Times New Roman','FontSize',24);

    if index==""
    else
        title(index,'Interpreter','latex','FontSize',36);
    end

    set(table,'Box','on','LineWidth',1,'GridLineStyle','-','MinorGridLineStyle','none');
    set(table,'XColor','k','YColor','k','ZColor','k','GridColor','k');
    set(table,'GridColor','k','MinorGridColor','k','GridAlpha',0.5);
    set(table,'TickLabelInterpreter','latex')
    grid on;
    hold on;
    
end

結果

あー,中学校の数学でこういうのをやった記憶,みなさんにあるはずっすよねぇ
$a2-4b<0$で虚数になるから$b>\dfrac{a2}{4}$で青十字から赤正円に切り替わる

ちなみに私は普段はvpasolverで8次方程式を解かせています
2次方程式だと計算スピードがプロットスピードを超えるのでちょっともたついて感じるかもしれん

OutlookがAzureから中間者攻撃を受けている…かもしれない?

Outlookのメールアカウント(xxx.outlook.com)において,2022年7月11日よりも後に,IPv4アドレス13.101.0.0-13.101.255.255(13.101.0.0./16)から,IMAPでの不正なログイン(通常とは異なるアクティビティ)が,世界各地で確認されています
「これはAzure(Microsoftクラウドサービス)を経由した攻撃である」「パスワードの変更と2要素認証を設定するべきだ」というのが公式スタッフ(モデレータ)による説明(初報)であるため,こちらでも全く同様の対処を推奨しています
またこの問題を報告する際は,以下のリンクから提出できるAbuse Reportの利用が指定されています,こちらはその名の通りにAzureの不正利用に関するものとなっています,なお私は既に提出していますがこれは皆さまの判断にお任せしたいと思います

msrc.microsoft.com


日本語コミュニティにおいても類似または同様の事象を指摘する幾つかのスレッドが立っているようですが,英語コミュニティの以下の該当スレッドが(恐らく最も早い段階から活発に)情報の収集や問題の議論が行われているようです,というより私がこれを立てたのですが想像よりも多くの投稿があって驚いています,Abuse Reportも何回か出されている雰囲気がありますね…

answers.microsoft.com

ちなみにサポートセンターの末端のスタッフは恐らくこれについては何も知らされていないはずなので,今からチャットや電話で何かを聞き出してやる〜とするのはかなり迷惑になると思います,サービスの品質維持に協力するという体裁で控えましょう


そのタイミング性から中間者攻撃およびリプレイ攻撃が疑われていますが,現在はMicrosoftからの報告が途絶しています
またAzureの公式のIPリストには「13.101.0.0/16」が対象として掲載されておらず,ならばOutlookの仕様によるものではないかとの見方もあり,しかしそれならばアラートが出るのは不誠実だろうというツッコミがされています(下はjsonダウンロード)

www.microsoft.com

matlabでpng画像をbase64経由のエンコード・デコード

やりかた

主に2つある,基本的には上で事足りるよね…?

matlab純正関数を使う

matlab.net.base64encodematlab.net.base64decodeを直で叩く

jp.mathworks.com

jp.mathworks.com

そもそも画像データを読み込んで云々という動作させるには工夫が必要,データと型の扱いとか…

org.apacheを拝借する

org.apache.commons.codec.binary.base64インスタンス化する

weblabo.oscasierra.net

ただなんでJavaのライブラリが使えているのかよくわからん,R2022aで動いたけどこれはおま環かもしれないので要注意
あとmatlabとの食い合わせが悪いかもしれないので工夫が必要

使用例

やること

  • htmlData URI Schemeを出す
    • Chromeなら見れるはず
  • encode/decodeで元のファイルを復元する
  • そのままslackのwebhookにも送りつけてみる
    • typeにはmrkdwnを指定する
    • 試したい人はapiを用意してね
clear;


imgUrl = 'test.png';

fid = fopen(imgUrl);
fbt = fread(fid);
fclose(fid);


% With Apache
base64Coder = org.apache.commons.codec.binary.Base64;
fbtB64apa = uint8(base64Coder.encode(uint8(fbt)));
fbtB64apaChr = char(fbtB64apa.');
revfbtapa = double(typecast(base64Coder.decode(fbtB64apa),'uint8'));

newfid=fopen('test1Apache.png','w+');
fwrite(newfid,revfbtapa);
fclose(newfid);

% With Matlab
fbtB64matChr = matlab.net.base64encode(uint8(fbt));
revfbtmat = double(matlab.net.base64decode(fbtB64matChr).');

newfid=fopen('test1Matlab.png','w+');
fwrite(newfid,revfbtmat);
fclose(newfid);


% html base64 uri data scheme
html = '<head></head><body><img src="data:image/png;base64,'+string(fbtB64apaChr)+'"></body>'; % easy html style

newfid=fopen('test1.html','w+');
fwrite(newfid,html);
fclose(newfid);


% try to send Slack webhook
url = 'https://hooks.slack.com/services/xxx/yyy/zzz'; % your Webhook API url

content.type="html";
content.text = '<img src="data:image/png;base64,'+string(fbtB64apaChr)+'">'; % easy markdown style

payload = jsonencode(content);
options = weboptions('CharacterEncoding','UTF-8','UserAgent','matlab_simulink','Timeout',8);

webwrite(url,payload,options);

どうなるか

こうなれば良い,matlabapacheで結果が一致するはず

htmlはブラウザで画像が見えていればOK
フォルダを見て画像が復元されていればOK

slackはクッソ長い文字列しか見えない,そもそもData URI Schemeに非対応か,もしくは文字数制限でインタプリタが動作しないか…
やはりどうしても画像投稿用のAPIを使う必要があるらしいな?

matlabの問題点

自前でuint8(binary)しないとダメ,内部で変換してくれるらしいのだが恐らくtypecast(binary,'uint8')をされている
画像データの読み込み時にdoublearrayになるので,それをcastしながらdecodeしてはいけない

apacheの問題点

恐らくちゃんとしたバイナリ列を返してくれるが,matlab側がint8で受け取るためオーバーフローみたいになる
ここではuint8castする必要がある

Powershell,ワンライナー,正規表現でファイルを選択,そのままtar.gzに圧縮

本当はあまりPowershelをは使いたくないが,需要に駆られたので適当に対策した時のメモ…
最近になってようやくtargzipが使えるようになったらしい,あまりにも最低限すぎるだろ

コード

ls | ? { $_.Name -match '.*.mp4'} -outvariable files |  tar -cf 'xxx.tar' $files | gzip -9 'xxx.tar'

最大圧縮以外あり得ない

ブロックダイアグラム作成御用達のdiagrams(旧称draw.io)

紹介

制御とか処理とかでブロックダイアグラムを作成したい時に利用できる

microsoftvisioみたいなものではあるが,例えば詳細な図面の設計に使えるものではないし,あくまで情報・工業・数学・産業といった分野において,ブロックダイアグラムなどの作成を支援してくれる

環境

ブラウザ

JavaScriptを有効にしておくだけでOK

app.diagrams.net

アプリ

インストールはこちらから,OSに対応してダウンロードしてね

get.diagrams.net

利点

  • 利用開始までの手順が簡単
  • 利用環境のハードルが低い
  • 機能が単純だから扱い易い
  • LaTeXの基礎的なコマンドに対応
  • 論文とかでも使えそうね

欠点

  • 美術的な創作業には不向き
  • レイヤーは扱いづらいかも

導入

ぶっちゃけhomebrewで入れられるのが強み

brew install --cask drawio                                             

ちなドキュメント

www.diagrams.net

faqは役に立つかも

www.diagrams.net

ビギナーズページ

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

drawio-app.com

作成

基本的には.drawioとそのバックアップ.drawio.bkpが作成される

加えてテンプレートも自由に選べる,ソフトウェア設計・クラウドネットワーク・ハードウェア設計のいずれも網羅している

ページ

下部でページを切り替えて作業できる,イラレっぽい

よく使う図形

各種テンプレートにはよく使う図形が同封されているが,これはライブラリ表示を有効化することでいつでも利用できる 左下の「図形を追加」から入って

適当にチェックを入れれば良い

または上部の「表示」->「図形」からでも設定できる

一応はそれぞれがライブラリという扱いをされている

数式組版

テキストを編集する際に$$で囲むと,その内部の文字列がLaTeXソースとして解釈され,編集後に組版されて表示される
上部の「拡張」->「数式組版」をオンにすれば良い

編集

ページ表示

グリッドが利用できるのがとても強い,ついでに点や線に合わせて自動的にスナップしてくれる

用紙サイズを設定してページビューをオンにすると境界線や中央線が表示される,背景色も一応は入れておこう

カラーパレットが使えるなら変えられる,枠線・テキスト・塗りつぶしで利用できる

グラデーション

4方向+放射状の5種類,かつ2色変化しか使えない,しょうがないね

レイヤー

上部の「表示」->「レイヤー」から展開できる

あとはお好みで作業しよう,やっぱりイラレっぽい

図形

左のブラウザからドラッグ&ドロップ,もしくは後述するインポートを行う

図のコネクタをドラッグで繋ぐとできる
ただし始点と終点の概念があるので方向に注意しよう,一応は右クリックから「逆」を選ぶと反転できる

drawio-app.com

分岐の書き方

接点が発生するのは図形と線の間のみなので,交点には必ず黒点を配置すると良い

交差の書き方

逆に交点を設けない場合は,わかりやすくジャンプさせると良い
線を選択して「スタイル」から「line jumps」で「円弧」を選択する

実際こうなるが,

前面と背面の関係によってどちらがジャンプするか決定される

入れ替えることも可能である

適度に大きい方がわかりやすいね!

矢マークの調整

「スタイル」の中段のリスト群の下列で,始点と終点のそれぞれにアイコンを適用できる
その下の「サイズ」で大きさを決められるが,線の太さに比例する係数であって絶対値ではないので注意

テキスト

選択した図形や線の上でダブルクリックすれば編集画面に入れる

drawio-app.com

フォント

「フォント」->「カスタム」に入れるので,

そこにシステムから入れるなりGoogleフォントを使うなりすれば良い

LaTeXコンパイルにおいて

フォントや自体は影響しないけど,色は上書きできるしレイアウトは変更できるし,サイズも比例して調整できる

インポート

上部の「ファイル」->「次をインポート:」

drawio-app.com

エクスポート

上部の「ファイル」->「形式を指定してエクスポート」->「(拡張子を選択)」

drawio-app.com

解像度について

このままでは弄れないので,「ファイル」->「形式を指定してエクスポート」->「高度な設定」から

DPIを上げれば良い,ついでに背景や余白についても設定しよう

「横幅」と「縦幅」は据え置きで,「ズーム」と「DPI」が変化するだけ
ちなみにここで言う余白は用紙サイズを基準としたものではないらしい,具体的には図形を収められる最小の長方形に勝手にトリミングしてくるので,pdfで出力すると表示やページがおかしくなってしまう,素直にpngで吐き出した方が良いかも

自作ライブラリ

drawio-app.com

作成

「ファイル」->「新規ライブラリ」で.xmlファイルとして作成できる お目当ての画像などを追加して保存する,上書き更新もできるはずだけど何故か動作がフリーズする場合があるかもしれないね(私は手元でよくバグるので)

読込

「ファイル->ライブラリを開く」で該当の.xmlファイルを選択する

自作シェイプ

図形そのものを自作できるが,コーディングが必要になってくるので辛い,そこまでする必要に駆られることはそうないと思う

www.diagrams.net

ググっても思った以上に資料が出ないので,本気でやるとなるとかなり辛そう
ただどこかsvgに似ているので,svgに慣れている人ならできそうな気もする

制御の資料を作るだけならぶっちゃけ不要だし,それくらいデフォルトのライブラリが充実しているので心配しなくても良い

dockの大きさをdefaults writeで限界突破させる

やり方

デスクトップの写真を設定しようとして間違えてdockのところ触っちゃうと,なぜか勝手に限界値に設定されちゃうので

defaults write com.apple.dock largesize -int 256 | killall Dock

する必要がある

デカい方がいいね!

defaultsについて

read [ファイル名]

読み込み
辞書型のうち1つだけを操作する場合のコマンド,という解釈が近そう

特定のアプリ

アプリ名に対応するファイル名を入れれば選択できる
入れない場合は全ての設定を吐き出す

辞書型

.plist<dict></dict>タグが{}で置換されるらしい
オプション名に後付けしていけば抜き出せるかも?

配列型

.plist<array></array>タグが()で置換されるらしい,JSONっぽいがJSONではない
ちなみに配列の番号を指定して読み込むのは不可能っぽい

export

.plistファイルを吐き出す

write [アプリ名] [オプション名] [値]

書き換え
辞書型のうち1つだけを操作する場合のコマンド,という解釈が近そう

addする場合

一般人の需要こそないだろうが,array-adddictionary-addがあるらしい

型指定

.plistではタグで型を指定する,-string-float-int-bool

Node.jsで.csv形式でログを取って.tar.gz形式で圧縮する

soluna-eureka.hatenablog.com

の続き

開発背景

電力が貴重なこの時代!学術研究でも計算機の消費電力は無視できない!そもそもCPU温度が下がらない状況は危ない!
ということで常駐で温度を監視してログを取れるやつにまで昇華しました,やってることは基礎的なことのはずなんだけどな…

ちなみに冷却装置の設定によっては室温との相関があるかもしれない,流体の熱伝導を考えればそれはそうという感じはあるが

機能

  • 1秒毎にCPU温度を監視
    • 設定温度を超過するとSlackにログを送信
  • 毎分10n秒でログを.csvに書き込む
    • excelとかで見るのを前提に
  • 毎時0分でSlackにログを送信
    • 動作確認の意味合いも兼ねて
  • 毎日0時5分に前日の.csv.tar.gzに圧縮し元の.csvを削除
    • もちろん最大圧縮

以下,反省と言い訳

csvの操作がクソ難しい

こちらもオブジェクト型や配列型の酷使に慣れていなかったというのもあるが,単純にnpmcsvの各ライブラリ(csv-parseとかcsv-stringifyとか)の仕様がかなり変わっていて導入が面倒だった,ごく最近のいい感じのブログとかもなかったので公式ドキュメントを見ないとなかなか実装できなかった,あっ営利目的でこの記事を参考にしてもいいけど参考にしたらここのリンクを広めといてね♡

async/await多すぎ問題

これってsyncなライブラリだよな〜と思ったらasyncでした〜みたいなことがちょっと多すぎて頭にきますよ〜,じゃけんasync/awaitかけますね(保険的な意味合いで),async functionで定義した関数をawaitなしで呼び出すとpromiseが返ってくるなんて初めて知りましたね…言われてみればそうなんだけど,本当にそうなんだ…

.csvファイルの成形問題

ヘッダの有無くらいしか実装できなかったが,実際の利用においては問題はないと判断したのでこれで行くことにした,調べた限りではヘッダありで.csvをパースしてjsonライクにオブジェクト型でデータを操作する的な場合もあるらしいが,わざわざ各ループの間で状態を渡し続ける実装なんて面倒だし,もう1行目の完全一致だけで判定した方がはるかに早そうなのでなぁ…

その無駄っぽい変数宣言いる?

いる(確信),書いてるうちにわからなくなりかけたので必要だった(確信),ちなみに文字数と修飾語で用途を分けている

地味に温度の精度が悪い

1℃単位の測定しか許容されていなかったり誤差が無駄に大きかったりという噂がOpen Hardware Monitorにはあるらしいが,まぁ気休め程度だと思って使うことにする,実際に測定結果を見ていても面白いから問題なし

コード

// IO plugin
const fs = require('fs-extra');
const tar = require('tar');
const tryCatch = require('try-catch');
const scheduler = require('node-schedule');

// API plugin
const { getJSON } = require('simple-get-json');
const { stringify } = require('csv-stringify/sync');
const { parse } = require('csv-parse/sync');
const csvStringify = stringify;
const csvParsing = parse;

//Util plugin
const datetime = require('date-and-time');
const { IncomingWebhook } = require("@slack/webhook");

// Parameters
const slackURL = "https://hooks.slack.com/services/xxx/yyy/zzz";
const slackWebhook = new IncomingWebhook(slackURL);
const monitorURL = "http://localhost:8085/data.json";
const coreTempLimit = 60;




// API for Open Hardware Monitor
async function accessJSON() {
    try {
        var res = await getJSON(monitorURL);
        var raw = JSON.stringify(res, null, 4);
        var obj = JSON.parse(raw)['Children']; // for Open Hardware Monitor
        var tgt = obj[0]['Children'][1]['Children'][1]['Children']; // for Open Hardware Monitor
        return tgt;
    } catch (err) {
        console.log("something wrong happened with CPU Monitor!")
        tgt = [
            { Text: "0", Value: "0" } // for open hardware monitor
        ];
        return tgt;
    }
}


// Shaper for Open Hardware Monitor
async function makeMessage(tgt) {
    var cpuParam = "";
    var coreName = "";
    var coreTemp = "";
    var heatFlag = false;

    var i = 0;
    var j = 0;
    tgt.forEach(cpuCore => {
        coreName = cpuCore['Text']; // for Open Hardware Monitor
        coreTemp = cpuCore['Value']; // for Open Hardware Monitor
        cpuParam += (coreName + " is " + coreTemp + "\n");
        floatCoreTemp = parseFloat(coreTemp);
        i = i + 1
        if (floatCoreTemp > coreTempLimit) {
            j = j + 1;
        }
    });
    if (i == j) {
        heatFlag = true; // whether need to notice or not
    }

    var txt = cpuParam + datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS') + "\n";
    var flg = heatFlag;
    return [flg, txt];
}


async function makeCSVtext(tgt) {
    var cpuParam = [];
    var coreName = "";
    var coreNames = ["date"];
    var coreTemp = "";
    var coreTemps = [datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS')];
    var heatFlag = false;

    var i = 0;
    var j = 0;
    tgt.forEach(cpuCore => {
        coreName = cpuCore['Text']; // for Open Hardware Monitor
        coreNames.push(coreName);
        coreTemp = cpuCore['Value']; // for Open Hardware Monitor
        floatCoreTemp = parseFloat(coreTemp);
        coreTemps.push(floatCoreTemp);
        i = i + 1
        if (floatCoreTemp > coreTempLimit) {
            j = j + 1;
        }
    });
    if (i == j) {
        heatFlag = true; // judge whether to notice or not
    }

    cpuParam = [coreNames, coreTemps];
    var txt = csvStringify(cpuParam, { header: false }, { delimiter: "," });
    var flg = heatFlag;
    return [flg, txt];
}



// csv(array)_txt(string) Parse&Build interface
async function parseCSVdata(txt) {
    var csv = await csvParsing(txt, { columns: false, delimiter: "," });
    return csv;
}

async function parseCSVdataHead(txt) {
    var csv = await csvParsing(txt, { columns: false, delimiter: ",", to_line: 1 }); // get only header line
    return csv;
}

async function buildCSVdata(csv) {
    var txt = await csvStringify(csv, { header: false, delimiter: "," });
    return txt;
}



// API for Slack
async function sendMessage(flg, txt) {
    if (flg) { // grasp whether to notice or not
        try {
            await slackWebhook.send({
                text: `${txt}`
            });
        } catch (err) {
            var text = "temp flag is " + flg + ", but something error happened with slack!";
            console.log(text);
        }
    }
}

async function sendLogging(flg, txt) {
    try {
        await slackWebhook.send({
            text: `${txt}`
        });
    } catch (err) {
        var text = "something error happened with slack!";
        console.log(text);
    }
}



// commonly file system utils
async function createFolder(path) {
    try {
        await fs.ensureDir(path);
    } catch (err) {
        var text = path + " (folder) can not be created!";
        console.log(text);
    };
}

async function createFile(path) {
    try {
        await fs.ensureFile(path);
    } catch (err) {
        var text = path + " (file) can not be created!";
        console.log(text);
    };
}

async function writeupFile(path, txt) {
    await createFile(path);
    try {
        await fs.appendFile(path, txt);
    } catch (err) {
        var text = path + " (file) can not be written!";
        console.log(text);
    };
}

async function writeupCSVdata(path, txt) {
    var preTxt;
    var preCsv;
    var csv;
    try {
        preTxt = await fs.readFile(path);
    } catch (err) {
        var text = path + " (file) can not be read!";
        console.log(text);
        preTxt = "";
    };
    preCsv = await parseCSVdataHead(preTxt);
    csv = await parseCSVdataHead(txt);
    if (JSON.stringify(preCsv) == JSON.stringify(csv)) { // check a header, need to developed
        csv = await parseCSVdata(txt);
        csv.shift();
        txt = await buildCSVdata(csv);
    }
    await writeupFile(path, txt);
}



async function createArchive(filePath, archivePath) {
    try {
        tar.create({
            sync: true,
            file: archivePath,
            gzip: '-k -9'
        }, [
            filePath
        ])
    } catch (err) {
        var text = archivePath + " (archive) can not be created!";
        console.log(text);
    };
}


async function removeFileFolder(path) {
    try {
        fs.removeSync(path);
    } catch (err) {
        var text = path + " (file) can not be removed!";
        console.log(text);
    };
}




// initialize
(async() => {
    createFolder(".", "/Logs");
})();




// routine definition
const scheduleSendMessage = scheduler.scheduleJob("0-59 * * * * *", function() {
    var tgt;
    var txt;
    var flg;
    (async() => {
        tgt = await accessJSON();
        [flg, txt] = await makeMessage(tgt);
        await sendMessage(flg, txt);
    })();
});



const scheduleSendLogging = scheduler.scheduleJob("0 0 */1 * * * ", function() {
    var tgt;
    var txt;
    var flg;
    (async() => {
        tgt = await accessJSON();
        [flg, txt] = await makeMessage(tgt);
        await sendLogging(flg, txt);
    })();
});



const scheduleLogging = scheduler.scheduleJob("0,10,20,30,40,50 * * * * *", function() {
    var date = datetime.format(new Date(), 'YYYY_MM_DD');
    var path = "./Logs/" + date + ".csv"
    var tgt;
    var txt;
    var flg;
    (async() => {
        tgt = await accessJSON();
        [flg, txt] = await makeCSVtext(tgt);
        await writeupCSVdata(path, txt);
    })();
});



const manageLogfiles = scheduler.scheduleJob("0 5 0 */1 * *", function() {
    var cur = new Date();
    var old = datetime.addDays(cur, -1);
    var curDate = datetime.format(cur, 'YYYY_MM_DD');
    var oldDate = datetime.format(old, 'YYYY_MM_DD');
    var curPath = "./Logs/" + curDate + ".csv";
    var oldPath = "./Logs/" + oldDate + ".csv";
    var curGzip = "./Logs/" + curDate + ".tar.gz";
    var oldGzip = "./Logs/" + oldDate + ".tar.gz";
    (async() => {
        await createFile(curPath);
        await createFile(curGzip);
        await createArchive(oldPath, oldGzip);
        await removeFileFolder(oldPath);
    })();
    console.log(datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS'));
    var text = "logging files are regularLy updated!";
    console.log(text);
});

CPUをOpen Hardware MonitorとNode.JSで監視してSlackで通知する

何がしたいか

重いシミュレーションを長い時間かけてリモートPCで回し続けるのはやっぱり不安な点があり,特に知らん間に冷却装置が不調を来したらどうしようという不安に苛まれる,まして多少はCPUをチューニングしているような状態だとそれは尚更になる
ということでスクリプトでCPU監視し続けて,温度が一定値を超えたらSlackに通知を投げ,Remote Desktopで非常措置を迅速に行えるようにした(ソフトの関係上でWindows限定だけど)(ネットワークが切れた場合のことは知らんものとする)

大まかな筋書き

  • サーバーモードのOpen Hardware MonitorでPCを監視
  • NodeJSでそのAPIを叩いてJSONを取得・加工
  • CPUの状態を評価して必要な場合はWebhookのAPIを叩く
  • Slackで確認して対応する

内容

Open Hardware Monitorの設定

インストール

openhardwaremonitor.org

サーバーモードで起動

管理者権限でOpenHardwareMonitor.exeを立ち上げた後に「Options」->「Remote Web Server」->「Run」で開始
localhostでアクセス可能,ポートはOptions->Remote Web Server->Portで設定できる,ここに出るリンクのIPアドレスはグローバルアドレスらしいけど,普通にlocalhostのほうが早いし楽なのでオススメ,環境によってはコロコロ変わるし

NodeJSの用意

ハードウェアが絡むせいで色々と面倒だからWSLとか使わずにPowerShellから直にNodeJSを動かせるようにする

管理(nvm)の入手

他のツールで工夫するのも面倒なので公式が推してるnvm-windowsを入れる

openhardwaremonitor.org

github.com

最新版のnvm-setup.zipを落として解凍,適当に回答してインストール

NodeJSの入手

PowerShell管理者権限で起動(じゃないとインストール後に有効化できない)
nvm list availableで出てきたバージョンから好きなものを選んでnvm install x.y.z
最後にnvm use x.y.z,なぜか管理者権限が必要でない場合はstatus:1が返ってくる

VSCodeの用意

あったら楽なので

code.visualstudio.com

入れたらgitの拡張機能も入れておくべし

gitの用意

soluna-eureka.hatenablog.com

実は上の記事でWSL2のUbuntuの中にgitを入れる前にWindowsにもgitを入れていた
特に変なことをしなければインストールに問題は起きないしVSCode拡張機能にも対応してくれている(この場合は絶対にAdjusting your PATH environmentで真ん中を選ぶ必要があるので注意)

git-scm.com

docs.microsoft.com

下の記事がわかりやすいかも

sukkiri.jp

Slackの用意

適当なメアドでサインインしてブラウザ上で適当にワークスペースを作って通知専用のチャンネルを作る
「設定と管理」「ワークスペースの設定」から「管理者設定」の画面が出るので左の「App管理」をクリック
上の「Appディレクトリ」で「webhook」を検索して「Incoming Webhook」をクリック,「Slackに追加」を選択
「チャンネルへの投稿」と「名前をカスタマイズ」「説明ラベル」を適当に編集して「Webhook URL」を確保

なお,複数のWebhook URLを使い分けたい場合は「Slackに追加」で更に作成できる,上限数は知らんけど

プロジェクト作成

良さげなディレクトリを作ったらVSCodeから開いて拡張機能からgit initする
次にVSCode内でPowershell(ターミナル)を開いてnpm init,適当に入力する<br. さらに以下をnpm installする

  • @slack/webhook
  • date-and-time
  • get-json
  • node-schedule
  • post-json
  • simple-get-json
  • simple-post-json

スクリプト作成

  • JSONデータのうちCPU温度がある場所については各自で確認すること
    • シングルCPUならobj[0]['Children'][1]['Children'][1]['Children']が持ってる配列に格納されている
  • 必ずhttp://localhost:[numTCPport]/data.jsonを叩くこと
    • これは実際ブラウザ上でJSONを確認できる
  • coreTempLimitで通知を出したいCPU温度の下限値を設定できる
const scheduler = require('node-schedule');
const { getJSON } = require('simple-get-json');
const datetime = require('date-and-time');
const { IncomingWebhook } = require("@slack/webhook");

const slackURL = "[slack webhook url]";
const slackWebhook = new IncomingWebhook(slackURL);
const monitorURL = "http://localhost:8085/data.json";

const coreTempLimit = 50;

async function accessJSON() {
    var res = await getJSON(monitorURL);
    var raw = JSON.stringify(res, null, 4);
    var obj = JSON.parse(raw)['Children'];
    var tgt = obj[0]['Children'][1]['Children'][1]['Children'];
    return tgt;
}

async function makeMessage(tgt) {
    var cpuParam = "";
    var coreName = "";
    var coreTemp = "";
    var heatFlag = false;

    tgt.forEach(cpuCore => {
        coreName = cpuCore['Text'];
        coreTemp = cpuCore['Value']
        cpuParam += (coreName + " is " + coreTemp + "\n")
        floatCoreTemp = parseFloat(coreTemp);
        if (floatCoreTemp > coreTempLimit) {
            heatFlag = true;
        }
    });
    var txt = cpuParam + datetime.format(new Date(), 'HH:mm:ss:SSS YYYY/MM/DD');
    var flg = heatFlag;
    console.log(flg);
    return [txt, flg];
}

async function sendMessage(txt, flg) {
    if (flg) {
        await slackWebhook.send({
            text: `${txt}`
        });
    }
}


const scheduleBase = scheduler.scheduleJob("*/1 * * * * *", function() {
    var tgt;
    var txt = "";
    var flg = false;
    (async() => {
        tgt = await accessJSON();
        [txt, flg] = await makeMessage(tgt);
        await sendMessage(txt, flg);
    })();
});

稼働

node index.jsで永久に稼働する,Slackにメッセージが飛べば成功
デバッグ代わりにflgを出力している

改善したいところ

  • 検知間隔の設定を改善
    • */1 * * * * *は1秒毎という意味,2なら2秒毎
  • GUI設定の実装
    • NodeJSのお得意技
  • データ化,グラフ化
  • ローカルネットワークで繋がる他の複数のPCと連携

complex modificationsのjson fileを利用したマルチボタンのマウス + macOS + Karabinerの設定

settings for multi button mouse + macOS + Karabiner with complex modifications .json file

何がしたいか

腱鞘炎対策にマルチボタンのついてる縦型マウスを買ったが,macOSは一部のメーカを除いてマウスごとのドライバというものがなく,またデフォルトではmagic mouse(左右ボタン+中ボタン兼スクローラ)と同程度の動作しか利用できない,そのためKarabinerといった入出力の変換ソフトが必要になる
今回はマルチボタンの有効化とその簡単な割り当てを紹介するが,基本的に左・右・中のボタン(1番・2番・3番)はあんまりイジらない,既存の動作に依存や干渉の関係があると面倒なので

注意

Karabinerは仕様の関係で割と動作が強力である(ほぼハードの動作と直結する)ため,下手をすると壊れて操作を受け付けなくなる可能性がある,なので自己責任で丁寧に作業をすること
一応はアプリごとに動作を変えられたりもするが,あくまでウィンドウがどこにあるか程度でしか判別してくれないので,もしそれがソフト側をイジるだけで解決する問題なら,なるべくソフト側だけをイジった方が良い
例えば ↓

soluna-eureka.hatenablog.com

インストール

cliで取得

brew install --cask karabiner-elements

formulae.brew.sh

これに限る
--caskなので/Applications/Karabiner-Elements.app/Applications/Karabiner-EventViewer.appがあればok,起動しよう

起動,システムへのアクセス許可

初回起動の時点で色々と権限を要求されるので,システム環境設定->セキュリティとプライバシーから適宜に承認する

フォルダの確認

~/.config/karabiner/assets/complex_modificationsが勝手に生成されていればOK,その配下に定義に則って作成された任意のjsonファイルを置くことで,自由に機能を追加できるし管理できる
実際なんだかんだでGUIには頼らずテキストで保存した方がいい,GUIだけでは複雑な挙動を検討できないし,ハードウェア設定をも考慮した本体プロファイルと分けないと混乱する

プログラムの確認

表からはKarabiner-ElementsKarabiner-EventViewerを起動できるが,前者は設定用・後者はデバッグ用に過ぎない
実際は初回起動において監視・制御のデーモンを裏で勝手に展開してくれる,次回起動からはログイン時に勝手にそれらが起動する,ログイン項目に登録する必要はない(ただし再読み込みはKarabiner-Elementsからやる必要がある)

f:id:Soluna_Eureka:20220221012821p:plain

設定

バイス登録

有線・無線・Bluetoothは関係なく,使いたいマウスを接続したらKarabiner-Elements->Devices->Basic configurationに移動,hoge Optional Mouse (fuga)が見つかるはずなので,左端チェックボックスから有効化する
ついでにVendor IDProduct IDをメモする,これらは後で条件設定の時に使いたい

f:id:Soluna_Eureka:20220221223804p:plain

Advancedは特に要らない

基本設定

混乱回避のため,Simple Modificationsを空欄にし,Function keysをリセットする(To Keyをそのまま割り振ると良い)

拡張ファイルの製作

以下に一例を示す

  • 共通設定
    • 5番のみでtab
    • 4番のみでshift+tab
  • ブラウザのみ
    • 1番+2番でcommand+r
    • 5番+6番でcommand+w
    • 1番+5番でcommand+click
    • 1番+4番でshift+click
    • 2番+5番でcommand+c
    • 2番+4番でcommand+v
{
    "title": "MouseWithFiveButtons",
    "rules": [{
        "description": "Define how how a mouse with 5 buttons works",
        "manipulators": [{
                "description": "Rcmd+'w',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button4"
                        },
                        {
                            "pointing_button": "button5"
                        }
                    ]
                },
                "to": [{
                    "key_code": "w",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "Rcmd+'w',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button1"
                        },
                        {
                            "pointing_button": "button2"
                        }
                    ]
                },
                "to": [{
                    "key_code": "r",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "Lsft+Lclk, for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button1"
                        },
                        {
                            "pointing_button": "button4"
                        }
                    ]
                },
                "to": [{
                    "pointing_button": "button1",
                    "modifiers": [
                        "left_shift"
                    ]
                }]
            },
            {
                "description": "Lcmd+Lclk,for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button1"
                        },
                        {
                            "pointing_button": "button5"
                        }
                    ]
                },
                "to": [{
                    "pointing_button": "button1",
                    "modifiers": [
                        "left_command"
                    ]
                }]
            },
            {
                "description": "Rcmd+'v',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button2"
                        },
                        {
                            "pointing_button": "button4"
                        }
                    ]
                },
                "to": [{
                    "key_code": "v",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "Rcmd+'c',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button2"
                        },
                        {
                            "pointing_button": "button5"
                        }
                    ]
                },
                "to": [{
                    "key_code": "c",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "global settings for mouse button 4",
                "conditions": [{
                    "type": "device_if",
                    "identifiers": [{
                        "vendor_id": 7119,
                        "product_id": 5,
                        "description": "a mouse with 5 buttons"
                    }]
                }],
                "type": "basic",
                "from": {
                    "pointing_button": "button4"
                },
                "to": [{
                    "key_code": "tab",
                    "modifiers": [
                        "right_shift"
                    ]
                }]
            },
            {
                "description": "global settings for mouse button 5",
                "conditions": [{
                    "type": "device_if",
                    "identifiers": [{
                        "vendor_id": 7119,
                        "product_id": 5,
                        "description": "a mouse with 5 buttons"
                    }]
                }],
                "type": "basic",
                "from": {
                    "pointing_button": "button5"
                },
                "to": [{
                    "key_code": "tab"
                }]
            }
        ]
    }]
}

これを~/.config/karabiner/assets/complex_modificationsに作成する

作成したらKarabiner-Elements->Complex Modifications->Rules->Add ruleを見る,構文ミスさえなければ表示されるので右端のEnableボタンでで有効化,ちなみにここにはファイル名は表示されないので注意(jsonオブジェクトのtitlerules.descriptionのみ表示される)

f:id:Soluna_Eureka:20220221225737p:plain

うまく有効化できたらMisc->Restart Karabiner-Elementsで再起動,これで完全に動作が反映されるはず

特徴

  • 同時押しsimultaneousによって修飾キー以外による同時押しに対応
    • 単一押しと使用するボタンやキーが被る場合は,simultaneousさせたい動作についてjsonオブジェクト上に先に記述すること,後に記述すると単一押しの方が優先されて判定を受けてしまい,同時押しの方は問答無用で無視されてしまう
    • それぞれの動作の定義がrules.manipulatorsの配列に格納される形であるため,処理の優先度が前後関係で決定されている可能性がある,そのためなるべく少ないjsonファイルに関連する設定をまとめた方がむしろ安心
    • 実はKarabiner-Elements->Complex Modifications->Parametersから設定できる
      • マウスで使うにはあまりにもシビアなので,猶予時間は長めにとった方が良いかも
      • f:id:Soluna_Eureka:20220221225755p:plain
  • アプリケーション判定frontmost_application_ifによってブラウザ限定動作に対応
    • マルチディスプレイで別々のアプリをトップに表示すると両方とも有効と扱われるので注意,シングルディスプレイを想定した実装しかされていない(意図しない動作が思わず発生する可能性がある)

カスタマイズ

  • rules.manipulators[].conditions[].identifiersには対応するvendor_idproduct_idが必要,各自で確認
  • マウスのボタン配置も確認して使いやすい組み合わせを検討
  • rules.manipulators[].conditions[].bundle_identifiersには対応するアプリのidentifiersが必要各自で確認
    • アプリを起動してlsappinfo info -only bundleid hogeすれば確認できる

qiita.com

解説

何を読めば理解できるか

公式ドキュメント

karabiner-elements.pqrs.org

jsonオブジェクトについて記載されている

判定時間の設定について

基本的には「キーを下げる」がpressの定義であって,一般的にはtoにて処理が起爆される.後述のhaltも使えるとは思うが動作は未確認,ドキュメントには例文どころか説明すら載ってないし使う価値もなさそう.
また「キーを(指定した時間より長く)下げ続ける」と処理が起爆するto_if_held_down,「下げたキーを上げる」と処理が起爆するto_after_key_upがあり,これら2つは両立して存在できる.その場合,to_if_held_downの中にhalt:trueを用いることで後続のto_after_key_upをキャンセルする,すなわちキーを下げ続ける時間で処理を変えることが可能になる.
to_if_aloneは「キーを(指定した時間より短く)単独で下げ続ける」と処理が起爆する,長く押し続けると判定がtoに移行する.同様にhalt:trueto_after_key_upをキャンセルして処理を変えることが可能になる.

もしくはfrom.simultaneousで修飾キー以外の複数入力を受け付けられるが,これはto系のメンバではなくfrom系のメンバであるために判定時間の概念が異なる.先に述べたようにrules.manipulatorsの配列の順番がfrom系のメンバの実行の優先順序になると推定されており,同じキーを利用した同時押しの判定をやりたいのであれば先に記述すべきである.

また包括されるキーの組み合わせを設定するのも全く推奨されない,例えばhoge+hugahogeを待ち受ける2つのfromオブジェクトがある場合,後者の設定をto_if_held_downでも設定しない限りaを下げて上げた瞬間に後者が起爆する.

総合的に考えると,今回の実装はえらく特殊なものである以上は,真似はしない方が良いだろう.
…実際に使うとなればKarabiner-Elements->Complex Modifications->Parametersto_if_alone_timeout_millisecondssimultaneous_threshold_millisecondsをイジるくらいで,これはデバイスの使用感に依存するだろう.
また変数をset_variableで設定してvariable_ifvariable_unlessで利用できるらしいが,使ったことがない…

`zsh`で`eval`する

evalとは

       eval [ arg ... ]
          Read the arguments as input to the shell and execute the resulting
          command(s) in the current shell process.  The return status is the
          same as if the commands had been executed directly by the shell;
          if there are no args or they contain no commands (i.e. are an
          empty string or whitespace) the return status is zero.

要するに文字列を標準入力としてシェルに与えて実行(した上でその返り値を取得)できる

何が嬉しいのか

変数展開の都合によって通常の記法によるシェルスクリプトでは設定できないようなコマンドをそのまま通すことができる
もっと手軽に言えば,変数展開の実行とevalの実行を分けてしまうことでより簡易に明確に文字列をインジェクションできる

soluna-eureka.hatenablog.com

例えば上記で扱ったc++ likeな文字列の取り扱いについても,シェルスクリプトの文面に直接的にコマンドを書かなくとも

#!/bin/zsh -eu
# this is testZsh5

arg="ab\ncd"

echo "for echo():"

cmd1="echo ${arg}"
cmd2="echo '${arg}'"
cmd3="echo \"${arg}\""
cmd4="echo $'${arg}'"
cmd5="echo $\"${arg}\""

eval ${cmd1}
eval ${cmd2}
eval ${cmd3}
eval ${cmd4}
eval ${cmd5}

echo "for printf():"

cmd6="printf \"%s\n\" ${arg}"
cmd7="printf \"%s\n\" '${arg}'"
cmd8="printf \"%s\n\" \"${arg}\""
cmd9="printf \"%s\n\" $'${arg}'"
cmd10="printf \"%s\n\" $\"${arg}\""

eval ${cmd6}
eval ${cmd7}
eval ${cmd8}
eval ${cmd9}
eval ${cmd10}
% testZsh5 
for echo():
abncd
ab
cd
ab
cd
ab
cd
$ab
cd
for printf():
abncd
ab\ncd
ab\ncd
ab
cd
$ab\ncd

とすればより使い分けやすくなるし挙動がより確実に追跡できる,これと同じものを生で記述するのはかなり面倒になるだろう

curl to ffmpeg

悪用厳禁

インスペクタを開いてネットワークを監視しながら動画を見ていると,ほぼ必ずcurl形式のダウンロードリンクがブラウザにエイリアスとして提供されていることが見て取れるが,そのheader要素をそのままffmpegに渡すことでローカルファイルに落とせる,というのもCookieやsessionによって行われる管理を維持したままブラウザ環境とCLI環境に対応させることができる

しかしいくらそれをコピペしようがcurlにはffmpegのような機能はなく(もし単純にmp4が落ちてくるならまだしもhlsの配信が相手だと無力である),またffmpegとはそれ自身のコマンドはもちろん渡されるオプションのフォーマットも違う,それなら以下のように--ffmpeg [動画ファイルの名前]を末尾に追記するだけで自動的にオプションを加工して(-Hを集めて-headersに渡す)ffmpegを呼び出せる(確実に実行ファイルを指定する)ようにすれば,かなり楽だろうと思われる
現状ではちゃんと動作している

#!/bin/zsh -eu
# this is curl with ffmpeg

default=$argv
ffmpeg='--ffmpeg'

zparseopts -D -E -a optionArray -A optionPair -ffmpeg: X+: H+:

if ((${+optionPair[$ffmpeg]})); then
    echo "--ffmpeg option allowed!"
    n=0
    type=""
    head=""
    for i in $optionArray; do
        (( n += 1))
        if [ $i = "-X" ]; then
            echo "type added"
            type=$type$optionArray[n+1]'\r\n'
        elif [ $i = "-H" ]; then
            echo "head added"
            head=$head$optionArray[n+1]'\r\n'
        fi
    done
    command="/usr/local/bin/ffmpeg -loglevel quirt -headers $'${head}' -i '${*}' -c copy ${optionPair[$ffmpeg]}"
    eval "${command}"
else
    /usr/local/opt/bin/curl ${default}
fi

youtubeとかは

ffmpegだけでは無理なサイトもあるが,そういう時はstreamlinkの方が強いのでそっちに任せたい,しかしstreamlinkも決して万能ではなく対応していないサイトの方がむしろ多い,表向きに.m3u8.mp4が出てくるサイトならffmpegで対応できるが…

soluna-eureka.hatenablog.com

youtube-dlとかよりstreamlinkの方が一元的に利用できて良い

formulae.brew.sh

`echo`と`printf`では制御文字の扱いが異なる件

ffmpegで気づいたこと

ffmpegでは-headersオプションを使えば複数のhttp headerを付与しつつ-iで指定したurlに向かってhttpで叩けるが,複数の内容を利用する際には改行するための制御文字を間々で認識させる必要があるらしく,その場合は$'...'で表せる.

note.kiriukun.com

そしてこの$'...'については確かにzshのmanページでも取り扱い方法が記載されていた,以下を参照のこと.

man zshmisc | col -b | expand -t 4 | pcre2grep -Moe ' {0}(QUOTING\n)( {7,}(\S* *)*\S*\n*)*'

見た感じではprintprintfc++の実装そのままで,対してechozsh用に作られた安全なものという印象を受けた.

zshで実験

準備

以下のシェルスクリプトを用いる

#!/bin/zsh -eu
# this is testZsh4

echo "all arguments : ${*}"

zparseopts -D -E -a optionArray -A optionPair test:

if ((${+optionArray})); then
    for i in $optionArray; do
        echo "echo: component: ${i}"
        printf "printf: %s\n" $i
    done
fi

if ((${+optionPair})); then
    for key in ${(@k)optionPair}; do
        echo "echo: flags: ${key} => value: ${optionPair[$key]}"
        printf "printf: flags: %s => value: %s\n" $key $optionPair[$key]
    done
fi

echo "other arguments : ${*}"

-testオプションにつける引数を変えれば処理の比較ができる

soluna-eureka.hatenablog.com

方針

改行文字を含めた文字列ab\ncdを対象に色々と変えてみよう!

ab\ncd

% testZsh4 -test ab\ncd
all arguments : -test abncd
echo: component: -test
printf: -test
echo: component: abncd
printf: abncd
echo: flags: -test => value: abncd
printf: flags: -test => value: abncd
other arguments : 

両者共に\が消えた

'ab\ncd'

% testZsh4 -test 'ab\ncd'
all arguments : -test ab
cd
echo: component: -test
printf: -test
echo: component: ab
cd

printf: ab\ncd
echo: flags: -test => value: ab
cd
printf: flags: -test => value: ab\ncd
other arguments : 

echoだけ改行文字が読まれた,printfでは文字として扱われた

"ab\ncd"

% testZsh4 -test "ab\ncd" 
all arguments : -test ab
cd
echo: component: -test
printf: -test
echo: component: ab
cd
printf: ab\ncd
echo: flags: -test => value: ab
cd
printf: flags: -test => value: ab\ncd
other arguments : 

'ab\ncd'と変わらない

$'ab\ncd'

% testZsh4 -test $'ab\ncd'
all arguments : -test ab
cd
echo: component: -test
printf: -test
echo: component: ab
cd
printf: ab
cd
echo: flags: -test => value: ab
cd
printf: flags: -test => value: ab
cd
other arguments : 

両者とも改行文字が読まれた

$"ab\ncd"

% testZsh4 -test $"ab\ncd"
all arguments : -test $ab
cd
echo: component: -test
printf: -test
echo: component: $ab
cd
printf: $ab\ncd
echo: flags: -test => value: $ab
cd
printf: flags: -test => value: $ab\ncd
other arguments : 

"..."の動作はそのまま先頭に$が入っただけだった

結論

printfをみるにc++系の実装なら$'...'することでほぼ確実に制御文字を反映させられるっぽい,またechoではそれよりも簡単に制御文字を反映させられるっぽい

ffmpegでの使いかた

-headersオプションは多重の呼び出しが不可能である(2回目からは内容が置き換わる)ため,前述の手法で制御文字を有効にすることではじめて複数の内容を利用できる.とは言えその中で頻用される幾つかはffmpeg側でも専用のオプションとして提供されており,本来ならばそういったものを利用するべきではないかとも考えられる(処理がめんどいって?それはそう).

詳細は以下で確認できる,-loglevel traceで動作が全て標準出力される,-loglevel quietで何も表示されない,また-i [url]-headers [header]より前に持ってくると-headers [header]が認識されない(なぜかドキュメントにはのってないけど)

% ffmpeg \
-loglevel trace \
-headers $'Hoge: hoge\r\nHuga: huga' \
-i 'http://example.com/playlist.m3u8' \
-c copy \
output.mp4
...
User-Agent: Lavf/58.76.100
Accept: */*
Range: bytes=0-
Connection: close
Host: example.com
Icy-MetaData: 1
Hoge: hoge
Huga: huga
...

以上の結果が得られればヘッダが正しく設定されたと言える(該当の動画は存在しないので最終的にはerrorが返される)
本来\r\nms-doswindowsで利用される作法であり,旧MacOSでは\rのみ・現macOSでは\nのみがそれぞれの「標準の改行コード」である,なので\nだけで十分ではある…のだが心配なので\r\nすることを癖にしても良いかもしれない,実際の\rmacOSにおいて「同じ行の先頭まで戻る(そして出力が順に置き換わる)」動作を表すので余計な改行にはならないはず

おまけ

これ読めばわかると思われるが…?

QUOTING
       A character may be quoted (that is, made to stand for itself) by
       preceding it with a `\'.  `\' followed by a newline is ignored.

       A string enclosed between `$'' and `'' is processed the same way as the
       string arguments of the print builtin, and the resulting string is
       considered to be entirely quoted.  A literal `'' character can be
       included in the string by using the `\'' escape.

       All characters enclosed between a pair of single quotes ('') that is not
       preceded by a `$' are quoted.  A single quote cannot appear within single
       quotes unless the option RC_QUOTES is set, in which case a pair of single
       quotes are turned into a single quote.  For example,

          print ''''

       outputs nothing apart from a newline if RC_QUOTES is not set, but one
       single quote if it is set.

       Inside double quotes (""), parameter and command substitution occur, and
       `\' quotes the characters `\', ``', `"', `$', and the first character of
       $histchars (default `!').

自作のzshスクリプトでzparseoptsを用いてコマンドラインオプションを解析したい

目標

  • その都度に設定ファイルを指定するタイプのスクリプトhomebrewで入れたmarp-cliとか)の実行をより便利にしたい
  • コマンドライン(CL)オプション(OPT)を使いたい(marpならば--pdfの有無で出力をhtmlかpdfで切り替えられる)

具体的には

その引数を忠実にパースして対応させたコマンドを呼び出せるようにする

下準備

実行環境の整理

パーミッションについて

これを読んでね

soluna-eureka.hatenablog.com

スクリプト置き場

~/zscripts/bin

にパスを通して諸々のシンボリックリンクを入れる,元のスクリプト

~/zscripts/test

のように分類することにした

シンボリックリンクを生成する際は必ずフルパスを指定して生成すること,ln -sする時は相対パス扱いで通っちゃうけど実際に走らせる時はシェル側が絶対パスとして使ってくる,つまり

pwd
# => ~/zscripts
ln -s test/testZsh1 bin

は動いてしまうのだが(これは何も間違ってはいない,この時のzshから見ればtest/testZsh1は存在するし,bin/testZshはちゃんと生成される),これを実行しようとすると

testZsh1
# => zsh「$PATHを読むぞ,~/zscripts/binを見るぞ,testZsh1があったぞ」
# => zsh「えーとtest/Zsh1に行け…?そんな場所ねぇよ!」
# => zsh「zsh: command not found」

という結果になる,ただしこの状態でも$PATH上にシンボリックリンクは存在するため,zshは可能な限りコマンド補完を提供してくれる,うーんこれはかなり紛らわしいね

現状ではlnに引数対象の絶対パスの補完機能はない,というよりもシンボリックリンクではなくハードリンクならiモード番号をまんま参照するためこんなことは起きない,シンボリックリンクリンクに特有の現象と考えて諦めて受け入れるしかなさそう

パスを通す

export PATH=~/zscripts/bin:$PATH

実行ファイル作成

以下の作業はパターン化しよう

cd ~/zscripts/test
touch testZshS1
chmod 0755 testZsh1 # <-rwxr-xr-x
ln -s ~/zscripts/test/testZsh1 ~/zscripts/bin/ # <-最後にスラッシュつけないとディレクトリと認識されないので注意
vi testZsh1
#!/bin/zsh
# this is testZsh1

echo "this is testZsh1"
% testZsh1
# => this is testZsh1

予約済み変数(特殊変数)

シェバンにzshで実行するよう指示されたシェルスクリプトは,起動時にユーザが操作しているシェルからzshのプロセスに渡される,つまりは「ログイン者と対話するシェル」と「スクリプトを実行するシェル」が必ず同じである必要がない.しかしそれでもzshで環境を統一することをオススメしたい理由として,zshが対話シェルとして渡す特殊変数と実行シェルとして受け取る特殊変数の扱いを共通化することによる利点がある.読み物が一つの同じドキュメントだけで済むために大きく労力を削減できるし,権限管理などもやりやすい上にコーディングも多少は楽になるだろうとと思われるからだ(最近になって「移行しておいてよかった」と思い始めている,それでもGoogle先生はよくzshを無視してbashの記事を出してくるけど).
シェルスクリプトはシェバンの書きようによってはshbashに実行させることも可能である,それ故にシェルを切り替えた時に迂闊なミスが起こらないよう,シェルごとにスクリプトの管理フォルダと対応するパスを切り分けるべきだと考えている.

そしてログインシェルとスクリプトシェルの間でやり取りされる情報を利用するための特殊変数がzshには用意されている,manページから以下のコマンドで抽出できるので確認しよう

# ログインシェルが渡すもの
man zshparam | col -b | expand -t 4 | pcre2grep -Moe ' {0}(PARAMETERS SET BY THE SHELL\n)( {5,}(\S* *)*\S*\n*)*'

ちなみに元から利用できる環境変数などは以下で抽出できる

# ログインシェルが使うもの
man zshparam | col -b | expand -t 4 | pcre2grep -Moe ' {0}(PARAMETERS USED BY THE SHELL\n)( {5,}(\S* *)*\S*\n*)*'

シェルスクリプトでは変数名の前に$をつけて展開できるが,これもzshも例外ではなく(以下で抽出できる)

# zshにおける変数展開について
man zshexpn | col -b | expand -t 4 | pcre2grep -Moe ' {0}(PARAMETER EXPANSION\n)( {5,}(\S* *)*\S*\n*)*'

,多くの場合は既に$がついた形式で覚えられがちなのだが,取り敢えず重要そうなものだけをドキュメント通りに並べると

  • $
  • 0
  • *
    • 呼び出し時に与えられた全ての引数
    • 配列扱い
      • bashでは文字列1個の扱いだったらしいな?
    • 引数がなければ未定義
  • @
    • Same as argv[@]らしい
    • 引数がなければ未定義
  • argv
    • Same as *らしい
    • こいつは変数として簡単に処理できる
      • *@は処理が面倒だぞ(後述)
    • 引数がなくてもargvは空の配列として定義される
  • #
    • 引数の個数
  • ?
    • 1つ前の関数の終了状態を示す
    • つまり関数が評価されるたびに値が入れ替えられる
      • シェバンに-eがあると異常終了で必ず止まるけど
      • -uすると未定義変数を参照した時に必ず止まるよ
  • status
    • Same as ?らしい

ちなみにzsh自身のオプションは以下で確認できる,-euに関してはbashと同じっぽい

man zshoptions | col -b | expand -t 4 | pcre2grep -Moe ' {0}(SINGLE LETTER OPTIONS\n)( {3,}(\S* *)*\S*\n*)*'

仕様などは以下を参考に

qiita.com

実験して確認する

#!/bin/zsh -u
# this is testZsh2
# -eu するとエラーで爆死しにくくなるらしい,みんなは-eをつけよう

true=1
false=0
# これは単なるおまじない

echo "pid: '${$}'"
echo "command: '${0}'"
echo "length: '${#}'"

cat OMMCvsCCCCM.txt
# 当然だけどそんな下品なファイルはないので異常なまま終了する(status: 1)
echo "status: '${?}'"

if ((${*:+true})); then
    echo "* is defined"
    echo "for \$* : '${*}'"
    for ((i=1;i<=${#*};i++));do
        echo "for \$*[${i}]: '${*[i]}'"
    done
fi

if ((${@:+true})); then
    echo "@ is defined"
    echo "for \$@ : '${@}'"
    for ((i=1;i<=${#@};i++));do
        echo "for \$@[${i}]: '${@[i]}'"
    done
fi

if ((${+argv})); then
    echo "argv is defined"
    echo "length of argv: '${#argv}'"
fi

unset argv
echo "after unset of 'argv', *: '${*}'"
echo "after unset of 'argv', @: '${@}'"

引数を入れたり消したりして試してみよう,この時点で半角スペースによる配列化が自動で実行されていることがわかるだろう

% testZsh2 -a b -c
# => pid: '[やる度に値が変わる]'
# => command: '[ここにフルパスが出てくる]'
# => length: '3'
# => cat: OMMCvsCCCCM.txt: No such file or directory
# => status: '1'
# => * is defined!
# => for $* : '-a b -c'
# => for $*[1]: '-a'
# => for $*[2]: 'b'
# => for $*[3]: '-c'
# => @ is defined!
# => for $@ : '-a b -c'
# => for $@[1]: '-a'
# => for $@[2]: 'b'
# => for $@[3]: '-c'
# => argv is defined!
# => length of argv: '3'
# => after unset of 'argv', *: ''
# => after unset of 'argv', @: ''

*@は定義されているか?

変数定義の確認方法として有名なものに((${+xxx}))があるものの,zsh 5.8においては((${+*}))((${+@}))zsh: bad substitutionを吐き出す…変数ではなく演算子として認識されているらしく,これに引数の有無や数は関係ないと思われる
だからtrue=1を用意して((${*:+true}))((${@:+true}))にする必要がある,これなら確実に先に*@が展開されて:で評価して定義されていれば+trueを返すことができる(((${xxx:+yyy}))については先に挙げたリファレンスにも載ってる)

他のやり方などは以下を参考に

unhexium.net

zparseoptsについて

zshをセットで導入すれば元から使えるやつらしい,他にも候補があるらしいけどzsh上の普遍性を考慮してこれにした
扱い上はzshmodulesの一部なのでmanページは以下で抽出できる

zscripts % man zshmodules | col -b | expand -t 4 | pcre2grep -Moe ' {7}(zparseopts.*\n)( {10,}(\S* *)*\S*\n*)*' 

特徴

zparseopts*を設定しなくても良い

どうやら勝手に*を覗いて変更してくるらしい,必要なのは動作の調整と扱うオプションの設定だけである

所用のハイフン数に制限なし

1つや2つに限らずいくらでも増やせる,使う機会があるかどうか?さぁ…

オプション文字数に制限なし

-x-xxも通る,-は1文字・--はそれ以外みたいな風潮はあるけど

楽に連想配列を使える

-Aを入れると-x yから勝手に-x: yみたいな連想配列を作ってくれる
-aなら-x yをくっつけて-xyを生成して配列に代入してくるだけだけど…

1オプションには1設定

-x y z -a -b -cとかを入れるとz以降が無視されz -a -b -cが余剰として扱われる,もちろん用意した連想配列は壊れる
複数の設定を1つのオプションに入れたいとなると,流石に*に対する処理を手動で実装した方が早そう,今回はやらんけど…

テンプレート

zparseopts -D -E -a optionArray -A optionPair a bc: -ddd:: --e:

-Dとは?

元の*からオプションとみなした部分を全て切り取ってくれる,複数のファイルを取り扱う場合はこれが必要になってくる
前述したようにパースが崩壊すると切り取れなかった部分が全て*に残ることになる

-Eとは?

オプション宣言の前に変数を置いたり合間に余剰な変数を置いたりしてもパースを続けてくれる,つまりはさっきの注意書きを無視できる,コマンド・ファイル・オプション・内容みたいにやる時には便利かも

オプションの指定方法について

ハイフンが1つだけ減る(連想配列から取り出すときは減らしちゃダメだよ)

  • -aならa
  • -bcならbc
  • --dddなら-ddd激安の殿堂ZOY
  • ---eなら--e

また:の数にも意味がある,オプション後に引数について

  • 必ずなければa
    • あるとエラー
  • 必ずあればa:
    • ないとエラー
  • 分岐したければa::
    • ないと空白の文字列が連想配列に代入される
    • ただし配列が完全に連結してしまう副作用がある
    • -x a[-x, a]となるところを[-xa]にしてしまう
      • ループ処理を自前でやりたい場合とか逆に辛いかも?
    • 連想配列では{-x :a}で変わらない

さらに++:+:::-::-は同じオプションを何回も呼び出した時に影響がある

  • +は倍プッシュを許可する
    • 配列に影響が出る
    • -x -x[-x]となるところを[-x, -x]にできる
  • +:は引数を連結させる
    • 連想配列に影響が出る,配列は倍プッシュされる
    • -x a -x bなら{-x: ab}[-x, a, -x ,b]になる
  • +::は引数を完全に連結させる
    • 配列に影響が出る
    • -x a -x bなら{-x: ab}[-xa, -xb]になる
  • :-::-は…ほとんど意味ない
    • まず-をつけるだけで::と同じ副作用が必ず出現する
      • +:+::みたいに有用と思われる違いがない
    • 引数の置き換えは配列でも連想配列でも重複の呼び出しで勝手に発生するので…
      • つまり:-::-も使い道がない,無視して良い

実験して確認する

#!/bin/zsh -eu
# this is testZsh3

echo "all arguments : ${*}"

zparseopts -D -E -a optionArray -A optionPair a+ bc+:: -ddd+: --e:

if [[ -n "${optionPair[(i)-a]}" ]]; then
    echo "-a option enabled"
fi

if [[ -n "${optionPair[(i)-bc]}" ]]; then
    echo "-bc option enabled: '${optionPair[-bc]}'"
fi

if [[ -n "${optionPair[(i)--ddd]}" ]]; then
    echo "--ddd option enabled: '${optionPair[--ddd]}'"
fi

if [[ -n "${optionPair[(i)---e]}" ]]; then
    echo "---e option enabled: '${optionPair[---e]}'"
fi

echo "other arguments : $*"

if ((${+optionArray})); then
    for i in $optionArray; do
        echo "component: '${i}'"
    done
fi

if ((${+optionPair})); then
    for key in ${(@k)optionPair}; do
        echo "flags: ${key} => value: ${optionPair[$key]}"
    done
fi

echo "${optionPair}"

key="---e"
echo "${#optionPair[$key]}"
% testZsh3 xyz stst -a -a -bc p -bc q --ddd daiou --ddd heika ---e rrr ---e r tsts
all arguments : xyz stst -a -a -bc p -bc q --ddd daiou --ddd heika ---e rrr ---e r tsts
-a option enabled
-bc option enabled: 'pq'
--ddd option enabled: 'daiouheika'
---e option enabled: 'r'
other arguments : xyz stst tsts
component: '-a'
component: '-a'
component: '-bcp'
component: '-bcq'
component: '--ddd'
component: 'daiou'
component: '--ddd'
component: 'heika'
component: '---e'
component: 'r'
flags: -a => value: 
flags: ---e => value: r
flags: -bc => value: pq
flags: --ddd => value: daiouheika
 r pq daiouheika
1

marpコマンドを改善

github.com

formulae.brew.sh

詳細については今は省く(また別のところでやるかも)が,CLIから叩けるmarpvscode拡張機能版とは異なり設定ファイルを-cオプションで指定できる(その設定ファイルも万能ではない)が,しかしmarp本体に記憶させることができない.そのためシェルスクリプトで事前に定めたパスを必ず指定できるように仕向けようと考えた,あとはvscodeに機能を追加するよりもzshシェルスクリプトを書く練習から始めたかった.
…とりあえず-m pdf-m html-m pptxで使い分けられるようにした.ちなみにmarpはまだ全く使いこなせていない.

#!/bin/zsh -eu
# this is marpit

template='[任意のパス]'

zparseopts -D -E -a optionArray -A optionPair i: o: m::

input='-i'
output='-o'
mode='-m'
pdf="pdf"
html="html"
pptx="pptx"

if ((${+optionPair[$mode]})); then
    type=${optionPair[$mode]}
    case $type in
    ($pdf) marp -c ${template} --pdf ${optionPair[$input]}.md -o ${optionPair[$output]}.pdf;|
    ($html) marp -c ${template} ${optionPair[$input]}.md -o ${optionPair[$output]}.html;|
    ($pptx) marp -c ${template} --pptx ${optionPair[$input]}.md -o ${optionPair[$output]}.pptx;|
    esac
else
    echo marp --template ${template}  ${optionPair[$inout]} -0 ${optionPair[$output]}
fi
% marpit -i test -o test -m html     
[  INFO ] An EXPERIMENTAL transition support for bespoke template is enabled. It is using the shared element transition API proposal and it is not yet stable. Recommend to use
          with --preview option for trying transitions. Track the latest information at https://github.com/marp-team/marp-cli/issues/382.
[  INFO ] Converting 1 markdown...
[  INFO ] test.md => test.html
% marpit -i test -o test -m pdf 
[  INFO ] An EXPERIMENTAL transition support for bespoke template is enabled. It is using the shared element transition API proposal and it is not yet stable. Recommend to use
          with --preview option for trying transitions. Track the latest information at https://github.com/marp-team/marp-cli/issues/382.
[  INFO ] Converting 1 markdown...
[  WARN ] Insecure local file accessing is enabled for conversion from test.md.
[  INFO ] test.md => test.pdf
% marpit -i test -o test -m pptx
[  INFO ] An EXPERIMENTAL transition support for bespoke template is enabled. It is using the shared element transition API proposal and it is not yet stable. Recommend to use
          with --preview option for trying transitions. Track the latest information at https://github.com/marp-team/marp-cli/issues/382.
[  INFO ] Converting 1 markdown...
[  WARN ] Insecure local file accessing is enabled for conversion from test.md.
[  INFO ] test.md => test.pptx

zshの制御構文については以下で抽出できる

man zshmisc | col -b | expand -t 4 | pcre2grep -Moe ' {0}(COMPLEX COMMANDS\n)( {7,}(\S* *)*\S*\n*)*'

感想

少しだけ仲良くなれたんじゃないかなと思います,以上で終わります

おまけ

man zshmoduleszparseoptsの部分を抜き出した

       zparseopts [ -D -E -F -K -M ] [ -a array ] [ -A assoc ] [ - ] spec ...
          This builtin simplifies the parsing of options in positional
          parameters, i.e. the set of arguments given by $*.  Each spec
          describes one option and must be of the form `opt[=array]'.  If an
          option described by opt is found in the positional parameters it
          is copied into the array specified with the -a option; if the
          optional `=array' is given, it is instead copied into that array,
          which should be declared as a normal array and never as an
          associative array.

          Note that it is an error to give any spec without an `=array'
          unless one of the -a or -A options is used.

          Unless the -E option is given, parsing stops at the first string
          that isn't described by one of the specs.  Even with -E, parsing
          always stops at a positional parameter equal to `-' or `--'. See
          also -F.

          The opt description must be one of the following.  Any of the
          special characters can appear in the option name provided it is
          preceded by a backslash.

          name
          name+  The name is the name of the option without the leading `-'.
             To specify a GNU-style long option, one of the usual two
             leading `-' must be included in name; for example, a
             `--file' option is represented by a name of `-file'.

             If a `+' appears after name, the option is appended to
             array each time it is found in the positional parameters;
             without the `+' only the last occurrence of the option is
             preserved.

             If one of these forms is used, the option takes no
             argument, so parsing stops if the next positional parameter
             does not also begin with `-' (unless the -E option is
             used).

          name:
          name:-
          name:: If one or two colons are given, the option takes an
             argument; with one colon, the argument is mandatory and
             with two colons it is optional.  The argument is appended
             to the array after the option itself.

             An optional argument is put into the same array element as
             the option name (note that this makes empty strings as
             arguments indistinguishable).  A mandatory argument is
             added as a separate element unless the `:-' form is used,
             in which case the argument is put into the same element.

             A `+' as described above may appear between the name and
             the first colon.

          In all cases, option-arguments must appear either immediately
          following the option in the same positional parameter or in the
          next one. Even an optional argument may appear in the next
          parameter, unless it begins with a `-'.  There is no special
          handling of `=' as with GNU-style argument parsers; given the spec
          `-foo:', the positional parameter `--foo=bar' is parsed as `--foo'
          with an argument of `=bar'.

          When the names of two options that take no arguments overlap, the
          longest one wins, so that parsing for the specs `-foo -foobar'
          (for example) is unambiguous. However, due to the aforementioned
          handling of option-arguments, ambiguities may arise when at least
          one overlapping spec takes an argument, as in `-foo: -foobar'. In
          that case, the last matching spec wins.

          The options of zparseopts itself cannot be stacked because, for
          example, the stack `-DEK' is indistinguishable from a spec for the
          GNU-style long option `--DEK'.  The options of zparseopts itself
          are:

          -a array
             As described above, this names the default array in which
             to store the recognised options.

          -A assoc
             If this is given, the options and their values are also put
             into an associative array with the option names as keys and
             the arguments (if any) as the values.

          -D     If this option is given, all options found are removed from
             the positional parameters of the calling shell or shell
             function, up to but not including any not described by the
             specs.  If the first such parameter is `-' or `--', it is
             removed as well.  This is similar to using the shift
             builtin.

          -E     This changes the parsing rules to not stop at the first
             string that isn't described by one of the specs.  It can be
             used to test for or (if used together with -D) extract
             options and their arguments, ignoring all other options and
             arguments that may be in the positional parameters.  As
             indicated above, parsing still stops at the first `-' or
             `--' not described by a spec, but it is not removed when
             used with -D.

          -F     If this option is given, zparseopts immediately stops at
             the first option-like parameter not described by one of the
             specs, prints an error message, and returns status 1.
             Removal (-D) and extraction (-E) are not performed, and
             option arrays are not updated.  This provides basic
             validation for the given options.

             Note that the appearance in the positional parameters of an
             option without its required argument always aborts parsing
             and returns an error as described above regardless of
             whether this option is used.

          -K     With this option, the arrays specified with the -a option
             and with the `=array' forms are kept unchanged when none of
             the specs for them is used.  Otherwise the entire array is
             replaced when any of the specs is used.  Individual
             elements of associative arrays specified with the -A option
             are preserved by -K.  This allows assignment of default
             values to arrays before calling zparseopts.

          -M     This changes the assignment rules to implement a map among
             equivalent option names.  If any spec uses the `=array'
             form, the string array is interpreted as the name of
             another spec, which is used to choose where to store the
             values.  If no other spec is found, the values are stored
             as usual.  This changes only the way the values are stored,
             not the way $* is parsed, so results may be unpredictable
             if the `name+' specifier is used inconsistently.

          For example,

             set -- -a -bx -c y -cz baz -cend
             zparseopts a=foo b:=bar c+:=bar

          will have the effect of

             foo=(-a)
             bar=(-b x -c y -c z)

          The arguments from `baz' on will not be used.

          As an example for the -E option, consider:

             set -- -a x -b y -c z arg1 arg2
             zparseopts -E -D b:=bar

          will have the effect of

             bar=(-b y)
             set -- -a x -c z arg1 arg2

          I.e., the option -b and its arguments are taken from the
          positional parameters and put into the array bar.

          The -M option can be used like this:

             set -- -a -bx -c y -cz baz -cend
             zparseopts -A bar -M a=foo b+: c:=b

          to have the effect of

             foo=(-a)
             bar=(-a '' -b xyz)

権限管理とシェルスクリプト

macOSです

諸々の確認

lsでフル表示させると

ls -l@FTOaehips

以上に加えて

  • -nするとownergroupがIDになる
  • -Rするとディレクトリ構造に対して再帰処理を行う
  • -Sするとサイズでソートする
  • -tすると時間でソートする
    • -tUすると作成created時間でソートする
    • -tcすると変更changed時間でソートする
    • -tuすると接触access時間でソートする
  • -rするとソートが逆転する
  • -Lすると最後までシンボリックリンクを追跡する
    • 表示されるファイル名はそのままだがそれ以外の情報は全て元のファイルのものになる
  • -Wするとホワイトアウトファイルも含めて表示する

ファイルの種類

英字10字の1つ目が該当する

soluna-eureka.hatenablog.com

まとめると

1文字目 名称 説明
- 普通のファイル エイリアスもこちらに含まれる,つまりエイリアスを相手にcdはできない /bin/zshとか
b ブロックデバイスブロックスペシャル)ファイル ブロックで1区画ずつ(ブロックずつ)データをやりとりする機器やソフトがosのファイルシステムを利用する際に使うエイリアス /dev/disk0とか,こういうのはdiskutil listで見れそう
c キャラクタデバイス(キャラクスペシャル)ファイル ストリームで1字づつ(キャラクタずつ)データをやりとりする機器やソフトがosのファイルシステムを利用する際に使うエイリアス /dev/nullとか/dev/stdinとか/dev/stdoutとか
d 普通のディレクト 特に考えなくて良い /binとか
l シンボリックリンク ls -Lでは表示されない,最後まで追跡されて解決する homebrew/usr/local/binシンボリックリンクを作りまくる
p FIFO,名前付きパイプ 先入先出(FIFO)でプロセス間の通信を実装できる,あくまで通信の順序によるFIFOでありプロセス立ち上げ順序ではない mkfifo testpipeで試してみると良いかも
s ドメインソケット ファイルシステムを利用してソケットが立ち上がるらしいが…わからん…なにこれ…
w whiteout(ホワイトアウト)ファイル -Wすると表示されるらしいが,恐らくこれは.whファイルではなく.whによって隠されるファイルを指す,やったことはないが事情を鑑みると後者だと思う
ネットで漁った情報を素人考えでまとめると,「互換性のあるファイルシステムを持つ異なるディレクトリ(それを含む別のディスクを新たに外から接続しても良い)を,1つのファイルシステムの特定のディレクトリにユニオンマウントすることで,従来よりも拡張されたストレージが構成できるが,もしそこに(ユニオンマウントする際に)読取のみの設定がされたディレクトリやファイルがある場合は,それを直接的に変更したり削除したりはできない,その代わりに変更や削除を記録できる場所に結果がコピーされるような設定ができて,『変更の場合はそのコピーを作成』・『削除の場合はそれを無視するための.whを作成』する」…ということになる?
BSD系osやLinux系osの一部が使える,UnionFSはその基本の存在でDockerはその改良版のAUFSを経て現在はOverlayFSが推奨されているらしい
osによってはlsが対応していない場合もありそう…

ascii.jp

dayflower.hatenablog.com

www.gcd.org

unix.stackexchange.com

www.techscore.com

ユニオンマウントの利点?

複数のディスク(物理)を用いる際に

  • 特定のディスクだけにRWを設定してアプリケーションに対するキャッシュサーバとして扱える
    • 書き込み負荷をキャッシュサーバに集中させられる
    • 壊れるディスクを敢えて絞り込めるから管理が楽
  • ROを設定したディスクに中身をまんまコピーして増やせる
    • 負荷分散(その性能はユニオンマウントするソフトにもよるだろうが)できる
    • もし壊れればコピペするだけで良いので管理が楽

ということなのだろうか…

権限の種類

英字10字の2つ目〜9つ目が該当する,別名ファイルモード(英語ではそう書かれる)

soluna-eureka.hatenablog.com

まとめると

  • 9つの文字は3つの欄で構成
    • owner,製作者・所有者
    • group,特定の利用者群
      • macOSではstaffadminwheelrootのどれか
      • デフォルトではownerprimary groupが優先される
      • どのユーザもprimary groupstaffになっている,UNIX系ならidと打って出てきたgidprimary groupを指すとか
    • other,それ以外の第三者
      • macOSではeveryoneと表記されているが,ここではotherで統一する
  • 1つの欄は3つの文字で構成
    • readable
      • ファイルなら読込可能
      • ディレクトリなら子要素の一覧が可能
      • rならOK,-ならNG
      • 数字では4
    • writable
      • ファイルなら書込可能
      • ディレクトリなら子要素の作成・削除・属性の編集が可能
      • wならOK,-ならNG
      • 数字では2
    • executable/searchable
      • ファイルなら実行可能
      • ディレクトリなら子要素の中身の利用・移動が可能
      • xならOK,-ならNG
      • 数字では1
  • それぞれの欄は数字の和算で表現することができる +124 はどう足しても重複が起きないので

オプション

上記に加えてsetuidsetgidstickyが各欄の3つ目に出現する時がある,ファイルシステムによってはその表示が異なるらしいのだが,macOSにおいては以下となる

owner group other
setuid s(1) or S(2) - -
setgid - s(3) or S(4) -
sticky - - t(5) or T(6)

これを各種の権限に当てはめて整理するとと以下となる

(1) (2) (3) (4) (5) (6)
file 実行可能+setuid 実行不可能+setuid 実行可能+setgid
Linuxだと効果がないらしい
実行不可能+setgidLinuxだと効果がないらしい - -
directory - - Linuxとは違いこっちは効果がない
あっちは子要素を所有するgroupを同期させられるらしい
Linuxとは違いこっちは効果がない
あっちは子要素の所有するgroupを同期させられるとか
利用可能+sticky 利用不可能+sticky

setuid

owner以外でもowner(の持つuid)の権限でファイルを実行できる,使い方を間違えたり脆弱性があったりすると危険
数字では4

setgid

ownerの所属するgroup以外でもownerの所属するgroup(の持つuid)の権限でファイルを実行できる←よくわからないけどmanページに本当にこう書いてあるからしょうがないもん!怖くて使えないけど(groupの操作に慣れてないだけ)
数字では2

sticky

このディレクトリの子要素であるファイル・ディレクトリは共にowner以外の削除と属性変更が拒否される,/tmpとか
数字では1

拡張属性

英字10字の後に@がある場合はOSによる拡張属性が設定されている,例えば~(ユーザのホームディレクトリ)は

 com.apple.FinderInfo      32B 
 0: group:everyone deny delete

と表示されるはず,このように単にrwxだけでは指定できない特定の操作(この場合は削除)をバインドしてる場合が多そう

拡張ACL

英字10字の後に+がある場合はOSによる拡張セキュリティが設定されている…らしいのだが,未だに見たことがない,これはアクセスコントロールリスト(ACL)とも呼ばれる

その他の情報

以上で基本的な管理については事足りると思うのだが,ls -l@FTOaehipsで全て表示されるものは何かをそれぞれメモしておく

[iノード番号] [ブロックサイズ] [権限+拡張属性+拡張ACL] [リンク数] [owner] [group] [ファイルフラグ] [ファイルサイズ] [最終更新日] [名前]

iノード番号

UNIX系システムは属性情報を管理・追跡するために固定番号を使用している.
該当のオブジェクトがファイルシステム上から消されたりしない限りは変わらない.

ブロックサイズ

UNIX系システムはデータを書き込む際にこの単位を利用している.
macOSにおいては,最低でもオブジェクトに対して1つあたり4KB(4KiByte)=4096B(4096Byte)が確保され,これが8blockに相当することから1blockあたり512Byteであるとわかる.この512Byteこそが一般的なSSD・HDD・RAMで用いられる物理的なブロックに相当するが,macOSのAPFSは4KiByte=8blockごとで使用するため(これをアロケーションと呼ぶ),要は4KiByte(これをアロケーションユニットサイズと呼ぶ)に満たないファイルを大量に生成することは,ストレージ節約の観点から見る限りでは無駄だということがわかる.一応ファイルサイズが0ならブロックサイズも0なので安心できる.(まぁiノード番号を浪費するかもしれないけど今時の64bitならそう困らないはず)

普通のディレクト

ディレクトリエントリの情報を持つのでファイルサイズは0ではない…のだがブロックサイズは0である.どこかしら実体があるだろうと思われるが情報が見つからない,iノード番号と似たような場所にあると見ているのだがAPFSの実装の肝になってそうなので割と社外秘(部外秘)な情報ではないかとも予想している.

名前付きパイプ

ファイルサイズが0,すなわちブロックサイズも0である.

シンボリックリンク

パスの情報を持つのでファイルサイズは0ではない…のだがブロックサイズはなぜか0である,普通のディレクトリと同じ事情があるのではないかと予想している.

ハードリンク

同じiノード番号を参照するので実体としてディスクを消費することはないが,それも考慮した上で丁寧にカウントしないと実際のディスク消費量から外れた値を得る羽目になる.表示されるファイルサイズとブロックサイズも,iノード番号が同じであれば同じである.

qiita.com

okmount.hatenablog.com

リンク数

あるiノード番号を持つオブジェクトに対するハードリンクの数,実は./(自己参照)も../(親参照)もハードリンクの扱いであるため,子ディレクトリを増やすと親ディレクトリのリンク数は必ず1増える.シンボリックリンクはiノード番号が異なるためにリンク数にはカウントされない.
ちなみにシンボリックリンクに対してハードリンクを生成しようとすると,シンボリックリンクが参照した実体のiノード番号を持つハードリンクが生成される,これは何重にシンボリックリンクを重ねても変わらない.つまりシンボリックリンクのiノード番号はシンボリックリンクについて固有であり,シンボリックリンクを重ねる場合はそれぞれにiノード番号が与えられている.

ファイルフラグ

通常の権限の種類とはまた異なるもので,アプリケーションも含めた様々な表示や動作について簡単に縛ることができる.
例えばmacOSSIPが有効な状態ではrestrictedフラグが付いたものは削除や変更ができなくなる,/bin/zshなんかそう.

apple.stackexchange.com

ファイルサイズ

普通なら単位はバイト,-hで単位も表示される.

最終更新日

-tUすると作成created時間,-tcすると変更changed時間,-tuすると接触access時間になる,使い分けよう.

権限の管理

chmod [1桁目][2桁目][3桁目][4桁目] [ファイル名orディレクトリ名]で数字を並べれば良い,前述したリストとパターンを記憶できたら以下を設定する
ちなみにアルファベットを並べての設定はできない,数字で考える習慣を身につけよう

オプション

私は保険のために常に-vhしておきたい,ログも残るしシンボリックリンク先を破壊してしまうのは非常によくない気がする

1桁目

オプション設定ができる
それぞれの欄で実行可能かどうかを設定するのは後の3桁で出来るのでここでは考えなくても良い
特に需要がなければ意図的に0を入れてもいいし入れなくても(省略しても)いい

2桁目

owner欄の設定をする

3桁目

group欄の設定をする

4桁目

other欄の設定をする

所有の管理

chownchgrpは現時点ではここでは扱わない(追記こそするかもしれないが),本題から逸れすぎるし私がmacOSで多用する機会もそうそうなさそうなので許してくれ

シェルスクリプト

1行目で#/bin/zshで実行シェルを指定してから(シェバンと言う)スクリプトを書く,所用のディレクトリにパスを通したら

touch zshTest1
ls -l@FTOaehips testZsh1
# => <inodeID> <BlockSize> -rw-r--r--  1 <UserName>  Staff  -   <FileSize> <Last Updated Time> testZsh1
vi zshTese1
#!/bin/zsh
#testZsh1

echo "this is test script 1"

しかしこの権限ではzshを通して実行できる(自分が権限を持つzshtestZsh1を読み込んで実行しただけなので)ものの単品では実行できない(明らかに-rw-r--r--なので),これは面倒というか不都合である

testZsh1 
# => zsh: permission denied: testZsh1
zsh testZsh1 
# => this is test script 1

なので権限を変更すれば良い,これで単品で実行できるようになる

chmod 0744 testZsh1 
testZsh1           
# => this is test script 1
ls -l@FTOaehips testZsh1
# => <inodeID> <BlockSize> -rwxr--r--  1 <UserName>  Staff  -   <FileSize> <Last Updated Time> testZsh1

なお,owner欄の実行権限の有無によってシェルスクリプトファイルの表示がFinder上で変化する今まで気づかなかった

おまけ

man lsDESCRIPTIONを抜き出した

DESCRIPTION
     For each operand that names a file of a type other than directory, ls
     displays its name as well as any requested, associated information.  For
     each operand that names a file of type directory, ls displays the names of
     files contained within that directory, as well as any requested, associated
     information.

     If no operands are given, the contents of the current directory are
     displayed.  If more than one operand is given, non-directory operands are
     displayed first; directory and non-directory operands are sorted separately
     and in lexicographical order.

     The following options are available:

     -@      Display extended attribute keys and sizes in long (-l) output.

     -A      Include directory entries whose names begin with a dot (‘.’) except
         for . and ...  Automatically set for the super-user unless -I is
         specified.

     -B      Force printing of non-printable characters (as defined by ctype(3)
         and current locale settings) in file names as \xxx, where xxx is
         the numeric value of the character in octal.  This option is not
         defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -C      Force multi-column output; this is the default when output is to a
         terminal.

     -D format
         When printing in the long (-l) format, use format to format the
         date and time output.  The argument format is a string used by
         strftime(3).  Depending on the choice of format string, this may
         result in a different number of columns in the output.  This option
         overrides the -T option.  This option is not defined in IEEE Std
         1003.1-2008 (“POSIX.1”).

     -F      Display a slash (‘/’) immediately after each pathname that is a
         directory, an asterisk (‘*’) after each that is executable, an at
         sign (‘@’) after each symbolic link, an equals sign (‘=’) after
         each socket, a percent sign (‘%’) after each whiteout, and a
         vertical bar (‘|’) after each that is a FIFO.

     -G      Enable colorized output.  This option is equivalent to defining
         CLICOLOR or COLORTERM in the environment and setting --color=auto.
         (See below.)  This functionality can be compiled out by removing
         the definition of COLORLS.  This option is not defined in IEEE Std
         1003.1-2008 (“POSIX.1”).

     -H      Symbolic links on the command line are followed.  This option is
         assumed if none of the -F, -d, or -l options are specified.

     -I      Prevent -A from being automatically set for the super-user.  This
         option is not defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -L      Follow all symbolic links to final target and list the file or
         directory the link references rather than the link itself.  This
         option cancels the -P option.

     -O      Include the file flags in a long (-l) output.  This option is
         incompatible with IEEE Std 1003.1-2008 (“POSIX.1”).  See chflags(1)
         for a list of file flags and their meanings.

     -P      If argument is a symbolic link, list the link itself rather than
         the object the link references.  This option cancels the -H and -L
         options.

     -R      Recursively list subdirectories encountered.

     -S      Sort by size (largest file first) before sorting the operands in
         lexicographical order.

     -T      When printing in the long (-l) format, display complete time
         information for the file, including month, day, hour, minute,
         second, and year.  The -D option gives even more control over the
         output format.  This option is not defined in IEEE Std 1003.1-2008
         (“POSIX.1”).

     -U      Use time when file was created for sorting or printing.  This
         option is not defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -W      Display whiteouts when scanning directories.  This option is not
         defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -a      Include directory entries whose names begin with a dot (‘.’).

     -b      As -B, but use C escape codes whenever possible.  This option is
         not defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -c      Use time when file status was last changed for sorting or printing.

     --color=when
         Output colored escape sequences based on when, which may be set to
         either always, auto, or never.

         always will make ls always output color.  If TERM is unset or set
         to an invalid terminal, then ls will fall back to explicit ANSI
         escape sequences without the help of termcap(5).  always is the
         default if --color is specified without an argument.

         auto will make ls output escape sequences based on termcap(5), but
         only if stdout is a tty and either the -G flag is specified or the
         COLORTERM environment variable is set and not empty.

         never will disable color regardless of environment variables.
         never is the default when neither --color nor -G is specified.

         For compatibility with GNU coreutils, ls supports yes or force as
         equivalent to always, no or none as equivalent to never, and tty or
         if-tty as equivalent to auto.

     -d      Directories are listed as plain files (not searched recursively).

     -e      Print the Access Control List (ACL) associated with the file, if
         present, in long (-l) output.

     -f      Output is not sorted.  This option turns on -a.  It also negates
         the effect of the -r, -S and -t options.  As allowed by IEEE Std
         1003.1-2008 (“POSIX.1”), this option has no effect on the -d, -l,
         -R and -s options.

     -g      This option has no effect.  It is only available for compatibility
         with 4.3BSD, where it was used to display the group name in the
         long (-l) format output.  This option is incompatible with IEEE Std
         1003.1-2008 (“POSIX.1”).

     -h      When used with the -l option, use unit suffixes: Byte, Kilobyte,
         Megabyte, Gigabyte, Terabyte and Petabyte in order to reduce the
         number of digits to four or fewer using base 2 for sizes.  This
         option is not defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -i      For each file, print the file's file serial number (inode number).

     -k      This has the same effect as setting environment variable BLOCKSIZE
         to 1024, except that it also nullifies any -h options to its left.

     -l      (The lowercase letter “ell”.) List files in the long format, as
         described in the The Long Format subsection below.

     -m      Stream output format; list files across the page, separated by
         commas.

     -n      Display user and group IDs numerically rather than converting to a
         user or group name in a long (-l) output.  This option turns on the
         -l option.

     -o      List in long format, but omit the group id.

     -p      Write a slash (‘/’) after each filename if that file is a
         directory.

     -q      Force printing of non-graphic characters in file names as the
         character ‘?’; this is the default when output is to a terminal.

     -r      Reverse the order of the sort.

     -s      Display the number of blocks used in the file system by each file.
         Block sizes and directory totals are handled as described in The
         Long Format subsection below, except (if the long format is not
         also requested) the directory totals are not output when the output
         is in a single column, even if multi-column output is requested.
         (-l) format, display complete time information for the file,
         including month, day, hour, minute, second, and year.  The -D
         option gives even more control over the output format.  This option
         is not defined in IEEE Std 1003.1-2008 (“POSIX.1”).

     -t      Sort by descending time modified (most recently modified first).
         If two files have the same modification timestamp, sort their names
         in ascending lexicographical order.  The -r option reverses both of
         these sort orders.

         Note that these sort orders are contradictory: the time sequence is
         in descending order, the lexicographical sort is in ascending
         order.  This behavior is mandated by IEEE Std 1003.2 (“POSIX.2”).
         This feature can cause problems listing files stored with
         sequential names on FAT file systems, such as from digital cameras,
         where it is possible to have more than one image with the same
         timestamp.  In such a case, the photos cannot be listed in the
         sequence in which they were taken.  To ensure the same sort order
         for time and for lexicographical sorting, set the environment
         variable LS_SAMESORT or use the -y option.  This causes ls to
         reverse the lexicographical sort order when sorting files with the
         same modification timestamp.

     -u      Use time of last access, instead of time of last modification of
         the file for sorting (-t) or long printing (-l).

     -v      Force unedited printing of non-graphic characters; this is the
         default when output is not to a terminal.

     -w      Force raw printing of non-printable characters.  This is the
         default when output is not to a terminal.  This option is not
         defined in IEEE Std 1003.1-2001 (“POSIX.1”).

     -x      The same as -C, except that the multi-column output is produced
         with entries sorted across, rather than down, the columns.

     -y      When the -t option is set, sort the alphabetical output in the same
         order as the time output.  This has the same effect as setting
         LS_SAMESORT.  See the description of the -t option for more
         details.  This option is not defined in IEEE Std 1003.1-2001
         (“POSIX.1”).

     -%      Distinguish dataless files and directories with a '%' character in
         long

     -1      (The numeric digit “one”.) Force output to be one entry per line.
         This is the default when output is not to a terminal.  (-l) output,
         and don't materialize dataless directories when listing them.

     -,      (Comma) When the -l option is set, print file sizes grouped and
         separated by thousands using the non-monetary separator returned by
         localeconv(3), typically a comma or period.  If no locale is set,
         or the locale does not have a non-monetary separator, this option
         has no effect.  This option is not defined in IEEE Std 1003.1-2001
         (“POSIX.1”).

     The -1, -C, -x, and -l options all override each other; the last one
     specified determines the format used.

     The -c, -u, and -U options all override each other; the last one specified
     determines the file time used.

     The -S and -t options override each other; the last one specified
     determines the sort order used.

     The -B, -b, -w, and -q options all override each other; the last one
     specified determines the format used for non-printable characters.

     The -H, -L and -P options all override each other (either partially or
     fully); they are applied in the order specified.

     By default, ls lists one entry per line to standard output; the exceptions
     are to terminals or when the -C or -x options are specified.

     File information is displayed with one or more ⟨blank⟩s separating the
     information associated with the -i, -s, and -l options.

DESCRIPTION
     In legacy mode, the -f option does not turn on the -a option and the -g,
     -n, and -o options do not turn on the -l option.

     Also, the -o option causes the file flags to be included in a long (-l)
     output; there is no -O option.

     When -H is specified (and not overridden by -L or -P) and a file argument
     is a symlink that resolves to a non-directory file, the output will reflect
     the nature of the link, rather than that of the file.  In legacy operation,
     the output will describe the file.

     For more information about legacy mode, see compat(5).


man chmodMODEを抜き出した

MODES
     Modes may be absolute or symbolic.  An absolute mode is an octal number
     constructed from the sum of one or more of the following values:

       4000    (the setuid bit).  Executable files with this bit set will
           run with effective uid set to the uid of the file owner.
           Directories with this bit set will force all files and sub-
           directories created in them to be owned by the directory
           owner and not by the uid of the creating process, if the
           underlying file system supports this feature: see chmod(2)
           and the suiddir option to mount(8).
       2000    (the setgid bit).  Executable files with this bit set will
           run with effective gid set to the gid of the file owner.
       1000    (the sticky bit).  See chmod(2) and sticky(7).
       0400    Allow read by owner.
       0200    Allow write by owner.
       0100    For files, allow execution by owner.  For directories, allow
           the owner to search in the directory.
       0040    Allow read by group members.
       0020    Allow write by group members.
       0010    For files, allow execution by group members.  For
           directories, allow group members to search in the directory.
       0004    Allow read by others.
       0002    Allow write by others.
       0001    For files, allow execution by others.  For directories allow
           others to search in the directory.

     For example, the absolute mode that permits read, write and execute by the
     owner, read and execute by group members, read and execute by others, and
     no set-uid or set-gid behaviour is 755 (400+200+100+040+010+004+001).

     The symbolic mode is described by the following grammar:

       mode     ::= clause [, clause ...]
       clause   ::= [who ...] [action ...] action
       action   ::= op [perm ...]
       who      ::= a | u | g | o
       op       ::= + | - | =
       perm     ::= r | s | t | w | x | X | u | g | o

     The who symbols ``u'', ``g'', and ``o'' specify the user, group, and other
     parts of the mode bits, respectively.  The who symbol ``a'' is equivalent
     to ``ugo''.

     The perm symbols represent the portions of the mode bits as follows:

       r       The read bits.
       s       The set-user-ID-on-execution and set-group-ID-on-execution
           bits.
       t       The sticky bit.
       w       The write bits.
       x       The execute/search bits.
       X       The execute/search bits if the file is a directory or any of
           the execute/search bits are set in the original (unmodified)
           mode.  Operations with the perm symbol ``X'' are only
           meaningful in conjunction with the op symbol ``+'', and are
           ignored in all other cases.
       u       The user permission bits in the original mode of the file.
       g       The group permission bits in the original mode of the file.
       o       The other permission bits in the original mode of the file.

     The op symbols represent the operation performed, as follows:

     +     If no value is supplied for perm, the ``+'' operation has no effect.
       If no value is supplied for who, each permission bit specified in
       perm, for which the corresponding bit in the file mode creation mask
       (see umask(2)) is clear, is set.  Otherwise, the mode bits
       represented by the specified who and perm values are set.

     -     If no value is supplied for perm, the ``-'' operation has no effect.
       If no value is supplied for who, each permission bit specified in
       perm, for which the corresponding bit in the file mode creation mask
       is set, is cleared.  Otherwise, the mode bits represented by the
       specified who and perm values are cleared.

     =     The mode bits specified by the who value are cleared, or, if no who
       value is specified, the owner, group and other mode bits are cleared.
       Then, if no value is supplied for who, each permission bit specified
       in perm, for which the corresponding bit in the file mode creation
       mask is clear, is set.  Otherwise, the mode bits represented by the
       specified who and perm values are set.

     Each clause specifies one or more operations to be performed on the mode
     bits, and each operation is applied to the mode bits in the order
     specified.

     Operations upon the other permissions only (specified by the symbol ``o''
     by itself), in combination with the perm symbols ``s'' or ``t'', are
     ignored.

     The ``w'' permission on directories will permit file creation, relocation,
     and copy into that directory.  Files created within the directory itself
     will inherit its group ID.


MODES
     644       make a file readable by anyone and writable by the owner
           only.

     go-w      deny write permission to group and others.

     =rw,+X    set the read and write permissions to the usual defaults, but
           retain any execute permissions that are currently set.

     +X        make a directory or file searchable/executable by everyone if
           it is already searchable/executable by anyone.

     755
     u=rwx,go=rx
     u=rwx,go=u-w  make a file readable/executable by everyone and writable by
           the owner only.

     go=       clear all mode bits for group and others.

     g=u-w     set the group bits equal to the user bits, but clear the
           group write bit.


manページで特定のサブセクションだけ読みたい…読みたくない?

manページで覚えたいところだけいい感じに.txtに保管しようにもデフォルトだと長すぎて編集がめんどくさいんだよな
macOSです

イデア

man xxx | backspase消去 | tab->space変換 | 正規表現でyyyにマッチ > xxx_yyy.txt

backspase消去にはcoltab->space変換にはexpand正規表現yyyにマッチにはpcre2grep…を使いたい
特に今回はセクションを判定して複数行を抜き取る動作をやりたいので,通常のgrepとは異なるpcre2grepを利用する

why pcre2grep

一応はgrepでもマルチライン処理はできるらしいが,もちろんデフォルトでは対応していない,それをするには

  • PCREPerl Compatible Regular Expressions)を-Pオプションで呼び出す必要がある
    • GNUによって用意されているlibpcreが処理に噛んでくる
  • -PオプションはGNUgrepにのみ実装されている
  • そしてmacOSgrepgnuのものではない
    • つまりmacOSではデフォルトでgrep -Pが使えない
  • homebrewからgnu grepを入れても良いが,
    • ggrepとかいう名前で使うのは嫌だし
    • 開発終了のzipならともかくmacOSでもgnuでも現在進行形でメンテされてるgrepはちょっと怖くて併用できん
  • そもそもPCRE.orgが出してるpcre2homebrewで入れれば良くね?
    • 規格を作ってる組織が出す公式ライブラリなんだから信頼できる
    • homebrewgitを入れている場合はpcre2が依存先になっているので既に入ってたりする
      • pcreは古くて非推奨なので(今もまだffmpegとか依存しているけど)なければpcre2を入れよう
    • そのライブラリにAPIとしてのpcre2grepがついてくる(他にも色々ついてくるっぽい)

…ということでpcre2grepを採用したい

why expand

trはちょっと難しそうだしexpandがあるならこっちの方が楽じゃん
タブをエクスパンドするからそう呼ばれるのか

テンプレート

 man [コマンド名] | col -b | expand -t [1タブあたりの空白数] | pcre2grep -Moe ' {[タイトルの深さ(空白数)]}([タイトル]\n)( {[本文の深さ],}(\S* *)*\S*\n*)*'

コマンドラインオプションの説明は省くがこれで過不足はない

.txtに出力

 man xxx | col -b | expand -t 4 | pcre2grep -Moe ' {3}(yyy\n)( {5,}(\S* *)*\S*\n*)*' > xxx_yyy.txt

リダイレクション演算子のうち>>は追記で>は置換らしい

使用例

% man ls | col -b | expand -t 4 | pcre2grep -Moe ' {3}(The Long Format\n)( {5,}(\S* *)*\S*\n*)*'
   The Long Format
     If the -l option is given, the following information is displayed for each
     file: file mode, number of links, owner name, group name, number of bytes
     in the file, abbreviated month, day-of-month file was last modified, hour
     file last modified, minute file last modified, and the pathname.  If the
     file or directory has extended attributes, the permissions field printed by
     the -l option is followed by a '@' character.  Otherwise, if the file or
     directory has extended security information (such as an access control
     list), the permissions field printed by the -l option is followed by a '+'
     character.  If the -% option is given, a '%' character follows the
     permissions field for dataless files and directories, possibly replacing
     the '@' or '+' character.

     If the modification time of the file is more than 6 months in the past or
     future, and the -D or -T are not specified, then the year of the last
     modification is displayed in place of the hour and minute fields.

     If the owner or group names are not a known user or group name, or the -n
     option is given, the numeric ID's are displayed.

     If the file is a character special or block special file, the device number
     for the file is displayed in the size field.  If the file is a symbolic
     link the pathname of the linked-to file is preceded by “->”.

     The listing of a directory's contents is preceded by a labeled total number
     of blocks used in the file system by the files which are listed as the
     directory's contents (which may or may not include . and .. and other files
     which start with a dot, depending on other options).

     The default block size is 512 bytes.  The block size may be set with option
     -k or environment variable BLOCKSIZE.  Numbers of blocks in the output will
     have been rounded up so the numbers of bytes is at least as many as used by
     the corresponding file system blocks (which might have a different size).

     The file mode printed under the -l option consists of the entry type and
     the permissions.  The entry type character describes the type of file, as
     follows:

       -     Regular file.
       b     Block special file.
       c     Character special file.
       d     Directory.
       l     Symbolic link.
       p     FIFO.
       s     Socket.
       w     Whiteout.

     The next three fields are three characters each: owner permissions, group
     permissions, and other permissions.  Each field has three character
     positions:

       1.   If r, the file is readable; if -, it is not readable.

       2.   If w, the file is writable; if -, it is not writable.

       3.   The first of the following that applies:

              S     If in the owner permissions, the file is not
                executable and set-user-ID mode is set.  If in the
                group permissions, the file is not executable and
                set-group-ID mode is set.

              s     If in the owner permissions, the file is executable
                and set-user-ID mode is set.  If in the group
                permissions, the file is executable and setgroup-ID
                mode is set.

              x     The file is executable or the directory is
                searchable.

              -     The file is neither readable, writable, executable,
                nor set-user-ID nor set-group-ID mode, nor sticky.
                (See below.)

        These next two apply only to the third character in the last
        group (other permissions).

              T     The sticky bit is set (mode 1000), but not execute
                or search permission.  (See chmod(1) or sticky(7).)

              t     The sticky bit is set (mode 1000), and is searchable
                or executable.  (See chmod(1) or sticky(7).)

     The next field contains a plus (‘+’) character if the file has an ACL, or a
     space (‘ ’) if it does not.  The ls utility does not show the actual ACL;
     use getfacl(1) to do this.


おまけ

PCRE2対応の正規表現チェッカーならここが良さそうね

regex101.com