先のシェルスクリプトを形にしました

一時キーボード無効

Temporary Disable Keyboard @GitHub

Xinputを使用してデバイスを無効/有効にするためのもの。

主な変更点は次の通り

  • 無効化を「時間制」「ダイアログ」「無限」から選択できるようにした
  • Zenityダイアログを若干調整
  • ランチャー用の.desktopファイルを追加

Zshスクリプトだが、case内で非常に珍しい;&(フォールスルーする)を使用している。

ターミナルエミュレータ選択機能

Terminal Selector @GitHub

主な変更点は次の通り

  • ランチャー用の.desktopファイルを追加
  • KDE Service用の.desktopファイルを追加
  • Nemo用の.nemo_actionファイルを追加
  • 対応する端末を大幅に増加
  • 利用できない端末を選択肢から除外するように変更
  • Zenityダイアログを調整
  • 柔軟に端末を追加できるようにファイルマネージャでの起動用の引数対応が連想配列で使用できるように変更

連想配列を使用するための方式は次のようなものだ。

わざわざ連想配列をテンプレート文字列とし、そこから配列に変換している理由は

  • 連想配列に格納できるのは文字列のみ
  • 置き換えしてからではDIRがIFSを含む可能性がある

【検索ワードに応えて】 ThinkPad X1 Carbon (2017, シルバー) のお話, Linux関連, その他

久しぶりの検索ワード反応企画。 ThinkPad X1 Carbon関連の検索が多かったので、併用されたワードに従ってコメントさせていただくことにする。

なお、ThinkPad X1 CarbonについてはYouTube (はるかみ☆ チャンネル)で開封動画を掲載させていただいているので、よかったらそちらもどうぞ。

また、そのほかLinux関連の検索にも回答させていただく。

ThinkPad X1関連

基本的なレビュー

ラップトップをガリガリ使うモバイラーなら持たない理由が見つからない。

XPS13のような13.3インチの中でも小型のものを除けばボディは13.3インチと変わりないサイズだ。 しかし14インチの画面は明らかに見やすく、作業もしやすいし、人に画面を見せるときにも良い。

そして非常に軽量でバッテリーマイレージも素晴らしい。 もちろん、キーボードはあらゆる現行ラップトップの中で最高だと思う。

唯一欠点としては天板の外板が弱い。 既に凹み、割れがある。それほど目立たないし、動作には問題ないのだが、ちょっとカナシイ。

買い時について

「高性能」「最先端」などを意識したい場合はリリース間もない頃が良い。

リリース直後は割引はなく、およそ半年程度で30%程度の割引になる。モデル末期は40%を越える程度の割引だ。

基本的にはThinkPadの場合30%程度割り引いた状態で競争力のある通常価格で、割引のない最初期というのはフリーク向けと考えられる。 なので買い時を気にするような人なら直後はないだろう。

春頃には30%前後の割引になっていたりするから(現時点でサイト公式のクーポンが28%、恐らくメルマガや店頭クーポンならもう少し行くだろう)、それぐらいが買い時ではなかろうか。

少しでも安く買いたい人は、翌年モデルが発表されてから決めるといい。 その頃にはかなり安くなっているし、在庫が残っていれば翌年モデルが出てからでも安く買える。 翌年モデルを待つべきかどうかの判断も出来てお得だ。

2017と2018に関しては、X1 Carbonとしての進化は微々たるものだったけれど、第8世代プロセッサになったという点がとても大きい。 ただ、私はかなり安く買えたので、そのタイミングでよかったと思う。

シルバーのThinkPadについて

良いと思う。

ThinkPadらしくないという批判はあるだろうけれども、マットだけれども黒のピーチスキンよりもサラッとした手触りで汚れもつきにくく目立たない。

シルバーのラップトップはそれなりに存在するので目立たないということは言えるけれども、「Hacker’s itemたるThinkPad」と「美しくおしゃれな高級ラップトップ」を両立させる意図は十分に達成できている。

控え目にいっても最高にかっこいい。

国産と中国産について

何も違わないから安心してほしい。 400万円するようなP920でも中国産だ。

なお、納期はかかる。X1は予定納期よりも遅かったという人が珍しくないようなので注意してほしい。

その他の検索ワード回答

シェルスクリプトの並列実行

基本的な形式は次のとおり

シェルスクリプトの並列実行は待ち合わせず投げっぱなしにするのが基本。

ただし、どうしても待ち合わたい場合はwaitを使う方法もある。

とした時、$!でサブシェルのPIDが取れるので、

としておけば待ち合わせる必要があるタイミングで

とすることができる。これはBashでもZshでも共通。

入出力をやりとりするのであれば、下流のパイプに流すべきで、親プロセスが出力を披露ようなことは考えないほうがよい。

どうしてもであればファイルに書いてwaitするか、もしくはZshならProccess Substitutionを使用する。

あるいは大量の処理があり、複数のワーカーを走らせたい場合はflockを使うのが無難だろう。 例えば以下のようにする。

要点は以下の通りだ。

  • 関数workerが各ワーカーが実行する内容
  • これをサブシェル内でworker nの形で実行する
  • ファイルデスクリプタはクローンされるので、標準入力から読んだ場合、親シェルもサブシェルも同じものを読み進めるし、位置も同時に変化する
  • しかし、もし同時に読んでしまうとおかしなことになるので、読んでいる間は他のシェルに読んでもらっては困る
  • そこでロックファイルを作り、これをロックすることで排他制御する。いわゆるドットロック。
  • exec 9>| .lockでファイルデスクリプタ9番をロック用に確保
  • 開きっぱなしになるので、flockでこのファイルをロック
  • ロックを獲得できたら標準入力から読む
  • 読み終わったらファイルデスクリプタを閉じてロックも解放
  • これでキューから1行読めたので、処理を進める
  • 「1行では足りないよ!」という場合、エントリをファイルに書いておいてディレクトリにまとめ、キューはファイル名にするとかすれば良い

AMD APU (Godavari/Kaveri) と Linux

特に問題はない。

以前はCatalystドライバのおかげでずいぶん苦労させられたけれども、 AMDGPUになって以来目立った問題は発生していない。

割と電気を食うけれど性能は微妙なので嬉しくはないと思う。 コストパフォーマンス的にみれば、これくらいの性能がおいしいという人は多いと思う。 ビデオ関連も充実しているし、Killerシリーズなんかは機能も充実しているしね。

XMPP

ちょっと話題が広すぎて何を求めているのかがわからない。ごめんよ。

DPI

これかなり広い。

LinuxでのHi-DPIの話なら/etc/X11/xorg.conf.d/以下にモニターの設定ファイルを書く。 40-monitor.confとかで。

Section "Monitor"
    Identifier             "<default monitor>"
    DisplaySize            286 179    # In millimeters
EndSection

で、KDE Plasmaを使うといい感じになる。 Plasmaを使わない場合については、Qtアプリケーションについてはなんとかなるけれど、GTKに関してはうまいことスケールしない。

LightDMに関してはGreeterの設定ファイルにxft-dpi=と書きましょう。

Hi-DPIはLinuxでは結構苦手にしている感じ。

フォントの設定はまた別。

某人物について

コメントする気はありません。

VP9のハードウェアエンコード

  • Intel QSVを使ってください
  • LinuxならVA-API経由ffmpegが良いよ
  • ただし画質は絶望的だったとは言っておく

Intel QSV * H.265

  • LinuxならVA-API経由ffmpeg
  • 速度はそこそこ。X.265とは雲泥の差。CPU負荷は0ではないというか、普通に40%くらいはいく
  • ちゃんとVA-APIドライバ入れようね

scale_vaapi

VA-APIを使用する場合の出力画面サイズ指定。 変更しない場合は省略して大丈夫。

AMD VCE / NVIDIA NVENC

VCEはLinux的にはVA-API経由。なのでQSVと一緒。

ただし、AMDのビデオドライバはVA-APIだけじゃなくVDPAUも使える。 ところがVA-APIがエンコード/デコードなのに対してVDPAUはデコードのみ。

VA-APIをVDPAUに転送するドライバ(libva-vdpau-driver)と、VDPAUをVA-APIに転送するドライバ(libvdpau-va-gl)があり、 VDPAUを有効にして、VA-APIに転送するドライバを使ってしまうと(Nvidiaの場合はVA-APIが使えないのでこうする)デコードのみになってしまって使えない。

ちゃんとlibva-mesa-driverを使いましょう。

Nvidiaの場合はVA-APIをサポートしておらず、VDPAUはエンコードができないので、 NVENCは専用のインターフェイスになっている。

どっちが優れているというのは難しいけれど、Nvidiaのほうが対応フォーマットは多い。

DiscordとSlack

基本的にはDiscordがいいと思うし、最近はSlackも微妙だと思うのだけれども、「両方あると嬉しい」という面もある。

Discordの場合「ユーザーという概念がある」ということが大きい。

Discrodでつながると、どのようなつながりであれ、「その人」というのが見えてしまう。 Discordは通知をカスタマイズできないため、「通知をオンにしている全ての人からの通知が一律に行われる」ということになる。 棲み分けをする方法がない。

これはLINEと同様の問題である。 恋人だろうが、ゲーム仲間だろうが、仕事の人間だろうが、区別する方法がないのだ。 アカウントの切り替えは難しいためアカウントで分けるのも現実的ではないし、アカウントで認識されているから一時的な連絡先にもしにくい。

Slackの場合はやりとりもつながりもあくまでそのチーム内でのことなので、仕事の関係などであればSlackのほうが便利だ。

自前メールサーバーからのメールをGMailが受け取らない

主にはホスト認証の関係。

GMailは送られてきたメールサーバーが、本当にそのメールを送る資格があるかをチェックする。

設定方法はいくつかあるけれど、SPFレコードを書くのが楽。 「メール SPF」で検索すると色々出てくる。

LinuxとRealTek ネットワークカード (RTL8152ほか)

ASIXよりはマシだけれど安定して苦しめてくれるRealTekのネットワークカード。

Linuxではネットワークアダプタは

Intel > Atheros (Killer) > Broadcom > RealTek > Asix

だからね。

RealTekのWiFiモジュールであるRTL8152のドライバはカーネルモジュールとしてある。 Arch Linuxの場合はDKMSになっていて、AURからインストール可能。 場合によってはRTL8153をブラックリストに入れる必要がある。

インストールしなくても動作はするけれど、うまく交信できなくなったりする。 安定性の面から言えば入れるべきだけれど、入れたところでうまくいかない場合もある。

「うちのディストリビューションにはないよ!!」という方。 Archにおいで。むしろManjaroにおいで。

Linuxでキーボードをつないだまま掃除したい

キーボードが汚れているなぁ、と気づいたとき、あるいはキーボードがべたつくとき、キーボードを掃除したいけれど電源を切りたくないということはないだろうか。 特にデスクトップの後方につないでいる場合や、ラップトップの場合にはキーボードを外すのも面倒な話だ。 スリープにしてもキーボードに触ったら復帰してしまうし、なかなか厄介だ。

そこで一時的にキーボードをつないだまま無効にする方法を考えてみた。

キーボードを無効にする方法としてはXによるデバイス認識であればxinputが利用できる。

次の方法で認識されているデバイスを確認する。

あまり見やすくはない。

⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ ELECOM ELECOM UltimateLaser Mouse         id=9    [slave  pointer  (2)]
⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Power Button                              id=7    [slave  keyboard (3)]
    ↳ FCL USB Keyboard                          id=8    [slave  keyboard (3)]

キーボード全体をオフにしてしまうと困るかもしれない。 なので、Virtual core keyboardをオフにすることは思いとどまったほうがいいだろう。

FCL USB Keyboard(idは8)を無効にするには次のようにする。(手前側の8がID、後のほうは固定の値)

有効に戻すにはこうだ。

外付けキーボードが他に用意できるならいきなり無効にしても構わないが、そうできない場合は時間で復帰するようにしたほうがいいだろう。

安全のためアプレットから復帰できるようにするほうがいいだろう。 マウスで操作できるよう、Zenityを使ってon/offできるようにした。 また、off時にはタイマーをかけることもでき、タイマーをかけない場合は確認される。

Gistで公開している。 Zshスクリプトであり、実際にZshの機能を使用しているのでBashでは動かない。 Zenityはなかなかコマンド置換が難しく、(f)は欠かせない。

Mimir Yokohama ウェブサイトの「タグ機能」の仕組み

Mimir Yokohamaのウェブサイトにこっそりとタグ機能が追加された。

だが、PureBuilder Simply自体にはタグ機能がない。 この実現方法は発想力勝負な部分があった。

ドキュメントにデータを持たせる

「記事情報」でも行われている方法として、Markdown YAML Frontmatter内に情報をもたせ、Pandocテンプレートで存在する場合だけエレメントを生成するような手法を取っている。

例えば帯域においては

というYAML Frontmatterが書かれている。 記事情報などは自動的に生成することができないため記事ごとにかかれており、若干執筆コストを上げている面もあるが、 なにしろMimir Yokohamaには力が入っているのでそれくらいどうということはない。 基本的なフォーマットをコピペしてしまえばそれほど難しくない部分でもある。

ちなみに、Pandocテンプレートで

$if(pickable)$
Something
$endif$

とした場合、pickable: no (つまりfalse)ならばここは生成されない。

この追加情報としてtagsが加わったのである。

問題は検索

タグを表示することは簡単だが、普通に考えればタグから同一タグの記事を辿りたいし、タグで検索もしたい。

単純な方法としてGoogleを使うこともできるのだが、それは必ずしもタグつきの記事が上位にくるわけではなく、思ったようには動作しない。 ちゃんと検索機能を用意する必要があったのだが、できればPureBuilder Simplyの枠組みの中で行いたいところである。

PureBuilder Simplyは原則として「MarkdownまたはReST文書から生成する」という前提になっており、 ACCSもindexデータベースからMarkdownドキュメントを生成し、このあとはPandocで処理している。

なのでPureBuilder Simplyの枠組みで処理するためにはMarkdownドキュメントを生成しなくてはいけない。

それなら全てのindexを探し回ってタグを集めればいいじゃない。

ARGV.eachしているので、全ての.indexes.rbmを指定すれば良い。 あとはpbsimply-pandocで処理できるが、タグに登場した.indexes.rbmは実際に記事が存在しているものではなく拾ってほしくないので消しておく。

.indexes.rbmとして書き出すようにした意図の一部に、このように外部からドキュメントデータにアクセスするというものがあった。

これによってドキュメント解析しなくてもメタデータを利用して機能拡張してページに含むことができる。

テンプレートにとうとう限界が

だいぶ魔改造されているPandocテンプレートだが、今回は限界が垣間見えた。

タグクラウドらしくエントリ数の多いタグを大きく表示したいのだが、Pandocテンプレートに計算機能や比較機能がなく、CSSにもないため、 Pandocテンプレートだけでは実現できない。MarkdownにHTMLを直接書くという方法はあるが(Markdown自体はRubyで生成しているため)。

また、URIエンコーディングをする方法はデータを二重に持たせる以外になく、それでもふたつの値を同時にとるイテレータがPandocテンプレートにないため、URIだけでURIエンコーディングをおこなう方法がない。

eRubyを使ってもいいのだが、できれば使いたくない。 現時点ではタグクラウドの大きさ分けはしておらず、URIもURIエンコーディングせずに使用している。

アメブロから更新時に一部を抽出して通知する

私はかなりの頻度でアメブロのとあるブログエントリの更新をチェックしているのだが、ほとんどの場合重要な情報はそのごく一部である。 しかし、アメブロにスマホでアクセスするたびに、くだらない広告や関心のないTV出演者の話に通信量が割かれ、時間も割かれることを大変に腹立たしく思っていた。

そこで、PCからページを取得し、スクレイピングする方針で考えたものである。 スクレイピングした内容はDiscordで通知する。

具体的な内容は差し障るので抽象化する。

コード

解説

「アメブロから」という難しさ

ユーティリティスクリプトでは「問題自体を可能な限り引き下げ、ゆるく解決する」のが重要である。 開発コストを下げるべきなのだ。

amebloの内容を見ると、独特なエディタから生成されるものだけに、エディタ上での記述はちょっとした違いで大幅に異なるHTMLコンテンツが生成される。 私は投稿者とは連携していないので、ちょっとした記述のブレで抽出すべきコンテンツを見失う可能性がある。

生成されるタグと比べると見た目上の違いは少ない。 言い回しの違いや、スペースの有無程度である。

そこで、w3mを活用する。w3m-dumpオプションは、見た目に近い形でレンダリングした上でテキストにして出力する。 アメブロの執筆者はエディタ上の見た目で書いている可能性が高く、このようにレンダリングしてしまったほうがブレは抑えられる。

その中から情報を抽出する。 ある正規表現に一致する行、またはある正規表現の範囲をアドレスとして出力すれば多くの場合は十分だろう。

続くエントリや広告のゴミ値を拾ってしまわないように、エントリ終了時にはSedを終了させる。 多くの場合、/^AD$/で良いかと思うが、/いいね!した人.*リブログ/というパターンも考えられる。

例えばおやつ工房 ひびのやをdumpした結果からメニューを取得するとすれば、筆者はメニューの表記前に本日のメニューは、という行を置く習慣があるようで、またブログの締めとして本日もよろしくお願いいたしますを置いている。 これに基づけば

メニューそのものを抽出するならば、で囲むようにしているようなので、そこに限定しても良い。

しかし、どうもこれは項目であるようだから、メニューとしては成立しないかもしれない。 項目終わりは空行を空けているようなので、この項目から空行まで抽出してみよう。 一応、スペースを入れていた場合にも対応しておく。

しかし次のようなケースが発見された。

*天然酵母パン*
⚫︎食パン1斤→焼き上がりました
自家製カボス酵母、春よ恋使用。
乳製品不使用のシンプルなパンです。国産のお粉の素朴な風味をそのまま味わっていただけます。 

⚫︎レーズンとくるみのパン→焼き上がりました
自家製レモン酵母、春よ恋使用。

⚫︎カンパーニュ →焼き上がりました
自家製ラ・フランス酵母使用。
ライ麦粉と全粒粉を配合。

なので、項目開始だけでなく小項目開始も受け入れることにする。

結果次のように抽出できた。

*マフィン*
プレーン⬇
ポップシュガーに戻しました。
きのこみたい

*シフォンケーキ*
プレーン小

*天然酵母パン*
⚫食パン1斤→焼き上がりました
自家製カボス酵母、春よ恋使用。
乳製品不使用のシンプルなパンです。国産のお粉の素朴な風味をそのまま味わっていた
だけます。 

⚫レーズンとくるみのパン→焼き上がりました
自家製レモン酵母、春よ恋使用。

⚫カンパーニュ →焼き上がりました
自家製ラ・フランス酵母使用。
ライ麦粉と全粒粉を配合。

日付も入れるならば、投稿時タイムスタンプの行を追加しよう。

/[0-9]\{4\}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/ p
/*.**\|^⚫/, /^\s*$/ p
/^AD$/ q
//いいね!した人.*リブログ/ q

なお、サンプルとして「ameblo 本日のメニュー」で検索して最上位に出たおやつ工房 ひびのやさんを使わせていただいた。 埼玉県越谷市にあり、東京スカイツリーライン大袋駅、せんげん台駅が最寄りとなる、パンとお菓子のお店だそうである。11時から18時営業、金曜日休業。

更新されたかどうか

単純に考えれば取得したページをteeで保存して比較したいところだが、 広告などランダムに生成される部分があるため、これは失敗する。

amebloはDateヘッダを含み、Content-Lengthヘッダを含まないため1、ヘッダ部分だけ保存するという方法もある。 curl--dump-headerを使用し、内容は捨てるという方法が良いだろう。

もうひとつはそもそも抽出した内容を比較するという方法がある。 リクエスト回数が減るため合理的だ。

いずれにせよ、前回取得分をローテーションして、今回取得分と比較する。

diff -q ~/.cache/someameblo/header ~/.cache/someameblo/header.prev > /dev/null

通知する

今回の場合、Discordのwebhookを使用している。

Twitterという手もある。ここではtwを使用する。 セルフDMの送信自体は可能だが、Twitter webや公式アプリでは確認/通知はなくなっている。

LINE Notifyという方法もある。

メールが配送できる環境からであればMMS宛というのも悪くない

ジョブスケジューラによる定期実行

スクリプトを書いても手動実行するというのはちょっとださい。 ジョブスケジューラによって自動的に実行してほしいところだ。

anacronあたりを使うのが妥当なのだろうけれど、私はSystemd Timer (User)を使用することにした。

まずはserviceユニットを書く。 ここでは~/.config/systemd/someameblo.serviceを作成している。

[Unit]
Description=Get ameblo entry.

[Service]
Type=simple
ExecStart=/bin/zsh "/home/aki/opt/someameblo/getamebloentry.zsh"

[Install]
WantedBy=default.target

次に対応したtimerユニットを書く。 ここでは起動から10分で初回実行し、それ以降4時間ごとに起動している。

[Unit]
Description=Get ameblo entry.

[Timer]
OnBootSec=10min
OnUnitActiveSec=4hour
Unit=someameblo.service

[Install]
WantedBy=timers.target

ユーザーユニットをリロードする。

タイマーを起動&有効化

「ゆるい解決」について

このようなユーティリティは基本的に人間を補助するものである。 オートメーションに属するものの、このユーテリティが人間に無断で何かを決断するわけではない。 また、人間にとってこのユーテリティによって与えられる唯一絶対の情報というわけでもない。

できるだけ精度が高ければ嬉しいけれども、もしも例外的ケース(例えば著者が普段とはまるで違う書き方をした場合など)においてうまく機能しなかったとしたならば、それは単に普通にブログを見に行けばいいだけである。 例外的ケースにおいて問題が発生しないように設計し、成功しない可能性については許容する。

同様に情報が常に完璧なフォーマットであることを求める必要もない。 ソースが完璧なフォーマットで書かれない可能性は高いので、完璧を求めることは不毛である可能性が高い。 これもまた、不十分な情報となったときには自らブログを見に行く選択肢もあるのだ。

ただし、十分に機能させるためには、抽出条件はfalse negative(取りこぼしがある)よりはfalse positive(ゴミがはいる)ほうが好ましい。 ゴミがあっても無視すればいいが、取りこぼしがあると必要な情報が損なわれる可能性があるためだ。

プログラミングはプログラムを書くことよりも、どのようにアプローチするかということのほうが重要な場合は少なくない。 プログラミング初級者のうちは設計をないがしろにしがちだが、プログラムの出来や困難性はだいたい設計によって決まる。 今回の主題も、正攻法ではかなり難しい条件を、考え方を変えることで簡単な問題に変換しているということだ。


  1. あるいはcurlがContent-Lengthを含まないのかもしれない

シェルスクリプト Forced Todo

Forced Todoはなかなか進まない進捗に対して、一定の時間でTODOを読み上げ、進捗を迫るものである。 あまりに進捗しないので作ってみた。

Gist

Systray

Notifyを開始するとSystrayに常駐し、Systrayから終了できるようにした。

Systrayへの常駐はMailDeliver2同様、yadを使うようにした。

notifypid=$!

yad --notification --image=player-time --text="TODO NOW!" --command="yad --text-info --timeout=15 --title=TODO --width 300 --height 300 --filename=$HOME/.yek/forcedtodo/todotext" --menu='Edit!'"$task_editor $HOME/.yek/forcedtodo/todotext"'|Quit!quit' --tooptip="Immidiately Task" 
kill $!

Yadのオプションについて分析してみよう。

オプション 意味
--notification Systrayに常駐する
--image=player-time Systrayのアイコンを選択
--text="TODO NOW!" Notificationの名前(ポップアップに影響)
--command 左クリック時の実行コマンド
--menu 右クリックメニュー。項目名とコマンドを!で分けている。quitで終了

ネストされているYadは次の通り

オプション 意味
--text-info テキストダイアログ
--timeout=15 15秒でダイアログが閉じる
--title=TODO ウィンドウタイトル
--width 300 ウィンドウの幅
--height 300 ウィンドウの高さ
--filename=... テキストの内容となるファイル

Zenityとは結構な違いがあることに注意。

Yadは通常ブロックするため、Yadが終了しなければ戻らない。 そのため、Notifyするためのプログラムをサブシェルでバックグラウンドプロセスとして実行し、そのプロセスID($!)を取得し、Yadが戻ったらKillすれば良い、ということになる。

Notify (Open JTalk)

Notifyは単一のプロセスIDを振るためにサブシェルで実行し、バックグラウンドプロセスとすることでループさせる。

(
  while sleep $notify_interval
  do
  perl -pe 's/\n/。/g' ~/.yek/forcedtodo/todotext | open_jtalk "$jtalk_tuning[@]" -m $hts_voicefile -x $mecab_dictdir -ow $jtalk_tmpfile
  play $jtalk_tmpfile >| /dev/null &|
  notify-send -t $notify_time "TODO" "$(<$HOME/.yek/forcedtodo/todotext)"
  done
) &

単純なsleepによるループである。

基本的な手順としては

  1. Open JTalkで音声ファイルを作成
  2. 合成した音声ファイルをバックグラウンドプロセスとして再生
  3. Notify Sendを使って通知に表示

となる。

ここで大きな情報はOpen JTalkだろう。

Open JTalkはスピーチシンセサイザーだが、eSpeak, SVOX Pico, Festivalといったソフトウェアと比べて非常に自然で、日本語に対応している。 ただし、かなり複雑なソフトウェアだ。

Manjaro(Arch)では、AURにあるopen-jtalkパッケージをインストールする。

NAIST JdicファイルはAURにも存在していないが、辞書ディレクトリとして/usr/share/open-jtalk/dic/を使用することができる。精度は異なるかもしれない。

NAIST JDicをインストールするためには、mecabをインストールし、さらにNaist JDicで/usr/libexecが指定されている部分を/usr/libに変更しなくてはいけない。

女声を使用するためにはopen-jtalk-voice-meiをインストールする。

Open JTalkは非常に自然で、Twitterの読み上げにも使えそうだが(twとの組み合わせがいいだろう)、複数行を扱うことができないため、Perlで変換している。 また、Open JTalkは標準出力に吐くことはできないようだ。

シェルスクリプトで並列実行制御を行う

シェルスクリプトを書く場合において、処理を順次おこないたいことは多いはずだ。
多くのディレクトリや処理のリストなど、順に処理を適用していくケース。

まず、この設計だが、「ある特定の場所でしか実行されないスクリプト」はnoexecが指定されていない限りはそのルートに設置すべきだと思う。
対象ディレクトリごとにスクリプトが異なってくる場合はその対象ディレクトリごとにスクリプトを設置する。
もしそうでなく、その違いがディレクトリのパス自体に含まれるのであれば、単純に対象ディレクトリに空のドットファイルを置けばよいと思う。

例えば、…/.somescriptを実行するのであれば、トップに

for i in **/.somescript
do
  ( cd ${i:h}; zsh .somescript )
done

のようなスクリプトを書けば良い。Gitが「Gitを実行するディレクトリがリポジトリになる」という仕様なので、このように方法にらも随分なれてしまった。
もし、パス要素自体が重要になるのであれば、

for i in **/.target
do
  fooscript ${${i:h}:t}
done

という方法も考えられる。

それは良いとして、その各処理が時間がかかるとしたらどうだろう?
これは別にディレクトリ単位であった場合に限らない。テキストファイルに1行1エントリ形式で書いて読みながら処理する場合も同様だ。

別に最初からターゲットをグループ分けしてもいいし、xargsを使って3つずつ実行する、といったことで並列化することもできる。
だが、できれば常に3スレッドで実行する、といったモデルのほうが好ましいのではなかろうか。

これを実現するためにシェルスクリプトで並列実行制御したいのだが、残念ながらこれはかなり難しい。
Zshでもzthreadがあるような話も目にするのだが、まぁ実際はそうもいかなそうだ。

並列実行の難しさは、「同時アクセス」にある。あるリソースに同時にアクセスした場合、いろいろな形でおかしなことになる。
ファイルデスクリプタを共有すれば良いのではないかと考えたのだが、

worker() {
  workern=$1
  typeset val
  while read val
  do
    print "Worker $workern: $val"
  done
}

print -l {1..100} | (
  for n in {1..3}
  do
    worker $n &
  done
)

wait

結局read時に同時アクセスするとおかしな値を取ることになり(空文字列だったり、複数行がぐちゃぐちゃに混ざったものだったりする)、ちゃんと動作しない。

なお、ここでのポイントをまとめておこう。

  • ()はfork子プロセスを生成し、子プロセスで実行する。
  • この子プロセスに渡されたパイプは、子プロセス自体の標準入力として受け取ることになる。リダイレクトしないプロセスはこのファイルデスクリプタを共有する
  • &でバックグラウンドで実行する。子プロセスを生成したかどうかは関係がない
  • 外部コマンドはfork+execで子プロセスを生成するが、関数は生成しない
  • waitはジョブを共有待ち合わせる。引数なしですべてのジョブを待つ
  • 同じファイルデスクリプタを共有している場合、IOの位置もいずれかのプロセスが動かせばすべてのプロセスで動く

結局、アクセスしたら要素をひとつ返してくれるQueueがほしいのだ。

そこでまじめに考えてみた。一番単純なのはflockを使う方法なのだが単純にはいかない。プロセスの中で処理したければ、ファイルデスクリプタを使った、複雑な方法が必要になる。
その中で比較的スマートに処理できると考えたのがGistのスクリプトだ。

この場合、.lockは空ファイルであり、単なるロックでしかない。無駄なファイルを作るのにはちょっと抵抗があるが、方法としては比較的簡単だ。
この方法はbashでもほぼ同様に書くことができる。あまりzshらしい方法とは言えない。

なんか悔しいので、Zshらしい方法として、Socketを使うという方法を提案してみる。
UNIXドメインソケットはファイルパスを用いてプロセス間通信を行う。TCP同様、サーバーが接続を受け付け、IOを確立するものだ。

一般的にはサーバーは並列処理できるように接続の受け付けはマルチスレッドで行う。

zsocket -l foo
integer server=$REPLY
	
while zsocket -a $server
do
(
  integer io=$REPLY
  ...
) &
done

だが、シングルスレッドで行った場合はどうなるか。listenはしているがacceptはしていない状態だと接続しようとするclientはブロックされ、acceptされるまで待たされることになる。
結果的に、あるリソースにアクセスし、供給する部分がシングルスレッドになる。同時にアクセスしてもproducerはそれをブロックして順番に渡していくことになる。

これはごく普通のマルチプロセスモデルであり、Perlはthreadが非推奨で、UNIXドメインソケットの利用を推奨している。
そのため特に目新しいものではないのだが、Zshでおこなう(シェルスクリプトで行う)というとまたちょっと味があるのではないか。

Gist

様々なブラウザと様々なプロファイルを切り替えて使うスクリプト

基本解説

GitHub

これまでFirefox Latestスクリプトを使ってきたが、これではFirefoxしか利用できなかったり、異常終了やフリーズすると問題が生じる可能性があった。

Firefox Latestの仕組みは次のようなものだ。

Gist

要はシンボリックリンクで.mozillaを切り替えて起動→終了したらシンボリックリンク修正、をしているだけだ。

ちなみに、もともとはMageiaがFirefoxをLTS版を採用していて、それが気に入らなかったのでこのようにしている。
だが、Manjaroはその必要はないので、Firefoxだけでなく、任意のブラウザを任意のプロファイルで起動できるようなスクリプトを書いた。

多くの関数が定義されているが、まずはプロファイル切り替えの方法をまとめる必要があった。

Chromium系は--user-data-dirオプションを使用する。

これに該当するのは

  • Google Chrome
  • Chromium
  • Opera (Blink系)
  • Vivaldi
  • SRWare Iron
  • Maxthon

Maxthon for LinuxはあまりにもOUT OF DATEが過ぎるが。

Firefox系は-P profile-profile pathのふたつ。設定全体を切り替えることはできず、プロファイルのみの保存切り替えだが、問題はあまりなさそうだった。これに該当するのは

  • Firefox
  • SeaMonkey
  • Palemoon

それ以外

  • Midoriは-c directory
  • qupzillaは-p directory
  • Rekonqは--config directory

この5タイプに対応した上で、それぞれのブラウザに対応した関数を用意し、簡単に起動できるようにしている。

例えば

mybrowsers[shopping]="chr $HOME/.browser-settings/chromium/shopping"

とした上で、

$ mybrowser.zsh shopping

とすれば、$HOME/.browser-settings/chromium/shoppingを設定ディレクトリとしてChromiumが起動することになる。

Zsh Assoc

今回のスクリプトではZshの連想配列を2つ使用している。

ひとつは、ブラウザの起動設定を行うためのものだ。これは

eval "$mybrowsers[$1]"

という部分で作用している。

もうひとつは、ブラウザのコマンドを修正するものだ。Chromeはgoogle-chromeだったりするかと思うが、Archではgoogle-chrome-stableだったりもする。

そこでこれを修正するため、ブラウザの各コマンドは"${${modify_browsers[$browser]}:-$browser}"とすることで修正可能にした。

サイト構築で画像関連機能

ウォーターマークをいれた画像を半自動で用意する

Gist

ウォーターマークのベースはさすがに自動生成できるレベルにはなかったので、Inkscapeで作成した。
縦と横の二種類を用意している。

fig/にはオリジナルサイズの、thumb/にはサムネイルサイズの画像を配置するが、この時にcompositeを使ってウォーターマークを合成している。

また、幅1600pxを越える画像はリサイズしている。

@figbaseuriの不完全さ

PureDocにもわずかな変更を加えたが、これはattrを排除するためにすぎない。

大きいのはParser::PureDocにおける

        # Other settings for PureDoc
        begin
        if config[:purebuilder_config][:puredoc_tune].kind_of? Proc
          config[:purebuilder_config][:puredoc_tune].call(::DOC)
        end
        rescue
          STDERR.puts "!!ERROR in PureDoc TUNE:" + $!
        end

という変更だろう。

これによって、@config[:puredoc_tune]を使って各PureDocオブジェクトに対して設定が可能になった。
これはMarkdownドキュメントに対しては無効となる。

Gist

「各article要素を取得してループ→各要素に対して各figure要素を取得してループ→イベントリスナー追加」という単純な構造ではある。

ライトボックス用に用意されたimg要素のsrcを変更し、さらに最大幅と最大高をウィンドウサイズに合わせてから表示する、という処理が追加され、ちゃんと機能するものになった。

figure要素に入っていないものはサムネイルの構造を持っていないとみなす方式。

暗号化ディスクをsystemdでがんばる

これまで単純にスクリプトで暗号化ディスクをマウントしていた。
systemdスクリプトにするのは簡単で、実際にSystemdで自動マウントしていた時期もある。

だが、今回は「ちゃんと」Systemdを使うことにした。

スクリプトはGitHubで公開している。

私の場合、btrfsのボリュームとして4つのデバイスを使い、その4つのデバイスはディスク全体をdm-crypt plainで暗号化したものだ。
つまり、おおまかにはcrpytsetupのあとmountする必要があり、かつcryptsetupは全ディスク分ループしなくてはいけない。

これ自体はスクリプトとして用意してあり、これまでそれを使っていた。
Systemd対応にするのも、単純にSystemd経由にするだけなら、以前のエントリの通り簡単なユニットで良い。

今回はスクリプトは、「後処理」に対応した。
もちろん、スクリプトに後処理を組み込むなら非常に簡単な話だ。
だが、それでは「後処理に失敗するとユニット全体が失敗」してしまう。
そこで、systemdのExecStartPostを使うことにした。
それに伴って、スクリプトは起動スクリプトらしくなったし、設定ファイルを使うようになったりしている。

elif [[ "$1" == post ]]
then
  ### StartPost
  if whence -f opendisk_after >&2
  then
	opendisk_after
  else
	exit 0
  fi

fi

これでだいぶ整った。これを呼ぶほうは

ExecStartPost=/usr/local/sbin/opencryptdisk.zsh post /etc/opencryptdisk/%I.conf

%Iについては後ほど説明。

しかしここでだいぶ躓いた。ExecStartの完了を待たずにExecStartPostしてしまうため、ここに処理が入っているとコケるのだ。

これは、Typeを省略しているとsimpleとして扱われる。これは、フォアグラウンドで走るデーモンのためのユニットタイプで、「実行と同時に起動完了・実行終了とみなす」というもの。
実際はスクリプトの実行が終わった時に起動完了・実行終了としてほしいため、

Type=oneshot

を追加した。

同じような理由でExecStopが入っているとこけていたため、スクリプトにはcloseも入っているが、ExecStopが外されている。
ExecStopでアンマウントするのであれば、

RemainAfterExit=yes

として、「スクリプト終了時に起動完了・実行は継続とみなす」にする必要がある。
ただ、マウントと暗号化デバイスは、多分終了時に自動的にうまくやってくれるため、必要ないと判断して外してある。

systemdユニットがname@.serviceの形になっていると、name@param.serviceとして起動することができ、@paramをユニット内で%Iとして使うことができる。
複数ボリュームのマウントを可能にするため、この機能を使用している。


Systemdについてだが、非常に高機能だが、一方で複雑でめんどくさい。

例えば、マウントもスクリプト内でやるのではなく、.mountユニットにして、そのAfterで復号化してから実行、という形もとれたが、明らかにmountを書くほうが早い。

やはりかなりの暗黒面だ。