OREMATOPEE

プログラミング、気になったこと、メモ書き...etc

coc.nvimでMarkdownをlintする

Notionを整理していたら結構前に書いていたものが見つかったのでポスト。
ほぼ参考記事のとおりなので、詳しくはそちらを見てもらえたらと思います。

参考記事

coc.vim で textlint する話 - げっとシステムログ
coc.nvimでtextlintを使う

前提

  • goコマンドが使えること

1. efm-langserverをインストール

概要

mattn/efm-langserver

特定のコマンドから生成されるエラーメッセージに特化したLanguage Server

以下の場合、導入する利点がある。

  • 言語のLSが存在しないが、Linterは存在する場合
  • LSはあるが、Linterを使いたい場合

今回MarkdownのLinterを使用するので、efm-langserverを使ってLS化を行う。

インストール手順

  1. インストールコマンドを実行
go get [github.com/mattn/efm-langserver](http://github.com/mattn/efm-langserver)

これでインストールできるが、実際は /go/bin以下にツールをコンパイルするだけのもの。
コンパイルされたツールは任意のパスに移動できる。 /go/binにパスを通してもよい。

  1. coc-settings.jsonにefm-langserverの設定を追加
{
  "languageserver": {
    "efm": {
      "command": "efm-langserver",
      "args": [],
      "filetypes": ["markdown"]
    }
  }
}
  1. efm-langserverのコンフィグを設定

$HOME/.config/efm-langserver/config.yaml に設定を追加

version: 2
root-markers:
  - .git/

tools:
  textlint: &textlint
    lint-command: 'yarn run textlint --format unix --stdin --stdin-filename ${INPUT}'
    lint-ignore-exit-code: true
    lint-stdin: true
    lint-formats:
      - '%f:%l:%c: %m [%trror/%r]'
    root-markers:
      - .textlintrc

languages:
  markdown:
    - <<: *textlint

2. textlintとルールプリセットをインストール

概要

textlint/textlint

校正ツール
ルールを追加、定義することによって実行対象の文章の問題点を指摘してくれる。

textlint-ja/textlint-rule-preset-ja-technical-writing
textlint-ja/textlint-rule-max-ten
azu/textlint-rule-spellcheck-tech-word
textlint-ja/textlint-rule-no-mix-dearu-desumasu

textlintルールプリセット

インストール手順

  1. インストールコマンドを実行
yarn add textlint
yarn add textlint-rule-preset-ja-technical-writing textlint-rule-max-ten textlint-rule-spellcheck-tech-word textlint-rule-no-mix-dearu-desumasu

上記はグローバルインストールではないことに注意

  1. textlintのコンフィグを設定

$HOME/.textlintrcに設定を追加

{
  "rules": {
    "preset-ja-technical-writing": true,
        "max-ten": {
      "max": 3
      },
        "spellcheck-tech-word": true,
        "no-mix-dearu-desumasu": true
  }
}

褒められたときに「すみません、ありがとうございます。」と言ってしまう

嬉しいことに、フィヨルドブートキャンプに入って褒められる機会が増えた。

自分では「大したことじゃない」と思っていたことも、褒めてもらうことで自信につながるし、新たな価値基準を知る機会になっていてとても嬉しい。

この褒めるというシーンには学べることがたくさんあった。


これまでフィヨルドブートキャンプに関するエントリを書いていなかったが、特に理由があるわけではない。
しいていえば日報やScrapboxなどアウトプットする場が増えていて、改めてブログで書く機会がなかったというのがあるかもしれない。
書かなかっただけで不満をもっているということは全くなく、むしろ楽しい。
成長する自分、横のつながりを強く感じられるコミュニティ、現役で著名なアドバイザーやメンターの方にすぐ見てもらえる環境、言い出せばきりがない。

エンジニアになるためのスクール選びに迷われている方がいたらフィヨルドブートキャンプを勧めたい。
"フィヨルドブートキャンプ 評判"で検索すれば、より詳しい情報がわかると思う。

フィヨルドブートキャンプに対してずっと感じていることは、「卒業して就職すること、スクールの利益を最大化することだけを目的としていない、様々なポジションにいるみんなが得をするような答えを追求する優しいコミュニティである。」ということだ。

フィヨルドブートキャンプに登場する人物たちとその構造から、"質問すること"の大切さを説いた@june29さんのこちらの記事が参考になる。

これまでと今

もともと自分は自己肯定感が著しく低い方で、誰かと話したあとに「あのとき、これを言っていれば...」とか、「つまらないと思われてたらどうしよう」と後悔することが日常茶飯事だった。
フィヨルドブートキャンプに入る前からこの問題を抱えていたが、今はこの問題を解決するために奔走している。
人によって様々だが、解決方法は至って簡単。
良い体験を増やすことだ。

なにかにチャレンジして失敗した経験があるからこそ、悪いほう悪いほうへ思考が流れていく。チャレンジしたことがないのに思考が悪いほうへ流れているとしたら、それは知らないことに対する"恐怖"だ。
他にも色々ご意見はあるだろうけど、自分の場合はこれが一番納得のいく答えだった。

一度失った自己肯定POWERの影響力はすさまじく、それはついには半永久デバフになる。
悪いほうへ思考が流れ果ててしまうと、失敗も成功もしていない、つまり結果が出ていないのに後悔することになる。 "結果がでていないのに後悔する"という表現が伝わるのかはわからないが、とにかくこんな状況になるともうチャレンジすることもなくなってしまう。
悪い思考が全ての行動を制限してしまう。

"決闘に敗けて、何を学んだか? 決闘では私は勝てないということだ。それは奴らの戦い、奴らの法だ。だから私は戦わずして奴らを殺す。自分は何を知っている? 自分とは何か? 我々は己を知ることで、欲しいものを手に入れられるんだ。"
Petyr Baelish from "Game of Thrones" © 2011-2019 D.B. Weiss, Home Box Office Inc.

ちなみにこのデバフへの対策として、以下のことを実践している。

  • オープンな場で発言する
  • 言いにくいことを作らない
  • 自分の好きを発信する

すべてチャレンジして失敗したことか、チャレンジせず後悔していたことだ。悪手だと思っていたことを地で行ってる感じ。
これでもし成功体験をつかめば、「やっぱり間違っていなかったんだ」と心の底から思えるし、幸いフィヨルドブートキャンプは失敗に対して寛容な場所だ。
程度の差はあるだろうが、誰かを傷つけない限りはコンティニューの機会は必ずある。

他のコミュニティや転職先企業という外海にでたらこの状況が逆転するかも知れない。
でも、大丈夫。
これらのチャレンジを繰り返して得た成功体験が自分のインベントリに絶対にある。

その体験が道を照らし、自分を守ってくれる。たとえなかったとしても、そう信じることが大事だ。諦めなければ次がある。
なにもしないのは行動をしないということで、そうすると結果が出ない。
結果が出ないとあなたの行動が正しかったのか間違っていたのか答え合わせすることができない。
まずは馬鹿正直になにか行動を起こしてみることをおすすめする。

"時として私たちの強みは表面の下に眠ってるもの。 ずっとずっと奥の方にある時もあるわ。"
Moana from "Moana" © 2018 Disney

そうそう!
先にあげた対策に共通することは、目立つことかもしれない。

目立つのは恥ずかしいし、こそこそと暮らしたい。できれば。
目立てば褒められる機会が増えるかもしれないけど、批判もされる。批判は怖い。

でも同時に、正当に評価されたいとも思う。
そのためには、人目にさらされることが前提として必要になる。
良い体験も正当な評価も自分が生み出せるものじゃない。他人が与えてくれるものだ。
ノイズも混じるかも知れないけど、この先生きのこるには誰かが与えてくれるアイテムが必要だ。

褒められたときに「すみません、ありがとうございます。」と言ってしまう

長々とえらそうに書いたけど本題。

長くデバフにかかっていたせいで、褒められたときに「すみません、ありがとうございます。」と口走ってしまうことが最近の悩み。
これを自分なりに分解しようとおもって書き始めたら、エモいことが書きたくなってしまってこの行まで文字を書いている。

ここまで書いてしまったのでもうちょっと無駄なことを書く。
ああ言葉ダイエットしたい。痩せたい。 これから本題書くのに、上で書いた内容より絶対的に短いのが申し訳ない。


「ありがとうございます」は問題ないとおもう。
これも@june29さんのScrapboxで書かれていた、@laco2netさんの謙遜しないと決めている話が参考になった。

  • 「褒めたいと思ったあなたを否定しない」
  • 相手が自分を褒めたいと思ったそのことはその人にとって疑いようのない事実である。「そう思った」という事実は誰にも否定できない。 だから、相手からの「承認を承認」する
  • 注意したり怒っても意に介さない人はそのうち誰も怒ってくれなくなる。それと同じで、褒めても嬉しくなさそうな人を褒め続けてくれる人も多くない。

とくに"褒めても嬉しくなさそうな人を褒め続けてくれる人も多くない"というのは自分にはなかった視点だし「たしかにそうだ」と思った。
「そんなことありませんよ」という謙遜は、褒めてくれたことに対する否定にもなっている。
謙遜しないのには抵抗があるけど、"褒めたいと思ったあなたを否定しない"ルールの上だったらやりやすい。

一方、「すみません」は問題だ。
褒められているのに謝っている。

この「すみません」は、褒めてくれた人に対して発している。
褒めてくれた人に不義理を働いたっけ。
褒めてくれることをお願いしたかな。
いやいやもしかするとその人は、ただ褒めたくて褒めたんじゃないのかな。

「すみません」ってどんな意味で言っていたのだろう。

すみません

「すみません」がなぜ出てくるのか。

「すみません」は謝罪の言葉。
理由や弁解の言葉が見つからないときに使う「申し訳ありません。」と変わらない言葉。
褒めてもらっているのになぜでてくるのか考えたけど、やっぱり自己肯定感のなさが原因だ。

褒めてもらったことが忍びなく、こんな私を褒めてもらって申し訳ないんだと思う。
色々自己肯定感を高めるために奔走していたけど、まだこのデバフは消えていないらしい。

これに気づいたとき、「どこまで自信がないねん!」と自分をひっぱたきたくなった。
自分で自分を傷つけていたのに、いざこのことに気づいたら自分を守る感情がわいてきた。

自分を守る感情がわいてきたってことは、褒めてもらって忍びないと思う感情を否定したいんだろう。きっとどこかで曲がりなりにも今の自分に対しての自信とか、譲れないポイントがあるんだと思う。

"自分のすることを愛せ。子供のころ映写室を愛したように"
Alfredo from "Cinema Paradiso" © 1989 CristaldiFilm

たしかに色々コンプレックスはあるんだけど、自分なりのやり方でここまできたので、他の人が完全にそれを再現するのは難しいはず。
"忍びない"という感情で行動を制限されているのなら、次に向き合うべきは"自分を守りたい"というこの感情だ。

この感情は私のもので、"守りたい"と思ったことは事実だから、誰もそれを否定できない。もう少し自分に優しくしてあげることから始めようと思う。

https://images.unsplash.com/photo-1494959764136-6be9eb3c261e?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2550&q=80

「ありがとうございます。」

前向きになれたのは、フィヨルドブートキャンプでメンターの方が1on1を開いてくれたことが一番大きい。これがあったおかげで深く考えることができたし、客観的に今抱えている問題を見つめることができた。
自分一人では、「考えても気分が下がるだけだしやめよう」としか思えなかったけど、「ちゃんと考えよう」と思えた。

考えてもしょうがないことは多いけど、今回は自信は残されているということを発見できたので嬉しい。

えぬぱくは レベルが あがった!
せいこうたいけんが 1ポイント あがった!

フィヨルドブートキャンプの宣伝でしょ」って思うだろうけど、その通り!
技術"だけ"教えてくれるスクールじゃないんですよ。
なんだか興味わいてきませんか? なんだか覗いてみたくありませんか?

ちなみにこのエントリ、書かされているわけではありません。
書きたいから書いております。

よく自分が出す日報にはシャウトアウトといって感謝を書くのでこのエントリでも書きたいんですが、正直いって感謝を伝えたい人が多すぎて書くのが面倒です。
フィヨルドブートキャンプには色んな人がいますしね。

なのでこれらを集合?結合?融合?した、コミュニティそのものに感謝申し上げます。
ありがとうございます。

タスク管理ツールの一長一短

タスク管理ツールとしてどれが自分に合っているのか、実際に使ってみた。
そこまで深くは使いこなしていないけど、それぞれメインとなる機能は触ったつもり。
自分へのメモとして記載していますが、だれかの参考になればうれしい😃

Notion

www.notion.so

  • タスク管理というよりEvernoteに近い。使いこなせばプロジェクト管理もできる。
  • リマインダーできる
    • 複数リマインダーを設定できる
    • メール通知はできない(アプリのプッシュ通知が届かない状況であれば代わりにメールで届くかも?)
  • Googleカレンダーと自動同期できない
  • ボードビュー
    • チェックボックスの一覧がページを開かないと見れない
    • 親子関係のサブタスクをぱっと見で把握できない
  • 画像をサムネイルにできる
  • タスクの表示サイズをカスタマイズできる
  • セクション毎のルールで並び替えられない
  • プロパティを無制限に作成でき、どう扱うか(締め切り、優先度など)の自由度が高い
    • プロパティの表示非表示を選択できる
  • 少しカスタマイズしようとすると一気に複雑度が増す
    • プロジェクトを横断してタスク把握したい、進捗を見たい場合など
    • データベースをリレーションさせればなんでもできる(メンテは大変だろうけど)
  • タスク=ページなのでなんでも書ける
  • 現時点では情報の保管庫として使うのが気楽でいい
  • リマインダーとカレンダー同期さえ強くなれば敵なし(複雑だができないことがないので)

Trello

trello.com

  • カンバンで管理、シンプル
  • リマインダーできる
    • 複数リマインダーを設定できない
    • いつリマインドするか設定できない
  • プラグインGoogleカレンダーと自動同期できる(2つ以上のボードで同期させたい場合、別途費用がかかる)
  • プラグインチェックボックスの一覧が表示できる(別途費用がかかる)
  • 画像をサムネイルにできる
  • タスクの表示サイズをカスタマイズできない
  • セクション毎のルールで並び替えが可能(Butlerという自動コマンドを使う必要があり、実行回数制限がある)
    • 無料でも一応つかうことはできるが、なんでもかんでも便利にしようとするとすぐ上限がくる
  • 優先度の概念がないので、ラベルで代用する必要がある
    • ラベルは色が固定
    • ラベルの色によってカードに表示される順番が決まっていて、ラベルの表示位置が思った通りにできない
  • 課金して少し便利にしたいと思ったら1000円かかる。Adobeフォトプランと同額。
    • プラグインを含めたら機能数もかなりあるけど、プラグインによっては別途費用がかかるのでもっと割高になる
    • 別途費用がかかる=プラグイン開発が活発という側面がある
  • プロジェクトを横断してタスク把握ができない(プラグイン使えばできるのかも)
    • プラグインでタスクのミラーリングができるので、他のプロジェクトへ同じタスクを生成することはできる(ミラー元への変更がミラー先へ反映されるのかは未確認)
  • シンプルだがいざ機能を求めようとすれば他と比べて割高になるが、課金すればカンバンビュー以外のビューも豊富だし誰にアサインされたのか把握もしやすく、プロジェクト管理ツールとしてはけっこう使えると思う

Todoist

todoist.com

  • リスト、ボードビューのみ
  • ボードビューはサムネイル画像を表示できない
  • リマインダーは有料
    • 複数設定でき、予定時刻も自由に設定できる
    • リマインドは最強
  • Googleカレンダーと同期できる(設定も簡単)
  • セクション毎のルールで並び替えができない
  • 自然言語で締め切り、ラベル、優先度を設定できるのでタスク追加の手間が少ない
  • プロジェクトを横断してタスク把握ができる
    • ただタスクのミラーリングができないので、統一的なプロジェクトに他プロジェクトからタスクを引っ張ってきてとかはできない
    • 与えられた手段の範囲でなら見れる
  • 料金は上二つと比べて一番安い(その分機能が少ない)
  • 機能が少ない分、シンプルに扱える。デッドラインを気にするようなプロジェクトには最適かも(リマインダーが強いので)

総評

プロジェクト管理ツールとして見たときの印象としては...

  • Notionは複雑だができる
  • Trelloはプラグインの力とお金の力を借りればできる
  • Todoistはビューの種類も少ないし複数人で進捗管理とかは厳しそう

あえておすすめをあげるとすれば...

  • Evernoteなみに情報を一カ所にまとめたいなら Notion
  • タスクにサムネイル画像が必要なら Trello
  • 個人運用でタスクにサムネイル画像が不要なら Todoist

どれも個人で軽く使うくらいだったらそこまで複雑ではないし、リマインダーとカレンダー同期を気にしないんだったらどれでもいい気がする。

自分は個人でタスク管理がしたいだけだったので、Todoistでタスク管理、Notionで残しておきたいタスクの結果とか見たい本とかをまとめることにした。(それもあってかTodoist推しに書いてしまった。)
Trelloはリマインダーが弱いのと、並び替えとGoogleカレンダー同期がわざわざプラグインを介さないといけないので諦めた。

SSLを使った暗号化通信

参考

xtech.nikkei.com

まとめ

Lesson1

xtech.nikkei.com

SSLが持つ機能は大きく2つある。

  • データの暗号化
  • 通信相手が信頼できることの確認

クライアントからサーバへ「サーバ証明書」と呼ばれる情報を送り、それを検証することで信頼できるかどうか判断している。

SSLはアプリの種類を問わない汎用的なプロトコルなので、メールやFTPといったアプリケーションのデータを暗号化することも可能。

ブラウザは「http://」で始まるURLで指定したサーバに要求を送るときはTCPにデータを渡すが、「https://」宛の時はブラウザとTCPの間にSSLが間に入り、暗号化してからTCPに託す。
暗号データを受け取ったTCPはそれがSSLによるデータと示すため、443番ポートを介してIP、イーサネットに送り出す。
受信側であるTCPは443番ポートで受け取ると暗号データと解釈してSSLに託し、SSLが復号化を行ってからWebサーバへ情報が渡る。

SSLは元々ネットスケープ社が開発した独自仕様のプロトコルだったが、これをベースとしてTLSという標準技術が生まれた。
現在はTLS1.3が最新となっている。

Lesson2

xtech.nikkei.com

SSL共通鍵暗号公開鍵暗号の2つの暗号方式を使う。
しかし実際には、アプリケーション同士でやりとりするデータの暗号化には共通鍵暗号を使っているが、不特定なインターネットの通信では両者が通信前に同じ共通鍵を持つことはできない。

そこで前処理として公開鍵を利用する。
クライアントはSSL通信の要求を送り、サーバが秘密鍵と公開鍵のペアを生成し、自身の公開鍵が入ったサーバ証明書を送信し、クライアントはサーバ証明書をみて信頼できるか判断してその中からサーバの公開鍵を取り出して共通鍵を送る。
これにより以後は互いに共通鍵を使った通信が可能なのである。
(正確には共通鍵そのものをやりとりしているわけではなく、あくまでイメージ。後述)

Lesson3

xtech.nikkei.com

共通鍵を使って暗号通信を始めるまでにはさまざまなメッセージのやりとりがあり、これらを支えるSSLの仕様が大きく2つある。

  • レコード・プロトコル(メッセージのフォーマットを定義)
  • ハンドシェーク・プロトコル(やりとりの手順を定義)

SSLで運ぶメッセージはレコードと呼び、ヘッダ部とデータ部に分かれる。

ヘッダ部 データ部
データの種類(タイプ ハンドシェーク・プロトコルのメッセージ
バージョン(SSLTLSか) 暗号通信時の暗号データ
データ長

STEP2のSSLのやりとりを少し詳細に述べるなら、まず暗号通信時に使う暗号方式を決めるところから始まる。クライアントからサーバに暗号方式などの提案を行い、サーバはその中から適切なものを一つ選んで返答し、続けてCertificateメッセージを使ってサーバ証明書を送り、そのことを知らせる。
クライアントはこの証明書にある公開鍵を使って共通鍵を生成する元となる値、プレマスタシークレットを暗号化して送信する。
実際にはプレマスタシークレットを送るのであって共通鍵を送っているわけではないことに注意。
このプレマスタシークレットをサーバは自身の秘密鍵で復号すると共通鍵が生成されるというしくみ。

共通鍵のやりとりはClientKeyExchangeメッセージによって実現されている。
あとは互いに暗号方式の採用を宣言して、ハンドシェークの終了を互いに知らせる。
ここまでがハンドシェークの一連の流れ。

Lesson4

xtech.nikkei.com

サーバ証明書認証局と呼ばれる組織に申請して発行してもらう。
この証明書は認証局秘密鍵で暗号化されていて、この発行された証明書をサーバがクライアントに渡して、クライアント側で認証局の公開鍵を使って復元できれば、認証局の証明書だとわかる仕組み。

証明書の内容
サーバ運営者の組織名
認証局の組織名
証明書の有効期限
サーバの公開鍵

※証明書には認証局の署名がついている。

認証局自体が信頼できるのか?

実は、サーバ証明書とともに認証局の証明書もクライアントに送られていて、もしその認証局が他の認証局から署名を受けている場合はその署名を書いた認証局の証明書も送られる。最終的には上位のルート認証局が必ずついてきて、まずはそのルート認証局が信頼できるのかをクライアントは確認する。
PCには元からルート認証局の証明書が入っていて、送られてきた証明書と一致しているかで信頼性を判断している。信頼できるとなれば上位から下位に向かって認証局の証明書を検証する。

クライアントはこのルート認証局の証明書にある公開鍵を使って、サーバー証明書の署名を復号したデータとサーバー証明書のハッシュを調べて一致すれば、ルート証明局から署名を受けたサーバー証明書であることがわかる。

NeovimにluaのLSPを導入する(sumneko)

参考

github.com

注意点

  • 事前にNeovimのlspconfigを導入しておくこと
  • ルートパスにgit cloneしてきたリポジトリの場所を指定すること
  • linuxmacなどそれぞれのOSに適したlua-language-serverを指定すること

手順

sumnekoのビルドにnijaというものが必要なのでインストールする

$ brew install ninja

ninjaを入れたら、sumnekoのモジュールをビルド、コンパイルする

$ cd 3rd/luamake
$ compile/install.sh
$ cd ../..
$ ./3rd/luamake/luamake rebuild

あとはconfigファイルに以下を記載する。

local sumneko_root = vim.fn.stdpath('cache') .. '/lspconfig/sumneko_lua/lua-language-server'

lspconfig.sumneko_lua.setup{
  cmd = {
    sumneko_root .. '/bin/macOS/lua-language-server',
    '-E',
    sumneko_root .. '/main.lua',
  },
  capabilities = custom_capabilities(),
  settings = {
    Lua = {
      runtime = { version = 'LuaJIT', path = vim.split(package.path, ';') },
      diagnostics = {
        enable = true,
        globals = {'vim'},
      },
    }
  },
}

はてなブログのコードブロックにテーマを設定する(シンタックスハイライト)

はてなブログで配布されているテーマをそのまま使っていたが、コードブロックが白と黒一色だったりしてぱっと見で変数や関数がわかりづらかったので、ちゃんとコードの構成要素に沿った形で色付け表示してみた。

highlight.jsというツールを使うことで簡単に導入可能で、少しはてなブログをカスタマイズするだけ。 まず、現在の状態を確認しておく。

f:id:npakk:20210725001335p:plain
コードブロックが白と黒で表現されていて見づらい...

highlight.jsを導入することでこのような表示になる。

f:id:npakk:20210725001532p:plain
エディタで見るような色付けがされていて見やすい

導入方法

以下のコードをはてなブログ管理画面 → 設定 → 詳細設定 → headに要素を追加に貼り付ける。

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/styles/base16/tomorrow-night.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/highlight.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/languages/erb.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/languages/plaintext.min.js">
</script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
  document.querySelectorAll('pre.code').forEach((block) => {
    hljs.highlightBlock(block);
    hljs.lineNumbersBlock(block);

    var classes = block.classList;
    if(classes.length > 0){
      if(classes[1].indexOf(':')){
        var values = classes[1].split(':');
        var filename = values[1];
        if(filename){
          block.setAttribute('data-filename', filename)
          block.classList.remove(classes[1]);
          block.classList.add(values[0]);

          var containerEle = document.createElement('div');
          containerEle.classList.add('filename-container');
          var filenameEle = document.createElement('span');
          filenameEle.classList.add('filename');
          filenameEle.append(document.createTextNode(filename));
          containerEle.append(filenameEle)

          block.parentNode.insertBefore(containerEle, block);
        }
      }
    }

    // ちらつき解消
    block.classList.add("visible");
  });
});
</script>
<style>
/* ファイル名表示 */
.filename-container {
  position: relative;
  z-index: 10;
  top: 29px;
}
.filename {
  display: inline-block;
  vertical-align: top;
  max-width: 100%;
  background: rgba(177,197,247,.25);
  color: #fff;
  font-size: 0.8em;
  height: 24px;
  line-height: 24px;
  padding: 0 6px 0 8px;
  font-family: monospace;
  border-radius: 4px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ちらつき解消 */
.entry-content pre.code {
  opacity: 0;
  transition: opacity 0.5s ease;
}
.entry-content pre.code.visible {
  opacity: 1;
}
.hljs-ln-numbers {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  text-align: center;
  color: #666;
}
/* 枠線の削除 */
.hljs-ln {
  margin-bottom: 0;
  border: none;
}
.hljs-ln tr, .hljs-ln td {
  border: none;
}
</style>

参考記事からそのまま貼り付けたものもあるので、もっとキレイに書けるはずです...。

行番号を表示したくない場合はこちら

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/styles/base16/tomorrow-night.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/highlight.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/languages/erb.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/languages/plaintext.min.js">
</script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
  document.querySelectorAll('pre.code').forEach((block) => {
    hljs.highlightBlock(block);

    var classes = block.classList;
    if(classes.length > 0){
      if(classes[1].indexOf(':')){
        var values = classes[1].split(':');
        var filename = values[1];
        if(filename){
          block.setAttribute('data-filename', filename)
          block.classList.remove(classes[1]);
          block.classList.add(values[0]);

          var containerEle = document.createElement('div');
          containerEle.classList.add('filename-container');
          var filenameEle = document.createElement('span');
          filenameEle.classList.add('filename');
          filenameEle.append(document.createTextNode(filename));
          containerEle.append(filenameEle)

          block.parentNode.insertBefore(containerEle, block);
        }
      }
    }

    // ちらつき解消
    block.classList.add("visible");
  });
});
</script>
<style>
/* ファイル名表示 */
.filename-container {
  position: relative;
  z-index: 10;
  top: 29px;
}
.filename {
  display: inline-block;
  vertical-align: top;
  max-width: 100%;
  background: rgba(177,197,247,.25);
  color: #fff;
  font-size: 0.8em;
  height: 24px;
  line-height: 24px;
  padding: 0 6px 0 8px;
  font-family: monospace;
  border-radius: 4px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ちらつき解消 */
.entry-content pre.code {
  opacity: 0;
  transition: opacity 0.5s ease;
}
.entry-content pre.code.visible {
  opacity: 1;
}
</style>

何をやっているのか

特別難しいことはしていません。
CDN経由でhighlight.jsと拡張機能であるhighlightjs-line-number.jsを読みこんでカスタマイズし、あとはCSSで見た目を整えているだけ。
ただ、実装が少し複雑でこのポストの独自性があるところでいえば、ファイル名を表示させている箇所です。

QiitaZennはコードブロックのコードソースを記述したあと、:以降でファイル名を表示できます。 f:id:npakk:20210725012437p:plain

f:id:npakk:20210725012158p:plain
左上に「Dockerfile」とファイル名が表示されていますね。

Markdownの記法には色々方言があるのですが、:を使ってファイル名を表示されているこの記法は拡張されたものであり、はてなブログでは使えません。
どうにか実装する方法はないかと、こちらに行き着きましたが、要件は満たせているものの横スクロール時にファイル名がついてきてしまい断念...。
データ属性としてdata-filenameというものを用意しているのは便利なのですが、MarkdownがHTMLに出力されるときに使われるpre要素の疑似要素としてファイル名を表示させているため、親要素であるpreのスクロールに追従してしまうんですね。(回避方法はあるかと思いますが、思いつきませんでした。)

諦めてZennの実装ではどうなっているのか見てみると、ファイル名表示をpre要素とは別にdiv要素で実装していました。
data-filenameのデータ属性を見つけたらpre要素とは別でファイル名を表示するためのdivを生成すればいける!」とのことで、ソースの以下の箇所を実装しました。

    var classes = block.classList;
    if(classes.length > 0){
      if(classes[1].indexOf(':')){
        var values = classes[1].split(':');
        var filename = values[1];
        if(filename){
          block.setAttribute('data-filename', filename)
          block.classList.remove(classes[1]);
          block.classList.add(values[0]);

          var containerEle = document.createElement('div');
          containerEle.classList.add('filename-container');
          var filenameEle = document.createElement('span');
          filenameEle.classList.add('filename');
          filenameEle.append(document.createTextNode(filename));
          containerEle.append(filenameEle)

          block.parentNode.insertBefore(containerEle, block);
        }
      }
    }

data-filenameが記載された要素を見つけ、その親との間にdivを新たに生成しています。

テーマを変える

以下のソースのbase16/tomorrow-nightの部分を好きなテーマの名前に変更します。

~
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.1.0/styles/base16/tomorrow-night.min.css">
~

highlight.jsのdemoに対応しているテーマがありますのでまずはテーマを選ぶ。
適当なURLではうまくcdnを取り込めないので、ここで選んだテーマの正確なURLをコピーして、上記ソースを書き換えてください。

参考文献

はてなブログで highlight.js を使う (Markdown記法向け) - ぺんぎんの布団
[Markdown] Qiitaのようにコードに自動でファイル名を付ける
highlight.jsに行番号を追加する方法 - Kotonoha
GitHub - wcoder/highlightjs-line-numbers.js: Line numbering plugin for Highlight.js
【JavaScript】要素を追加するinsertBeforeとappendChildについて - TASK NOTES
JavaScriptで親や兄弟要素を取得する | cly7796.net
JavaScript | 複数のノードをまとめて追加(DocumentFragment)

最後に

実装中にこんな記事を見かけてzennを併用している私としては、絶対こっちの方がおすすめです... zenn.dev

Linuxの基礎

参考文献

新しいLinuxの教科書

シェルとカーネル

コマンドを実行する部分は、Linuxカーネルが担当する。
CPUやメモリなどのハードウェアを管理するとともに、最終的なコマンド実行を処理するプロセス管理も担っている。

なお、Linuxカーネルは直接人間が操作できるようになっていないので、カーネルのインタフェースとなるシェルが仲介役を担ってユーザーからのコマンドを探してカーネルに実行を依頼し、その結果を受けてユーザーの画面に表示する。

プロンプトとコマンドライン

ホスト名やユーザー名から$%までをプロンプト、以降のコマンド入力部分をコマンドラインと呼ぶ。

[user@localhost ~]$ ls

操作方法

カーソル移動

操作 内容
C + b 後方に1文字分移動
C + f 前方に1文字分移動
C + a 行頭に移動
C + e 行末に移動
M + b 後方に1単語分移動
M + f 前方に1単語分移動

文字列操作

操作 内容
C + h 後方に1文字分削除
C + d カーソル位置の1文字削除
C + w 後方にスペース区切りで1単語分削除
C + k カーソル位置から行末までを削除
C + u カーソル位置から行頭までを削除
C + y 最後に削除した内容を挿入する

画面ロック

操作 内容
C + s 画面表示をロック
C + q 画面表示のロックを解除

コマンド終了、入力のやりなおし

操作 内容
C + c コマンドの強制終了および、入力途中のコマンドの破棄

オプションについて

ロングオプション

--で始まるオプションをロングオプションと呼ぶ。
他のオプションと重複せずに一意に特定できる範囲まで文字列を省略可能。

# --quote-nameを指定したことになる
ls --quote

オプション形式

形式 内容
UNIXオプション ハイフン付きでオプションを指定。ps -aefなど。
BSDオプション ハイフンなしでオプションを指定。ps xfなど。

コピー・ハードリンク・シンボリックリンク

  • コピー
    ファイルを複製する
  • ハードリンク
    ファイルに複数の名前をつけるイメージで、リンク元と同じ内容がリンク先でも表示される。ファイルの実体は1つだがリンク元を消してもリンク先は正常にリンク元のファイル内容を表示できる。
  • シンボリックリンク
    リンク元のファイルを参照できる。ショートカットやエイリアスに近い。リンク元が削除された場合、リンク先は正常にリンク元のファイル内容を表示できない。

基本的なコマンド

findコマンド

ファイル・ディレクトリを検索する。指定した位置から再帰的にディレクトリを掘って探してきてくれる。

-typeでファイル種別を絞り込み可能。

指定 ファイル種別
-type f 通常ファイル
-type d ディレクト
-type l シンボリックリンク

-name-iname(大文字小文字の区別なし)を指定しファイル名で絞り込み可能。
*などのワイルドカードを使用する場合、シェルにパス名展開させないようにシングルまたは、ダブルクオートでオプション引数を囲むこと。

$ find -name '*.txt'

-aオプションでAND検索が可能。

$ find . -type f -a -name '*.txt' -print

locateコマンド

パスが格納された専用のデータベースから指定ファイル・ディレクトリを検索する。そのため、最初だけ手動でデータベースの更新が必要。
findより高速だが、デフォルトでは1日1回だけデータベースの更新をするため、リアルタイムな検索結果が返ってこない点に注意(すでに存在しないファイルが検索に該当する可能性がある)。

導入後、最初にデータベースを更新

# updatedb

通常locateはファイルパスのどこかに指定したパターンが含まれていたら、一致したものとしてみなす。
ファイル名だけにマッチさせたい場合は、-bオプションを使うこと。

$ locate -b python

複数の検索パターンを指定すると、OR検索になる。

$ locate docs document

AND検索を行う場合は、-Aまたは--allオプションを使う。

$ locate -A bash doc

manコマンド

manで対象コマンドのマニュアルを参照できる。

$ man cat

対象コマンドがわからない場合、何をしたいかというキーワードがわかればそれでマニュアルを検索できる。

$ man -k copy

セクション番号

マニュアル名称の後ろにある括弧付き数字は、マニュアルのセクション番号であり以下の内容を意味する番号が付与されている。

セクション番号 内容
1 コマンド
2 システムコール
3 ライブラリ関数
4 バイスファイル
5 ファイルの書式
6 ゲーム
7 そのほかいろいろ
8 システム管理コマンド
9 カーネルルーチン

複数のセクションに同じ名前のマニュアルが存在する場合もあるので、明示的にセクション番号を指定しない限り、小さい番号のマニュアルが表示される。

$ man 1 crontab

特定のマニュアルがどのセクションに含まれているかは、-waオプションを使う。

$ man -wa crontab

whichコマンド

シェルがどのコマンドを実行するかを知りたい場合に使う。
-aオプションでコマンドが見つかったすべての場所を表示してくれる。

typeコマンド

対象コマンドが本当にコマンドなのかエイリアスなのかを確認できる。

$ type ls

エイリアスを無視してコマンド実行

エイリアスとなっているコマンドの前に\もしくは、commandを付与する。

$ command ls
$ \ls

変数

シェル変数

bashなどの内部で使用される変数。数値や文字列を保存できる。 値にスペースを含む場合はシングルまたはダブルクオートで囲み、=の左右にスペースをつけないこと。

$ var1='test variable'
$ echo $var1

シェル変数には特別な意味をもつものがあり、これらを設定することでさまざまな機能のカスタマイズが行える。

変数名 内容
PS1 プロンプト設定
PATH コマンド検索パス
LANG ロケール

など。

しかし実行ファイルとしてファイルシステム上に存在する外部コマンドでは、このシェル変数を参照できず、シェル自体に内蔵している組込みコマンドのみ参照ができる。
外部コマンドはいわばシェルの「外側」で実行されるため、このような挙動になっている。

そのため、LANGなどの外部コマンドで常に設定を反映したい変数は環境変数というしくみを利用している。

環境変数

外部コマンドから参照できる変数。
実は特に明示的に指定しなくても変数LANGなどは、自動的に環境変数として設定されている。

以下で環境変数を表示できる。

$ printenv

自分で設定するにはexportコマンドを使用する。

$ export LESS='--no-init'

権限

所属グループ確認

groupsコマンドで自分が所属しているグループを確認できる。

$ groups

パーミッションの記号と意味

ファイルの場合

記号 意味
r 読み取り
w 書き込み
x 実行

ディレクトリの場合

記号 意味
r 読み取り(ディレクトリに含まれるファイル一覧の取得)
w 書き込み(ディレクトリの下にあるファイル・ディレクトリの作成・削除)
x 実行(ディレクトリをカレントディレクトリにする)

chmodコマンド

ファイルやディレクトリのパーミッションを設定するには、ファイルモードを変更するchmodコマンドを使う。
シンボルモード数値モードによる指定方法がある。

シンボルモード

以下のように実行する。

$ chmod u+w test.txt

chmodのあとは誰にどのような権限を追加・削除するのかを記載する。

  • ユーザー指定
記号 意味
u オーナー
g グループ
o そのほかのユーザ
a ugoすべて
記号 意味
+ 権限を追加
- 権限を禁止
= 指定した権限と等しく

最後は読み取り、書き込み、実行のrwxを記述する。

たとえば以下ではグループとそのほかのユーザーの権限を読み取りのみにしている。

$ chmod go=r text.txt

シンボルモードは指定したパーミッション以外変化しないため、一部だけを変更したいときに便利。(相対的な指定方法)

数値モード

シンボルモードとは対象的に絶対的な指定方法であり、元のパーミッションにかかわらず新しいパーミッションへ変更するときに便利。

rwxの各パーミッションに数値をあて、その合計値で最終的なパーミッションを決定する。

意味 数値
r 4
w 2
x 1

rwxすべて付与する場合は7、読み取りと実行権限だけを付与する場合は4となる。
chmodにオーナー・グループ・その他ユーザーそれぞれに対する数値を合わせて指定する。

オーナーは読み書き、そのほかのユーザーは読み取りと実行を許可する場合は以下のようになる。

$ chmod 755 text.txt

sudoコマンド

suコマンドではスーパーユーザーのパスワードを求められたが、sudoではsudoを実行したユーザーのパスワードを入力する。

suは一度実行すればexitで終了するまでスーパーユーザーのままだが、sudoは一度コマンドを実行すれば元の一般ユーザーに戻る。

sudoコマンドはどのユーザーにどのコマンドの実行権限を与えるのかを設定でき、それは/etc/sudoersファイルで管理されている。
sudoersファイルは<ユーザ><マシン名>=(<権限>)<コマンド>の形式で記述し、<ユーザ>の部分は直接ユーザー名を書くか、%<グループ名>の形でグループを指定する。

以下はwheelというグループ(システム管理を行うグループ)に属するユーザーは、すべてのマシンですべてのコマンドを実行できるという意味。

%wheel ALL=(ALL) ALL

安易にALL設定をするとスーパーユーザーのパスワードを知らなくても管理者権限を行使できるので注意する。

また、このsudoersファイルは書き方を誤ると、sudoが動作せずにどのユーザーもsudoが使えなくなってしまうので、直接エディタで編集せずにvisudoコマンドを使用すること
実行すると特にエディタを設定していない場合はVimが立ち上がり、編集内容に文法エラーがある場合は終了しようとしたときに警告してくれます。
このようにvisudoコマンドを経由してsudoersファイルを編集すると文法チェックを行ってくれるので、単にエディタで編集するより安全に作業ができる。

プロセスとジョブ

プロセスとは

メモリ上で実行状態にあるプログラムのこと。
Linuxカーネルはディスクから実行ファイルを読み出してメモリに格納、その内容に従ってCPUがプログラムを実行する。
プロセスとは、Linuxカーネルから見た処理の単位。
システム全体で一意なプロセスIDという識別番号が割り振られている。

デーモン(daemon)とは

ターミナルに接続していないプロセスのこと。

ジョブとは

シェルから見た処理の単位。
シェルのコマンドラインに入力している1行が1ジョブにあたる。
シェル毎にジョブ番号をもっており、複数のターミナルで2つ以上のシェルを同時に使っている場合、ジョブ番号は重複する。

ジョブの状態遷移

コマンドを実行中にほかのコマンドを実行したい場合、C + zで現在実行しているジョブを一時停止できる。

ジョブの一覧を表示するにはjobsコマンドを実行する。

$ jobs

ユーザーが対話的に操作できるジョブの状態をフォアグラウンドと呼ぶ。
停止状態のジョブをフォアグラウンドにするには、fgコマンドを使用する。
%でジョブ番号を指定しないと、カレントジョブ(jobsコマンドで+が表示されているジョブ)がフォアグラウンドとなる。

$ fg %2

ユーザーが対話的に操作できるジョブの状態をバックグラウンドと呼ぶ。
ジョブを停止すると動作が止まってしまうため、バックグラウンドで動作を継続したい場合は、bgコマンドを使用する。
%でのジョブ番号の指定はfgコマンドと同じ。

$ bg %2

なお、始めからジョブをバックグラウンドで実行したい場合は、コマンドラインの末尾に&を追加する。

$ cp file1 file2 &

ジョブとプロセスの終了

コマンドがフォアグラウンドの場合は、Ctrl + cでよい。

バッググラウンドジョブは、killコマンドでジョブ番号を指定する。

$ kill %2

プロセスを終了する場合も、killコマンドでプロセスIDを指定する。

$ kill <プロセスID>

プロセスを終了できるのはそのプロセスの実行ユーザーのみだが、スーパーユーザーはすべてのプロセスを終了できる。

killコマンド

killコマンドはシグナルを送信しており、プロセスはそのシグナルによってさまざまな振る舞いをする。
シグナル名を省略すると、TERMというシグナルを送信します。このTERMシグナルはTerminateを表している。

シグナルの種類は-<シグナル名>で指定でき、シグナル番号という数値でも指定できる。
以下は同じTERMシグナルを送信している。

$ kill 4695
$ kill -TERM 4695
$ kill -15 4695

シグナル一覧は-lオプションで確認できる。

例外的なシグナルとして、TERMシグナルを受け付けなくなった場合に強制終了する手段として、SIGKILLシグナルが存在する。
これはプロセスへシグナルを送らずLinuxカーネルに送り、プロセスの強制終了を促す。
プログラムの種類によって終了時に現在の状態を保存したりなどの必要処理があるが、このシグナルはそれらを行わせずに強制終了してしまうため注意が必要。

標準入出力

リダイレクト

コマンドの実行と結果の経路である標準入出力を変更する機能のことをリダイレクトと呼ぶ。
Linuxではコマンド起動時に"標準的な"入出力チャネルが開かれ、標準入力はキーボード、標準出力と標準エラー出力は端末ディスプレイに通常は割り当てられている。

標準入力をキーボードからファイルに変更(リダイレクト)したい場合は、<を使用する。

$ cat < /work/file.txt

標準出力を端末ディスプレイからファイルに変更したい場合は、>を使用する。

$ cat /work/file.txt > log.txt

上記では標準エラー出力は端末ディスプレイに表示される。
標準エラー出力のリダイレクトは以下のように2>を使用する。

$ cat /work/file.txt 2> log.txt

標準出力と標準エラー出力をまとめる場合は以下。
2>標準エラー出力&1(標準出力)と同じものへリダイレクトするという意味になる。

$ cat /work/file.txt 2>&1 log.txt

>を使ってのリダイレクトでは、同名ファイルを指定した場合、元の記載内容が上書きされてしまう。
上書きせずファイル末尾に追記する形でのリダイレクトは、>>を使用する。

$ cat /work/file.txt >> log.txt
$ cat /work/file.txt 2>> log.txt

/dev/nullというスペシャルファイルと呼ばれる特別なファイルを標準入出力で使うと以下のような結果になる。

  • 入力先として指定しても何も内容を返さない
  • 出力先として指定しても、書き込んだデータがどこにも保存されずになくなる

よくある使い方としては、エラーメッセージだけ確認したいときに標準出力を空にする方法。

$ ls ./work > /dev/null

パイプライン

標準出力を次のコマンドの標準入力として扱うためのしくみ。

$ ls | less

標準エラー出力もまとめてパイプラインに送りたい場合は、2>&1の記法を使う。

$ ls -l /xxxxx 2>&1 | less

フィルタ

標準入力を入力として受け取り標準出力に出力するコマンドのこと。

# コマンド履歴の先頭10行を出力する
$ history | head

代表的なフィルタコマンド

コマンド 役割
cat 入力をそのまま出力する
head 先頭の部分を表示する
tail 末尾の部分を表示する
grep 指定した検索パターンに一致する行だけを表示する
sort 順番に並べ替える
uniq 重複した行を取り除く
tac 逆順に出力する
wc 行数やバイト数を出力する

シェルスクリプトについて

実行方法

シェルスクリプトの先頭に書いてある#から始まる行はshebang(シバン)という。
スクリプトの実行依頼を受けたLinuxカーネルがこの行を解釈して、#以降に書いているシェル(/bin/bashなど)をつかってスクリプトファイルに書かれた処理を実行する。

実際には下のようなコマンドラインを、シバンを解釈してから実行するイメージ。

$ ./foo.sh
↓
$ /bin/bash ./foo.sh

シェルスクリプトsourceコマンドを使ってカレントシェルで実行することも可能。
ただしカレントシェルで設定したエイリアスの影響を受けたり、その逆でシェルスクリプト内に記述されたエイリアスが実行後もカレントシェルの環境に残ったりするので注意が必要。

なお直接ファイル名で実行したり、シェルの引数として実行した場合に起動するシェルは、カレントシェルから読み出された子プロセスであるサブシェルなので、環境変数は引き継がれるがエイリアスなどの設定は引き継がれない。

シェルスクリプト実行時になぜ./から書き始めるのか

それはシェルがコマンドを実行する際に、コマンドの実体ファイルがあるディレクトサーチパスに、カレントディレクトリが登録されておらず、コマンドが見つからないためである。
サーチパスにないファイルを実行するには、相対パス絶対パスでファイルを指定する必要がある。

サーチパスは環境変数PATHで確認できる。

$ echo $PATH

長いコマンドを1行で書く

シェルスクリプトでは複数のコマンドを;で区切ることにより1行で書ける。

1つのコマンドが長い場合は行末に\を書くことにより途中で改行できる。これはコマンドラインでも使用可能。

$ echo \
>

この>からコマンドの続きを書ける。これをセカンダリプロンプトと呼ぶ。

またパイプライン|の直後にも改行でき、長くなりがちなパイプラインを見やすくできる。

変数の書き方

変数の書き方の注意点

  • =の後に空白を挟まない
  • 変数名はアルファベットと数値と_(アンダースコア)だけ。先頭に数値は使えない。
  • 変数展開した後に文字列連結したい場合などは、${foo}のように波括弧で区切る。
foo=bar
echo ${foo}_baz
  • シングルクオート内の変数は展開できない。

あんまり使わないが、ダブルクオートでも\を使って$そのものを出力できる。

echo "\$foo"

コマンド置換

コマンド置換について

$(foo)のようにコマンドを括弧で囲むことでコマンド実行結果を受け取れる。
バッククオートで囲む記法もあるが、開始と終了位置がわかりづらいのと、中でさらにコマンド置換する場合はバッククオートをエスケープする必要があって不便。

コマンドライン引数

シェルスクリプトではコマンドライン引数を、$1,$2...で受け取れる。
$0にはシェルスクリプト名が入る。
なお、コマンドライン引数に*ワイルドカードを使用すると、カレントディレクトリのファイルが展開されてシェルスクリプトに渡る。

引数の個数は、$#で参照可能。