OREMATOPEE

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

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

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

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にはシェルスクリプト名が入る。
なお、コマンドライン引数に*ワイルドカードを使用すると、カレントディレクトリのファイルが展開されてシェルスクリプトに渡る。

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

gistリポジトリに一発で移動したい

Linuxコマンドの勉強の一貫としてシェルコマンドを考えてみた。
もっとスマートなやり方などがあれば教えていただきたいです🙇🏻‍♂️


gistをメモとして利用するにあたってリポジトリghq経由で落としてきたが、編集のたびにいちいちディレクトリを移動するのが面倒...

gistのリポジトリ名にはgithubとは違いハッシュ値が振られてしまうので、ぱっと見でどのgistなのかわかりにくい...

ということで、"一発で"は誇張しすぎたかもしれないが、簡単にghq配下のgistリポジトリへ移動するシェルコマンドを考えた。

先に結論

このコマンドを実行することで、ghq配下のgistリポジトリにfuzzy検索を用いて移動することができる。

$ cd "$(ghq list -p | grep $(gh gist list | awk '{print $1, $2}' | fzf | awk '{print $1}'))"

やっていることとしては単純で、ghq list -pで出力されるghq配下のローカルリポジトリのフルパスを、あいまい検索で特定されたgistリポジトリハッシュ値grepしている。
gistリポジトリへ移動するために大枠をcdで囲んでおり、個々のコマンドの出力には無駄な情報が含まれているので、awkコマンドで必要な情報だけに絞っている。

必要なもの

実際に使用するには以下ツールが必要。

ちなみにMaczsh上で動作しているが、Linuxでもおそらく動くはず。

注意点

どのgistリポジトリにするのかという部分は、gistの説明文をfzfで曖昧に検索しているため、説明文が設定されていないと移動したい対象をハッシュ値で特定することになる。
また、gh gist listはリモートリポジトリ上の全てのgistが出力される。
そのためハッシュを特定したとしてもローカルリポジトリディレクトリ)が存在しなければ移動できない。

本当はローカルに存在するgistだけを表示したいが、更にコマンドが複雑化しそうだったのと、必要であればローカルにghq経由でダウンロードできるので、一旦これでよしとしたい🥲

導入方法

コマンドを.zshrcなどの設定ファイルに記載する。
注意点にも書いたがローカルに存在しないgistリポジトリを選んでしまった場合のためにifで回避し、エイリアスでfunctionを呼び出している。

# ghq配下のリポジトリに移動する
function cd_ghq_on_fzf {
  local dir="$(ghq list -p | grep $(gh gist list | awk '{print $1, $2}' | fzf | awk '{print $1}'))"
  if [ -n "$dir" ]; then
    cd "$dir"
  else
    echo "no such ghq directory"
  fi
}

alias sd='cd_ghq_on_fzf'

あとはターミナル上でsdと叩けばおk

github上のリポジトリも検索対象にする

以下のコマンドを使用する。
上記のコマンドの結果にghq list -pでローカルにあるgistではなくgithub側のリポジトリの一覧を足しています。

$ cd "$(ghq list -p | grep $((gh gist list | awk '{print $1, $2}' ; ghq list -p | grep -v gist) | fzf | awk '{print $1}'))"

同じように設定ファイルに記載する。

# ghq配下のリポジトリに移動する
function cd_ghq_on_fzf {
  local dir="$(ghq list -p | grep $((gh gist list | awk '{print $1, $2}' ; ghq list -p | grep -v gist) | fzf | awk '{print $1}'))"
  if [ -n "$dir" ]; then
    cd "$dir"
  else
    echo "no such ghq directory"
  fi
}

alias sd='cd_ghq_on_fzf'

実際につかってみよう

まずghqで管理しているリポジトリの一覧を確認してみます。

ふむふむ、github.comとgist.github.comというサービス名でディレクトリが別れていて、その下にユーザー名のディレクトリがありますね。
更にその下に各リポジトリを配置していますが、gistリポジトリ53be64ff4a82e9770c744146724a07cbというハッシュ値ディレクトリ(リポジトリ)名になってますね。
これでは、このリポジトリが指しているgistがどれなのかわからない...

そこで今回作ったsdを実行してみましょう。
最初はdotfilesというディレクトリにいます、覚えておいてください。

実行するとfzfが立ち上がり、github.comとgist.github.comの全てのリポジトリが表示されました!
でもまだ3件しかないのでいいけど、もし1000件あったらどうしよう...

安心してください!fzfはあいまい検索を提供してくれるのでぱっと思い浮かんだワードをターミナルに入力すれば、リポジトリを絞ってくれます!

あとはEnterを押すだけ。
dotfilesから対象のレポジトリに移動していることがわかりますね!
パット見ではどのgistなのかわからなかったこのディレクトリに、gistの説明文からハッシュ値を特定して移動してくることができました!

最後に

pecoを使ってghq配下のリポジトリへ移動する記事をいくつか見かけたが、fzfでかつgistを対象にしたものはなかったので作ってみた。
これでターミナル上でgistをメモツールとして使えるようになったので、積極的に活用したい。

作ってる最中に、gistをgithubリポジトリとして集約する方法を見つけて「あれ、こっちのやり方でやればgistリポジトリハッシュ値も回避できて管理が楽じゃね」と思ったのは内緒の話。 Github Gist を1個のレポジトリでまとめて管理する ( git submodule を利用 )

あと都度更新するメモとしてはzennのスクラップでも可能なため、cli対応されたら100%乗り換えると思う🤣

Debianにlocateコマンドをインストール

新しいLinuxの教科書を読んでいて、locateコマンドの説明がでてきたが、自分の使っているVirtual Box上のDebianでは使えなかったので導入方法を調べてみた。

結論

apt-getをrootユーザーで実行する。

$ su #rootユーザに変更
$ apt-get install mlocate

補足

locateコマンドはmlocateパッケージに含まれているため、mlocateパッケージを導入する必要がある。

ソフトウェアをひとまとめにしたパッケージを管理するシステムは、Linuxディストリビューション(正しくは派生形態)によって異なる。CentOSではyumというパッケージ管理システムが使われているらしいが、Debianではaptというものを使用する。
なお、シェルのコマンド上ではaptではなく、apt-getでパッケージをインストールしたり検索したりするので注意。

参考文献

yumコマンドとAPT系コマンドの違い