Technology Engineering

178inaba の技術ブログ

$(git branch)でカレントディレクトリのファイル一覧も出てしまう時の対処法

とあるシェルでgitのブランチ一覧を取りたくて echo $(git branch) と書いたのだが、なぜかファイル一覧も出てしまい戸惑った。

前提

下記のようなディレクトリがあって、それがgit管理されていたとする。

$ ls
README.md   main.go     main_test.go

そして以下のようなブランチが存在する。

$ git branch
  develop
  feature/bar
  feature/baz
  feature/foo
* master

事象

echo $(git branch) を実行すると下記のようにブランチ一覧の間にファイル一覧が差し込まれてしまう。

$ echo $(git branch)
develop feature/bar feature/baz feature/foo README.md main.go main_test.go master

原因

勘のいい人はもうお分かりかもしれませんが、 git branch した時にmasterの前に * がついていますよね。
それが echoワイルドカードとみなされてファイル一覧が出てしまいます。

$ echo *
README.md main.go main_test.go

と同じになるわけです。

対処法

ダブルクオートで囲めばワイルドカードが展開されずに出力されます。

$ echo "$(git branch)"
  develop
  feature/bar
  feature/baz
  feature/foo
* master

ブランチ一覧を1行で取得したい場合

tr を使って * をスペースに変換したリストをダブルクオート無しで echo すれば取得可能です。

$ echo $(echo "$(git branch | tr '*' ' ')")
develop feature/bar feature/baz feature/foo master

でも、そもそも git branch を使ってスクリプトを書かないほうがいいらしい。

stackoverflow.com

上記によれば、Gitはスクリプトで使用するために明示的に設計されたplumbingインターフェースを提供しているとのこと。
なので自分はplumbingインターフェースを使ってみようと思います。

参考

stackoverflow.com

特定のディレクトリ内にあるjavascriptを全てMinifyする

例えば static/js ディレクトリにjsが置いてあるとする。

$ tree
.
└── static
    └── js
        ├── bar.js
        ├── baz.js
        └── foo.js

そのディレクトリ内にあるjavascriptを全てMinifyしたい場合のbashスクリプト

for f in $(ls static/js/*.js); do
  uglifyjs -cm -o "${f/.js/.min.js}" "${f}"
done

Minifyには uglify-js を使用する。
オプションの c が単純なソース圧縮、 m が変数名の圧縮(1文字にする)、 o はアウトプットするファイルを指定。

これを minify.sh というファイル名で作成し、実行する。
注意点としては ${f/.js/.min.js}bashの文字列置換を使用しているのでbashで実行すること。

$ bash minify.sh

すると全てのjavascriptがMinifyされる。

$ tree
.
├── minify.sh
└── static
    └── js
        ├── bar.js
        ├── bar.min.js
        ├── baz.js
        ├── baz.min.js
        ├── foo.js
        └── foo.min.js

ちなみにMinifyしたjavascriptをgit管理したくない場合は .gitignore*.min.js を入れると無視してくれる。

もうひと工夫

シバンを入れて実行権限をつけるとスッキリして良い。
実行権限をつけるのでファイル名も minify にすると綺麗かも。

#!/bin/bash

for f in $(ls static/js/*.js); do
  uglifyjs -cm -o "${f/.js/.min.js}" "${f}"
done
$ mv minify.sh minify
$ chmod u+x minify
$ ./minify
$ tree
.
├── minify
└── static
    └── js
        ├── bar.js
        ├── bar.min.js
        ├── baz.js
        ├── baz.min.js
        ├── foo.js
        └── foo.min.js

参考

CircleCIのYAMLの仕様が変わった

CircleCIを使っている。
今週水曜(2018年10月10日)なぜか本番デプロイができなくなっていた。

調査すると設定を書いているYAMLの仕様が変更されたようで、キーの上書きができなくなっていた。

元々の仕様

デプロイにはFabricを使用している。

fab dev deploy で開発環境、 fab prod deploy で本番環境にデプロイされるように設定ファイルを書いている。

references:
  env_dev: &env_dev
    environment:
      ENV: dev

  env_prod: &env_prod
    environment:
      ENV: prod

jobs:
  deploy_dev: &deploy
    docker:
      - image: circleci/python:3.6
        environment:
          AWS_DEFAULT_REGION: ap-northeast-1
    steps:
      - checkout
      - run:
          name: Install python package
          command: sudo pip install -r requirements.txt
      - run:
          name: Deploy
          command: fab $ENV deploy
    <<: *env_dev

  deploy:
    <<: *deploy
    <<: *env_prod

Anchor(&name)とAlias(*name)を使用してコンパクトに書いている。

CircleCIは各jobページのConfigurationタブで展開されたYAMLを確認することができる。

f:id:i178inaba:20181014050410p:plain

展開されたYAMLを確認すると下記のようになっている。

jobs:
  deploy_dev:
    environment:
    - ENV: dev
    steps:
    - checkout
    - run:
        name: Install python package
        command: sudo pip install -r requirements.txt
    - run:
        name: Deploy
        command: fab $ENV deploy
    docker:
    - image: circleci/python:3.6
      environment:
        AWS_DEFAULT_REGION: ap-northeast-1
  deploy:
    environment:
    - ENV: prod
    docker:
    - image: circleci/python:3.6
      environment:
        AWS_DEFAULT_REGION: ap-northeast-1
    steps:
    - checkout
    - run:
        name: Install python package
        command: sudo pip install -r requirements.txt
    - run:
        name: Deploy
        command: fab $ENV deploy

ちゃんと environmentENV が上書きされて展開されているのがわかります。

仕様変更後

水曜日、本番デプロイのCIが失敗することで仕様変更を知る。
仕様変更後に展開されたYAMLを確認すると deploy jobの ENV が上書きされず、 dev のままになってしまっていた。

jobs:
  ...
  deploy:
    ...
    environment:
    - ENV: dev

Twitterでつぶやかれている方も居た。

修正

AnchorとAlias周りがおかしいのではないかという事はすぐわかったので、以下のように修正を行った。

   deploy:
-    <<: *deploy
-    <<: *env_prod
+    <<: [*env_prod, *deploy]

元々は *deploy を先に書き、そこに *env_prod を上書きするという意図で書いていた。
しかし、上書きされないため書く順番を逆にし、 *env_prod を先に書き、 *deploy を後に書いた。
この時、Aliasの書き方もCircleCIで推奨されている [] を使用した書き方に変更した。

すると下記のように以前の想定通り展開されました。

jobs:
  ...
  deploy:
    environment:
    - ENV: prod
    docker:
    - image: circleci/python:3.6
      environment:
        AWS_DEFAULT_REGION: ap-northeast-1
    steps:
    - checkout
    - run:
        name: Install python package
        command: sudo pip install -r requirements.txt
    - run:
        name: Deploy
        command: fab $ENV deploy

そして無事デプロイされるようになった。

ブログ再始動

お久しぶりです。

178inabaです。

久しぶりにブログを書いています。

最近、技術書典で技術書を販売した方のブログを見ました。

shu223.hatenablog.com

このブログを見て

(自分もいつかは本が書けたら楽しいだろうな)
(沢山の人が来場していて交流できるし、報酬も得られる!)

などと思い、

でも本を出すにはまず、休止状態のブログを再始動して技術的な記事とか書くべきだろうと思いこのブログを書いています。

Kyash始めてみた

Kyashは同僚と食事に行った時に割り勘代金を渡すために入れてみたのですが便利です。

上の技術書典のブログを読んだときに、KyashのAndroidエンジニアのこにふぁーさんのブログを思い出しました。

konifar.hatenablog.com

このこにふぁーさんのブログには投げ銭の話が書いてあって、上の技術書典の話と 『技術情報の発信で報酬を得る』 という所で通ずるなと思いました。

なので真似してKyashのIDとQRコードを貼ってみます。

kyash_id: 178inaba

f:id:i178inaba:20181012005104j:plain

今後

今後は定期的に技術情報をブログに書いていけたらと思います。
よろしくです!

GitHub Gistにファイルをアップロードするコマンドをgolangで作った

Gistを作る時、毎回catでファイル内容を表示してコピーして、GistのWeb画面にペーストしてたんだけど、
これが地味にめんどくなってきたので、ファイルを渡すとGistを作ってくれるコマンドをgolangで作った。

github.com

使い方

  • target.goをGistにアップロードする場合。
$ gistup target.go
  • 複数ファイルをGistにアップロードする場合。
$ gistup target.go target_test.go target_linux.go
  • 標準入力をGistにアップロードする場合。
$ stdin | gistup

アップロードが成功するとブラウザで作成されたGistのページを開きます。
(開けなかった場合はURLを表示。)

デフォルトでは最初にユーザ名とパスワードでログインを求められます。
(後述のオプションで匿名を選択すればログイン不要。)

また、デフォルトでは非公開のGistとして作成されます。
(こちらもオプションで公開のGistとして作成することも可能です。)

オプション

  • -a
    • 匿名のGistを投稿します。
  • -d <description>
    • Gistの説明を追加します。
  • -n <file_name>
    • 標準入力からGistにアップロードする場合のファイル名を設定します。
    • 指定しない場合はGistのデフォルトgistfile1.txtになります。
  • -p
    • パブリック(公開)のGistを投稿します。

所感など

入力時のコンテキストキャンセル検知

ユーザ名、パスワード入力は下記のようにコンテキストのキャンセルを検知できるようにしている。

func readString(ctx context.Context, hint string, readFunc func(t *tty.TTY) (string, error), t *tty.TTY) (string, error) {
    fmt.Printf("%s: ", hint)
    ch := make(chan string)
    errCh := make(chan error)
    go func() {
        s, err := readFunc(t)
        if err != nil {
            errCh <- err
        }
        ch <- s
    }()
    var s string
    select {
    case <-ctx.Done():
        return "", ctx.Err()
    case s = <-ch:
    case err := <-errCh:
        return "", err
    }
    return s, nil
}

理由はgistupはInterruptシグナル(Ctrl+C)を受けてコンテキストをキャンセルする仕組み(下記参照)になっているのだが、

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
ctx, cancel := context.WithCancel(context.Background())
go func() {
    <-sigCh
    cancel()
}()

これだと、入力中にCtrl+Cを押しても終わらない。
なので、ゴルーチンで入力処理を実行して、コンテキストのキャンセルと、入力終了をselectで同時に待つようにした。

結果、入力中にCtrl+Cを押すと終了するようにできた。

ユーザ名、パスワードの入力はTTYを使用

gistupではユーザ名とパスワードの入力にTTYを使用している。

理由は、最初、標準入力からユーザ名とパスワードを取得していたけど、
標準入力をGistにアップロードする箇所を実装してる時にユーザ名、パスワードの取得ができなくなる事に気づいた。

いろいろ調べたけど引き続き標準入力からユーザ名、パスワードを取得するのは難しそうで、調べて出てきたのはperlの質問でTTYを使っているやつだった。

stackoverflow.com

「goでTTYって確かmattnさんが作ってたよな」と探したらあって、

github.com

ただ、パスワードを読むメソッドしか無かったのでReadStringメソッドをプルリクエストした。
その後、速攻でマージして頂けたのでユーザ名の入力にはReadStringを使用。

結果、ユーザ名、パスワードを入力しつつ、標準入力から取得した文字列をGistにアップロードすることができた。

まとめ

これでコピペせずにGistにアップロードできるようになった。

またなんか作ったら書きます。

あと、PR大歓迎です😆

Raspberry Piで使うSDカードをコマンドラインで作る

新しいRaspberry Piを買ったので起動SDを作るコマンドをメモしておきます。
使用するOSはRaspbianです。

# ダウンロード
$ wget https://downloads.raspberrypi.org/raspbian_lite_latest -O raspbian.zip

# zip解凍
$ unzip raspbian.zip

# SDカードのマウント箇所を確認
$ diskutil list
...
/dev/disk2 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *2.0 GB     disk2
...

# SDカードのマウントを解除
$ diskutil unmountDisk /dev/disk2

# SDカードにイメージを書き込む
$ sudo dd bs=1m if=2017-03-02-raspbian-jessie-lite.img of=/dev/rdisk2

イメージを書き込むのが結構時間かかります。

マウント箇所(上記だとdisk2)はPCによって異なります。
イメージファイル名(上記だと2017-03-02-raspbian-jessie-lite.img)もRaspbianがアップデートされると変わります。

参考:

www.raspberrypi.org

Raspberry Pi(Raspbian)の更新

新しいRaspberry Piを買ったので、ついでに以前GitLabを入れたRaspberry Piの更新をした。

# パッケージリスト更新
$ sudo apt-get update

# パッケージ更新
$ sudo apt-get dist-upgrade

# ダウンロードしたパッケージの削除
$ sudo apt-get clean

sudo apt-get dist-upgradeは結構時間かかる。

GitLabもRaspbian版は長らくバージョン8.7.9で止まっていたのだが、最近更新されてバージョン9.0.2が使えるようになっていた。
Dockerレジストリも使えるようになった。

参考:

www.raspberrypi.org

gitlab.com