docker volumeの名前がハッシュ値のものを一括削除する

普段開発環境はdockerを使っています。dockerを使って開発していくとvolume名が64桁のハッシュ値のものがどんどん増えていきディスクを圧迫していきます。ディスクの容量がいっぱいになると、dockerの起動がしなくなったりして開発に支障が出たことがありました。それからはこまめに削除していますが、volume名がハッシュ値のものを一括で削除するスために下記スクリプトで削除しています。

$ docker volume ls | awk 'length($2)==64 {print $2}' | xargs docker volume rm

下記にそれぞれのコマンドの内容を簡単に記載しておきます。

docker volume ls

docker volume lsにより全てのvolumeを表示する。
1列目はdriverが表示され、2列目がvolume nameが表示される。

$ docker volume ls
DRIVER              VOLUME NAME
local               0b54333e1df6786dec2c213dae87d10ccc75a85ceade2498cc6abbed140c5ab9
local               2f87d4b910d4dae508c04edb7d004a7aecc1eddc7dc0e60d92d29306f7346279
local               3ee8993cda4164ab5e764ea7a097bd67cb0e4bc3b3702073a957c58c4c64dc59
local               pgsql-data

awk

awkコマンドにより上記のdocker volume lsで出力された結果を加工する。
awkコマンドは空白やカンマなどで区切られたテキストを処理できるコマンドである。文字列を検索したり、文字列を抜き出したりすることが可能である。
今回の場合、文字列を検索しているはlength($2)==64の箇所である。これは2列目の文字の長さが64文字のものを検索している。
そして{print $2}で2列目を抜き出している

$ docker volume ls | awk 'length($2)==64'
DRIVER              VOLUME NAME
local               0b54333e1df6786dec2c213dae87d10ccc75a85ceade2498cc6abbed140c5ab9
local               2f87d4b910d4dae508c04edb7d004a7aecc1eddc7dc0e60d92d29306f7346279
local               3ee8993cda4164ab5e764ea7a097bd67cb0e4bc3b3702073a957c58c4c64dc59

$ docker volume ls | awk '{print $2}'
0b54333e1df6786dec2c213dae87d10ccc75a85ceade2498cc6abbed140c5ab9
2f87d4b910d4dae508c04edb7d004a7aecc1eddc7dc0e60d92d29306f7346279
3ee8993cda4164ab5e764ea7a097bd67cb0e4bc3b3702073a957c58c4c64dc59
pgsql-data

$ docker volume ls | awk 'length($2)==64 {print $2}'
0b54333e1df6786dec2c213dae87d10ccc75a85ceade2498cc6abbed140c5ab9
2f87d4b910d4dae508c04edb7d004a7aecc1eddc7dc0e60d92d29306f7346279
3ee8993cda4164ab5e764ea7a097bd67cb0e4bc3b3702073a957c58c4c64dc59

xargs

標準入力からコマンドラインを構築して実行することができる。例えばcommandA | xargs commandBcoomandAで実行した結果をcommandBの引数としてcommandBを実行する。
今回のスクリプトでは| xargs docker volume rmとしているのでawkコマンドで得られた64桁のvolume名をdocker volume rmに引数として渡している。docker volume rm volume名volume名のvolumeを削除する。

Rustのエラーハンドリングの基本

普段仕事ではRubyを使ってWebアプリケーションの開発をしているのですが、Rustに興味があり、勉強をしています。
Rustのエラーハンドリングについて勉強したのでまとめてみました。

Rustのエラーハンドリング

多くのプログラミング言語にはエラーハンドリングに例外や戻り値を利用しますが、Rustでは戻り値を使ってエラーハンドリングをします。
Rustのエラーハンドリングは大きく分けて2つの種類があり、リカバリーできるエラーとリカバリーできないエラーであります。 リカバリーできるエラーはResult<T, E>値があり、リカバリーできないエラーはpanic!マクロなどがあります。 リカバリーできないエラーはプログラムの実行を中止するが、多くのエラーはプログラムを完全にストップさせるほどの必要はないため、基本的にはResult型を使ってエラーハンドリングします。

Result型

Rustには例外がないが、代わりに失敗する可能性のある関数はResult型を返してエラーの場合を対処します。 Result型は下記のようなenumとしてOkErrの2列からなるよう定義されています。

enum Rusult<T, E> {
  Ok(T),
  Err(E)

Ok(T)は成功結果でErr(E)はエラー結果を返します。
TEジェネリックな型引数を表しており、Tが成功したときに返される値の型を表し、Eが失敗したときに返される型を表します。

Result型の処理方法

Result型の処理方法の一つはmatch式を使い、関数がResult型を返したときにパターンマッチさせます。

use std::num::ParseIntError;

fn parse_str(num_str: &str) -> Result<u32, ParseIntError> {
    num_str.parse::<u32>() 
}

fn main() {
    let num_str = "3";
    match parse_str(num_str) {
        Ok(num) => println!("{}", num),
        Err(err) => eprintln!("{}", err),
    }

    let num_str = "str";
    match parse_str(num_str) {
        Ok(num) => println!("{}", num),
        Err(err) => eprintln!("{}", err),
    }
}

上記を実行すると下記のように出力されます。

3
invalid digit found in string

parse_strへ引数に渡された文字列により、その処理の結果に対してのResult型が返されます。その返された結果をmatch式でパターンマッチさせ、返された結果がOkErrによりそれぞれの結果を処理します。
また、match式を使う以外の別の方法としてResult型に用意されているメソッドがあるので、こちらを使って処理することができます。

use std::num::ParseIntError;

fn parse_str(num_str: &str) -> Result<u32, ParseIntError> {
    num_str.parse::<u32>()
}

fn main() {
    let num_str = "3";
    parse_str(num_str)
        .map(|num| println!("{}", num))
        .unwrap_or_else(|err| eprintln!("{}", err));

    let num_str = "str";
    parse_str(num_str)
        .map(|num| println!("{}", num))
        .unwrap_or_else(|err| eprintln!("{}", err));
}

実行結果はmatch式を使ったコードと同じ結果となります。
mapはResult型の値がOk時に、mapの引数に与えられた関数かクロージャOkの中の値に対して実行しその結果をOkの中身として返し、Errの場合はそのまま値を返します。
unwrap_or_elseOkの場合はそのまま値を返し、Errのときは関数かクロージャを実行してその結果を返します。

?演算子

エラーハンドリングするためにすべてのエラーをその場で処理してmatch式を記載する必要はない。Rustでは?演算子を用いてエラーハンドリングを呼び出し元に任せることができます。
例えば、下記のコードのようなコードの場合、parse_two_strメソッド内でparseメソッドを2回呼び出しているが、それぞれのエラーハンドリングはparse_two_stの呼び出し元に任せることができます。

use std::num::ParseIntError;

fn parse_two_str(first_num_str: &str, second_num_str: &str) -> Result<(u32, u32), ParseIntError> {
    let first_num = first_num_str.parse::<u32>()?;
    let second_num = second_num_str.parse::<u32>()?;
    Ok((first_num, second_num))
}

fn main() {
    let first_num_str = "3";
    let second_num_str = "5";
    match parse_two_str(first_num_str, second_num_str) {
        Ok((first_num, second_num)) => println!("{}, {}", first_num, second_num),
        Err(err) => eprintln!("{}", err),
    }

    let first_num_str = "str";
    let second_num_str = "5";
    match parse_two_str(first_num_str, second_num_str) {
        Ok((first_num, second_num)) => println!("{}, {}", first_num, second_num),
        Err(err) => eprintln!("{}", err),
    }
}

実行結果は下記のような結果になります。

3, 5
invalid digit found in string

関数parse_two_strは与えられた引数それぞれに対してparseメソッドを実行しており、Result型が返されます。?演算子を使用することでOkが返された場合にはResult型を解いてOk列挙列の中身を取り出し、プログラムを継続する。Errが返された場合は呼び出し元にErr値を即時にリターンします。
?演算子と同じような動作をmatch式と使って実装できます。

fn parse_two_str(first_num_str: &str, second_num_str: &str) -> Result<(u32, u32), ParseIntError> {
    let first_num = match first_num_str.parse::<u32>() {
        Ok(first_num) => first_num,
        Err(err) => return Err(err),
    };
    let second_num = match second_num_str.parse::<u32>() {
        Ok(second_num) => second_num,
        Err(err) => return Err(err),
    };

   Ok((first_num, second_num))
}

異なる種類のエラーの扱い

上記のparse_strを修正して標準入力から入力した文字をparseするメソッドに変更したいとします。 その場合、標準入力を読み取るためにstdinを使用するが、ただ追加しただけだと下記のようになります。

use std::num::ParseIntError;
use std::io;

fn parse_str() -> Result<u32, ParseIntError> {
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer)?;
    let num = buffer.trim().parse::<u32>()?;
    Ok(num)
}

fn main() {
    match parse_str() {
        Ok(first_num) => println!("{}", first_num),
        Err(err) => eprintln!("{}", err),
    }
}

このコードはコンパイルに失敗します。なぜならio::stdin().read_line(&mut buffer)Errの型がParseIntErrorと違うからです。read_lineErrの型はstd::io::Errorです。
?演算子の作用でErr型を定義された関数の戻り値(今回の場合、ParseIntError)のErr型に変換しようとするが、ParseIntError型への変換は定義されていないため、コンパイルエラーとなります。
これを解決する方法は、関数のErr型の戻り値の定義をトレイドオブジェクトで定義すると解決します。定義はBox<dyn std::error::Error>と書きます。標準ライブラリーのエラーはすべてErrorトレイトを実装しているため、トレイトオブジェクトで定義すると変換できます。
修正したコードは下記になります。これでコンパイルができるようになります。

use std::io;

fn parse_str() -> Result<u32, Box<dyn std::error::Error>> {
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer)?;
    let num = buffer.trim().parse::<u32>()?;
    Ok(num)
}

fn main() {
    match parse_str() {
        Ok(first_num) => println!("{}", first_num),
        Err(err) => eprintln!("{}", err),
    }
}

まとめ

Rustのエラーハンドリングについて基本的な部分について勉強したことをまとめてみました。
実際にエラーハンドリングはfailureというライブラリが使われるのが一般的みたいです。こちらについてもいずれまとめてみたいと思ってます。

参考文献

Gitのコマンド集

普段仕事や日々の勉強でソースコードの管理としてGitを使っています。 そこで、普段良く使うコマンドやトラブルになったときに解決できたコマンドなどをまとめてみました。 このブログを読んでくださっている皆様にも役立つと思います。

コミット内容を確認する

指定したコミットの内容を確認します。また、確認したいコミットのハッシュ値とファイルを指定することで、そのファイルのコミット時の変更内容が確認できます。

# HEADの指すのコミット内容 
$ git show

# ファイル、コミットを指定
$ git show [コミットのハッシュ値]:[ファイル名]

コミットメッセージのみを変更する

間違えた内容のコミットメッセージを書いてしまったときに、コミットメッセージのみを修正します。コミット時に起動するエディタが立ち上がりません。

$ git commit --amend --only -m '[コミットメッセージ]'

前回のコミットからコミット内容を修正する

前回のコミットからファイルを修正します。

$ git add [変更したファイル]
$ git commit --amend

# --no-editでコミットメッセージはそのままで修正内容だけを書き換える
$ git commit --amend --no-edit

直前のコミットを取り消す

直前のコミットを取り消します。引数によりコミットを取り消したあとの変更内容の扱いが変わります。 --hardをつけなければ変更した内容はそのまま残る

# コミットを取り消して、変更内容を残す
$ git reset HEAD^

# コミットを取り消して、変更内容は残したまま、ステージングに追加された状態にする
$ git reset HEAD^ --soft

# コミットを取り消して、変更内容も取り消す
$ git reset HEAD^ --hard

追跡されていないファイルを削除する

# untrakedなファイルやディレクトリを確認する
$ git clean -dn

# untrakedなファイルやディレクトリを削除する
$ git clean -df

直前のコミットからファイルを削除する

直前のコミットからファイルの変更内容を修正、または直前のコミットから新しいファイルを追加したときにファイルを削除します。

$ git checkout HEAD^ [ファイル名]

# ファイルを修正したい場合は修正してステージングに追加する
$ git add [ファイル名]
$ git commit --amend --no-edit

gitだけから削除し、指定したファイルをuntrakedな状態にしたい場合

$ git rm --cached [ファイル名]
$ git commit --amend --no-edit

直前のコミット以外のコミット内容を取り消す

直前のコミット以前にコミットした内容を書き換えます。 例えば最後に行ったコミットから4番目を変更したい場合。

$ git rebase -i HEAD~4

するとエディタが立ち上がるので下記のように変更します。

pick 8f74ea3 first commit
pick a358c18 second commit
pick 1e6431f third commit
pick 8c8509c fourth commit
edit 8f74ea3 first commit
pick a358c18 second commit
pick 1e6431f third commit
pick 8c8509c fourth commit

変更後エディタを閉じるとrebaseが開始され、先程指定したコミットがHEADを指した状態で停止します。
ファイルを変更し、ステージングに追加してから下記コマンドを実行します。

$ git commit --amend --no

これによりエディタで指定されたコミットは修正されました。そして残りのコミットに対してrebaseを進めます。

$ git rebase --continue

コンフリクトが起こればその都度上記のように修正して解消していきます。

修正した変更をもとに戻す

HEADのコミットから変更した内容をHEAD時の状態に戻します。これはステージングに追加したファイルの変更をすべて戻します。

$ git reset HEAD --hard

# リモートブランチと同じ状態に戻すにはHEADではなく、リモートブランチを指定する
$ git reset [origin/ブランチ名] --hard

ステージングに追加した内容を除いた変更のみ、元に戻す場合は下記コマンドで変更します。

$ git checkout .

# ファイルを指定してもどに戻すこともできる
$ git checkout [ファイル名]

ステージングに追加した内容を取り消す

$ git reset
# ファイルを指定してもどに戻すこともできる
$ git reset -- [ファイル名]

ステージングに追加した内容と追加してない内容を入れ替える

ステージングに修正内容を追加してから、追加で修正していき内容を入れ替えたくなった時に使用します。

# -kでステージングにある修正を除く
$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A

ブランチを削除する

マージ済みなどの必要のないブランチを削除します。

$ git branch -d [ブランチ名]

# マージされていないブランチを削除する
$ git branch -D [ブランチ名]

また、複数ブランチを削除したいときは正規表現などで指定して削除します。
例えばmasterブランチ以外を削除するには下記コマンドを入力します。

$ git branch | grep -v master | xargs git branch -d

gitの操作を取り消す

gitはgitのコマンドなどの操作したログを保存しているので、その履歴からコマンドの取り消しができます。

$ git reflog

過去のgitのコマンド履歴が表示されるので戻りたいハッシュ値を探してresetします。

$ git reset --hard [ハッシュ値]

新しいブランチを作る

現在のブランチからの分岐で新しいブランチを作ります。

$ git checkout -b [新ブランチ名]

# コミットのハッシュ値を指定して、新しいブランチに修正内容を移動します。
$ git checkout -b [新ブランチ名] [コミットのハッシュ値]

別のブランチのファイルの修正内容を反映させる

別のブランチのファイルの修正内容をの内容を現在のブランチに反映させます。

$ git checkout [ブランチ名] -- [ファイル名]

別のブランチのコミット内容を反映させる

別のブランチで変更した内容を別のブランチでもその内容を反映させたいときにおこないます。

# あらかじめ反映させたいコミットのハッシュ値を確認しておく
$ git cherry-pick [コミットのハッシュ値]

リモートのブランチの内容をローカルブランチに反映させる

他の人と同じtopicブランチで作業をしていて、他の人がforce pushなどして自分のローカルにpullできなくなったとき、下記コマンドでローカルブランチを上書きします。

$ git fetch origin
$ git reset --hard oritin/[ブランチ名]

特定バージョンにファイルを戻したい

特定のコミットのときのファイル内容に戻したとき、コミットのハッシュ値を確認しておき、下記コマンドで反映させます。

$ git checkout [コミットのハッシュ値] -- [ディレクトリ/ ファイル名]

ブランチ名を変更する

ブランチ名を変更した時は下記コマンドを入力します。

$ git branch -m [ブランチ名] [新ブランチ名]

修正した内容を一時的に別の場所に退避させる

変更した内容を一時的に退避させて、他のブランチに切り替えたり、他の変更作業を行ったりします。

# -uでuntracked fileも退避の対象にする
$ git stash -u
# 作業ディレクトリから特定のファイルのみstashしたい時
$ git stash [ファイル名]
# branchを切り替えたり、作業したあと
$ git stash apply

stashしたファイルを現在のブランチに適用します。

# stashしたlist一覽を表示
$ git stash list

# 適用したいstashを選び、ブランチ適用する
$ git stash apply "stash@{n}"

# 適用したあとのstashを適用と同時に削除したいとき
$ git stash pop "stash@{n}"

複数のコミットを一つにまとめたい

topicブランチで作業しているときに細かくコミットしながら作業をすすめると最後にtopicブランチの内容をすべてまとめていたいことがあります。
このときは下記コマンドでコミットをまとめることができます。

$ git rebase -i HEAD~4

edit画面が下記のように表示される

pick 8f74ea3 first commit
pick a358c18 second commit
pick 1e6431f third commit
pick 8c8509c fourth commit

例えば、上記のsecond commitから新しいコミットをまとめたい時、second commitにすべてまとめたいコミットを組み合わせたいとすると、second commit以外のpickの箇所をfに変更し保存して閉じると一番古いコミットにすべてまとめることができます。

pick 8f74ea3 first commit
pick a358c18 second commit
f 1e6431f third commit
f 8c8509c fourth commit

組み合わせるコミットの名前を変更したい場合はpickfの代わりにsに変更するとエディタが立ち上がりコミットメッセージを編集することができます。

過去のコミットを見る

$ git log

# 各コミットで変更された内容をみたいとき
$ git log -p

# 変更されたファイル一覽などをみたいとき
$ git log -stat

# ある時間内のlogを見る。たとえば10/1~11/7のlogを見たい時
$ git log --since='2019-10-1' --until='2019-11-7'

# ある文字列が追加されたコミットのみを抜き出す
$ git log -S [検索したい文字列]

# 特定のファイルを含むコミットを検索する
$ git log -- [ディレクトリ/ ファイル名]

# マージコミットのみ表示する
$ git log --merges 

# マージコミットを覗いて表示する
$ git log --no-merges 

# マージコミットを覗いて表示する
$ git log --no-merges 

# コミットログを検索する
$ git log --grep="[検索したい文字列]"

ステージングに追加したファイルの変更を確認します。

$ git diff --cached

コミットやブランチ間の変更内容を確認したい

HEADのコミットやブランチと以前のコミットや別のブランチの内容を比較します 。 HEAD以外のコミットやブランチも同士もdiffを取ることは可能です。

# HEADのコミット内容と以前のコミット内容の差分を確認する
$ git diff HEAD [コミットのハッシュ値]

# 上記のファイルのみの差分を確認する
$ git diff HEAD:[ファイル名] [コミットのハッシュ値]:[ファイル名]

# ブランチ間の差分を確認する
$ git diff master:[ファイル名] develop:[ファイル名]

あるコミットが含まれているマージコミットを探す

あるコミットを含んだマージコミットを検索します。

# コミットのハッシュ値と検索するブランチを指定する
$ git log [コミットのハッシュ値]..[ブランチ名] --ancestry-path --merges

各行が変更されたコミットを確認する

対象のファイルで各業が変更されたコミットを調べたいときに下記コマンドで確認します。

$ git blame [ファイル名]

# -Lで行番号を指定できる
$ git blame -L n,m [ファイル名]

# 関数名でもしてできる
$ git blame -L :funciton [ファイル名]

変更の内容を同時に表示したい場合はlogコマンドで表示させることができます。

$ git log -L n,m:[ファイル名]

コミットを取り消す

特定のコミットを取り消します。revertで取り消すとコミットとして記録されます。

$ git revert [コミットのハッシュ値]

# コミットメッセージを編集する
$ git revert [コミットのハッシュ値] -e

# コミットせずステージングに追加した状態の場合
$ git revert [コミットのハッシュ値] -n

# マージコミットを取り消すとき、2つの親にどちらを戻すか指定するひつがある
# git log で Merge: xxxxxxx yyyyyyy のコミットIDの一番目か2番目を指定する
$ git revert -m 1  [マージコミットのハッシュ値]

取り消した内容をコミットに残したくない場合はrebaseでコミットを取り消します。

# 取り消したいコミット値よりも前のコミット値を指定
$ git rebase -i [コミットのハッシュ値]

エディタが立上がり、下記のように表示されます。

pick 8f74ea3 first commit
pick a358c18 second commit
pick 1e6431f third commit
pick 8c8509c fourth commit

上記からいらないコミットを削除し保存して、エディタを終了すると削除したコミットが削除されます。

リベースやマージを取り消す

リベースやマージの変更のリセットする場合はこの変数を指定することで取り消すことができます。

$ git reset --hard ORIG_HEAD

以上で、今までよく使っているまたは、使ったことのあるGitのコマンドをまとめてみました。 また新しくコマンドを使用したときに随時更新してきたいと思っております。

ブログ開設してみました

はじめまして。hideokaです。ブログを開設して技術的な内容や普段の出来事などを発信してみたいと思いつつ、今までできてなかったのでブログを始めてみました。

プログラマになってもうすぐ2年になります。仕事ではサーバーサイドエンジニアとしてWebの自社会社でプログラマとして働いています。社会人歴は8年近いですが、もともとは別の分野で働いていました。ジョブチェンジした形になります。きっかけはプログラマになって最先端の技術に携わりたいと思い、思い切って仕事を変えてみました。この辺の生い立ちもまたの機会にお話できたらと思ってます。

記事の内容は仕事で使っているRuby, Vue.js, shell script, vimなどや今一番興味があるRustなどの技術内容をメモしていきたいと考えてます。もちろんブログとして公開しているので正確な情報でわかりやすいようにまとめていきたいです。

それでは、次回から技術記事をどんどん書いていきます。