yossy-dev

I’m Software Enginner.

block・yield・Procについて(Ruby)

はじめに

yield・Procを見るたび調べて思い出すということを繰り返してたのでまとめておく。

まとめのために「プロを目指す人のためのRuby入門」の10章を読み直したのでその備忘録。


ブロックを使うメソッドの定義とyieldの挙動

yield はメソッド呼び出し時に紐付けられたブロックを実行する。

例)

def greeting
  puts 'おはよう'
  # ここでブロックの処理を呼び出す
  yield
  puts 'こんばんは'
end

# ブロック付きでメソッドを呼び出す
greeting do
  puts 'こんにちは'
end
# => おはよう
# こんにちは
# こんばんは

# ブロックなしでメソッドを呼び出すとyieldでブロックを呼び出そうとした際にエラーになる
greeting
# => おはよう
#    LocalJumpError: no block given (yield)

# --- エラーにしたくない場合 --- #
# block_given?でブロック付きで呼び出されたか確認できる
def greeting
  puts 'おはよう'
  # ブロックが渡されたかどうかを確認→渡されている場合true
  if block_given?
    yield
  end
  puts 'こんばんは'
end

greeting do
  puts 'こんにちは'
end
# => おはよう
# こんにちは
# こんばんは

greeting
# => おはよう
#    こんばんは

参考:プロを目指す人のためのRuby入門 第10章

yieldはブロックに引数を渡したり、ブロックの戻り値を受けとったりできる。

def greeting
  puts 'おはよう'
  # ブロックに引数を渡し、戻り値を受け取る
  text = yield 'こんにちは'
  puts text
  puts 'こんばんは'
end

greeting do |text|
  text * 2
end
# => おはよう
# こんにちはこんにちは
# こんばんは

参考:プロを目指す人のためのRuby入門 第10章

ブロックはメソッドの引数として明示的に受け取ることもできる。

ブロックを引数として受け取る場合は、引数名の前に & をつける、またそのブロックを実行する場合は call メソッドを使って実行する。

後述するが引数のブロックはProcオブジェクト(なのでcallメソッドで呼び出せる)。

def メソッド(&引数)
  # ブロックを実行する
  引数.call
end

def greeting(&block)
  puts 'おはよう'
  # ブロックが渡されていなければblockはnil
  unless block.nil?
    text = block.call('こんにちは')
    puts text
  end
end

greeting
# => おはよう

greeting do |text|
  text * 2
end
# => おはよう
#    こんにちはこんにちは

参考:プロを目指す人のためのRuby入門 第10章

Procオブジェクトについて

Procクラスはブロックをオブジェクト化するためのクラス。

procedure = 手続き、手順

Procクラスはブロック(何らかの処理)を表す。

Procクラスのインスタンス(Procオブジェクト)を作成する場合は、Proc.newにブロックを渡す

# --- Procについて --- #

# Hello!という文字列を返すProcオブジェクトを作成する。
hello_proc = Proc.new do
  'Hello!'
end

# {}でも可
hello_proc Proc.new { 'Hello!' }

# Procオブジェクトを実行したい場合はcallメソッドを使う
hello_proc Proc.new { 'Hello!' }
hello_proc.call # => "Hello!"

# 実行時に引数を利用するProcオブジェクトも定義できる。
add_proc = Proc.new { |a, b| a + b }
add_proc.call(10, 20) # => 30

# 引数にデフォルト値をつけることもできる(デフォルト値だけでなく、可変長引数やキーワード引数なども普通のメソッドと同じように受け取れる)
add_proc = Proc.new { |a = 0, b = 0| a + b }
add_proc.call # => 0
add_proc.call(10) # => 10
add_proc.call(10, 20) # => 30

# Proc.newのかわりにprocメソッドを使うこともできる
add_proc = proc { |a, b| a + b}

# --- ブロックの代わりにProcオブジェクトをメソッドの引数として渡すこともできる --- #

# メソッドが受け取れるブロックの数は1つまで
def greeting(&block)
  puts 'おはよう'
  text = block.call('こんにちは')
  puts text
end

repeat_proc = Proc.new { |text| text * 2 }
# Procオブジェクトをブロックの代わりに渡す際は&を付けて渡す。(ないとブロックではなく普通の引数とみなされる)
greeting(&repeat_proc)
# => おはよう
#    こんにちはこんにちは

# --- Procオブジェクトを普通の引数として受け取って実行する --- #

# Procオブジェクトはただのオブジェクトなので引数として渡す分には制限がない
def greeting(original_proc)
  puts 'おはよう'
  text = original_proc.call('こんにちは')
  puts text
end

repeat_proc = Proc.new { |text| text * 2 }
# 普通の引数としてProcオブジェクトを渡す
greeting(repeat_proc)
# => おはよう
#    こんにちはこんにちは

# --- lambdaでProcオブジェクトを作成する --- #

# 以下の方法でもProcオブジェクトを作成できる
->(a, b) { a + b }
lambda { |a, b| a + b}

参考:プロを目指す人のためのRuby入門 第10章

&とto_proc

Procオブジェクトをブロックとして渡したい場合は引数の前に&をつける必要がある

reverse_proc = Proc.new { |s| s.reverse }
# mapメソッドにブロックを渡すかわりにProcオブジェクトを渡す(&が必要)
['Ruby', 'Go', 'Rust'].map(&reverse_proc) # => ['ybuR', 'oG', 'tsuR']

&の役割

  • Procオブジェクトをブロックとして認識させる
  • 右辺(上の例だとreverse_proc)のオブジェクトに対して, to_proc メソッドを呼び出して戻り値として得たProcオブジェクトをブロックを利用するメソッドに与える
    • → ただし元からProcオブジェクトだった場合はto_procメソッドを呼んでも自分自身が返るだけ。

シンボルは to_proc メソッドを持っている。

シンボルを変換してできたProcオブジェクトが変わっている点

  • シンボル自身はレシーバに対して呼び出すメソッドの名前になる
  • 第1引数がシンボルで指定したメソッドのレシーバになる
  • 第2引数以降はシンボルで指定したメソッドの引数になる

例)

# シンボル→Procオブジェクトに変換したときの挙動
split_proc = :split.to_proc
split_proc # => #<Proc:0x00007fd43a890df8(&:split)>

# 引数が一つの場合、'a-b-c-d e'.splitと同じ
split_proc.call('a-b-c-d e') # => ["a-b-c-d", "e"]

# 引数が複数の場合、'a-b-c-d e'.split('_', 3)のように
# 第2引数以降はシンボルで指定したメソッドの引数になる
split_proc.call('a-b-c-d e', '-', 3) # => ["a", "b", "c-d e"]

参考:プロを目指す人のためのRuby入門

map(&:upcase)のような書き方の挙動

['ruby', 'go', 'rust'].map { |s| s.upcase } # => ["RUBY", "GO", "RUST"]
['ruby', 'go', 'rust'].map(&:upcase) # => ["RUBY", "GO", "RUST"]

上記からこの書き方の挙動が理解できる

  1. &:upcaseはシンボルの:upcaseに対してto_procオブジェクトを呼び出す
    • :upcase.to_proc
  2. シンボルの:upcaseがProcオブジェクトに変換され、mapメソッドにブロックとして渡される(こんな感じ)
  3. 上記②で作ったProcオブジェクトはmapメソッドから配列の各要素を実行時の第一引数として受け取る。
  4. 第一引数はupcaseメソッドのレシーバとなる。→ つまり配列の各要素に対してupcaseメソッドを呼び出す。

    ruby # 2でブロックとしてmapに渡すので、2 ~ 4は単にこれをやってる ['ruby', 'go', 'rust'].map do |block_arg| block_arg.upcase end

  5. 配列の各要素が大文字に変換された新しい配列がmapメソッドの戻り値になる

おわりに

まとめ

  • ブロックは手続き(一連の処理)のまとまり
  • ブロックやProcオブジェクトを渡せるようなメソッドを定義すると処理の一部に対して外部からカスタマイズすることができる振る舞いを組み込める

map(&:upcase)みたいな書き方なんとなくしてたけどどういう動きで処理を実行できてるのかちゃんと理解できてよかった。

時々yieldとか見るとなんだっけ、、ってなってしまってたので今後に活きるといいな。

参考資料

  • プロを目指す人のためのRuby入門

VSCodeのVim拡張(VSCodeVim)でNomalMode切り替え時に英数入力に変更する。

はじめに

VSCodevim拡張機能で日本語入力の状態で escctrl + [ などでNomalModeに切り替えた際に っj みたいに日本語入力のままキー打ってしまうことが多くつらかったので NomalMode切り替え時に入力方式も英数入力に切り替えるという設定を加えたのでその備忘録です。


環境

流れ

  1. im-selectのインストール
  2. im-selectを利用した設定をVSCodeに設定する。

1. im-selectのインストール

ドキュメントの記載のとおりim-select というコマンドラインツールを利用するのでインストールします。

curl -Ls https://raw.githubusercontent.com/daipeihust/im-select/master/install_mac.sh | sh

上記コマンドでインストールします。

実行後以下のようにコマンドが実行できるようになっていればインストール完了です。

$ curl -Ls https://raw.githubusercontent.com/daipeihust/im-select/master/install_mac.sh | sh
* Downloading im-select...
* im-select is installed!
*
*
*
* now run "im-select" in your terminal!

$ im-select                                                                                                                                                                                                                                                                                                [15:58:54]
com.google.inputmethod.Japanese.Roman

im-selectコマンドで入力方式の切り替えができます。

私はGoogle日本語入力を利用しているので以下のような指定の仕方になります。

# Google英数入力へ切り替え
$ im-select com.google.inputmethod.Japanese.Roman

# Google日本語入力へ切り替え
$ im-select com.google.inputmethod.Japanese.base

# 現在の入力方式を出力
$ im-select
com.google.inputmethod.Japanese.Roman

2. im-selectを利用した設定をVSCodeに設定する。

settings.json に以下の設定を追加します。

// insertモードを抜けた際のコマンド実行の有効切り替え
"vim.autoSwitchInputMethod.enable": true,

// デフォルト(ノーマルモード)の入力方式の設定
"vim.autoSwitchInputMethod.defaultIM": "com.google.inputmethod.Japanese.Roman",

// 現在の入力状態のパラメータを取得するコマンドのパス
"vim.autoSwitchInputMethod.obtainIMCmd": "/usr/local/bin/im-select",

// 入力状態の切り替えを行うコマンドのパス。{im}にデフォルトの入力方式が渡される。
"vim.autoSwitchInputMethod.switchIMCmd": "/usr/local/bin/im-select {im}"

設定を反映してノーマルモード切り替えした際に入力方式が英数入力に切り替われば設定完了になります。

Special Thanks!

シェルスクリプトのif文とコマンドの終了ステータス

はじめに

新しいLinuxの教科書を読んだので備忘録

シェルスクリプトのif文の基本と終了ステータスについて


シェルスクリプトのif

基本の書き方

if <コマンド1>; then
    <コマンド1>の結果が真の場合の処理
elif <コマンド2>; then
    <コマンド2>の結果が真の場合の処理
elif <コマンド3>; then
    <コマンド3>の結果が真の場合の処理
else
    上記の結果がすべて偽である場合の処理
fi

sample-if.sh

#!/bin/bash

if [ "$1" = "bin" ]; then
    echo "OK"
else
    echo "NG"
fi

上記を実行

$ ./sample-if.sh bin
OK

$ ./sample-if.sh hello
NG

ifにはコマンドを指定する。

ちなみに上記sampleのifの [] もコマンドなので、直前直後にスペースを開けないとエラーになる。

ifで指定したコマンドが真か偽かはコマンドの終了ステータスで判断する

コマンドの終了ステータスが 0 の場合は真、 0 以外の場合は偽と判定される。

コマンドの終了ステータス

lsやgrepなどのコマンドはすべて終了時に終了ステータスと呼ばれる整数値を返す。

  • コマンドが正常に終了した場合は 0 を返す。
  • エラー時には0以外の値を返す。

終了ステータスがどのような値を取るかはコマンドによって違いがある。

ちなみに $? というシェル変数で前回のコマンドの終了ステータスを参照できる

$ ls
parameters.sh sample-if.sh

$ echo $?
0

ファイルのパーミッションの設定について

はじめに

「新しいLinuxの教科書」を読んだので備忘録として書く。

ファイルのパーミッションの設定の仕方


ファイルの権限の付与

r(読み取り) w(書き込み) x(実行)

それぞれ上記権限を持つ

該当のファイルがどの権限を持っているかは ls -l で確認できる。(ディレクトリのパーミッションも確認する場合は -d オプションを付ける)

$ ls -la
total 8
drwxr-xr-x   3 taiki  staff    96 Nov 14 16:59 .
drwxr-xr-x+ 83 taiki  staff  2656 Nov 14 17:01 ..
-rw-r--r--   1 taiki  staff    94 Nov 14 16:56 parameters.sh

パーミッションの設定は chmod コマンドで設定できる

シンボルモード(相対指定)と数値モード(絶対指定)での指定があるが数値モードだけ書いておく。

数値モードは数値を指定して絶対指定で指定する(過去の権限に依存しない設定)。

それぞれ

  • r → 4
  • w → 2
  • x → 1

の数値が設定されているので、設定したいパーミッションの数値を足した値を

「オーナー」「グループ」「その他のユーザ」の順に並べて指定する。

以下ではオーナーにすべての権限(4+2+1)、グループ、その他のユーザに読み取り権限と実行権限(4+1)を付与している。

$ chmod 755 parameters.sh

$ ls -la
total 8
drwxr-xr-x   3 taiki  staff    96 Nov 14 16:59 .
drwxr-xr-x+ 83 taiki  staff  2656 Nov 14 17:03 ..
-rwxr-xr-x   1 taiki  staff    94 Nov 14 16:56 parameters.sh

他のユーザに見られてもいい実行可能ファイルは755でパーミッションを設定しておけばOK。

シェルスクリプトでのコマンドライン引数の受け取り方

はじめに

新しいLinuxの教科書を読んだので備忘録

シェルスクリプトでのコマンドライン引数の受け取り方


コマンドライン引数の受け取り方

$1などの変数に格納される

$# で引数の数も出力できるので、必要な引数の数を指定したいときなどに利用する。

例えば以下のようにシェルスクリプトを記述する。

./parameters.sh
#!/bin/bash

echo "\$0 = $0"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$# = $#" 

そして以下のようにしてコマンドライン引数を指定すると指定した引数が受け取れる。

$ ./parameters.sh aaa bbb ccc
$0 = ./parameters.sh
$1 = aaa
$2 = bbb
$3 = ccc
$# = 3

$@ で受けとったすべてのコマンドライン引数を出力できる(ダブルクォートで囲むとそれぞれのコマンドライン引数がダブルクォートで囲まれる)

$* で同じようにすべての位置パラメータを取得できるがこちらはダブルクォートで囲むとすべての引数が一つの文字列としてダブルクォートで囲まれるので、なにかのファイルにそのまま引数で渡したいときなどは $@ を使うことが多い。

$0 で実行時のシェルスクリプト名が出力できる

コマンドの出力結果をシェルスクリプト内で利用する

はじめに

新しいLinuxの教科書を読んだので備忘録

コマンドの出力結果をシェルスクリプト内で利用したいときの書き方。


コマンド置換

コマンドの出力結果をシェルスクリプト中で利用したいとき、コマンド置換でコマンドの結果を文字列として取得することができる。

  • $(<command>) で出力結果を取得できる。
$ date '+%Y-%m-%d'
=> 2020-11-1

hoge.shに以下のように記述。
#!/bin/bash

filename=$(date '+%Y-%m-%d')
touch "$filename"

$ ./hoge.sh
=> 2020-11-1というファイルができる。

ghq + peco + GitHub CLIの組み合わせが便利

はじめに

今はpecoではなくfzfを使って同様のことをしているのですが、 以前利用していた有名なghq・pecoの組み合わせがとても便利だったので一例を記事にしてみました。


目的

今更ですがghq,peco,GitHubCLIが超便利だったので紹介します。 一例としてこんな感じでターミナルからブラウザでリポジトリを開けるようにします。

f:id:yossy-dev:20210605185120g:plain

ghqとは

リポジトリ管理ツールです。 ghq get <リポジトリのURL>ghqで設定したディレクトリにgit cloneしてくれます。 さらにghq listghqで管理しているリポジトリ一覧を一覧で出力してくれます。 詳細は作者の方のブログ公式のリポジトリを参照してください。

  • インストール例(Mac OS
$ brew install ghq
  • 設定例
# ~/.gitconfig
[ghq]
  root = ~/projects # gitで開発を行っているディレクトリ
  root = ~/go/src # goのリポジトリは別で管理しているという人用(一例です)

pecoとは

標準入力で渡された一覧を表示しつつ、インクリメンタルサーチでテキストを絞っていけるツールです。 もともとctrl + rインクリメンタルサーチできると思いますが、pecoだとコマンド履歴を表示した上で絞っていけるので超便利です。 いつもありがとうございます。 詳細は公式やこちらの記事とか色んな方が紹介してくださっているのでそちらを参照してください。

  • インストール例(Mac OS
$ brew install peco

Github CLIとは

GitHub2020年9月に正式版を公開した、コマンドライン上でGitHubの操作を行えるツールです。 マニュアルが用意されているので使い方はこちらに載っています。

  • インストール例(Mac OS
$ brew install gh
  • インストール後の認証
$ gh auth login

認証まで完了したらgh repo view <リポジトリ名>とかでリポジトリの内容を閲覧してみましょう。 ターミナルにREADMEの内容が出力されると思います。

実用例

ghq,peco,GitHubCLIがインストールできて準備ができたら実際に使ってみます。 エイリアスは設定しなくてもコマンド打てますが、設定しておくのがおすすめです!

対象リポジトリをブラウザでターミナルから開く

  • 以下を.zshrc(or .bashrcなど)に記載
# aliasはお好きなのを設定してください。
alias ghw='gh repo view -w $(ghq list | peco)
  • 設定を反映する
$ source ~/.zshrc
  • Demo こんな感じでターミナルからブラウザでリポジトリを開くことができます。

f:id:yossy-dev:20210605185241g:plain

番外編

以下でこんなのもあるよというのも記載しています!

# ~/.zshrc
# ghqで管理しているリポジトリにpecoで絞って移動する。
alias cdp='cd $(ghq list -p | peco)'

# ghqで管理しているリポジトリをpecoで絞ってVSCodeで開く。
alias vs='code $(ghq list -p | peco)'

# カレントディレクトリのOpenになっているPRをブラウザで開く。
alias pr='gh pr view --web'

終わりに

ghqとかpecoなどを使った組み合わせは他にもたくさん探したらでてくると思いますが、 今回はGithubCLIとの組み合わせを載せてみました。 こんな便利なものを公開してくれている方々に本当に感謝ですね。

GithubCLI便利と言っておきながら仕様頻度高いのはVSCode開くコマンドとディレクトリ移動のコマンドだと思います😂 「これもええで!」みたいなのがあればぜひ教えて下さい!

参考

ありがとうございます!