AWS CloudFormation テンプレートの検証をローカルで行えるようにした(AWSCLI使用)

Win7 x86 で AWSCLI を用いて CloudFormation テンプレート検証をローカルで行えるようにした。

Thanks! awscli(Python版)でCloudFormationテンプレートをValidateする | DevelopersIO

1 結論

cat template.yaml | xargs -0 aws cloudformation validate-template --template-body --no-verify-ssl

感想:

  • 管理コンソールのデザイナーからいちいちコピペするよりも何十倍も快適
  • コマンド叩いて数秒くらいで結果返ってくる。良き

2 上記コマンドラインを動作させるのに

AWS CLI:

  • pip install awscli

各種 AWS 関連環境変数:

  • 環境変数 - AWS Command Line Interface を参考に
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION など
    • AWS_DEFAULT_REGION は ap-northeast-1(東京リージョン)
    • アクセスキー等は IAM ユーザーからゲットする

Git for Windows:

  • cat, xargs などはこいつに同梱されているのがてっとり早い
  • Cygwin やら MinGW などでも良い

プロキシ:

  • 社内環境などで通信が通らない場合は適宜設定する
  • HTTPS_PROXY と HTTP_PROXY

SSL 証明書(--no-verify-ssl で無視したくない人向け):

  • AWS_CA_BUNDLE 環境変数に crt ファイルのパスを指定する等
  • AWSCLI の証明書サーチアルゴリズムは AWS CLI 1.9.0がリリースされました | DevelopersIO あたりにまとまってる
  • (余談) Firefox に組み込んでる証明書取り出そうとしたけど無理だった
    • %appdata%\Mozilla\Firefox\Profiles\XXXXXXXX.YYYYYYYY\cert9.db
    • db ファイルって何?crt ファイルが欲しかったのよー……

Outlook 2016 で現在のビューをすべてのフォルダーに適用(反映)する

最後の再起動が地味に罠だった。Outlook 2013 の時は普通に反映されてたのに。

手順

  • 1: 適当なフォルダを開く
  • 2: 表示 > ビューの設定 を開く

列ボタンから不要な列を弾く。

グループ化ボタンからグループ化(今日とか三週間前とかでグループ化されるやつ)を解く。「並び替え方法に従って……」のチェックを外せば良い。

その他のビュー設定も必要なら行って。

  • 3: 表示 > ビューの変更 > 現在のビューを新しいビューとして保存

MyView など適当な名前をつけて保存。

これを選択した後、

  • 4: 表示 > ビューの変更 > 現在のビューを他のメールフォルダーに適用する

適用先フォルダーを選ぶダイアログが出るので、すべてのフォルダーにチェックを入れる。

  • 5: 再起動する

再起動でハマった

5: の再起動をしないと反映されないことがある。私は 30 分無駄にした。

Outlook 2016 で F1 キーを押して既読するようにする

デフォでは閲覧した後に勝手に既読になるが、これだと使いづらい。既読は自分のタイミングで行いたい。そこで F1 キーで既読させてみる。

Outlook 側の設定と AutoHotkey が必要。

1: Outlook 側の設定

オプション > 詳細設定 > outlook ウィンドウ > 閲覧ウィンドウ > 閲覧ウィンドウでの表示が終わったら開封済にする を外す。

2: AutoHotkey の設定

Outlook デフォでは Ctrl + Q キーで既読操作になるが、これは押しづらいので、F1 キーで押せるようにする。

#IfWinActive ahk_class rctrl_renwnd32
F1::Send,^q
#IfWinActive

テキストファイルをコンビニで印刷するために markdown to pdf する

自宅で印刷する必要性に駆られたのだが、コンビニだと Office や PDF 形式しか対応してない。Office は持ってないので PDF しかない……というわけで、テキストファイル(Markdown)から PDF をつくる方法を調べた。

結論

以下の二段階にした。

  • markdown to html は Pandoc で行う
  • html to pdf は Prince で行う

前提

  • Windows 10 x64

markdown to html

Pandoc を導入し、以下コマンドラインで可能。

$ pandoc text.md -o text.html

html to pdf

まずは Prince をインストールする。非商用なら無料で使えます。

次に以下 CSS ファイルをつくる。

@font-face {
    font-family: serif;
    src: url("C:/WINDOWS/Fonts/MSMINCHO.TTC");
}

@font-face {
    font-family: sans-serif;
    src: url("C:/WINDOWS/Fonts/MSGOTHIC.TTC");
}

そして Prince を起動し、

  • CSS & JavaScript の CSS 欄に上記 CSS ファイルを読み込む
  • Add files から text.html を読み込む
  • CONVERT ボタンで変換する

これで text.pdf が生成されるはず。

Q&A

Q: CSS ファイルを指定するのはなぜ?

何も指定しないと日本語フォントが効かずに PDF が文字化けするから。

参考: Trouble-Free Travel/TechWiki

Q: コンビニでの印刷はどうやっている?

USB メモリに PDF ファイルを入れて、コンビニのプリント機で。

Q: なぜ Pandoc で PDF 変換まで完結させない?

ちょっと試したけど動かなかったから。

以前は LaTex をインストールしてたから多分動いてたんだけど、ある時「これ容量食うしもういいや」と削除した。だからなのだろう、PDF エンジンが無くて Pandoc の PDF 変換が動かなかった。他のエンジンということで、pdfroff http://gnuwin32.sourceforge.net/packages/groff.htm も試してみたけど、これも動かず(たぶん groff と pdfroff は別物)。

……と困惑してきたので、いったんやめて、別手段探してたら Prince でいけそうだったのでそうした。

Markdown をプレビューできる mkup を Win 7 で試した

Markdown プレビュー手段として mattn さんの mkup が便利そうだったので試したら、ちょっと詰まったのでまとめとく。

前提

  • Windows 7 32bit

導入まで

そのまま go get するとエラーが出る。

$ go get github.com/mattn/mkup
package gopkg.in/fsnotify.v1: unrecognized import path "gopkg.in/fsnotify.v1" (https fetch: Get https://gopkg.in/fsnotify.v1?go-get=1: proxyconnect tcp: EOF)

どうも mkup が import してる "gopkg.in/fsnotify.v1" というパスが古いみたい。

fsnotify.v1 - gopkg.in/fsnotify/fsnotify.v1 とか gopkg.in/fsnotify.v1: unrecognized import path · Issue #1323 · revel/revel を見て、正しくは github.com/fsnotify/fsnotify の模様。

というわけで mkup のソースを直接直す。

$ pushd %gopath%\src\github.com\mattn\mkup\main.go

$ hide main.go
エディタは何でもいいので

import (
    ...
    "github.com/fsnotify/fsnotify"
    ...

↑ こうする

ビルド。コマンドラインってこれで良いんだろうか。

$ go build -o mkup.exe

試す。

$ mkup
Listening at :8000

動いた。試しに README.md を修正してみると、動的に反映された。

最後に mkup.exe を %gopath%\bin に移して(行儀良くないだろうけど)完了。どこからでも mkup 使えるようになった。

Win10 だとエラー出ません

しかし Win10 x64 環境だとエラーが出ず、 go get github.com/mattn/mkup だけで使えるようになった。

……どういうことだろう。この手の unrecognized import path エラー、割と遭遇するのでちゃんと調べたいところ。なんとなく Win7 x86 という古い環境のせいな気はしているが、いまいち情報はヒットせず。

【第二版】ファイアウォールやネットワークにおける上り、下り、イングレス(Ingress)、エグレス(Egress)、インバウンド(Inbound)、アウトバウンド(Outbound)の違い

一年以上前に以下記事を書いたが、

上手く整理できておらず誤りもあったので、改めて整理した。

まとめ

  • ingress = inbound = 受信 = Download = 下り
  • egress = outbound = 送信 = Upload = 上り
  • 英語で言えば ingress/egress は名詞で、inbound/outbound は形容詞
  • ただし一部では意味が逆になるケースがある

したがって上記の言葉が出現した時は、同義語であるどれか(自分にとって直感的に理解しやすいもの)で置き換えれば読みやすい。たとえば inbound、ingress という言葉を見たら「あ、受信方向なんだな」と解釈すればいい。

ただし、どの用語を使うかはサービスや界隈次第で変わってくる&用語として定められていることがある。たとえばファイルウォールの文脈では「ダウンロードフィルタリング」などとは言わないし、AWS のセキュリティグループでも Ingress/Egress などとは言わない(言っても通じるとは思うが)。

以下ソース

VPC のセキュリティグループ - Amazon Virtual Private Cloud

  • 引用
    • Inbound …… 同じセキュリティグループに割り当てられたインスタンスからのインバウンドトラフィックを許可する

    • Outbound …… すべての発信 IPv4 トラフィックを許可する

  • まとめ
    • インバウンド(アウトバウンド)とは「インバウンド(アウトバウンド)トラフィック」のこと
    • インバウンド = 外から中に入ってくる方向 = 受信
    • アウトバウンド = 中から外に出ていく方向 = 送信

ファイアウォール ルールの概要  |  VPC  |  Google Cloud

  • 引用
    • 双方向ではなく、受信(ingress)トラフィックまたは送信(egress)トラフィックに適用されます。

    • 暗黙の下り許可ルール: アクションが allow、宛先が 0.0.0.0/0、優先度が可能な限り低い(65535)egress ルールでは、GCP によってブロックされたトラフィックを除き、すべてのインスタンスが任意の宛先にトラフィックを送信できます。

    • 暗黙の上り拒否ルール: アクションが deny、送信元が 0.0.0.0/0、優先度が可能な限り低い(65535)ingress ルールでは、受信トラフィックをブロックすることによって、すべてのインスタンスが保護されます。

  • まとめ
    • ingress(egress) とは「ingress(egress) トラフィック」のこと
    • ingress は受信
    • egress は送信
    • 下りとは中(インスタンス)から外に出ていく方向
    • 上りとは外から中(インスタンス)に入ってくる方向

ingress とegress の考え方 | NetFlow Analyzer ナレッジベース

  • 引用
    • (1)ingress 装置へ入力するタイミングで帯域情報計測

    • (2)egress 装置から出力するタイミングで帯域情報計測

  • まとめ
    • ingress は外から中(装置)に入ってくる方向
    • egress は中(装置)から外に出ていく方向

[用語集]通信速度で使われる「上り」「下り」はどのような通信を表しますか。 | よくあるご質問(FAQ) | サポート | ソフトバンク

  • 引用
    • 「上り」 パソコンや携帯電話からインターネット上へファイルを転送する際の通信

    • 「下り」 インターネット上からファイルをパソコンや携帯電話に受け取る際の通信。

  • まとめ
    • 上りは送信(Upload)
    • 下りは受信(Download)

英和辞典

Windows で cal コマンドを使ってカレンダー一覧を作る

テキストのカレンダー一覧が欲しかったのでつくることにした。Windows には cal コマンドがないので何とかして入手し、これを何度も呼び出すようなバッチファイルを書くことで実現。

前提

  • Windows7 32bit

1: Gcal の入手

cal コマンドの代替として GnuWin32 の Gcal を使う。

上記はタイトルが「GnuWin」となっているが、ダウンロードするのは Gcal に必要な部分のみである。ちなみにインストーラーなので普通に実行してインストールする。

(参考) インストール後のファイル構成

Gcal に必要なファイルのみが整っている。

$ dir /b "C:\Program Files\GnuWin32\bin"
gcal-daily
gcal-daily.orig
gcal-ddiff
gcal-ddiff.orig
gcal-ddiffdrv
gcal-ddiffdrv.orig
gcal-dst
gcal-dst.orig
gcal-gcalltx
gcal-gcalltx.orig
gcal-gcalltx.pl
gcal-gcalltx.pl.orig
gcal-moon
gcal-moon.orig
gcal-mrms
gcal-mrms.orig
gcal-srss
gcal-srss.orig
gcal-wlocdrv
gcal-wlocdrv.orig
gcal.exe
gcal2txt.exe
libiconv2.dll
libintl3.dll
rxspencer.dll
tcal.exe
txt2gcal.exe

2: Gcal を試しに実行してみる

年と月を指定してみた。

$ "C:\Program Files\GnuWin32\bin\gcal.exe" 01 2019

     January 2019
 Su Mo Tu We Th Fr Sa
        1  2  3  4  5
  6  7  8  9 10 11 12
 13 14 15 16 17 18 19
 20 21 22 23 24 25 26
 27 28 29 30 31

年だけ指定してみた(このオプションは続く一覧作成時に使う)。

$ "C:\Program Files\GnuWin32\bin\gcal.exe" 2004


                                  2004


      January                   February                   March
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
              1  2  3       1  2  3  4  5  6  7          1  2  3  4  5  6
  4  5  6  7  8  9 10       8  9 10 11 12 13 14       7  8  9 10 11 12 13
 11 12 13 14 15 16 17      15 16 17 18 19 20 21      14 15 16 17 18 19 20
 18 19 20 21 22 23 24      22 23 24 25 26 27 28      21 22 23 24 25 26 27
 25 26 27 28 29 30 31      29                        28 29 30 31



       April                      May                       June
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
              1  2  3                         1             1  2  3  4  5
  4  5  6  7  8  9 10       2  3  4  5  6  7  8       6  7  8  9 10 11 12
 11 12 13 14 15 16 17       9 10 11 12 13 14 15      13 14 15 16 17 18 19
 18 19 20 21 22 23 24      16 17 18 19 20 21 22      20 21 22 23 24 25 26
 25 26 27 28 29 30         23 24 25 26 27 28 29      27 28 29 30
                           30 31


        July                     August                  September
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
              1  2  3       1  2  3  4  5  6  7                1  2  3  4
  4  5  6  7  8  9 10       8  9 10 11 12 13 14       5  6  7  8  9 10 11
 11 12 13 14 15 16 17      15 16 17 18 19 20 21      12 13 14 15 16 17 18
 18 19 20 21 22 23 24      22 23 24 25 26 27 28      19 20 21 22 23 24 25
 25 26 27 28 29 30 31      29 30 31                  26 27 28 29 30



      October                   November                  December
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
                 1  2          1  2  3  4  5  6                1  2  3  4
  3  4  5  6  7  8  9       7  8  9 10 11 12 13       5  6  7  8  9 10 11
 10 11 12 13 14 15 16      14 15 16 17 18 19 20      12 13 14 15 16 17 18
 17 18 19 20 21 22 23      21 22 23 24 25 26 27      19 20 21 22 23 24 25
 24 25 26 27 28 29 30      28 29 30                  26 27 28 29 30 31
 31

3: カレンダー一覧をつくる

Gcal が動くことがわかったので、カレンダー一覧をつくる。

まともにコマンドラインオプションを読み解くと骨が折れる ので手抜きすることに。前記にて、年数だけ与えると「その年の月表示を 3 列 4 行で並べて表示する」ことことがわかっているので、この出力を並べることにした。バッチファイルを使う。

output.bat

@echo off
setlocal
set calcmd="C:\Program Files\GnuWin32\bin\gcal.exe"
set calstart=1970
set calend=2100

set name_merged=all_%calstart%_%calend%.txt

echo.> %name_merged%
for /L %%i in (%calstart%,1,%calend%) do (
    echo %%i...
    %calcmd% %%i > %%i.txt
    type %%i.txt >> %name_merged%
)

これを実行すると、以下のようにファイルがつくられる。

$ dir /b
...
2019/06/27  19:12             2,428 1970.txt
2019/06/27  19:12             2,428 1971.txt
...
2019/06/27  19:12             2,428 2100.txt
2019/06/27  19:12           318,070 all_1970_2100.txt
2019/06/27  19:12               299 output.bat

all_1970_2100.txt は 1970.txt、1971.txt、……の内容を一ファイルでずらりと並んだもの。

とりあえず最低限カレンダー一覧をゲットできたので良しとする。

(余談1) cal コマンドで Gcal を呼び出せるようにする

以下のバッチファイルを PATH の通ったフォルダに置く。

cal.bat

@echo off
"C:\Program Files\GnuWin32\bin\gcal.exe" %*

(余談2) カレンダーのスペル

  • calendar
  • calender

突然ですが正しいのはどっちでしょう。

……前者、calendar が正解です。dar なんですね(最近まで普通に間違ってた)。

(余談3) カレンダーへのアクセス

今回つくったカレンダーにどうやってアクセスするか。私は以下のようにした。

  • github\stakiran\text\calendar に 1970.txt, 1971, ... 2100.txt を配置
  • 愛用している fenrir を再スキャン

これで fenrir から 2019 と打つだけで 2019 年のカレンダーにアクセスできる。fenrir は AutoHotkey にて Alt + 1 で起動できるようにしているので、実際は Alt + 1 → 2019 の二段階でアクセス可。かなり素早くカレンダーを開けるようになった。

Q: 「普通に余談 1 を整備してからコマンドプロンプトで cal 2019 と叩けばいいのでは?」

ダメ。コマンドプロンプト上の表示は見づらい。愛用のテキストエディタで見れることが重要なのである。

参考

メモリ 4 GB の Windows で頑張るマン

大人の事情でメモリ 4GB の Windows で頑張らないといけない人向けに、「こうすれば Windows が軽くなるよ」「こんな工夫をしたら上手いことやりくりできるよ」といったテクニックを雑多にまとめる。

前提は Windows7 32bit だが、他の Windows でも一部は通用すると思う。

コントロールパネル

ひととおり目を通して、要らないものは無効にする。

特に以下。

  • Windows Update(頻度を落とすなど)
  • コンピューターの簡単操作センター
  • システム > システムの詳細設定 > 詳細設定 > パフォーマンスの設定 > 視覚効果 > パフォーマンスを優先する
  • 電源オプション(省エネしない方向に倒す)
  • プログラムと機能(一通り目を通して要らないプログラムはアンインストール)

サービス

services.msc の「開始」「自動」になっているサービスに注目、一通り見て要らないものは停止/無効にする。

いくつか例を挙げる。

  • IP Helper(ipv6 使ってないから要らない)
  • Windows Defender(既に他のセキュリティソフトがあるので要らない)
  • Windows Search(たまに最適化処理が走って重くて邪魔だし検索はあまり使ってないので要らない)
  • Tabret PC Input Service(タブレット使わないので要らない)

私もあまり詳しくない。詳細はサービス名をググって調べて勉強する。

タスクスケジューラ

これも一通り目を通して、要らないものは無効に。

  • タスクスケジューラライブラリ(AdobeやGoogleの設定がある)
  • タスクスケジューラライブラリ\Microsoft\Windows 配下
    • フィードバック送信系の Autochk, Application Experience, Customer Experience Improvement Program, DiskDiagnostic など

これもサービスと同じく、よくわからないものはググって調べる。

HDD の音がうるさい → 何かが悪さしてるので対処する

HDD のファン音や書き込み音がうるさい=何か活発に動いているプロセスがある、のでそいつを突き詰めて対処(たいていは停止して無効化する流れになる)する。

確認には Process Explorer を使う。CPU観点(CPU列)、メモリ観点(Private Bytes列)、I/O観点(Disk Writes列)などで降順ソートして、たくさん消費しているプロセスを探す。

見つけたら詳細を開いて、プロセス名やコマンドラインをメモ。ググって詳細を調べる。たいてい「このプロセスはどういう役割があるの?」「無効化してもいいの?」「無効化手段は?」的な情報にヒットする。

この対処の過程でサービスやタスクスケジューラを触ることも多い。

ブラウザ

ここでは Firefox を想定する。

  • 可能なら古いバージョン(最近のブラウザはメモリをやたら食う)を使う
    • Quantum 以前(しかしセキュリティ的に問題があるので非推奨)
  • タブは 5 個以内で抑えたい。不要なものはすぐ閉じる。
  • アドオンは最小限に
    • 本当に必要なものだけ厳選する。10個以内、できれば5個以内に抑えたい
  • ページは開きっぱなしではなく、必要な時に都度開くようにする(アドレスバーを使えば履歴やブクマから探しやすい)
  • about:memory ページの GC ボタンはこまめに押す

セキュリティソフト

なるべく無効にする方向に倒す。

対象:

  • スキャンのスケジュール
  • アップデートのスケジュール
  • リアルタイム監視系
  • 例外設定系

対処:

  • 実行頻度は毎日→週一くらいにする
  • 実行時間帯は昼休憩や深夜など触ってない時間帯にする(特に深夜の場合は PC つけっぱなしが許される環境でないとダメだが)
  • リアルタイム系はオフにしてしまうのが望ましい
  • オフにできないなら例外設定から「よく Read/Write するフォルダ」を弾く

組織のルールによっては設定 NG なこともあるので事前に確認しておくこと。

おそらく良くて「業務に支障があるなら適宜設定を緩めてもいいが自己責任で」的な結論になっている。これに対して「そりゃ怖い。じゃあ今のままでいいや」とビクビクしないこと。必要以上のビクビクは「事故起きるから車使うのやめよ」レベルの無知。リテラシーがあるなら「これくらいまでなら緩めてもまあ大丈夫かな」という塩梅がわかるはず。

メール、チャット、インスタントメッセンジャー

  • 非同期チェックにする
    • 例:1日3回、決まったタイミングで見る(見る時に起動して見終わったら終了する)
  • 使っていない手段は封じる
    • 例:滅多に使ってないインスタントメッセンジャーは「使ってません。メール or チャットで」とメッセージ欄に残しておいて普段は起動しない
  • 使っていないメールは削除する
    • Outlook を例にすると……
      • 削除後は圧縮操作も必要
      • pst ファイルは数百MB以下に抑えたい
  • チャットクライアントは最も軽い組み合わせを模索した方が良いかも
    • ブラウザでログインするのが良いのか、デスクトップクライアントを使うのが良いのか……
    • ブラウザの場合、Firefox がいいのか Chrome がいいのか……
    • はたまた Franz のような多環境オールインワンタイプのクライアントがいいのか……

アプリ系

使ってないアプリは最小化する or 終了する

  • 行儀いいアプリなら最小化したらメモリ量が減る(使い始めたらまた増えるけど)
  • 最小化でも追いつかないなら終了して、必要な時だけ使うようにする

おすすめは「仕事セット(この仕事の時はこのアプリとこのアプリを使う、という組み合わせ)」を洗い出した上で、仕事セット以外のアプリを全部終了すること。もちろん、何度も仕事セットを切り替えるのはだるいので、「今日は仕事セットAだけやる」「明日は午前にB、午後にCにしよう」のようにある程度のスパンで集中的に使えるように仕事をコントロールする必要がある。

EXCEL/WORD/POWERPOINT/PDF はなるべく使わない

Microsoft Office 系のファイルは総じて重たいなので、なるべく関わらない。

自分が使う場合は、テキストエディタ + テキストファイルで済むようにする。もし「なんだ手抜きは」という輩がいたら、内向きの見栄えにどれほどの価値があるのか等、本質を突いてバトっても良いレベル。

Virtualbox や IDE を使う必要があるような仕事には近づかない

4GB で仮想マシンや IDE を使うこと自体に無理があるので、そういう仕事には近づかない。

ちなみに Visual Studio Code で HTML/CSS/Javascript(ES5) を開発する場合、何の拡張も使わないなら(他のテクニックを駆使すれば)かろうじて可能。

おわりに

以上、他にもあるかもしれないが、ぱっと思いつくものを書き殴ってみた。4GB はキツイぜ……。

括弧のタッチタイピングができないので AutoHotkey で `a[[` で括弧 `()` を入力してカーソルを真ん中に持っていく Hotstring をつくった

括弧らへん(右上段 8,9 あたり)のタッチタイピングが全然身についてくれないので、AutoHotkey の Hotstring でカバーしてみることにした。

AutoHotkey コード

#Hotstring ? ;単語途中でも発動させる

::a[[::
  Clipboard = ()
  Send,^v
  SendInput {Left}
return

#Hotstring ?0

解説

Clipboard について

Hotstring で文字列を貼り付ける方法には二種類がある。Send でキー単位で送るのと、Clipboard で全部コピーしてから Ctrl + V を送るのと。前者は IME 状態に左右されるのがデメリット、後者はクリップボード内容が消えちゃうのがデメリット。……今回は IME に左右されず常に半角括弧を入れたかったのでクリップボードにした。

SendInput {Left} について

これは () 挿入後にカーソルをこの括弧の中に移動させる命令。Left キーを一回分送っているだけ。

#Hotstring ? について

これは Hotstring を単語途中でも発動させるためのオプション。これを有効にすると uaaaa[[ と打った時に uaaa() となる。uaaa…… と単語入力の途中で一致しているのに発動している(普通は誤発動防止のため発動しない)。

ただし、この言わば「誤発動防止を無効するオプション」をずっと無効化しておくと(この記述より先の HotString でも全部無効になっちゃってしまい)望ましくないので、#Hotstring ?0 で元に戻している。

感想

かなり便利。括弧 () は地味に多用するが、いままで 2 回に 1 回は間違っていた。これがほぼミスらなくなり、また手元を見たりすることもなくなった。

AutoHotkey は素晴らしい。

もう年だからか、タッチタイピングみたいな手続き記憶的なことがなかなか身についてくれないので、こういう機転は今後ますます大事になってくるかもしれない。

Git で改行コードだけ変えた時の挙動やら autocrlf の意味やらよくわからんかったのでまとめた

Golang が LF しか扱わない件を受けて、CRLF を多用してる GitHub ユーザーの私は対処を余儀なくされている。いい機会なので、Git の改行コード変更時の挙動やら autocrlf あたりの仕様とか、そのへんをまとめることにした。safecrlf はまた今後。

Git コマンド絡み(LF でコミット済みのファイルを CRLF に直してコミットする場合)

既に改行コード LF のテキストファイル file.md がコミットされているとして。この file.md の改行コードを CRLF にしてから git 操作を行うことを考える。

git status すると?

autocrlf によらず変更が検出される。

       modified:   file.md

git add すると?

autocrlf が false の時はステージされる。コミットも行える。修正数は file.md が 100 行だとしたら 1 file changed, 100 insertions(+), 100 deletions(-) になる。

autocrlf = true または input の時はステージはされず、変更そのものが無かったことになる。CRLF に変えた file.md の改行コードは変わらず CRLF のまま。

Git コマンド絡み(CRLF でコミット済みファイルを LF に直してコミットする場合)

既に改行コード CRLF のテキストファイル file.md がコミットされているとして。この file.md の改行コードを LF にしてから git 操作を行うことを考える。

git status すると?

autocrlf によらず変更が検出される。

       modified:   file.md

git add すると?

autocrlf が false の時はステージされる。コミットも行える。修正数は file.md が 100 行だとしたら 1 file changed, 100 insertions(+), 100 deletions(-) になる。

autocrlf = true の時はステージされるが警告 warning: LF will be replaced by CRLF in file.md が出る。コミットも行える。修正数は file.md が 100 行だとしたら 1 file changed, 100 insertions(+), 100 deletions(-) になる。

autocrlf = input の時はステージされる。コミットも行える。修正数は file.md が 100 行だとしたら 1 file changed, 100 insertions(+), 100 deletions(-) になる。

Git コマンド絡み(まとめ)

改行コードだけ変えた場合の挙動は以下のとおり。

  • git status
    • 常に変更が検出される
  • git add、autocrlf=false
    • ステージする
  • git add、autocrlf=true、LF → CRLF に変換していた場合
    • ステージしない
    • 変更も無かったことになる(ただし CRLF に直したローカルのファイルはそのまま)
  • git add、autocrlf=input、LF → CRLF に変換していた場合
    • ステージしない
    • 変更も無かったことになる(ただし CRLF に直したローカルのファイルはそのまま)
  • git add、autocrlf=true、CRLF → LF に変換していた場合
    • ステージする
    • 警告 warning: LF will be replaced by CRLF in file.md が出る
  • git add、autocrlf=input、CRLF → LF に変換していた場合
    • ステージする

よりわかりやすく要点をピックアップすると、

  • false の時は常に警告無しでステージする
  • true or input の時に、LF でコミットされていたファイルを CRLF に直してもステージしない
  • true or input の時に、CRLF でコミットされていたファイルを LF に直したらステージできる
  • true の時は、改行コード違いでステージングが走った時にWarning が出る

Warning については、単に「autocrlf = true の時は、チェックアウト時に LF のファイルは全部 CRLF に変換しますんで」とお知らせしているだけ。 コミット時も チェックアウト時もとにかく表示されるので面食らうが、単なるお知らせなので身構える必要はない。

autocrlf について

core.autocrlf とは gitconfig の一種であり、コミット or チェックアウトを行う時に CRLF 派が楽するためのもの。元々 Git は LF オンリーであったが、Windows では CRLF を使う文化があるため用意されている。

コマンド

global の例で。

見る:

$ git config --global core.autocrlf
false

input に変える:

$ git config --global core.autocrlf input

true, false, input がある。未指定時のデフォルトは false

意味は以下のとおり。

  • true
    • CRLF で使いたい人用
    • コミット時は CRLF を LF に変換してコミット
    • チェックアウト時は LF を CRLF に変換してコミット
  • false
    • 何もしない
  • input
    • 手元では CRLF を使いたいけど LF 派を尊重する
    • コミット時は CRLF を LF に変換してコミット

つまり true/false で「いいから CRLF で使わせろや」するか「何もしないか」を選ぶのが基本。ただこれだけだと改行コード混在時(ファイル A は LF だが、ファイル B は CRLF になっている等)に不便なので、input という対処がある。input は「もし CRLF なファイルがあったら、Git リポジトリに入れる(コミットする)タイミングで LF にしちゃうね」オプションである。false だと CRLF なファイルが CRLF のまま入っちゃうから(Windows 圏以外の LF 前提の人たちにとっては)面倒くさい。input だと CRLF は全部 LF になって入るから CRLF が混ざる心配はない。

.gitattributes について

※詳しく見てないので触りだけ。

.gitattributes とはリポジトリに対して「このファイルの改行コードはこれにしろよ」的なルールを指定できる設定ファイル。.gitignore が「こういうファイルは管理対象から無視しろよ」的なルールを指定するのと同じ。

たとえば *.sh text eol=lf と書いておけば、sh ファイルは autocrlf = true でも常に LF になる(チェックアウト時もコミット時も LF に変換される)。 .gitattributes を使うことでプラットフォームによらず改行コードを固定できる、と。

参考

Golang でファイルに書き込んだ時の改行コードが CRLF ではなく LF になる件

ファイルに書き込む系の処理の話だが、Python で書いてきたコードたちは CRLF になってくれていた。しかし Golang だと LF になる模様。CRLF にする方法はないものかと調べた。

結論

Golang 的には LF のみ

GitHub で議論されてる。

Issue での提案は「Windows なら \n は CRLF にしろや」だけど、最終的な結論は We aren't going to do this, so closing. で「この件はやらないっす。ばいばい」

どうしても CRLF で出したいなら?

自力で \r\n を Write するしかなさそう。

あるいは mattn さんの mattn/go-textwriter を使うとか。

余談

なぜ LF であるべき?

同じく mattn さんのコメント が勉強になる。

  • Golang はクロスプラットフォームに強い
  • 改行コードも LF に揃えた方が楽
  • Win10 のメモ帳も LF に対応したし、もう LF の流れでしょ

という感じだろうか。本当に仰るとおりです。

そもそもなぜ私は CRLF を使っている?

以下の事情による。

  • 昔からずっと使い続けてきた名残(かれこれ10年以上は Windows でテキスト書いてる)
  • 基本的に Windows しか使ってない
  • Gitconfig の autocrlf は true がデフォで運用している
  • バッチファイルや秀丸エディタマクロなど、CRLF じゃないと動かないものに依存している(たぶん他にもある)

Golang で Markdown TOC 作成ツールを作り直してみた

ここ数週間くらいは Golang を触っていた。stakiran/intoc という Markdown から TOC をつくる Python 製ツールがあって、これを Golang で実装し直すことで学習を図った。

ここまで学んだことをざっくりまとめておく。

前提

  • Windows
  • エディタは軽いやつ(私は秀丸エディタ)を使う
    • 1KL 以内の小さなコードを書くことのみ想定
    • IDE は用意しない
  • コード例はたまに Python との比較を出す(ので知らないなら無視すること)

環境構築

言語環境のインストール

公式サイト Downloads - The Go Programming Language からダウンロード。インストーラーになっている。インストール先は気に食わないなら変えて良い。私は D:\bin1\Go にした。

インストール後は環境変数 GOPATH と GOROOT を設定する。私は以下のようにした(go env で環境情報を見れる)。

$ go env
...
set GOPATH=D\bin1\gopath
...
set GOROOT=D\bin1\Go
...

併せて環境変数 PATH に %GOPATH%\bin を通す。

パッケージのインストール

go get (URL) コマンドを使う。プロキシ環境下なら環境変数 HTTPS_PROXY と HTTP_PROXY に事前にプロキシを設定しておくこと。

go get を実行するとファイル群は GOPATH 配下に保存される。GOPATH 配下は以下の構造になっている。

D:\bin1\gopath
 |
 +--- bin    コマンドとして実行できるものはここに入る
 |
 +--- pkg    binに入れてる実行ファイルを作る時の生成物(コンパイル時の生成物)
 |
 +--- src    開発時に import して使う分はここに入る

パッケージがコマンドとして提供されたものなら(binディレクトリに実行ファイルが入るので)、どのディレクトリからでもコマンドを実行できるようになるはず。

パッケージがライブラリとして提供されたものなら、import 文でインポートできる。

go get 例: テストフレームワーク testify

入手:

$ go get github.com/stretchr/testify

使用:

package main

import (
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestAdd(t *testing.T) {
    assert.Equal(t, 1, 10-8-1)
}

要するに go get した URL と同じ URI を import 文で指定する。ここでは assert 部分のみ import しているが。

Hello World

package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

実行

実行は go run hello.go。これでコンパイルが走ってテンポラリフォルダ( %temp% )に実行ファイルができた後、それが実行される。

ビルド

ビルドは go build hello.go。これで hello.exe が生成される。

フォーマッティング

go fmt コマンドでソースコードの書式整形を行う。

go fmt hello.go を実行すると、hello.go が Go のコーディングスタイルに整形される。

インデントはタブ派だスペース派だとか、{ の位置とか、そういうので論争する余地はない。ただ「go fmt を通せ」で済む。

コーディング全般

他言語(少なくとも私が知っている Python, Javascript)とは色々と違ってくるので、最初に目を通しておくと良い。

もう少し効率的かつ優しいのが欲しいなら、先人が残してくれてる日本語情報を。

ただ最初から全部読んでもピンと来ないので、手を動かしながらの方が良い。

  • A Tour of Go
    • チュートリアル
    • 根気があるならこれでいいかと
    • 退屈でつまらない(言語フェチとかなら楽しめる気はするが)
  • The Go Playground
    • ブラウザだけで実行できるのでちょっとした学習や試行に最適

ちなみに私は拙作の stakiran/intoc: TOC generator for Markdown. これを Go で書き直してみることで学習をした。intoc はファイル I/O、パス操作、引数解析、リスト操作、文字列操作、クラス、Markdown 構文のパース等を含んでいるため、(各種処理を Go でどう書くかという)基礎を一通り学べると目論んだ。

ユニットテスト

go test コマンドを実行する。

go test は、testing パッケージベースで書かれた「XXXXX_test.go というファイル」の「TestXXXXX という関数」を全部実行していく。

go test を実行する時は go test intoc_test.go intoc.go のように「テストコード」と「テストコードが読みに行くソース」の両方をコンパイル対象に含める必要がある。.go をちゃんと書いてるなら go test ./ みたいにディレクトリごと指定しちゃうのもアリ。

テストコード

典型的には以下のようになる。

  • テスト対象ソースへのアクセスは...
    • import する
    • 対象ソースと同じパッケージにする(下記ソースはこれ。main パッケージにしている)
  • testing パッケージを import する
  • アサーション関数等はないので、自分で actual と expect を比較して、違うなら Errorf でエラーを出す、みたいなことをやる
package main

import (
    "testing"
)

func TestAdd(t *testing.T) {
    actual := 10
    expected := 2 + 8
    if actual != expected {
        t.Errorf("actual %v\nwant %v", actual, expected)
    }
}

func TestSub(t *testing.T) {
    actual := -5
    expected := 2 - 8
    t.Skip("skip sub test.")
    if actual != expected {
        t.Errorf("actual %v\nwant %v", actual, expected)
    }
}

ただしこれでは最低限すぎて面倒くさいので、テスト用のパッケージを使った方が楽。

私は今回 stretchr/testify を使った。testify を使うと assert.Equal とか assert.True とか使える。他にも mock とか色々あるみたいだが。

文字列操作

基本的に strings パッケージに頼るので import しておくこと。あと書式整形(C言語でいう printf)系は fmt パッケージを使うので、必要なら import しておく。

format

s := fmt.Sprintf("%s-%d", ret, dupCount)

参考: 忘れがちなGo言語の書式指定子を例付きでまとめた - Qiita

utf8 string の先頭 1 文字を slice する

Go では string は bytestring なので、そのまま slice しても「文字」単位が正しく認識されない。

以下のように、いったん []rune にキャストしてから slice する。slice 後は、string で使うためにキャストで戻すことも忘れずに。

peekfirst = string([]rune(line)[:1])

Python2 時代を思い出す。

replace

ret = strings.Replace(ret, " ", "-", -1)

第4引数は置換回数。0 未満なら無限回。

lower

ret = strings.ToLower(ret)

startswith, endwith

startsWith := strings.HasPrefix("prefix", "pre") // true
endsWith := strings.HasSuffix("suffix", "fix") // true

strip

ret = strings.TrimSpace(ret)

"a"*32 ← こういう繰り返し

strings.Repeat("a", 32)

リスト操作

Go にはリスト(list)は無い。あるのは配列(array)。

loop/scan(各要素を走査する)

[]string 型の lines 配列(文字列配列の lines 変数)があるとして。Python の enumerate に近い書き方。

for i, line := range lines {
    fmt.Printf("%d: %s\n", i, line)
}

添字 i を使わない場合は、_ で伏せる。伏せないとコンパイルエラー not used で怒られる。

for _, line := range lines {
    fmt.Println(line)
}

join(array を string にする)

strings.Join(tocLines, "\n")

split(string を array にする)

strings.Split(aString, "\n")

append

s := []int{}
s = append(s, 1)
s = append(s, 2, 3, 4)

extend(array に別の array を concat する)

append 対象 b を b ではなく b... の形で与える。

package main

import (
    "fmt"
)

func main() {
    a := []string{"a","b","c"}
    b := []string{"1","2","3"}
    a = append(a, b...)
}

参考: append - Concatenate two slices in Go - Stack Overflow

引数解析

Go 標準で簡単なのが flag パッケージ。

大まかな流れは、

// オプションを定義していく.
// これは文字列値の -input オプションを定義した例.
argInputFilepath := flag.String("input", "", "An input filename.")
...

// 与えられたコマンドラインの解析を走らせる.
flag.Parse()

// 定義した分に実際の値が入るので適宜アクセス.
// ポインタになっているので注意.
fmt.Printf("Input filepath is '%s'.\n", *argInputFilepath)
...

required(必須パラメーター)

flag では実現できないので、自力で頑張る。

以下は(上例の) -input オプションが与えられなかった時に「必須ですよ、的なメッセージ」「Usage 表示」して終了する例。

 if *argInputFilepath == "" {
        fmt.Println("-input required.")
        flag.PrintDefaults()
        os.Exit(2)
    }

与えられたオプションすべてを標準出力する

flag の VisitAll を使う。VisitAll には(渡されてくる各オプションをどう処理するかを定義した)関数を渡してあげる感じ。

以下は -debug-print-all オプションが指定された時に、オプション全部を表示する例。

argDebugPrintAll = flag.Bool("debug-print-all", false, "[DEBUG] print all options with name and value.")

...

printOption := func(flg *flag.Flag) {
    fmt.Printf("%s=%s\n", flg.Name, flg.Value)
}
if *argDebugPrintAll {
    fmt.Println("==== Options ====")
    flag.VisitAll(printOption)
}

flag.Flag 型について flag - The Go Programming Language を。

ファイルパス走査

Big Sky :: Golang で物理ファイルの操作に path/filepath でなく path を使うと爆発します。 が詳しい。

所感

総合

良い。

小物ツール(特にバイナリで配布したいものや性能が欲しいもの)は今後 Go で開発していきたい。また純粋なマンネリ防止や娯楽としても、あえて(慣れてる Python ではなく)Go で書いていきたい……かな。

とりあえず Tritask の Go 化はやりたい。そしたらバイナリ化も楽にできて普及啓蒙が楽になる。性能も出て、今まで 1 万件程度(で秒時間がかかってしまっていた遅さ)だったソート処理も 10 万件くらいまで扱えるようになるだろう。

良い

  • go get、go run、go test、go env など簡潔なコマンドで完結するので快適
  • go run コンパイルでつまらん構文エラーを検出できるので楽
  • go build だけで実行ファイル化できる気軽さが嬉しい
    • Python だと実行ファイル化は難題だったので……
  • 実行ファイルの動作が高速で素晴らしい
    • intoc も Python スクリ版と比べると 10 倍は軽く違う印象
  • 型推論が働くので aString := "aaaaaa" で済むのが楽
    • いちいち var aString string = "aaaaaaa" と書かなくていい
  • 標準ライブラリ充実してて、小物ツール書く程度なら困らないと思う
  • ググったら大体答え出てくる
    • ググる時は go ではなく golang が良い

つらい

  • unicode string を扱うのにいちいち rune が必要
  • OOP をサポートしていない
    • 構造体 + レシーバ(この構造体にこの関数を紐付ける、的な概念)である程度は模倣できる
    • あと interface なるものもある(まだ試してない)
  • 型の指定に伴って記号が増えるので最初は「ウッ」となる
    • 例1: lines := []string{"a", "aaa", "aaaa"}
    • 例2: func createOutLines(lines []string, tocLines []string, args Args, editTargetPos int) ([]string) {
  • C言語時代に散々苦しめられたポインタの概念がある
    • ただ Python で Shallow/Deep copy に親しんでたので今はあまり抵抗はない
  • 標準出力系が fmt.XXXXX と書かないといけないのが少しだるい

Golang でファイルを書き込んだ時に古い内容が末尾に残る件

File に Write して、ちゃんと Flush もしてるのに、なぜかゴミが append されているような挙動になる。ゴミデータはどうも「前回そのファイルに存在していた内容」っぽい。つまり古いファイル内容が残ったままになっている?……という、よくわからない挙動に遭遇。

os.OpenFile やら bufio.NewWriter やら I/O まわりに何かしら問題があることは特定したが、その先がわからない。半日くらいハマって、ようやく解決できたのでまとめる。

結論

ファイル書き込み時(特に毎回指定内容で丸々新たに書き込むような使い方の時)は os.OpenFile ではなく os.Create を使う。

試行過程

解決に至るまでの過程をまとめておく。

1 問題だったコード

一行一要素の文字列配列をファイル化する list2file 関数を、以下のようにつくっていた。

func list2file(filepath string, lines []string) {
    fp, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil {
        abort(err.Error())
    }
    defer fp.Close()

    writer := bufio.NewWriter(fp)
    for _, line := range lines {
        writer.WriteString(line + "\n")
    }
    writer.Flush()
}

これを実行すると、filepath で示すファイルの末尾に何故か古い内容がひっついてくる。

たとえばファイル filepath の内容が

aaa
bbb
ccc

だとして、list2file 関数で ["aaa", "bbb"] を書き込んだとする。この時 filepath の内容は

aaa
bbb

こうなるはずだが、なぜか

aaa
bbb
cc

こんな感じで末尾に 古い内容の一部が残っている ような挙動になる。

2 原因は Truncate?

調べていると、どうも Truncate という概念があるらしい。

Golang ファイル入出力メモ - Qiita

書き込みで開いた場合、os.O_TRUNCを指定しないと、以前の内容をリフレッシュせずに上書きするので、書き込み内容が短い場合は以前の内容が残ってしまう。

[Go言語]Golangの上書き処理が少し慣れない[memo] | 鈍色スイッチ

O_TRUNCを追加しておくと、書き込み先ファイルが存在したときにその内容を消し、そのあと出力する形になる。つまり、中身を消した上で上書きするのである。

Brian Beardさんのツイート: "A Bug's Life #6378932: Replaced os.Create(file) with os.OpenFile(file, flags, perms) for more options like appending. ...

trying to figure out why saves were strange at the end when not appending but overwriting. File flag os.O_TRUNC was missing.

I/O 低レイヤーの事情はよくわからんが、どうも「既存ファイルに対して丸々新しい内容を書き込みたい場合」は、Truncate という「元々の内容をいったん全部消す」的な処理が必要、という事情?仕様?があるようだ。

で、 os.OpenFile 関数を使った場合は、この Truncate が行われない。つまりは「元々の内容をいったん全部消す」も行われないわけで、それゆえに末尾にゴミが残ってしまっていた……と。

3 os.OpenFile で Truncate するには?

じゃあ os.OpenFile で Truncate するようフラグを付ければいい……と考える。幸いにも os.TRUNC が用意されている

1 のコードを以下のように修正した。

func list2file(filepath string, lines []string) {
    // ★ os.O_TRUNC を付けただけ。あとは同じ
    fp, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    if err != nil {
        abort(err.Error())
    }
    defer fp.Close()

    writer := bufio.NewWriter(fp)
    for _, line := range lines {
        writer.WriteString(line + "\n")
    }
    writer.Flush()
}

これでも本現象は発生しなくなった。めでたし。

4 そもそも OpenFile の必要ある?という話

もう少し踏み込む。

そもそも OpenFile する必要があるのか という点に気付く。このツイートのおかげです。

のぼのぼ☂️さんのツイート: "#golang これ自前でos.OpenFileしてO_TRUNC指定忘れ?...

単にos.Create使えばいいです。os.OpenFileを使うのは追記や特殊な事情がある場合だけ。

そのとおりだ。公式ドキュメント os.OpenFile - The Go Programming Language にも

most users will use Open or Create instead.

と書いてある。OpenFile は、追記など特殊な事情用の関数なのだと読み取れる。

5 最終的なコード

私が今回つくった list2file 関数は、指定したファイル名で指定した文字列配列を書き込む関数。追記という事情は無い。毎回丸々上書きするだけだ。これは毎回 Create することに等しい。たしかに、Create を使えば済む。

というわけで、最終的なコードは以下のように。

func list2file(filepath string, lines []string) {
    // ★Create を使うようにした
    fp, err := os.Create(filepath)
    if err != nil {
        abort(err.Error())
    }
    defer fp.Close()

    writer := bufio.NewWriter(fp)
    for _, line := range lines {
        writer.WriteString(line + "\n")
    }
    writer.Flush()
}

所感やら反省やら

これで半日くらいハマった。Truncate という概念にたどり着くのに時間がかかった。

結局、golang の i/o についてあれこれググりまくってる最中に Golang ファイル入出力メモ - Qiita にヒットして、

os.O_TRUNCを指定しないと、以前の内容をリフレッシュせずに上書きする

という文言と出会ったおかげで解決できた。あとは Twitter で golang trunc - Twitter検索 を見てみて、ああこれだ間違いないと確信。無事解決できたのであった。

反省点としては、こんな感じだろうか。

  • Truncate について知らなかったのは仕方ない
  • でも公式ドキュメントに os.O_TRUNC という定数が存在している(のでここから TRUNC の意味や存在意義を知ることはできた)
  • 私は一度そこを読んだが、『// truncate regular writable file when opened.』 ← open する時にいったん削除?するのか、ふーんとテキトーに読み流していた
    • もっと深掘りしようぜ……

コマンドプロンプト起動時に自動で実行させるバッチファイルを登録する

よく忘れるのでメモ。

HKEY_CURRENT_USER\Software\Microsoft\Command Processor キーの AutoRun(REG_SZ) エントリに、自動実行させたいバッチファイルのパスを指定。

autorun.reg

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"AutoRun"="D:\\work\\github\\stakiran\\conf\\autorun\\autorun.bat"

autorun.bat

@echo off
prompt $$ 

プロンプトを $ に変える例。デフォのやつは長いので嫌い。