コピペコードで快適生活

明日使えるソースを自分のために

シェルのtestコマンド

シェルのtestコマンドの理解が怪しかったので、使い方をメモしておく。

#!bin/sh

# $?==0 で正常終了

# 数値の比較
## 1 == 1
test 1 -eq 1; echo $?

## 1 != 2
test 1 -ne 2; echo $?

## 1 < 2
test 1 -lt 2; echo $?

## 2 > 1
test 2 -gt 1; echo $?

## 1 <= 1
test 1 -le 1; echo $?

## 1 >= 1
test 1 -ge 1; echo $?


# 文字列の比較
## "abc" == "abc"
test "abc" = "abc"; echo $?

## "abc" != "aaa"
test "abc" != "aaa"; echo $?


# ファイルチェック
## file2が新しいか
touch file1
touch file2
test ./file1 -ot ./file2

## fileが存在するか
touch file3
test -f ./file3; echo $?

## fileが存在しない
test ! -f ./file4; echo $?

## ディレクトリが存在するか
mkdir dir1
test -d ./dir1; echo $?


# 略式 [ ]
# [ は testコマンド
# ] は [を使うためのオプション
[ 1 -lt 2 ]; echo $?


# AND条件 OR条件
[ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]; echo $?


# if文
## 一般的な書き方
if [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]; then
  echo "if文の中の実行"
fi

## これでもOK
if [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]
then
  echo "if文の中の実行"
fi

Goことはじめ

http://gihyo.jp/dev/feature/01/go_4beginners で学んだことをメモしていく。

はじめに

# インストール
brew install go

# ./bash_profileの設定
export GOROOT=/usr/local/opt/go/libexec
export GOPATH=$HOME
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

# ソースコードから実行
go run hello.go

# コンパイルして実行
go build hello.go
./hello

# コーディング規約に合わせてフォーマット
go fmt hello.go

# パッケージ(ここではfmt)のドキュメントを表示
go doc fmt

プロジェクトを作る

# プロジェクトディレクトリ基本形
# Goのコマンドは,このGOPATHとその下にある先の3つのディレクトリの命名規則を用いて,Makefileなどの構成ファイルは一切なしで,依存関係を解決してビルドできるようになっている。
tree
hello/
├── bin
├── pkg
└── src

# プロジェクトディレクトリにパスを通す
cd hello
export GOPATH=`pwd` # helloをGOPATHに登録

# ソースを書く1
cat src/hello/hello.go
# ---
package hello

var Message string = "hello world"

# ソースを書く2
cat src/main/main.go
# ---
package main

import (
    "fmt"
    "hello"
)

func main() {
    fmt.Println(hello.Message) // hello world
}

# 実行してみる
cd $GOPATH/src/main
go run main.go
hello world

# 実行ファイルをbin以下に作る
cd $GOPATH/src/main
go install

# binにパスを通しておくと、すぐに実行できて便利
export PATH=$PATH:$GOPATH/bin
main
hello world

ActiveRecord::Base.transactionで並列で同じレコードを扱ったときの動きメモ

排他制御まわりがちゃんとわかっていなかったので整理。

Article.find(1).value # => 1

def inc
  row = Article.find(1)
  row.value = row.value + 1

  row.save
end

# 同時並行処理する
Thread.new { inc }
Thread.new { inc }

Article.find(1).value # => 2になるか,3になるかは神のみぞ知る

だけど、

Article.find(1).value # => 1

def inc
  ActiveRecord::Base.transaction do
    row = Article.find(1)
    row.value = row.value + 1

    row.save
  end
end

# 同時並行処理する
Thread.new { inc }
Thread.new { inc }

Article.find(1).value # => これなら3になるよね!たぶん

こうするとうまくいくはず。

検証

Article.find(1).value # => 1

def inc(wait1 = 0, wait2 = 0)
  sleep wait1 if wait1 > 0
  ActiveRecord::Base.transaction do
    row = Article.find(1)
    row.value = row.value + 1

    sleep wait2 if wait2 > 0

    row.save!
  end
end

# 同時並行処理する
Thread.new { inc(0, 10) } # ... t1
Thread.new { inc(5, 0) }  # ... t2

# t1.transaction -> t1.find -> t2.transaction(待ちになる) ->
# t1.save -> t1.commit -> t2.find -> t2.save -> t2.commit
# になる想定。

Article.find(1).value # => 3にならず、2になってしまった...

だめじゃん。

検証2

悲観的ロックをかける
https://qiita.com/upinetree/items/b3329501561268f7678a

Article.find(1).value # => 1

def inc(wait1 = 0, wait2 = 0)
  sleep wait1 if wait1 > 0
  ActiveRecord::Base.transaction do
    # SELECT * FROM "articles" FOR UPDATE
    row = Article.all.lock.find(1)
    row.value = row.value + 1

    sleep wait2 if wait2 > 0

    row.save!
  end
end

# 同時並行処理する
Thread.new { inc(0, 10) } # ... t1
Thread.new { inc(5, 0) }  # ... t2

# t1.transaction -> t1.find -> t2.transaction(待ちになる) ->
# t1.save -> t1.commit -> t2.find -> t2.save -> t2.commit
# になる想定。

Article.find(1).value # => 3になった!

これだとうまくいった。

最後に

トランザクションについて

トランザクションは、完全に排他してくれるわけではなく、あくまで、複数テーブルのUPDATEで「すべて更新される」か「すべて更新されないか」を保証するだけのもの。
トランザクション内でロックがかかった場合は、COMMITかROLLBACKされるまでロックは維持される。

DBレベルのロックについて(PostgreSQLの場合)

【テーブルレベルのロック】
ROW EXCLUSIVE
UPDATE、DELETE、およびINSERTコマンドは、(参照される他の全てのテーブルに対するACCESS SHAREロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。 通常、このロックモードは、テーブルのデータを変更する問い合わせにより獲得されます。
→ つまり何も指定せずに、INSERT、UPDATE、DELETEすると、ROW EXCLUSIVEが自動でかかる。

【行レベルのロック】
FOR UPDATE
FOR UPDATEによりSELECT文により取り出された行が更新用であるかのようにロックされます。
→ 基本的にSELECTではロックはかからないけど、かけたければFOR UPDATE使う。

https://www.postgresql.jp/document/9.4/html/explicit-locking.html より

ロックについて

ロックには種類がある。

【楽観的ロック】
アプリケーションレベルのロック。
更新する直前に、対象レコードが取得時と変わっていないかを確認する。

【悲観的ロック】
DBレベルのロック。
レコード取得した時点で、対象レコードにロックをかける。
SELECT … FOR UPDATE を利用したもの。
→ 今回話しているのはこっちのロックの方。

補足

RedisObject使っているとクラスメソッドのlockが上書きされちゃうようなので、Article.lock... ではなく Article.all.lock... としたほうが無難っぽい。

https://github.com/nateware/redis-objects/blob/master/lib/redis/lock.rb#L36

シェルでクロス集計するメモ

クロス集計をBash(とawk)だけで実装した話 より学んだことメモ

処理の流れ

1.必要なカラムの抽出
2.クロス集計の行と列の要素を取り出す。

# クロス集計表の行になる部分を抽出してユニーク化&ソート
cat table.csv | cut -f 1 -d , | sort -u > rowname.txt

# クロス集計の列になる部分を抽出、改行をタブに変えて縦横変換
cat table.csv | cut -f 2 -d , | sort -u | gtr '¥n' '¥t' > header.txt

# cutコマンド補足
cut -f {取り出したい項目} -d {区切り文字}

3.awkでファイル分割する。

cat table.csv
a,ice,130
a,ice,180
b,juice,120
b,ice,130
i,oreo,210
i,oreo,210
i,oreo,210

# awkで分割した結果をファイル出力できる
cat table.csv | gawk -F, '{print > "split_"$2}'

ls
split_ice	split_juice	split_oreo	table.csv

4.awkで集計する
5.sortでソート

ls | grep "split_" | gsed -e 's/split_//' | while read file; do gawk -F, '{a[$1]+=$3;}END{for(i in a)print i","a[i];}' split_${file} | sort -k 1 > sort_${file}; done

とか

gawk '{print FILENAME","$0}' split_* | gsed 's/split_//' | gawk -F, '{a[$1","$2]+=$4}END{for(i in a)print i","a[i];}' | sort -t, -k1,2 | gawk -F, '{print $2","$3 > "sort_"$1}'

6.2で取り出した行の要素と5の結果をJOIN

# joinできなかったキーも含めて出力し、空行は0で埋める
ls | grep sort_ | gsed 's/sort_//' | while read file; do join -t ',' -1 1 -2 1 -a 1 rowname.txt sort_${file} | gawk -F, '{print $2}' | gsed 's/^$/0/g' > split_sum_${file}; done

cat split_sum_juice
0
120
0

# joinコマンドメモ
# -t 区切り文字を指定
# -1 1番目のファイルで結合キーに使う項目番号
# -2 2番目のファイルで結合キーに使う項目番号
# -a 1 LEFT JOIN (未指定の場合はINNNER JOIN)
# -a 2 RIGHT JOIN
join -t ',' -1 1 -2 1 -a 1 rowname.text sort_${file}

7.paste と cat でマージしてリストを表に加工

# pasteで列同士を結合する
cp header.txt sum_result.txt
paste rowname.txt split_sum_* >> sum_result.txt


すっかりスルー気味でしたが、クロス集計用のコマンドがあります。 – 上田ブログ

初めの方のシェル芸勉強会メモ

初めの方の問題見たらとてもピュアだった。

2012年10月27日 Hbstudy#38

ファイルの一括変換

# カレントディレクトリの中からUtilの文字をHogeに変えてDesktopに保存する。
# ※basenameはディレクトリ名を除外したファイル名を返すコマンド
grep -r "Util" ./. 2> /dev/null | gsed 's/:..*$//' | while read f; do gsed 's/Util/Hoge/' $f> ~/Desktop/$(basename $f); done

集計

# 数値範囲でグルーピングして集計する
cat /dev/urandom | gtr -dc 0-9 | fold -w 2 | gsed 's/^0//' | head -n 100000 > ages
cat ages | gawk '{print int($1 / 10)}' | sort -n | uniq -c | gawk '{print $2*10"-"$2*10+9,$1}'
0-9 9961
10-19 9805
20-29 9813
30-39 10137
40-49 10026
50-59 10198
60-69 10144
70-79 9888
80-89 10025
90-99 10003

Uspstudy20121208qonly

条件でデータを抽出

cat hoge
a 12
a 13
b 13
a 432
b 111
b 43

cat hoge | gawk '{if(a[$1] < $2){a[$1] = $2}}END{for(i in a){print i, a[i]}}'

# awkで配列の中身を出力
for(i in array) {
  print i, a[i]
}

条件でデータを集計

cat num2
a 1
b 2 3 4
a 5 6
b 7 8 9 10

cat num2 | gawk '{for(i=2; i<=NF; i++){a[$1]+=$i}}END{for(i in a){print i,a[i]}}'
a 12
b 43

曜日ごとの集計

01/01の曜日を各年ごとに集計

seq 1990 2020 | gawk '{print $1 "0101"}' | while read i; do gdate -d $i '+%w'; done | sort | uniq -c | gawk '{print $2, $1}'

# 曜日を算出(0が日曜日)
gdate -d YYYYMMDD '+%w' 

ファイルの比較

# file2から、file1にない要素を取り出す
diff <(sort file1.txt) <(sort file2.txt) | grep '>' | gsed -e 's/> //'

# 標準入力同士の diff
diff <(command1) <(command2)

RubyのThreadメモ

まずは超簡単なところから。

n = 1

# この記述に至った時点で非同期で実行開始される
t1 = Thread.new do
  puts "t1 start"
  puts "t1 n is #{n}"
  n = 2
  sleep 5
  puts "t1 end"
end

t2 = Thread.new do
  puts "t2 start"
  sleep 5
  puts "t2 n is #{n}"
  # => 2 スレッド間で変数は共有される
  puts "t2 end"
end

# joinメソッドを呼ぶと、
# スレッドの処理が終了するまで以下の処理が実行されない
t1.join
t2.join

ActiveSupport::CallbacksでRubyクラスにコールバックを差し込む

ActiveRecordじゃなくてもコールバックは使えるんやで。

class Article
  include ActiveSupport::Callbacks

  define_callbacks :before_submit
  set_callback :before_submit, :before, :validation

  def validation
    puts 'Check!!!'
  end

  def submit
    puts 'SubmitStart'

    run_callbacks :before_submit do
      puts 'Submitting....'
    end

    puts "SubmitEnd"
  end
end

Article.new.submit
# SubmitStart
# Check!!!
# Submitting....
# SubmitEnd