日記帳

プログラミングのことをつぶやく日記です。

Amazon.co.jpとAmazon.comの統合ができなくなっていた。

Amazon.co.jpAmazon.comの統合ができなくなっていた。

f:id:leokun0210:20180301130854j:plain

Kindleで英語版ジョジョを読もうと思ったので、日米のAmazonアカウントを統合しようと思った。ブログ記事とかを読み漁って、Amazon.comのアカウントのcountryをJapanにしたが、統合するボタンが出てこない。Amazon.co.jpに公式で調べてみたら、「注:アカウント結合のお申し込みは受け付けておりません。」の文言が。。。色々問題があって今は中止しているみたいだ。

Amazon.co.jp ヘルプ: Amazon.comのアカウントを結合済みのお客様

よくみたらAmazon.co.jpにもジョジョの英語版が売ってた。

f:id:leokun0210:20180301131452p:plain

ジョジョの英語版は、ペーパーブック取り寄せかなーと思っていたら、Amazon.co.jpに売ってた。統合する意味は全くなかった。早速買って中身をみてみたが、英語の勉強になる。

f:id:leokun0210:20180301131702p:plain

【後編】「プロを目指す人のためのRuby入門」(チェリー本)のMinitest部分をRSpecに書き換える

プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ)

この記事は、下記の記事「【前編】「プロを目指す人のためのRuby入門」(チェリー本)のMinitest部分をRSpecに書き換える」の続きです。前編と同じようにテストをRSpecで書いていきます。前編との変更点としてピュアなRubyでRSecのテストを書くのはなく、RSpecらしく書くことを目標にしました。理由は、DRYしやすいためです。

leokun0210.hatenablog.com

第7章 改札機プログラムの作成

前編と同じようにテスト対象のプログラムとRSpecのコードを記述します。**なおlib/フォルダのプログラムは全てチェリー本の引用です

lib/gate.rb

# 改札機を表すクラス
class Gate
  STATIONS = [:umeda, :juso, :mikuni] #:nodoc:
  FARES = [150, 190] #:nodoc:

  # Gateオブジェクトの作成
  # ==== 引数
  # *+name+ - 駅名
  def initialize(name)
    @name = name
  end

  # 改札機に通って駅に入場する
  # ==== 引数
  # *+ticket+ - 切符
  def enter(ticket)
    ticket.stamp(@name)
  end

  # 改札機を通って駅から出場する
  # ==== 引数
  # *+ticket+ - 切符
  # ==== 戻り値
  # *+boolean+ - 運賃が足りて入れば+true+、不足して入れば+false+
  def exit(ticket)
    fare = calc_fare(ticket)
    fare <= ticket.fare
  end

  # 運賃を計算する
  # ==== 引数
  # *+ticket+ - 切符
  def calc_fare(ticket)
    from = STATIONS.index(ticket.stamped_at)
    to = STATIONS.index(@name)
    distance = to - from
    FARES[distance - 1]
  end
end

lib/ticket.rb

class Ticket
  attr_reader :fare, :stamped_at

  def initialize(fare)
    @fare = fare
  end

  def stamp(name)
    @stamped_at = name
  end
end

spec/gate_spec.rb

require_relative '../lib/gate'
require_relative '../lib/ticket'

describe 'gate' do
  let(:umeda) { Gate.new(:umeda) }
  let(:juso) { Gate.new(:juso) }
  let(:mikuni) { Gate.new(:mikuni) }
  let(:ticket) { Ticket.new(fare) }

  describe '梅田から' do
    before do
      umeda.enter(ticket)
    end

    context '十三まで' do
      let(:fare) { 150 }

      it { expect(juso.exit(ticket)).to be_truthy }
    end

    context '三国まで' do
      context '運賃が足りている' do
        let(:fare) { 190 }

        it { expect(mikuni.exit(ticket)).to be_truthy }

      end

      context '運賃が足りていない' do
        let(:fare) { 150 }

        it { expect(mikuni.exit(ticket)).to be_falsey }
      end
    end
  end

  context '十三から三国まで' do
    let(:fare) { 150 }

    before do
      juso.enter(ticket)
    end

    it { expect(mikuni.exit(ticket)).to be_truthy }
  end
end

前編と比べて、RSpecのコードが複雑になってきています。梅田から他の駅で降車する処理は、3つあるのでdescribeでblockにしてあります。梅田駅に入場する処理はbeforeblockの中で処理できるよう共通化しています。letを使って共通部分は変数にしています。letの遅延展開については下記のページが参考になります。booleanの判定は、be_truthy,be_falseyでmatchさせるようにしています。。

qiita.com

実行結果は、後編は省略します。

第8章 deep_freezeメソッドの作成

lib/bank.rb

require_relative '../lib/deep_freezable'

class Bank
  extend DeepFreezable

  CURRENCIES = deep_freeze(
    {
      'Japan' => 'yen', 'US' => 'dollar',
      'India' => 'rupee'
    }
  )
end

lib/team.rb

require_relative '../lib/deep_freezable'

class Team
  extend DeepFreezable

  COUNTRIES =
    deep_freeze(['Japan', 'US', 'India'])
end

lib/deep_freezable.rb

module DeepFreezable
  def deep_freeze(array_or_hash)
    case array_or_hash
    when Array
      array_or_hash.each do |element|
        element.freeze
      end
    when Hash
      array_or_hash.each do |key, value|
        key.freeze
        value.freeze
      end
    end
    array_or_hash.freeze
  end
end

spec/deep_freezable_spec.rb

require_relative '../lib/bank'
require_relative '../lib/team'

describe 'deep_freeze_to_array' do
  context '配列の値は正しいか?' do
    it '正しい' do
      expect(Team::COUNTRIES).to eq(['Japan', 'US', 'India'])
    end
  end

  context '配列自身がfreezeされているか?' do
    it 'freezeされている' do
      expect(Team::COUNTRIES.frozen?).to be_truthy
    end
  end

  context '配列の要素が全てfreezeされているか?' do
    it 'freezeされている' do
      expect(Team::COUNTRIES.all? { |country| country.frozen? }).to be_truthy
    end
  end
end

describe 'deep_freeze_to_hash' do
  context 'ハッシュの値は正しいか?' do
    let(:currencies) { {
        'Japan' => 'yen',
        'US' => 'dollar',
        'India' => 'rupee'
    } }

    it '正しい' do
      expect(Bank::CURRENCIES).to eq(currencies)
    end
  end

  context 'ハッシュ自身かfreezeされているか?' do
    it 'freezeされている' do
      expect(Bank::CURRENCIES.frozen?).to be_truthy
    end
  end

  context 'ハッシュの要素(キーと値)が全てfreezeされているか?' do
    it '全てfreezeされている' do
      expect(Bank::CURRENCIES.all? { |key, value| key.frozen? && value.frozen? }).to be_truthy
    end
  end
end

ここは今まで通り書いています。特筆する箇所もないでしょう。

第10章 ワードシンセサイザーの作成

Effectクラス

この章は、テストが2つあるのでそれぞれ見ていきましょう。

lib/effects.rb

module Effects
  def self.reverse
    ->(words) do
      words.split(' ').map(&:reverse).join(' ')
    end
  end

  def self.echo(rate)
    ->(words) do
      # スペースならそのまま返す
      # スペース以外ならその文字を指定された回数だけ繰り返す
      words.chars.map { |c| c == ' ' ? c : c * rate }.join
    end
  end

  def self.loud(level)
    ->(words) do
      # スペースで分割 > 大文字変換と"!"の付与 > スペースで連結
      words.split(' ').map { |word| word.upcase + '!' * level}.join(' ')
    end
  end
end

spec/effects_spec.rb

require_relative '../lib/effects'

describe 'reverse' do
  let(:effect) { Effects.reverse }
  subject(:effect_reverse) { effect.call('Ruby is fun!') }

  it '文字列が反転する' do
    expect(effect_reverse).to eq('ybuR si !nuf')
  end
end

describe 'echo' do
  let(:effect) { Effects.echo(arg) }
  subject(:effect_echo) { effect.call('Ruby is fun!') }

  context '引数が2の場合' do
    let(:arg) { 2 }

    it '各文字が2回ずつ繰り返される' do
      expect(effect_echo).to eq('RRuubbyy iiss ffuunn!!')
    end
  end

  context '引数が3の場合' do
    let(:arg) { 3 }

    it '各文字が3回ずつ繰り返される' do
      expect(effect_echo).to eq('RRRuuubbbyyy iiisss fffuuunnn!!!')
    end
  end
end

describe 'loud' do
  let(:effect) { Effects.loud(arg) }
  subject(:effect_loud) { effect.call('Ruby is fun!') }

  context '引数が2の場合' do
    let(:arg) { 2 }

    it '文字列が大文字になり、単語毎の末尾に`!!`がつく' do
      expect(effect_loud).to eq('RUBY!! IS!! FUN!!!')
    end
  end

  context '引数が3の場合' do
    let(:arg) { 3 }

    it '文字列が大文字になり、単語毎の末尾に`!!!`がつく' do
      expect(effect_loud).to eq('RUBY!!! IS!!! FUN!!!!')
    end
  end
end

describeごとにexpectで実行するメソッドと引数は同じなので、新しくsubjectを使用しています。subjectexpectの引数として使用するのが一般的です。

WordSynthクラス

lib/word_synth.rb

class WordSynth
  def initialize
    @effects = []
  end

  def add_effect(effect)
    @effects << effect
  end

  def play(original_words)
    @effects.inject(original_words) do |words, effect|
      effect.call(words)
    end
  end
end

spec/word_synth_spec.rb

require_relative '../lib/word_synth'
require_relative '../lib/effects'

describe 'play' do
  let(:synth) { WordSynth.new }
  subject(:play) { synth.play('Ruby is fun!') }

  context 'effectなし' do
    it 'エフェクトも付与される' do
      expect(play).to eq('Ruby is fun!')
    end
  end

  context 'reverseエフェクトを加える' do
    let!(:add_effect) { synth.add_effect(Effects.reverse) }

    it '文字列が反転する' do
      expect(play).to eq('ybuR si !nuf')
    end
  end

  context '多くのエフェクトを加える' do
    let!(:add_effect1) { synth.add_effect(Effects.echo(2)) }
    let!(:add_effect2) { synth.add_effect(Effects.loud(3)) }
    let!(:add_effect3) { synth.add_effect(Effects.reverse) }

    it '文字列が反転する' do
      expect(play).to eq('!!!YYBBUURR !!!SSII !!!!!NNUUFF')
    end
  end
end

ここではlet!を新しく使用しています。let!itblockに入る直前に実行されます。let!の後にletが評価されます。

最後に

今回はピュアRuby風に書くのではなく、RSpecに則ってDRYな書き方にしました。subjectletの違い、beforelet!の実行タイミングなど、実際に書き直してみると新しい発見がありました。本当はshared_contextなど使用して、共通化したかったのですが、技量が足りず実現には至らなかったです。今後もRSpecのコードをネタにできる素材があったら、記事を書きます。

【前編】「プロを目指す人のためのRuby入門」(チェリー本)のMinitest部分をRSpecに書き換える

きっかけ

プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ)

「プロを目指す人のためのRuby入門」を買いました。評判に違わず素晴らしい本です。しかし、業務ではRSpecを使うのにMinitestのコードを書いても自分のためにならないと思いRSpecで書き換えることにしました。これをみてRSpecの書き方がわからない人が少しでも慣れてくれれば嬉しいです。

そもそも、プロを目指すというタイトルなのに、テストコードがテスティングフレームワークデファクトスタンダートであるRSpecではないのは何故なのだろうと疑問に思いました。その疑問に答えるヒントが、著者の伊藤惇一さんのスライドに書かれていました。

21ページ、xUnit形式(Minitest)とSpec形式(RSpec)の対比にて、

  • xUnit形式はRubyの言語がフルに利用できる
  • Spec形式はDSLに制約されやすい

と書かれています。つまり独自言語色の強いRSpecは、チェリー本のテーマであるピュアなRubyの入門からは多少外れた位置あると考えられます。


追記(2018.1.27)

コメント欄にて、著者の伊藤惇一さんよりMinitestを選んだ理由を教えていただきました。以下引用です。

  • RSpecはgemインストールの手順が必要になるので、初心者のハードルやトラブル発生のリスクが上がってしまう
  • RSpecDSLを使うので、DSLの説明が必要になる。これも初心者のハードルを上げる要因になる
  • RSpecは現場レベルのそこそこ複雑なテストを書くときには便利だが、本書ぐらいシンプルなテストであればMinitestでも十分
  • Railsチュートリアルも最近はMinitestを使っているので、Railsチュートリアル経験者にも馴染みやすい
  • xUnit形式はJUnitのような他のテスティングフレームワークでも考え方が共通しているので、他言語経験者も理解しやすい(他言語でもSpec形式のフレームワークは増えてきていますが)

前準備

RSpecを入れましょう。そのあとで、MinitestとRSpecのテストを分けるために、specフォルダを作りましょう。

gem install rspec
mkdir spec

実践

第3章 FizzBuzzブログラム

このコードをテストします。lib/のコードはチェリー本からの引用です。引用をなるべく減らそうと思ったのですが、説明する上でテスト対象のコードは残した方が理解しやすいと思い、そのまま引用しています。

lib/fizz_buzz.rb

def fizz_buzz(n)
  if n % 15 == 0
    "Fizz Buzz"
  elsif n % 3 == 0
    "Fizz"
  elsif n % 5 == 0
    "Buzz"
  else
    n.to_s
  end
end

これをRSpecで書き直しましょう。ファイル名はfizz_buzz_spec.rbにしましょう。

touch spec/fizz_buzz_spec.rb

これがRSpecのコードはこちらです。describeブロックでテストをグループ化しましょう。ここではtest_fizz_buzzというfizz_buzzメソッドをテストすることを宣言しています。さらにcontextブロックでテストで確認したい項目ごとに分けています。このテストでは、以下の4つに分けています。

  • 3の倍数を引数にしたら、Fizzが返却される
  • 5の倍数を引数にしたら、Buzzが返却される
  • 3と5の倍数を引数にしたら、Fizz Buzzが返却される
  • 上記以外だと引数が返却される

itブロックで期待している動作を明文化します。itブロックの中のexpectは、Minitestでいうassert_equalとほぼ同様の動きをします。このテストのみMinitestで書かれたコードも引用します。

test/fizz_buzz_test.rb(チェリー本より引用)

require 'minitest/autorun'
require_relative '../lib/fizz_buzz'

class FizzBuzzTest < Minitest::Test
  def test_fizz_buzz
    assert_equal '1', fizz_buzz(1)
    assert_equal '2', fizz_buzz(2)
    assert_equal 'Fizz', fizz_buzz(3)
    assert_equal '4', fizz_buzz(4)
    assert_equal 'Buzz', fizz_buzz(5)
    assert_equal 'Fizz', fizz_buzz(6)
    assert_equal 'Fizz Buzz', fizz_buzz(15)
  end
end

spec/fizz_buzz_spec.rb

require_relative '../lib/fizz_buzz'

describe 'fizz_buzz' do
  context '3または5の倍数ではない数字が引数の場合' do
    it '引数が返却される' do
      expect(fizz_buzz(1)).to eq('1')
      expect(fizz_buzz(2)).to eq('2')
      expect(fizz_buzz(4)).to eq('4')
    end
  end

  context '3の倍数が引数の場合' do
    it 'Fizzが返却される' do
      expect(fizz_buzz(3)).to eq('Fizz')
      expect(fizz_buzz(6)).to eq('Fizz')
    end
  end

  context '5の倍数が引数の場合' do
    it 'Buzzが返却される' do
      expect(fizz_buzz(5)).to eq('Buzz')
    end
  end

  context '3及び5の倍数が引数の場合' do
    it 'Fizz Buzzが返却される' do
      expect(fizz_buzz(15)).to eq('Fizz Buzz')
    end
  end
end

実行します。

$ rspec spec/fizz_buzz_spec.rb
....

Finished in 0.00374 seconds (files took 0.10134 seconds to load)
4 examples, 0 failures

オールグリーンですね!RSpecの詳しい構文はチェリー本の著者が書いた記事がわかりやすいです。

qiita.com

第4章 RGBカラー変換プログラム

同じように「第4章 RGBカラー変換プログラム」もRSpecに書き換えましょう。最初にテスト対象のコードです。

lib/rgb.rb

def to_hex(r, g, b)
  [r, g, b].inject('#') do |hex, n|
    hex + n.to_s(16).rjust(2, '0')
  end
end

def to_ints(hex)
  hex.scan(/\w\w/).map(&:hex)
end

次にRSpecです。ファイル作成は省略します。

spec/rgb_spec.rb

require_relative '../lib/rgb'

describe 'to_hex' do
  context 'r,g,bそれぞれの値が同じ値' do
    it '16進数に変換されたカラーコードが返却される' do
      expect(to_hex(0, 0, 0)).to eq('#000000')
      expect(to_hex(255, 255, 255)).to eq('#ffffff')
    end
  end
  context 'r,g,bそれぞれの値が違う値' do
    it '16進数に変換されたカラーコードが返却される' do
      expect(to_hex(4, 60, 120)).to eq('#043c78')
    end
  end
end

describe 'to_ints' do
  context 'r,g,bそれぞれの値が同じ値' do
    it '10進数に変換されたカラーコードが返却される' do
      expect(to_ints('#000000')).to eq([0, 0, 0])
      expect(to_ints('#ffffff')).to eq([255, 255, 255])
    end
  end
  context 'r,g,bそれぞれの値が同じ値' do
    it '10進数に変換されたカラーコードが返却される' do
      expect(to_ints('#043c78')).to eq([4, 60, 120])
    end
  end
end

ここでは、to_hexto_intsがそれぞれtrueになることが確認できるようにメソッドごとにdescribeブロックを分けています。早速実行してみましょう。

$ rspec spec/rgb_spec.rb
....

Finished in 0.00517 seconds (files took 0.10886 seconds to load)
4 examples, 0 failures

第5章 長さの単位変換プログラム

lib/convert_length.rb

UNITS = {m: 1.0, ft: 3.28, in: 39.37}

def convert_length(length, from: :m, to: :m)
  (length / UNITS[from] * UNITS[to]).round(2)
end

spec/convert_length_spec.rb

require_relative '../lib/convert_length'

describe 'convert_length' do
  context 'fromがメートル、toがインチの場合' do
    it 'メートルからインチに変換できる' do
      expect(convert_length(1, from: :m, to: :in)).to eq(39.37)
    end
  end

  context 'fromがインチ、toがメートルの場合' do
    it 'インチからメートルに変換できる' do
      expect(convert_length(15, from: :in, to: :m)).to eq(0.38)
    end
  end

  context 'fromがメートル、toがインチの場合' do
    it 'フィートからメートルに変換できる' do
      expect(convert_length(35000, from: :ft, to: :m)).to eq(10670.73)
    end
  end
end

どれ単位からどの単位の変換をテストしたいかをcontextブロックを分けています。実行してみましょう。

$ rspec spec/convert_length_spec.rb
...

Finished in 0.00338 seconds (files took 0.13897 seconds to load)
3 examples, 0 failures

第6章 ハッシュ記法変換プログラム

lib/convert_hash_syntax.rb

def convert_hash_syntax(old_syntax)
  old_syntax.gsub(/:(\w+) *=> */, '\1: ')
end

spec/convert_hash_syntax_spec.rb

require_relative '../lib/convert_hash_syntax'

describe 'convert_hash_syntax' do
  old_syntax = <<~TEXT
    {
      :name => 'Alice',
      :age => 20,
      :gender => :female
    }
  TEXT
  expected = <<~TEXT
    {
      name: 'Alice',
      age: 20,
      gender: :female
    }
  TEXT

  it '=>が:に置き換わる' do
    expect(convert_hash_syntax(old_syntax)).to eq(expected)
  end
end

追記(2018.1.27)

チェリー本著者である伊藤惇一さんよりコメントで指摘があり、describe直下にローカル変数を書くとスコープ範囲が広くなりテストが複雑化した時に可読性が落ちます。したがって、上記のコードはよくない例としてください。itブロックの中でローカル変数を書いてスコープ範囲を狭めましょう。

require_relative '../lib/convert_hash_syntax'

# example(it)内でローカル変数を宣言する
describe 'convert_hash_syntax' do
  it '=>が:に置き換わる' do
    old_syntax = <<~TEXT
      {
        :name => 'Alice',
        :age => 20,
        :gender => :female
      }
    TEXT
    expected = <<~TEXT
      {
        name: 'Alice',
        age: 20,
        gender: :female
      }
    TEXT
    expect(convert_hash_syntax(old_syntax)).to eq(expected)
  end
end

実行しましょう。

$ rspec spec/convert_hash_syntax_spec.rb
.

Finished in 0.00273 seconds (files took 0.1079 seconds to load)
1 example, 0 failures

最後に

RSpecDSLなので、最初は読み書きしにくいです。この続きではもっと慣れて書いてる記事をお見せしたいです。それでは。

中学時代の友人がスパロボのスタッフロールにのっていた

中学生の友人と5年振りに再会した

f:id:leokun0210:20171220231500p:plain

先日急に中学の頃の友人から久しぶりに会わないかという連絡が来た。最後に会ったのは大学生の時だから5年前だ。最初は新手の勧誘かと思ったが、彼はそんなことするキャラじゃないなと思い出した。

再開して早々に「話したいことがある」と切り出して来た。この歳だし、結婚とか転職とか地元に帰るとかかなーとか思ってたら急にスマホの画面を見せて来て一言。









友人「俺、スパロボのスタッフロールに名前がのったわ(WEBサイト制作だけど)

youtu.be

自分「マジで?緑川光に肩並べてんじゃん、すげー」

予想外すぎた。WEBサイトの制作を請け負ったらスタッフロールに名前が載ることになったみたいだ。友人はフロント部分担当らしい。ゲームのサイトを作りたくて、WEBの世界に飛び込んで来た友人は、スタッフロールにのった経緯を嬉々として話してくれた。10年以上前にスパロボという思い出を共有した仲なので、とても嬉しい。青春のワクワクが蘇った気分だった。

スパロボといえば、自分と友人との中学生の強い思い出だ。丁度D,MX,第三次α,Jが立て続けに発売してスパロボに勢いがあった時期だ。アホみたいにスパロボを2人でやり込んだ。ディジェSE-Rとかグレートゼオライマーとかの取得方法やリアル系とスーパー系のどちらをよく使うかとかで盛り上がってた。そんな友人がスパロボの作る側(ゲームではないが)になるのは想像もしなかった。大人になるにつれて忘れていくものをいつまでも忘れない友人は眩しくみえた。

スパロボX

丁度今日(2017年11月21日)、スパロボXのサイトがオープンした。このサイトにも関わっているらしい。こりゃスパロボXのスタッフロールにも期待できる。そういえば最後にやったのはUXだなぁ。久しぶりにやろうかな。

tmuxとMacのクリップボードを共有する(tmux2.4以降対応)

tmuxとMacクリップボードを共有する(tmux2.4以降対応)

f:id:leokun0210:20171216114259p:plain

なぜ書いたか

この記事を読んで、tmuxとMacクリップボードの共有をしたらエラーになりました。ネットで調べたら情報が少ないので、まとめました。

qiita.com

再現手順

環境:

macOS Sierra 10.12.4

zsh


まずrettach-to-user-namespaceをインストールします。tmuxセッションの中で、MacOS Xクリップボードにアクセスできるパッケージです。

github.com

$ brew install reattach-to-user-namespace

~/.tmux.confを以下のように書きます。

# ~/.tmux.conf

# Mac OS X pasteboardを使用できるようにする
set-option -g default-command "reattach-to-user-namespace -l zsh"

# コピーモードでvimキーバインドを使う
setw -g mode-keys vi

# 'v' で選択を始める
bind-key -t vi-copy v begin-selection
bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy"

# `Enter` でもcopy-pipeを使う
unbind -t vi-copy Enter
bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy"

# ']' でpbpasteを使う
bind ] run "reattach-to-user-namespace pbpaste | tmux load-buffer - && tmux paste-buffer"

bind-keyの引数の使い方が間違っているとのエラーメッセージが表示されます。

$ tmux source-file ~/.tmux.conf
/Users/ryuutarou/.tmux.conf:8: usage: bind-key [-cnr] [-T key-table] key command [arguments]
/Users/ryuutarou/.tmux.conf:9: usage: bind-key [-cnr] [-T key-table] key command [arguments]
/Users/ryuutarou/.tmux.conf:12: usage: unbind-key [-an] [-T key-table] key
/Users/ryuutarou/.tmux.conf:13: usage: bind-key [-cnr] [-T key-table] key command [arguments]

原因

tmux2.4からコマンドがリネームされているみたいです。

qiita.com

これが公式のドキュメントです。

https://raw.githubusercontent.com/tmux/tmux/master/CHANGES

CHANGES FROM 2.3 TO 2.4, 20 April 2017

Incompatible Changes
====================

* Key tables have undergone major changes. Mode key tables are no longer
  separate from the main key tables. All mode key tables have been removed,
  together with the -t flag to bind-key and unbind-key.

  The emacs-edit, vi-edit, emacs-choose and vi-choose tables have been replaced
  by fixed key bindings in the command prompt and choose modes. The mode-keys
  and status-keys options remain.

  The emacs-copy and vi-copy tables have been replaced by the copy-mode and
  copy-mode-vi tables. Commands are sent using the -X and -N flags to
  send-keys. So the following:

解決

まずは、tmuxのバージョン確認からです。私が今使用しているtmuxは2.6です。

$ brew  list tmux
/usr/local/Cellar/tmux/2.6/bin/tmux
/usr/local/Cellar/tmux/2.6/etc/bash_completion.d/tmux
/usr/local/Cellar/tmux/2.6/share/man/man1/tmux.1
/usr/local/Cellar/tmux/2.6/share/tmux/example_tmux.conf

2.4以降の記法にて.tmux.confを書き換えていきましょう。

2.3から2.4へ書き換える際に気をつけることは以下の通りです。

  1. フラグ-t-Tに変更する
  2. vi-<name><name>-mode-viに変更する
  3. 実行するコマンドの前にsend -Xもしくはsend-keys -Xを追加する

参考 github.com

書き換えた後の.tmux.confがこちらです。

# .tmux.conf

# Mac OS X pasteboardを使用できるようにする
set-option -g default-command "reattach-to-user-namespace -l zsh"

# コピーモードでvimキーバインドを使う
setw -g mode-keys vi

# 'v' で選択を始める
bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"

# `Enter` でもcopy-pipeを使う
unbind -T copy-mode-vi Enter
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"

# ']' でpbpasteを使う
bind ] run "reattach-to-user-namespace pbpaste | tmux load-buffer - && tmux paste-buffer"

再度読み込みます。

$ tmux source-file ~/.tmux.conf
tmux source-file ~/.tmux.conf

エラーがなくなり使えるようになりましたね!!

これでctrl + bの後に[を押して、選択モードにしてから、vで選択開始、yで選択終了してコピーします。