rmdirの使い方

rmdirは対象のディレクトリが空の時だけ削除できる。そのため、中身は問わずディレクトリ消したいときは、rm -frを使用していた。Linuxを使い始めてから長いことそうしていて、rmdirを使うことはほとんどなかった。

確実に空のディレクトリだけを削除できる

この1年くらいコードの整理をする機会が増えたため、確実に空のディレクトリだけ削除したいということが増えた。最初はfindなどで中身が空か確認してから削除するようにしていた。しかしfind -> rm -frというのも手間なので、rmdirを使用するようになった。

rm -rでいいのでは?とも思ったが、rm -frとついつい誤って打ってしまうことがありコマンド自体を使い分けることにした。

rmdirの対象が空ではない場合、

  1. rmdirで失敗する
  2. 中身を確認する
  3. 残っているファイルを処理する(だいたいrmする)
  4. 改めてrmdirを実行

というフローを踏むようになった。たまに本当に消してはいけない何かがあることがあり、誤って消してしまうような致命的なミスは避けられるようになった。

ちょっと便利なオプション

rmdirにも当然オプションがあり、とりあえず空のディレクトリをガサッと消すぞ!みたいなときに使える。

--ignore-fail-on-non-empty: 対象が空のディレクトリではなく、削除できなかった時にfailしない。forやxargsと組み合わせて削除していく際に、このオプションを付与したりする。

-p: 対象のディレクトリの親も削除する。mkdir -pの逆の動作をする。manには以下のように書いてある。

'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a'

とりあえず雑に以下のような使い方をする。

$ find target_dir -type d | xargs rmdir --ignore-fail-on-non-empty -p

あまり嬉しくないのは、どのディレクトリを消したか教えてくれるオプションがないこと。なので、その辺を確認したいときはあらかじめfindで一覧を残しておく。

あとrmdir.を渡すとrmdir: failed to remove '.': Invalid argumentとして怒られる。

おしまい

iPhone/Macで使えるMarkdownエディタを検討する

ブログの執筆を前提にしたエディタを評価してみた。

あるといい機能

  • ファイル名を指定せずに書き始めることができる
    • 先頭行がタイトルとなる
    • メモ一覧でもタイトルが表示される
  • iPhone/Macの同期がスムーズにできる
    • Dropboxなどオンラインストレージがいらない
    • iCloudでも良い
    • 明示的に「同期」アクションをしなくてもよい
  • iPhoneでは、Markdown用のキーボードがあるとよい
    • 使用できるマークアップがカスタマイズできるとよい
    • 自分の使用頻度の高いものが使いやすい位置にあるとよい
  • Macでは、独立した編集/プレビューのモードがある
    • Split表示嫌いなんで
    • 編集モードであってもヘッダーやリストなどはスタイルが変わるとうれしい
  • iPhoneではプレビューが容易だとうれしい
  • タグ付けができるとよい

ちなみに、この辺り全部満たしてる感じのやつはなかった。

bear

最終的にこれを使うことにした。iPhone/Macで同期を行う機能は有料で年間1500円。やすっ。

Bear - Notes for iPhone, iPad and Mac

いいところ

  • 編集中でもスタイルが変わるのでわかりやすい
  • ファイル名を指定せずに書き始めることができる
  • データの同期がすごいスムーズ
    • 別の端末でアプリを開いた際の待ち時間を感じたことがない

おしいところ

  • 逆にプレビューモードがない
    • iPhoneでは、HTMLに出力する機能がプレビューの代替として使えるがやや手間
  • リストが完全にリストになってしまうので、ちょっとインデントの編集で手間取ることがある
    • MacだとTabで変更可能
  • 自分が頻繁に使用するマークアップへのアクセスが良くない(iPhone)
  • タグ付けできるが、文章内に#tagという感じで書かないといけない
  • TODOというドキュメントの種類があるが、文章中に- [ ]を含んでいるだけでは該当しない
    • タスク数などが簡単に確認できるわけでもない

MWeb

ちょっと同期周りがめんどくさい気がした。ちゃんと試してない。

MWeb - Pro Markdown writing, note taking and static blog generator App - MWeb

いいところ

おしいところ

  • ファイル名を指定しないと書き始めることができない

Inkdrop

作者の人、すごいなーと思って試してみた。

Note-taking App with Robust Markdown Editor - Inkdrop

いいところ

  • タグ付けとステータスの管理ができる
    • Completeとかにするとデフォルトのメモ一覧から表示されない
  • TODOが含まれているとカウントされる
  • ファイル名指定せずに書き始めることができる
  • メモ一覧の情報量と視認性がとてもいい

おしいところ

  • Macではなんか全体的にもっさりする
  • iPhoneでメモ一覧からスライドして削除できないのがちょっと…

Mac版の方はすごく良かった。しばらくしたらもっと使いやすくなっているかもしれないので、たまに試してみたい。

Boostnote

会社では使ってるけど、iPhoneアプリが非公開になっているらしく、候補から外れた。

1つ気になるのが、編集モードからフォーカスが外れるとプレービューモードに切り替わってしまうのが好きではなくて、Snippetをもっぱら使用している。

Issueも上がっているけど、しばらく進展してないっぽい。

Disable preview? · Issue #1767 · BoostIO/Boostnote

コンテナのzoneinfoとGoのLocationについて

以前書いたAlpine Linuxで時刻をJSTに設定する(Dockerfile)で、コンテナ内の時刻をJSTとして扱えるようにした。

しかし、以下が気になった。

  • コンテナのイメージ作成においてタイムゾーンを変更するような記述を見かけたことがなかった
  • コンテナのタイムゾーンを変更しなくても、アプリ内でJSTとして扱うことはできるので、不要なセットアップではないか

ということでアプリケーション(Go)の方で対処できないか、検証してみた。

結論

結果、以下のような挙動となった。

  • time.LoadLocationはコンテナの中に該当するzoneinfoがないと失敗する
    • Alpine LinuxのデフォルトではAsia/Tokyoなどは存在しないため確実に失敗する
    • /etc/localtime/usr/share/zoneinfo/Asia/Tokyoなどで上書きされていても、 /usr/share/zoneinfo/Asia/Tokyo自体がないとやはり失敗する
  • time.FixedLocationは直接time.Locationを生成するためzoneinfoの有無の影響を受けない

軽量コンテナを使用する際にはUTCを前提とするか、time.FixedLocationを使用した方が良さそう

もしJSTをデフォルトとしたコンテナを作成したかったら、以下のようなDockerfileにすべき。

FROM alpine:latest

# インストールしたtzdataは削除しない
RUN apk --no-cache add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

以下、検証した内容。

Goのプログラム

以下のように4回時刻を出力するようにした。

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    fmt.Println(t)

    jst, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(t.In(jst))
    }

    jst = time.FixedZone("JST", 9*60*60)
    fmt.Println(t.In(jst))

    time.Local = jst
    fmt.Println(time.Now())

}

表示は以下のようになると予想していた。

  • time.Nowではコンテナのタイムゾーンに基づいた時刻表示
  • t.In(jst)ではJSTの時刻表示がされる
    • time.LoadLocationでerrが起きるとは思ってなかった
  • 最後のtime.NowではJSTの時刻が表示される

バイナリは以下のようにしてtime-testとして出力しておく。

GOOS=linux GOARCH=amd64 go build -o time-test

検証1: zoneinfoを特に設定しない

まずはコンテナを作っとく。Dockerfileは以下の通り。

FROM alpine:latest
 
COPY ["time-test", "/"]
CMD [ "/time-test" ]

docker buildして、jst-time-test1というタグにしておく。

docker build -t jst-time-test1 -f Dockerfile1 .

docker runで実行する。

$ docker run --rm jst-time-test1
2018-08-30 00:36:56.4097601 +0000 UTC m=+0.000328601
open /usr/local/Cellar/go/1.9.2/libexec/lib/time/zoneinfo.zip: no such file or directory
2018-08-30 09:36:56.4097601 +0900 JST
2018-08-30 09:36:56.4098805 +0900 JST m=+0.000448301

最初はUTCが出力され、最後はJSTが出力された。しかしtime.LoadLocationではエラーが出力された。

open /usr/local/Cellar/go/1.9.2/libexec/lib/time/zoneinfo.zip: no such file or directoryと表示されるのは、time.LoadLocationの説明にあるとおりで、

The time zone database needed by LoadLocation may not be present on all systems, especially non-Unix systems. LoadLocation looks in the directory or uncompressed zip file named by the ZONEINFO environment variable, if any, then looks in known installation locations on Unix systems, and finally looks in $GOROOT/lib/time/zoneinfo.zip.

最終的に$GOROOT/lib/time/zoneinfo.zipを参照するということなので、システムのzoneinfoもないし、$GOROOT/lib/time/zoneinfo.zipもない(GoのSDKは存在しない)ためエラーとなった様子。

検証2: JSTをローカルタイムに設定しzoneinfoを削除した状態

今度はAlpine Linuxで時刻をJSTに設定する(Dockerfile)で設定した環境で、dateコマンドだとJSTが表示される状態になっている。Dockerfileは以下の通り。

FROM alpine:latest

RUN apk --no-cache add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata

COPY ["time-test", "/"]
CMD [ "/time-test" ]

docker buildする。

docker build -t jst-time-test2 -f Dockerfile2 .

実行する。

$ docker run --rm jst-time-test2
2018-08-30 09:37:09.7907743 +0900 JST m=+0.000254901
open /usr/local/Cellar/go/1.9.2/libexec/lib/time/zoneinfo.zip: no such file or directory
2018-08-30 09:37:09.7907743 +0900 JST
2018-08-30 09:37:09.7908918 +0900 JST m=+0.000372901

最初はJSTが出力され、最後はJSTが出力された。しかしtime.LoadLocationではエラーが出力された。

このコンテナでは/etc/localtimeは上書きされているが、zoneinfo(/usr/share/zoneinfo/Asia/Tokyo)がないから、やはりエラーとなった様子。

検証3: JSTをローカルタイムに設定しzoneinfoを残した状態

検証2の結果から、Dockerfileを以下のように修正した。

FROM alpine:latest

# 前回実施していた apk del tzdata を消去
RUN apk --no-cache add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

COPY ["time-test", "/"]
CMD [ "/time-test" ]

docker buildする。

docker build -t jst-time-test3 -f Dockerfile3 .

実行してみる。

$ docker run --rm jst-time-test3
2018-08-30 09:37:15.3019175 +0900 JST m=+0.000325101
2018-08-30 09:37:15.3019175 +0900 JST
2018-08-30 09:37:15.3019175 +0900 JST
2018-08-30 09:37:15.3020498 +0900 JST m=+0.000457301

今度はうまくいった!やはり参照するzoneinfoがなかったからエラーになっていたのだろう。

time.FixedLocationを使用した場合にはすべて問題なく実行できたのも確認できた。

終わりに

今回検証した辺りのzoneinfo(tzdata)について詳しくなかったので、勉強になった。

とりあえず今後は

package main

import "time"

func init() {
    time.Local = time.FixedZone("JST", 9*60*60)
}

としておくようにしよう。

Alpine Linuxで時刻をJSTに設定する(Dockerfile)

公式の手順があるのでそれを参考にした。
Setting the timezone - Alpine Linux

Dockerfileには以下のように記述した。

FROM alpine:latest

RUN apk --no-cache add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata

公式の手順に従って/etc/timezoneを設定するとGMTになってしまったので、それは設定しないようにした。

作ったイメージでdateを実行して確認する。

$ docker build -t jst-alpine .
(snip)

$ docker run --rm jst-alpine date
Tue Aug 28 23:07:21 JST 2018

$ docker run --rm alpine date
Tue Aug 28 14:07:31 UTC 2018

上手くいった。JSTと表示されている。

というか、そもそもアプリケーション側でlocaltimeがJSTだってのを前提にするのが良くない気がする(今までそうしてたけど)。今度検証してみよう。

「入門 Kubernetes」を読んだ

入門 Kubernetes

入門 Kubernetes

k8sについてちゃんと勉強したかったので読んだ。

k8sをやるにあたって知っておくべき最低限のことが書いてあって、それを実際に動かしながら確認していくスタイル。著者の執筆時点から変わっていることについて訳注が入っているのだけれど、それもさらに変わっているところもあった。

それだけ進化が速いってことかな。

コンテナ化されたアプリケーションを作る方は楽になりそうだけど、k8s自体の運用は大変そうだなぁとも思った。まだプロダクションでやってないんで、わからんけど。

Docker/Kubernetes辺りはキャッチアップできてなかったところなので、他にも新しい出るみたいだし、そっちも読んでみたい。

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

Gitのユーザ情報を細かく管理する

$HOME/.gitconfigをgitで管理して、エイリアスなどの設定を会社と自宅で共有したいが、ユーザ名とメールアドレスは場所によって異なるので、それらを含んだまま管理ができていなかった。

で、cloneしてきた端末ごとにユーザ情報を設定すると、git上での差分となってしまって、何かエイリアスなどを変更した際に、ユーザ情報を含まないようにコミットする必要がある。

ユーザ情報を別の設定ファイルとして切り出し、includeを使用することで上手く使い分けができる。

ユーザ情報を別ファイルに設定する

例えば$HOME/.gitignore.localというファイルにユーザ情報を設定する。

コマンドから追加するときは以下のように実行する。

$ git config --file ~/.gitconfig.local --add user.name 'Kentaro Kawano'
$ git config --file ~/.gitconfig.local --add user.email emailaddress@example.com

--file によって設定を保存するファイル名を指定できる。

内容は以下のようになる。

# $HOME/.gitconfig.local
[user]
    name = Kentaro Kawano
    email = emailaddress@example.com

別のファイルになっているだけで、Gitのための設定ファイルなので、別にユーザ情報だけではなく、端末に依存する設定を含んでもよい。

include を使用する

gitconfigで、別の設定ファイルを読み込むためにはincludeを使用する。

$HOME/.gitconfigの中でincludeを使用して別の設定ファイル($HOME/.gitconfig.local)を読み込むようにする。

# $HOME/.gitconfig
[include]
    path = .gitconfig.local

.gitconfig.local の作成忘れがたまにある

includeの対象となるファイルが存在しなくても、gitの操作を行うにあたって警告が出ることはない。なので、.gitconfig.localを作成せずに(ユーザ情報が設定されないままに)コミットをしてしまうことがある。特に端末のセットアップ直後とか。

その際には、ユーザ情報を勝手に設定したよという注意が出てくる。

Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

なので、.gitconfig.localを上述の手順で作成した上で、git commit --amend --reset-authorを実行するとよい。

「Goならわかるシステムプログラミング」を読んだ

Goならわかるシステムプログラミング

Goならわかるシステムプログラミング

ウェブの連載自体は知ってたものの結局見てなかったので、時間のできた今のうちに読んでおこうと購入した。

扱っている内容から難しいのかと勝手に想像していたが、Goをやっている人なら苦労せずに読める内容だと思う。自分は、一部は実際に実装したことがある内容だったので、スムーズに読むことができた。

ウェブアプリケーションを作っていると普段はそこまで知らなくてもまぁなんとかなるんだけど、パフォーマンスチューニングしないといけないような状況になった時には、やはり低レイヤーの知識は必要になるので、その辺りの足がかりとして良かった。

ついでに、過去にシステムプログラミングの本を読みかけて断念したことがあったので、読み直してみよう。