シェルスクリプト分岐・ループ構文例
概要
Linux 上で便利な機能やマクロ的に処理を行うためにシェルスクリプトを記述することがある。
その中でも比較的よく見かける分岐・ループ構文をメモ。
ファイル状態によって操作を変える
テストコマンドでファイルの状態を確認できる。他にも様々なことができる。詳しくは下記参照。
Linuxコマンド集 - 【 test 】 条件式の真偽を判定する:ITpro
if [ -e 'aaa.txt' ]; then # ファイル aaa.txt が存在するかどうか echo 'exist' # 存在するならexist出力 fi
条件満たす場合・満たさない場合の処理実行
if [ -e 'aaa.txt' ]; then # ファイル aaa.txt が存在かどうか echo 'exist' # 存在するならexist出力 else echo 'not found' # 存在しないならnot found出力 fi
複数条件および処理実行
if [ -s 'aaa.txt' ]; then # ファイル aaa.txt が存在して空ファイルでない場合 echo 'size not 0' # 存在して空ファイルでないならsize not 0出力 elsif [ -e 'aaa.txt' ]; # ファイルが存在するかどうか echo 'empty' # 空ファイルが存在するならempty出力 else echo 'not found' # ファイルが存在しないならnot found出力 fi
exitコードを利用した処理分岐
bash ではexitコードが0なら真、そうでなければ偽となる。これらを利用して &&, || を利用して分岐を書ける。
- && は直前のステータスが 0 なら次を実行
- || は直前のステータスが 0 以外なら次を実行
なお、「{」「}」はコマンドのグループ化であり、各文字前後にはスペース必要。「}」の直前コマンドにはセミコロンが必要。
{ [ -s 'aaa.txt' ] && echo 'size not 0'; } || { [ -e 'aaa.txt' ] && echo 'exist'; } || echo 'not found'
上記を branch.sh として作成してテストすると下記のように出力内容が変化する。
$ rm aaa.txt # ファイルが存在しないようにする rm: 'aaa.txt' を削除できません: No such file or directory $ . branch.sh not found $ touch aaa.txt # サイズ 0 のファイル作成 $ . branch.sh exist $ echo "aaa" >> aaa.txt # ファイルに aaa を追加してサイズを増やす $ . branch.sh size not 0
変数の中身に応じて操作を変更する
NUM=100 case $NUM in # NUMの値が 100) echo 'ok';; # 100 なら 0) echo 'not work';; # 0 なら *) echo '???';; # それ以外 esac
ある範囲の数値に対する操作
SUM=0 for NUM in `seq 1 100`; do # 1-100までに対して操作 SUM=`expr $SUM + $NUM` # 加算 done echo $SUM # 結果出力
bashならば以下の記法が使える。
SUM=0 for ((i=1; i<=100; i++)) do SUM=`expr $SUM + $i` done echo $SUM
ある条件を満たすまで操作する
合計が5000超えるまで計算。
SUM=0 NUM=0 while [ $SUM -lt 5000 ] && NUM=`expr $NUM + 1`; do SUM=`expr $SUM + $NUM` done echo $SUM # 合計値
引数に END が出るまで引数を出力する
while [ -n "$1" ] && [ "$1" != "END" ]; do # 現在処理中の引数が空文字でも END でもないなら処理を行う echo "$1" # 位置パラメータ1番目出力 shift # 位置パラメータをシフト done
シェル引数に対する処理
# シェル引数は何もしなければ位置パラメータに入っている for ARG in "$@"; do # 各位置パラメータに対して操作 echo "$ARG" # 出力 done
標準入力から各要素を取り出し操作
区切り文字としてカンマ(,)が使われた場合の例
read INPUT # このコマンドで入力待ちになる IFS=',' # 区切り文字の指定 set -- $INPUT # 位置パラメータに入力内容をセット for ELEMENT in "$@"; do # それぞれに対して操作 echo "$ELEMENT" done
特定ファイル群に対する操作
for FILE in *.txt; do # カレントディレクトリのテキストファイルに対して操作 echo "$FILE" # ファイル名出力 done
直前のコマンド処理結果を操作
cat 'aaa.txt' | grep "AAA" | while read LINE; do # aaa.txt 内にある AAA が存在する行に対して操作 echo "$LINE" # 内容出力 done
ファイル各行に対する処理
IFS=',' # 区切り文字指定。今回はCSVを処理するためカンマ指定 while read LINE; do # 一行読み込み set -- $LINE # 位置パラメータに一行情報セット echo "(1)$1, (2)$2, (3)$3" # 各列情報出力 done < 'test.txt' # 読み込むファイルはリダイレクトで受け取る
RaspberryPi で複数 Wifi 環境に個別設定を行う方法
概要
勉強会参加するときに RaspberryPi3 を持ち歩いているのだが、
自宅・外出先で Wifi のプライベートアドレス構成が微妙に異っており、事前に設定しておかないといけなかった。
もし設定を忘れた場合、その場でディスプレイ・マウス・キーボードを借りて設定するという若干手間になっていた。
文献がない
RaspberryPi に固定 IP を設定する方法はかなりの数存在する。
しかし、これが複数 Wifi に設定を適応するとなると途端に記事は少なくなる。
その中でも見つけたのがコレ。
Raspberry Piで複数Wifiで個別の固定IPを指定する方法 – 1ft-seabass.jp.MEMO
設定方法をザックリいってしまえば。
- Wifi の設定は /etc/wpa_supplicant/wpa_supplicant.conf
- ネットワーク設定は /etc/network/interfaces
そして、wpa_supplicant.conf にネットワークごとの識別子(id_str要素)を作成しておき、
それを interfaces で認識して各々設定するというもの。
設定例
/etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=GB network={ ssid="SSID-xxxx" psk="pass_phrase" key_mgmt=WPA-PSK id_str="home" } network={ ssid="SSID-yyyy" psk="pass_phrase" key_mgmt=WPA-PSK id_str="out" }
/etc/network/interfaces
auto wlan0 allow-hotplug wlan0 iface wlan0 inet manual wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf iface default inet dhcp iface home inet static address 192.168.1.100 netmask 255.255.255.0 gateway 192.168.1.1 iface out inet static address 192.168.20.30 netmask 255.255.255.0 gateway 192.168.20.1
ネットワーク設定構成が異なる場合(dhcpcd.conf も設定する場合)
RaspberryPi 3 を購入したとき最新の raspbian はネットワークの設定が変わっていた。
- Wifi の設定は /etc/wpa_supplicant/wpa_supplicant.conf
- ネットワーク定義 /etc/network/interfaces
- 個別ネットワーク詳細設定 /etc/dhcpcd.conf
に分かれてしまっており、どうやら dhcpcd.conf は別の仕組みで動くらしい。
個別の設定部分を dhcpcd.conf に書いてみるが動かない・・・。(違う仕組みなので当然なのだが)
しかし、デフォルトの設定構成を変えるような interfaces を変更したくない。
といっても、該当する文献が見つからなかった。・・・ということで、dhcpcd.conf マニュアルを眺めてみたら。
$ man dhcpcd.conf
なんか気になる設定要素発見。
ssid ssid Subsequent options are only parsed for this wireless ssid.
設定したら、あっさりできてしまった。
/etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=GB network={ ssid="SSID-xxxx" psk="pass_phrase" key_mgmt=WPA-PSK } network={ ssid="SSID-yyyy" psk="pass_phrase" key_mgmt=WPA-PSK }
/etc/network/interfaces
iface eth0 inet manual allow-hotplug wlan0 iface wlan0 inet manual wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
/etc/dhcpcd.conf
interface wlan0 ssid SSID-xxxx static ip_address=192.168.1.100/24 static routers=192.168.1.1 static domain_name_servers=192.168.1.1 ssid SSID-yyyy static ip_address=192.168.20.30/24 static routers=192.168.20.1 static domain_name_servers=192.168.20.1
どこでも気軽にもっていけるようになった!
普段持ち歩いている無線 Wifi に繋げるようにしておき、
新しい環境でもノート PC と無線 Wifi があれば一度設定すればいつでも使用可能に。
- 無線 Wifi にノート PC と RaspberryPi 接続
- 無線 Wifi のプライベートIP経由で SSH 接続
- 外出先のネットワーク設定を行う(接続優先度は無線 Wifi より低く設定)
- 無線 Wifi 停止・RaspberryPi 再起動
- 動かなかったら無線 Wifi 使って再度 SSH ログインして調査。
仮にネットワーク設定ミスってしまっても、無線 Wifi にはいつでもつなげるのがいいですね。
VirtualBox+Vagrant+Chef(knife-solo) 環境構築(Windows 7+Cygwin) 苦戦記
前回記事
VirtualBox+Vagrant 環境構築(Windows 7+Cygwin) 苦戦記 - Status Code 303 - See Other
前回記事概要
Windowsでコンソールとして Cygwin を利用し、VirtualBox + Vagrant で仮想環境を作成できるようにした。
このとき、苦戦した内容と解決に至るまでをメモった。
この記事で記述すること
Chef を導入し、前回の仮想環境上にインフラ環境をセットアップする。
しかし、Cygwin で行うために少し面倒な設定が必要になる。
ソフトウェアについて
VirtualBox? Vagrant? Cygwin?
前回記事参照。
Chef?
Chef 社が提供。インフラ設定をコードで記述、設定したいサーバにコード内容を実行するためのツール。
サーバの構築内容をコードとして管理することができる。(Infrastructure as Code)
Chef には大きく2通りの形態があり(Chef-solo/Chef-Server + Chef-Client)、
今回は小規模向けである Chef Solo を用いる。この中でも拡張ユーティリティの knife-solo を用いる。
その他の Chef の種類や特徴については下記を。
あなたに合ったChefはどれ? 〜 おすすめ構成確認チャート #getchef - クリエーションライン株式会社
なんのため?
- 本番を想定したテスト環境を作成したり、その上で簡単なテストしたりするため。
- 継続的デリバリー(CD)を実現するため。
(参考:DevOps時代の開発者のための構成管理入門(終):継続的デリバリ/デプロイを実現する手法・ツールまとめ (1/2) - @IT)
環境構築
手順
Chef インストール 手順
gem を使ってインストールする方法もあるようだが、現在ではインストーラを使うのが主流らしい。
基本的に手順は以下を参考にさせてもらった。
Chef for Windows
Windows7にChef(11.6.0)とVagrant(1.7.2)を入れてプロビジョニングしてみた - カタカタブログ
つまずいたところ
インストール先の変更
インストーラがインストール先の選択が出る。ここで「E:\opscode\」を選択。
そうするとインストールは完了するが、Cygwin 上で実際に使ってみるとなぜか動かない。
調べるとどうやら、インストーラのバグらしい。(c:\opscode にあることを前提としたコードがある?)
Windows can't install into anything other than C:\opscode · Issue #68 · chef/chef-dk · GitHub
しかたないので、シンボリックリンクを作成し C:\opscode を E:\opscode に関連付けしてから再インストールした。
(再インストールしなくても動くかもだけど念のため)
※ 以下のコマンドに限り、管理者モードで起動したコマンドプロンプトを使用。
> mklink /d C:\opscode E:\opscode
Cygwin上のパスを制御する
Cygwin は Linux 環境をWindows上に作るだけあってそのフォルダ構成が異なる。
これが原因でエイリアスを設定しないと動かない。(コマンドが実行できない)
$ chef -v C:\opscode\chefdk\embedded\bin\ruby.exe: No such file or directory -- /cygdrive/c/opscode/chefdk/bin/chef (LoadError)
関連するエイリアスを全て設定に書き込み、その設定を現在のセッションに適用する。
(下記ではEドライブだが、Cドライブのファイル名で指定してもOK)
$ echo "alias chef='E:/opscode/chefdk/bin/chef'" >> ~/.bashrc $ echo "alias knife='E:/opscode/chefdk/bin/knife'" >> ~/.bashrc $ echo "alias gem='E:/opscode/chefdk/embedded/bin/gem'" >> ~/.bashrc $ source ~/.bashrc
これで次回から Cygwin を起動するたびに設定される。
knife solo の障害
Chef コマンドは動くようになった。次は knife solo コマンドを使うため下記を実行する。
gem install knife-solo gem install berkshelf chef gem install knife-solo
インストール完了後 knife solo コマンドを動かすがどうも動かない。knife configure も同様だ。
どうやら gem で使用するライブラリ(net-ssh)との依存関係が原因のようだ。
$ knife solo init . C:/opscode/chefdk/embedded/lib/ruby/site_ruby/2.1.0/rubygems/specification.rb:2288:in `raise_if_conflicts': Unable to activate knife-solo-0.5.1, because net-ssh-3.1.1 conflicts with net-ssh (< 3.0, ~> 2.7) (Gem::ConflictError) from C:/opscode/chefdk/embedded/lib/ruby/site_ruby/2.1.0/rubygems/specification.rb:1408:in `activate' from C:/opscode/chefdk/embedded/lib/ruby/site_ruby/2.1.0/rubygems.rb:200:in `rescue in try_activate' from C:/opscode/chefdk/embedded/lib/ruby/site_ruby/2.1.0/rubygems.rb:193:in `try_activate' from C:/opscode/chefdk/embedded/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:126:in `rescue in require' from C:/opscode/chefdk/embedded/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:40:in `require' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.5.1/lib/chef/knife/cook.rb:1:in `<top (required)>' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife/core/subcommand_loader.rb:100:in `load' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife/core/subcommand_loader.rb:100:in `block in load_commands' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife/core/subcommand_loader.rb:100:in `each' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife/core/subcommand_loader.rb:100:in `load_commands' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife/core/subcommand_loader.rb:110:in `load_command' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife/core/subcommand_loader.rb:124:in `command_class_from' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:153:in `subcommand_class_from' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:214:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application/knife.rb:148:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/bin/knife:25:in `<top (required)>' from E:/opscode/chefdk/bin/knife:61:in `load' from E:/opscode/chefdk/bin/knife:61:in `<main>'
knife solo ユーティリティの既知の障害みたい。 Unable to activate knife-solo-0.5.1 · Issue #811 · chef/chef-dk · GitHub
直す方法を探したが、ダウングレード記事が多くあまり気が進まなかった。
ここでしばらく諦めモードに。
しかし、ver 0.6.0 でこの問題が直ったためアップデートして 0.5.1 -> 0.6.0 にアップデートすると動いた
$ knife configure WARNING: No knife configuration file found Where should I put the config file? [E:/cygwin/home/hoshikouki/.chef/knife.rb] Please enter the chef server URL: [https://hoshikouki-PC.flets-east.jp:443] Please enter an existing username or clientname for the API: [hoshikouki] Please enter the validation clientname: [chef-validator] Please enter the location of the validation key: [/etc/chef-server/chef-validator.pem] Please enter the path to a chef repository (or leave blank): ***** You must place your client key in: E:/cygwin/home/hoshikouki/.chef/hoshikouki.pem Before running commands with Knife ***** You must place your validation key in: E:/etc/chef-server/chef-validator.pem Before generating instance data with Knife ***** Configuration file written to E:/cygwin/home/hoshikouki/.chef/knife.rb
rsync コマンド失敗
Cookbook を作成し、debian-jessieホストを以下のコマンドで設定。
$ vagrant ssh-config --host debian-jessie >> ~/.ssh/config
リモート先で Chef 実行しようと knife solo bootstrap コマンド(prepare + cook コマンド)を実行。
$ knife solo bootstrap debian-jessie Bootstrapping Chef... % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 19602 100 19602 0 0 17371 0 0:00:01 0:00:01 --:--:-- 17377 debian 8 x86_64 Getting information for chef stable 12.9.41 for debian... downloading https://omnitruck-direct.chef.io/stable/chef/metadata?v=12.9.41&p=debian&pv=8&m=x86_64 to file /tmp/install.sh.8361/metadata.txt trying wget... sha1 2f8503b7177437418337e7ad01064b77aefdafad sha256 3f54dd6121acaea52620a58cd06f4192cd7b0e6c82f2047f10876494c93ac89d url https://packages.chef.io/stable/debian/8/chef_12.9.41-1_amd64.deb version 12.9.41 downloaded metadata file looks valid... downloading https://packages.chef.io/stable/debian/8/chef_12.9.41-1_amd64.deb to file /tmp/install.sh.8361/chef_12.9.41-1_amd64.deb trying wget... Comparing checksum with sha256sum... Installing chef 12.9.41 installing with dpkg... (Reading database ... 63807 files and directories currently installed.) Preparing to unpack .../chef_12.9.41-1_amd64.deb ... Unpacking chef (12.9.41-1) over (12.9.41-1) ... Setting up chef (12.9.41-1) ... Thank you for installing Chef! Running Chef on debian-jessie... Uploading the kitchen... WARNING: Local cookbook_path 'E:/cygwin/home/hoshikouki/cookbooks' does not exist WARNING: Local role_path '.\roles' does not exist WARNING: Local data_bag_path '.\data_bags' does not exist WARNING: Local environment_path '.\environments' does not exist Generating solo config... rsync: link_stat "/cygdrive/C/Program Files/cygwin64/tmp/solo.rb20160605-18680-1tgoomz" failed: No such file or directory (2) rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2] ERROR: RuntimeError: Failed to launch command ["rsync", "-rL", "--chmod=ugo=rwX", "--rsh=ssh vagrant@debian-jessie", "--delete-after", "-zt", "--exclude=revision-deploys", "--exclude=.git", "--exclude=.hg", "--exclude=.svn", "--exclude=.bzr", "/cygdrive/C/Program Files/cygwin64/tmp/solo.rb20160605-18680-1tgoomz", ":~/chef-solo/solo.rb"]
最後の行から察するに rsync か。またかい。
原因の切り分けをするために、knife solo prepare コマンド実行。(リモートに Chef solo 環境作成)
処理は成功。次に、knife solo cook コマンド(リモートに cookbook 適用)を実行すると同じエラーが出た。
どうやらここが原因らしい。
そして、調べてみると -VV (Vが2個) がデバッグモードになるらしく、これを使って調べてみた。
$ knife solo cook -VV debian-jessie INFO: Using configuration from E:/cygwin/home/hoshikouki/chef-repo/.chef/knife.rb Starting 'Run' Running Chef on debian-jessie... Checking Chef version... (長いので中略) sent 124 bytes received 11 bytes 270.00 bytes/sec total size is 0 speedup is 0.00 DEBUG: 'encrypted_data_bag_secret' not set DEBUG: ["rsync", "-rL", "-v", "--chmod=ugo=rwX", "--rsh=ssh vagrant@debian-jessie", "--delete-after", "-zt", "--exclude=revision-deploys", "--exclude=.git", "--exclude=.hg", "--exclude=.svn", "--exclude=.bzr", ".\\environments/", ":~/chef-solo/environments"] building file list ... done sent 124 bytes received 11 bytes 270.00 bytes/sec total size is 0 speedup is 0.00 Generating solo config... DEBUG: ["rsync", "-rL", "-v", "--chmod=ugo=rwX", "--rsh=ssh vagrant@debian-jessie", "--delete-after", "-zt", "--exclude=revision-deploys", "--exclude=.git", "--exclude=.hg", "--exclude=.svn", "--exclude=.bzr", "/cygdrive/C/Program Files/cygwin64/tmp/solo.rb20160607-9868-1vtkfpf", ":~/chef-solo/solo.rb"] building file list ... rsync: link_stat "/cygdrive/C/Program Files/cygwin64/tmp/solo.rb20160607-9868-1vtkfpf" failed: No such file or directory (2) done sent 81 bytes received 11 bytes 184.00 bytes/sec total size is 0 speedup is 0.00 rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2] C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/knife-solo/tools.rb:4:in `system!': Failed to launch command ["rsync", "-rL", "-v", "--chmod=ugo=rwX", "--rsh=ssh vagrant@debian-jessie", "--delete-after", "-zt", "--exclude=revision-deploys", "--exclude=.git", "--exclude=.hg", "--exclude=.svn", "--exclude=.bzr", "/cygdrive/C/Program Files/cygwin64/tmp/solo.rb20160607-9868-1vtkfpf", ":~/chef-solo/solo.rb"] (RuntimeError) from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:279:in `rsync' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:233:in `upload' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:258:in `write' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:229:in `generate_solorb' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:97:in `block in run' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:212:in `time' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:79:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:421:in `block in run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/local_mode.rb:44:in `with_server_connectivity' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:420:in `run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:219:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application/knife.rb:148:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/bin/knife:25:in `<top (required)>' from E:/opscode/chefdk/bin/knife:61:in `load' from E:/opscode/chefdk/bin/knife:61:in `<main>'
やはり rsync のようだ。最初は "Program Files の半角スペースが原因でコマンドが認識できていないのかと思ったが、
配列からコマンドを作成するこの記述方法なら問題なさそうだ。・・・原因分からねえ。
そして、ある時閃いた。
- cygwin 直下のこのパスってtmpフォルダなんてあったっけ? →残念あった
- Program Files 以下って書き込み権限あったっけ? →なかった
ユーザの書き込み権限を「書き込み」許可すると動いた。(tmp フォルダ上で右クリック->プロパティのセキュリティタブ)
どうやら「/cygdrive/C/Program Files/cygwin64/tmp/solo.rb20160607-9868-1vtkfpf」に書き込みができなくてダメだったようだ。
ユーザの書き込み権限をエクスプローラから「書き込み」許可すると先ほどの場所は抜けた。
謎のエラー
rsync は直ったようだが、まだエラーが残っていた。次の問題部分。
DEBUG: sudo -p 'knife sudo password: ' chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json -l debug stdout: DEBUG: sudo -p 'knife sudo password: ' chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json -l debug stdout: from /usr/bin/chef-solo:51:in `<main>' from /usr/bin/chef-solo:51:in `<main>'DEBUG: sudo -p 'knife sudo password: ' chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json -l debug stdout: C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:315:in `cook': chef-solo failed. See output above. (RuntimeError) from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:99:in `block in run' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:212:in `time' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:79:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:421:in `block in run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/local_mode.rb:44:in `with_server_connectivity' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:420:in `run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:219:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application/knife.rb:148:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/bin/knife:25:in `<top (required)>' from E:/opscode/chefdk/bin/knife:61:in `load' from E:/opscode/chefdk/bin/knife:61:in `<main>'
- VV オプション付けるとメッセージが違う。不思議。
Generating solo config... Running Chef: sudo chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/config_fetcher.rb:47:in `read': Is a directory @ io_fread - /home/vagrant/chef-solo/solo.rb (Errno::EISDIR) from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/config_fetcher.rb:47:in `read_local_config' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/config_fetcher.rb:36:in `read_config' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application.rb:107:in `load_config_file' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application.rb:85:in `configure_chef' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application.rb:47:in `reconfigure' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application/solo.rb:210:in `reconfigure' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application.rb:56:in `run' from /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/bin/chef-solo:25:in `<top (required)>' from /usr/bin/chef-solo:51:in `load' from /usr/bin/chef-solo:51:in `<main>' ERROR: RuntimeError: chef-solo failed. See output above.
/home/vagrant/chef-solo/solo.rb はディレクトリだよ?
パスを見るに 仮想環境上のようだ。乗り込んで確かめてみると、
たしかに「/home/vagrant/chef-solo/solo.rb」はディレクトリだった。
なんでこんなことになっていたのか・・・。謎のディレクトリを削除してみると、進んだ。
$ sudo rm -rf /home/vagrant/chef-solo/solo.rb
リソースが見つからない。
Cookbook に作成した hello リソースが見つからないらしい。
/usr/bin/chef-solo:51:in `<main>' [2016-06-07T15:43:59+00:00] ERROR: Cookbook hello not found. If you're loading hello from another cookbook, make sure you configure the dependency in your metadata DEBUG: sudo -p 'knife sudo password: ' chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json -l debug stdout: [2016-06-07T15:43:59+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1) [2016-06-07T15:43:59+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1) C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:315:in `cook': chef-solo failed. See output above. (RuntimeError) from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:99:in `block in run' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:212:in `time' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:79:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:421:in `block in run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/local_mode.rb:44:in `with_server_connectivity' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:420:in `run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:219:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application/knife.rb:148:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/bin/knife:25:in `<top (required)>' from E:/opscode/chefdk/bin/knife:61:in `load' from E:/opscode/chefdk/bin/knife:61:in `<main>'
ためしに dstat リソースを新しく作成して同様の手順を行う。しかし、結果は同じ。
DEBUG: sudo -p 'knife sudo password: ' chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json -l debug stdout: [2016-06-07T15:54:55+00:00] ERROR: Cookbook dstat not found. If you're loading dstat from another cookbook, make sure you configure the dependency in your metadata [2016-06-07T15:54:55+00:00] ERROR: Cookbook dstat not found. If you're loading dstat from another cookbook, make sure you configure the dependency in your metadataDEBUG: sudo -p 'knife sudo password: ' chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json -l debug stdout: [2016-06-07T15:54:55+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1) C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:315:in `cook': chef-solo failed. See output above. (RuntimeError) from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:99:in `block in run' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:212:in `time' from C:/Users/hoshikouki/AppData/Local/chefdk/gem/ruby/2.1.0/gems/knife-solo-0.6.0/lib/chef/knife/solo_cook.rb:79:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:421:in `block in run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/local_mode.rb:44:in `with_server_connectivity' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:420:in `run_with_pretty_exceptions' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/knife.rb:219:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/application/knife.rb:148:in `run' from C:/opscode/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/bin/knife:25:in `<top (required)>' from E:/opscode/chefdk/bin/knife:61:in `load' from E:/opscode/chefdk/bin/knife:61:in `<main>'
作成したリソースを Chef が認識していないようだ。
- Berkshelf が悪さしている? (違った)
chef (11.6.0) + knife-solo (0.3.0.pre5)は相性が悪いっぽい? - じゅにゃくんのはてブロ。 - cookbook の設定を書く場所が悪かった (ビンゴ)
knife-soloによるChefの実行 - Qiita
cookbook_path ["cookbooks", "site-cookbooks"] node_path "nodes" role_path "roles" environment_path "environments" data_bag_path "data_bags" #encrypted_data_bag_secret "data_bag_key" knife[:berkshelf_path] = "cookbooks"
※ これを ~/.chef/knife.rb (ホームディレクトリ直下)に書いてたから動かなかった
そして、 knife solo cook が成功。(gitリソースまで作成してから行ってます)
$ knife solo cook debian-jessie Running Chef on debian-jessie... Checking Chef version... Installing Berkshelf cookbooks to 'E:/cygwin/home/hoshikouki/.berkshelf/knife-solo/741e61ac3fba69a26087f2d0c26ffc30e967a5f2'... Resolving cookbook dependencies... Uploading the kitchen... WARNING: Local cookbook_path 'E:/cygwin/home/hoshikouki/.berkshelf/knife-solo/741e61ac3fba69a26087f2d0c26ffc30e967a5f2' does not exist Generating solo config... Running Chef: sudo chef-solo -c ~/chef-solo/solo.rb -j ~/chef-solo/dna.json Starting Chef Client, version 12.9.41 [2016-06-07T17:10:27+00:00] WARN: The cookbook(s): dstat exist in multiple places in your cookbook_path. A composite version has been compiled. This has been deprecated since 0.10.4, in Chef 13 this behavior will be REMOVED. at /opt/chef/embedded/lib/ruby/gems/2.1.0/gems/chef-12.9.41/lib/chef/cookbook_loader.rb:89:in `load_cookbooks' Installing Cookbook Gems: Compiling Cookbooks... Converging 3 resources Recipe: dstat::default * apt_package[dstat] action remove (up to date) * apt_package[git] action install (up to date) Recipe: hello::default * log[hello world!!!!!] action write Running handlers: Running handlers complete
Ruby on Rails チュートリアル2章
概要
Ruby の Web Framework である Ruby on Rails のチュートリアルについてまとめる。
対象読者としては、チュートリアルを一度は読んだけどチュートリアルからいちいちコマンドなどを再確認するのが面倒な場合など。
使用環境
※ 全部基本的に無料
IDE (Integrated Development Environment)
Cloud 9 (Cloud9 - Your development environment, in the cloud)
Heroku
事前準備 (前回記事とほぼ同じ)
今回は、前回のアプリは扱わないため、新しくプロジェクトを作成する。
なお、今回は チュートリアルに習い、プロジェクト名を toy_app として記述する。
プロジェクト作成
$ cd ~/workspace $ rails _4.2.2_ new toy_app $ cd toy_app/
Gemfile 置換
$ rm ~/workspace/toy_app/Gemfile $ vi ~/workspace/toy_app/Gemfile
以下をコピペで貼付けて作成。
source 'https://rubygems.org' gem 'rails', '4.2.2' gem 'sass-rails', '5.0.2' gem 'uglifier', '2.5.3' gem 'coffee-rails', '4.1.0' gem 'jquery-rails', '4.0.3' gem 'turbolinks', '2.3.0' gem 'jbuilder', '2.2.3' gem 'sdoc', '0.4.0', group: :doc group :development, :test do gem 'sqlite3', '1.3.9' gem 'byebug', '3.4.0' gem 'web-console', '2.0.0.beta3' gem 'spring', '1.1.3' end group :production do gem 'pg', '0.17.1' gem 'rails_12factor', '0.0.2' end
本番環境用のgemを除いたローカルgemをインストール
$ bundle install --without production
Gitの管理下におく
$ git init $ git add -A $ git commit -m "Initialize repository"
Bitbucket 登録
リボジトリ作成 (bitbucket のサイトで作成)
次に以下のコマンドで作成
$ git remote add origin git@bitbucket.org:${USER_NAME}>/toy_app.git $ git push -u origin --all
Applicationコントローラにhelloを追加
デフォルトページを変更する
2章内容(名前変更必要)
作成するデータ定義
ユーザ情報 (users)
- id :integer
- name :string
- email :string
マイクロポスト (microposts)
- id :integer
- content :text
- user_id :integer
Userリソース作成
Railsのscaffoldを用いて作成する。
$ rails generate scaffold User name:string email:string
Rakeを使用してデータベースをマイグレート(データベース更新・usersデータモデル作成)
※ rake db:migrate でもいける場合もあるがシステム依存らしい
$ bundle exec rake db:migrate
サーバ起動して追加リソースを見る
サーバ起動
ローカル
$ rails server # ローカル環境 $ rails server -b $IP -p $PORT # Cloud 9
リソース先にアクセス
下記のRestful APIが自動生成される。
アクセス可能なページ(URL)一覧
メソッド | URL | アクション | 用途 |
---|---|---|---|
GET | /users | index | すべてのユーザーを表示するページ |
GET | /users/1 | show | id=1のユーザーを表示するページ |
GET | /users/new | new | ユーザーを新規作成するページ |
GET | /users/1/edit | edit | id=1のユーザーを編集するページ |
メソッド | URL | アクション | 用途 |
---|---|---|---|
POST | /users | create | ユーザーを作成するアクション |
PATCH | /users/1 | update | id=1のユーザーを更新するアクション |
DELETE | /users/1 | destroy | id=1のユーザーを削除するアクション |
(筆者追記) 動作実行前との差分を知りたければ $ git status を実行する。
リソースの自動生成について
挙動説明
プログラムとDBがどのように連携しているかの説明。本記事は手順書的な意味合いが強いので割愛。
挙動確認・変更方法
上記リソースのアクセス先ルーティングを変更したい場合、routers.rbを変更する。
vi config/routes.rb
Rails.application.routes.draw do resources :users # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". # You can have the root of your site routed with "root" root 'users#index' # 'application#hello から変更 (以下省略)
もし、さきほどの Restful API の各挙動を知りたければ、app/controllers/users_controller.rb を参照する。
$ less app/controllers/users_controller.rb
自動生成したリソースの欠点
- データ検証がされていない
- 誰でも作成・参照・更新・削除可能
- 業務要件を満たすテストが存在しない
- レイアウトやスタイルはデフォルト
- 理解が困難
micropostsリソース作成
microposts リソース作成
基本的にUserリソースと同じ。
$ rails generate scaffold Micropost content:text user_id:integer $ bundle exec rake db:migrate
動作確認
挙動変更
マイクロポストの文字数に制限をかける。
vi app/models/micropost.rb
class Micropost < ActiveRecord::Base validates :content, length: { maximum: 140 } # 追加 end
データモデル同士の関連付け
※ユーザとユーザ ID が一致した microposts を事前に作成しておくこと。
user.rb を編集する
$ vi app/models/user.rb
class User < ActiveRecord::Base has_many :microposts # 追加 end
micropost.rb を編集する
vi app/models/micropost.rb
class Micropost < ActiveRecord::Base belongs_to :user # 追加 validates :content, length: { maximum: 140 } # 追加 end
関連付け確認方法
rails console を使用して確認する。
$ rails console >> first_user = User.first >> first_user.microposts
コンソール実行例 (この例では、ユーザが二人いて二人目がデータ持っていた)
2.3.0 :003 > second_user = User.second User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1 => #<User id: 8, name: "XYZ", email: "xyz@aaa.com", created_at: "2016-07-02 07:37:50", updated_at: "2016-07-02 07:37:50"> 2.3.0 :004 > second_user.microposts Micropost Load (0.3ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? [["user_id", 8]] => #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 2, content: "あああああ", user_id: 8, created_at: "2016-07-02 07:44:23", updated_at: "2016-07-02 07:44:23">, #<Micropost id: 3, content: "AAAAAAAAAAAA", user_id: 8, created_at: "2016-07-02 07:44:56", updated_at: "2016-07-02 07:44:56">]>
Ctrl + D で終了する。
※ なお、user.rb で設定していないと例外が発生する。
2.3.0 :002 > second_user.microposts NoMethodError: undefined method `microposts' for #<User:0x007faa00082788> from /usr/local/rvm/gems/ruby-2.3.0/gems/activemodel-4.2.2/lib/active_model/attribute_methods.rb:433:in `method_missing' from (irb):2 from /usr/local/rvm/gems/ruby-2.3.0/gems/railties-4.2.2/lib/rails/commands/console.rb:110:in `start' from /usr/local/rvm/gems/ruby-2.3.0/gems/railties-4.2.2/lib/rails/commands/console.rb:9:in `start' from /usr/local/rvm/gems/ruby-2.3.0/gems/railties-4.2.2/lib/rails/commands/commands_tasks.rb:68:in `console' from /usr/local/rvm/gems/ruby-2.3.0/gems/railties-4.2.2/lib/rails/commands/commands_tasks.rb:39:in `run_command!' from /usr/local/rvm/gems/ruby-2.3.0/gems/railties-4.2.2/lib/rails/commands.rb:17:in `<top (required)>' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:274:in `require' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:274:in `block in require' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:240:in `load_dependency' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:274:in `require' from /home/ubuntu/workspace/toy_app/bin/rails:9:in `<top (required)>' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:268:in `load' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:268:in `block in load' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:240:in `load_dependency' from /usr/local/rvm/gems/ruby-2.3.0/gems/activesupport-4.2.2/lib/active_support/dependencies.rb:268:in `load' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/commands/rails.rb:6:in `call' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/command_wrapper.rb:38:in `call' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application.rb:180:in `block in serve' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application.rb:153:in `fork' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application.rb:153:in `serve' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application.rb:128:in `block in run' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application.rb:122:in `loop' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application.rb:122:in `run' from /usr/local/rvm/gems/ruby-2.3.0/gems/spring-1.1.3/lib/spring/application/boot.rb:18:in `<top (required)>' from /usr/local/rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /usr/local/rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from -e:1:in `<main>'2.3.0 :003 >
継承階層
作成したモデル(Users, Microposts) は ActionController::Base を継承する。
このため、作成したモデルはデータベースにアクセスしカラムをあたかもRubyの属性のように扱える。
また、作成したモデルコントローラ(UsersController, MicropostController) は ApplicationController を継承する。
このため、ApplicationControllerで定義したルールはすべてのコントローラに反映される。
デプロイ
ローカルに変更をコミットし、bitbucket に push する
$ git status $ git add -A $ git commit -m "Finish toy app" $ git push
herokuにデプロイ。
$ git push heroku # デプロイ $ heroku run rake db:migrate # マイグレーション
Ruby on Rails チュートリアル1章
概要
Ruby の Web Framework である Ruby on Rails のチュートリアルについてまとめる。
対象読者としては、チュートリアルを一度は読んだけどチュートリアルからいちいちコマンドなどを再確認するのが面倒な場合など。
使用環境
※ 全部基本的に無料
IDE (Integrated Development Environment)
Cloud 9 (Cloud9 - Your development environment, in the cloud)
Heroku
1章内容
Ruby on Rails の知識・チュートリアルの基本的な取り決めなど
Rails こんなすごい
- オープンソース
- 最新の技術動向に敏感
- 熱心なコミュニティ
チュートリアル実施の前提知識
- 基本的に前提知識はなくても大丈夫。もちろん知っているに越した事はない。
- 個人的には、プログラミングを過去に書いた事ある人ならできる内容
足りない所は適宜補完する(Ruby, HTML, CSS, JavaScript, SQLなど) - 記法の説明、流し読みすれば OK
- コンソール上のコマンドの書き方
- パスの記述
- プログラムの書き方。省略記号をそのままソースコードに記述しないこと!
とりあえず動かしてみる
上記使用環境の利用方法説明がひたすら続く
※ 当然だけど、各サービスのWeb画面もいつかは変化するので適宜読み替える
※ 事前にローカル環境もしくは Cloud 9 のどちらで開発するか決めること。個人的に Cloud 9 の方が環境依存なくて良いと思う。
- Ruby の導入 (※Cloud9 は不要)
※ Windows では ruby がデフォルトで入ってないので導入する。
※ Mac OS はデフォルトで入ってるけど、バージョン古いかも。(私の環境では Ruby 2.0.0)
Ruby のインストール確認方法$ ruby -v ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin13]
- Rails をインストール
$ gem install rails -v 4.2.2
- プロジェクト用フォルダ作成、そこにプロジェクト作成
$ APP_NAME=アプリ名をココにいれて! $ mkdir ~/workspace $ cd ~/workspace $ rails _4.2.2_ new ${APP_NAME}
- Gemfile の置換
$ rm ~/workspace/${APP_NAME}/Gemfile $ vi ~/workspace/${APP_NAME}/Gemfile
以下をコピペで貼付けて作成。
source 'https://rubygems.org' gem 'rails', '4.2.2' gem 'sass-rails', '5.0.2' gem 'uglifier', '2.5.3' gem 'coffee-rails', '4.1.0' gem 'jquery-rails', '4.0.3' gem 'turbolinks', '2.3.0' gem 'jbuilder', '2.2.3' gem 'sdoc', '0.4.0', group: :doc group :development, :test do gem 'sqlite3', '1.3.9' gem 'byebug', '3.4.0' gem 'web-console', '2.0.0.beta3' gem 'spring', '1.1.3' end
- Rails サーバを起動する
サーバ起動はそれ専用の端末になってしまうため、別タブ・別ウィンドウで起動すること。$ APP_NAME=アプリ名をココにいれて! $ cd ~/workspace/${APP_NAME} $ rails server
Cloud 9 の場合
$ APP_NAME=アプリ名をココにいれて! $ cd ~/workspace/${APP_NAME} $ rails server -b $IP -p $PORT
なお、APP_NAME を2回いれるのが面倒な場合は、以下のコマンドを実行する事。
これによって、ターミナル起動時に設定が読み込まれるため、別タブに移動しても同様の操作が可能になる。
Mac OSXの場合echo "APP_NAME=hello_app" >> ~/.bash_profile
Linux環境の場合
echo "APP_NAME=hello_app" >> ~/.bashrc
- 動作確認
ローカル環境なら
http://localhost:3000/ にアクセスし、以下のようなデフォルトページが表示されれば OK。
Cloud 9 であれば、[Share]ボタンをクリックして、環境設定中の[Application]にある URL にアクセスする。
デフォルトページに Hello World 表示
- コントローラにweb表示時の挙動を記述。
$ vi ~/workspace/${APP_NAME}/app/controllers/application_controller.rb
hello という挙動を定義する。(hello, worldって表示するだけ)
class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception def hello # hello メソッド追加 (3行) render text: "hello, world!" # hello, world を表示 end end
- 現在のアプリケーションのルーティング情報を設定する。
vi ~/workspace/${APP_NAME}/config/routes.rb
アプリケーションルート(http://localhost:3000/など)に hello の挙動を設定する。
Rails.application.routes.draw do # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". # You can have the root of your site routed with "root" root 'application#hello' # ApplicationController クラスの hello メソッドを呼び出す (以下省略)
再度動作確認。hello, world って書かれてればOK。
開発におけるソースコード管理からデプロイまで
以下は、Ruby on Rails 以外に関わる内容のため、概要のみ。
ソースコードのバージョン管理
初回作業
Git 設定
- Git インストール (※ Cloud 9 では不要)
- Git 設定
バージョン情報で誰が更新したかの情報などに使われる。$ git config --global user.name "Your Name" $ git config --global user.email your.email@example.com $ git config --global push.default matching
- Bitbucket 登録
Bitbucket — The Git solution for professional teams - 公開鍵作成(※ 既に存在する場合・Cloud 9 では不要)
$ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/Users/XXXXXX/.ssh/id_rsa): Created directory '/Users/XXXXXX/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/XXXXXX/.ssh/id_rsa. Your public key has been saved in /Users/XXXXXX/.ssh/id_rsa.pub. The key fingerprint is: be:e9:16:45:e1:56:d3:e3:01:61:0e:ac:3e:61:42:18 XXXXXX@YYYYY-ZZZZZ.local The key's randomart image is: +--[ RSA 2048]----+ | | | | | . . | | E+ | | o = o S | | + B o. | | o = B . | | o * o . | | o . Bo+ | +-----------------+
公開鍵の内容出力
$ cat ~/.ssh/id_rsa.pub
公開鍵を Bitbucket ->[アカウントの管理]->[SSH キー] に登録
開発作業手順
- トピックブランチ作成 + 現ブランチ確認
$ git checkout -b ${BRANCH_NAME} $ git branch
- ソースコード修正
- 現ブランチの状態確認
以下の例では、変更状態にあることが示されている。$ git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: toy_app/app/controllers/application_controller.rb no changes added to commit (use "git add" and/or "git commit -a")
- 変更をステージ/コミット
ステージ - 変更をコミット対象にする(コミットはまだされない)
コミット - 変更をブランチの最新状態にする
特定のファイルをステージ段階にする$ git add ${TARGET_FILES}
全てのファイルをステージ段階にする
$ git add -A
ステージ対象をコミット
$ git commit -m "コミット内容"
全変更対象をステージ段階をすっとばしコミット (ただし新しく追加したファイルは適用されない)
$ git commit -a -m "コミット内容"
- トピックブランチの変更をマスタにマージ
$ git checkout master $ git merge ${BRANCH_NAME}
- トピックブランチ削除
$ git branch -d ${BRANCH_NAME}
- ローカルリポジトリの内容をリモートに反映する
$ git push
デプロイ
ここでは、Heroku を利用したデプロイを記述する。
初回作業
- Gemfile 設定追加
DB に PostgreSQL を利用するため、この内容を Gemfile に記述。$ vi ~/workspace/${APP_NAME}/Gemfile
末尾に4行追加。
source 'https://rubygems.org' gem 'rails', '4.2.2' gem 'sass-rails', '5.0.2' gem 'uglifier', '2.5.3' gem 'coffee-rails', '4.1.0' gem 'jquery-rails', '4.0.3' gem 'turbolinks', '2.3.0' gem 'jbuilder', '2.2.3' gem 'sdoc', '0.4.0', group: :doc group :development, :test do gem 'sqlite3', '1.3.9' gem 'byebug', '3.4.0' gem 'web-console', '2.0.0.beta3' gem 'spring', '1.1.3' end # 下記4行追加 group :production do gem 'pg', '0.17.1' gem 'rails_12factor', '0.0.2' end
ローカル環境で追加した内容(production)を反映しないように bundler に設定
(さらに、この変更をコミット)$ bundle install --without production $ git commit -a -m "Update Gemfile.lock for Heroku"
- Heroku アカウント登録
Heroku - Heroku Toolbelt インストール (Cloud 9 の場合不要)
Heroku Command Line | Heroku Dev Center - Heroku CLI がインストール済か確認
$ heroku version heroku-toolbelt/3.43.3 (x86_64-linux) ruby/2.3.0 heroku-cli/5.2.14-a56a9ae (linux-amd64) go1.6.2 You have no installed plugins.
- Heroku に新しいインスタンスを作成
Heroku ログイン + 認証鍵追加 + インスタンス作成$ heroku login $ heroku keys:add $ heroku create
アプリケーションを Heroku にデプロイする
- デプロイ方法
$ git push heroku master
- デプロイしたアプリケーションの挙動確認
heroku create 時のコマンドからアクセスすべき ホストが分かるので、
これを元にアクセスして確認する。(以下の例では、http://damp-fortress-5769.herokuapp.com/ になる)$ heroku create Creating damp-fortress-5769... done, stack is cedar http://damp-fortress-5769.herokuapp.com/ | git@heroku.com:damp-fortress-5769.git Git remote heroku added
VirtualBox+Vagrant 環境構築(Windows 7+Cygwin) 苦戦記
概要
ソフトウェアについて
VirtualBox?
仮想化環境を提供するソフトウェア。その他、有名なソフトウェアには VMWare がある。
Oracle が提供している。基本的に無料で使える。
Windows?
た・・大変申し訳ありませんが、多分その方にはこの記事の内容はおそらく理解できないです。(^^;
なんのため?
- 本番を想定したテスト環境を作成したり、その上で簡単なテストしたりするため。
- 継続的デリバリー(CD)を実現するため。
(参考:DevOps時代の開発者のための構成管理入門(終):継続的デリバリ/デプロイを実現する手法・ツールまとめ (1/2) - @IT)
環境構築
今回使用環境
Operating System
- Windows 7 Professional Service Pack1(64bit)
Console
- Cygwin (mintty 2.2.2)
- 英語だけど文献いっぱいあるからなんとかなる。(cygwin 導入 - Google 検索)
- 個人的に使いやすいと思ってるから使ってる
Vagrant を使うのに必要なアプリケーション
導入
- VirtualBox (ver5.0.20)
- Vagrant (ver1.8.1)
構築手順
VirtualBox + Vagrant Install
これらの環境セッティングは世の中にいっぱいあるので、それらに任せる。
注意点として、Ruby のパッケージ管理ツール gem からの Vagrant 提供は終了しているらしいので、
インストーラでインストールすること。(参考:Vagrant 1.1.x のインストール - oooooooo)
なお、私はこのサイトを参考にさせてもらった。操作も少なく、かなり簡単。
Windows上でVirtualBox+Vagrant+CentOSによる仮想環境構築 - Qiita
起動失敗
仮想化設定
問題なくインストールは終了したのだが、起動しても仮想マシンが起動しない。
「仮想化支援機能(VT-z/AMD-V)を有効化できません。・・・・」ってアラートが出る。
(同一事象:仮想化支援機構(VT-x/AMD-V)を有効化できません | Futurismo)
調べてみると、マザボの BIOS から CPU の仮想化設定を有効にしないといけないそうだ。
システム設定 | VirtualBox Mania
CPU は Intel core i7-3770K。仕様を調べるとサポートしてた。(Intel® Core™ i7-3770K Processor (8M Cache, up to 3.90 GHz) 仕様)
で。BIOS から探し出して有効化すると起動。
もし、サポートしてない場合は・・諦めるか、CPU 取り替えましょう。
rsync 実行が失敗
インストール後は Cygwin のコマンドで仮想化などの操作を実行できる。
また、仮想化用の OS は box と呼ばれており、以下のサイトで入手できる。
私は、Raspberry Pi の OS が raspbian jessie なので、そのテスト環境 Debian jessie を入れることに。
(実際は少し違うかもしれないけど)
仮想マシン起動コマンド
$ vagrant box add "debian-jessie" https://github.com/holms/vagrant-jessie-box/releases/download/Jessie-v0.1/Debian-jessie-amd64-netboot.box $ vagrant init debian-jessie $ vagrant up
で、エラーがでると。
$ vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Checking if box 'debian/jessie64' is up to date... ==> default: Clearing any previously set forwarded ports... ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key default: Warning: Remote connection disconnect. Retrying... default: Warning: Remote connection disconnect. Retrying... ==> default: Machine booted and ready! GuestAdditions 5.0.20 running --- OK. ==> default: Checking for guest additions in VM... ==> default: Rsyncing folder: /cygdrive/e/cygwin/home/hoshikouki/ => /vagrant There was an error when attempting to rsync a synced folder. Please inspect the error message below for more info. Host path: /cygdrive/e/cygwin/home/hoshikouki/ Guest path: /vagrant Command: rsync --verbose --archive --delete -z --copy-links --chmod=ugo=rwX --no-perms --no-owner --no-group --rsync-path sudo rsync -e ssh -p 2222 -o ControlMaster=auto -o ControlPath=C:/Program Files/cygwin64/tmp/ssh.363 -o ControlPersist=10m -o StrictHostKeyChecking=no -o IdentitiesOnly=true -o UserKnownHostsFile=/dev/null -i 'E:/cygwin/home/hoshikouki/.vagrant/machines/default/virtualbox/private_key' --exclude .vagrant/ /cygdrive/e/cygwin/home/hoshikouki/ vagrant@127.0.0.1:/vagrant Error: ssh: Could not resolve hostname files/cygwin64/tmp/ssh.363: Name or service not known rsync: connection unexpectedly closed (0 bytes received so far) [sender] rsync error: error in rsync protocol data stream (code 12) at io.c(226) [sender=3.1.2]
rsync コマンドが失敗したみたい。
「ControlPath=C:/Program Files/cygwin64/tmp/ssh.363」で半角スペースが混入してるのが問題になっている。
どうやら cygwin 環境で実行すると起こる問題らしく、ソースコードを直接編集して対処。
Windows + vagrant 1.8.1 + rsync 3.1.1 で発生する、config.vm.synced_folderの失敗の直し方 - Qiita
ruby のアプリケーションはインタプリタだから最悪手で調整できるのはありがたい。
もし、壊したら再度インストールすればいいんだし。(^-^;
$ vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Checking if box 'debian/jessie64' is up to date... ==> default: Clearing any previously set forwarded ports... ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key default: Warning: Remote connection disconnect. Retrying... default: Warning: Remote connection disconnect. Retrying... ==> default: Machine booted and ready! GuestAdditions 5.0.20 running --- OK. ==> default: Checking for guest additions in VM... ==> default: Rsyncing folder: /cygdrive/e/cygwin/home/hoshikouki/ => /vagrant ==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision` ==> default: flag to force provisioning. Provisioners marked to run always will still run.
以下コマンドで動作確認。
$ vagrant ssh
rsync を使って共有する場所は、デフォルト設定では、カレントディレクトリになるっぽい。
実際に共有するフォルダは独自に設定したいので カレントディレクトリにある Vagrantfile に以下の設定を追加する。
config.vm.synced_folder "./vagrant_data/", "/vagrant"
さらなる応用的情報
ドキュメント
英語だけど、本家ということもあり最も信用できる情報。
Documentation - Vagrant by HashiCorp
オブジェクト返却メソッドの異常系実装
概要
今回は、メソッド仕様の話。
オブジェクト値を返却するメソッドにおいて、入力値・プログラム状態によって異常な結果になったときにそれを示したい。
その方法として、以下の3通りが考えられる。
- null 返却
- 例外発生
- NULL オブジェクト返却 (参考:サルでもわかる 逆引きデザインパターン 第4章 逆引きカタログ その他 Nullオブジェクト)
これらの使いどころやそれぞれの利点について記述する。
詳細内容
実装内容
コンソールアプリケーションでユーザが入力した文字列からコマンド(コード内に定義)を実行する。
実行例
ls カレントのファイルリスト出力 ls /usr/local/bin /usr/local/bin ディレクトリのファイルリスト出力 rm /usr/local/bin /usr/local/binの削除 aaa 不正なコマンド aaa
基本実装
※ この後実装するため getCommandメソッドを空実装にしている。
package com.example1; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Main { /** * コマンド定義. * */ public static enum Command { ls { @Override public void execute(String... params) { if(params.length == 1) { System.out.println("カレントのファイルリスト出力"); }else { System.out.println(params[1] + " ディレクトリのファイルリスト出力"); } } }, rm { @Override public void execute(String... params) { if(params.length > 1) { System.out.println(params[1] + "の削除"); } } }; public abstract void execute(String... params); } /** * メインロジック.標準入力からコマンドを入力して,登録された処理を実行する. */ public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { String[] elements = line.split(" "); getCommand(elements[0]).execute(elements); } } /** * 入力文字列から処理するコマンドを選択する. * @param cmd コマンド文字列 * @return コマンド実体 */ public static Command getCommand(String cmd) { return null; // TODO: 空実装 } }
getCommandメソッドの異常系処理、それに伴うロジック部の対応
登録されていないコマンドを指定した場合の処理を3通りの方法を実装する。
null を返却する場合
- getCommandの修正
public static Command getCommand(String cmd) { for(Command c : Command.values()) { if(c.name().equals(cmd)) { return c; } } return null; }
- ロジック部修正
public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { String[] elements = line.split(" "); Command cmd = getCommand(elements[0]); if(cmd != null) { cmd.execute(elements); }else { System.out.println("不正なコマンド " + elements[0]); } } }
例外を発生させる場合
- getCommandの修正
public static Command getCommand(String cmd) { for(Command c : Command.values()) { if(c.name().equals(cmd)) { return c; } } throw new IllegalArgumentException(cmd + " is not defined."); }
- ロジック部修正
public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { String[] elements = line.split(" "); try { getCommand(elements[0]).execute(elements); }catch (IllegalArgumentException e) { System.out.println("不正なコマンド " + elements[0]); } } }
NULL オブジェクトを返却する場合
- NULLオブジェクトの定義
public static enum Command { ls { @Override public void execute(String... params) { if(params.length == 1) { System.out.println("カレントのファイルリスト出力"); }else { System.out.println(params[1] + " ディレクトリのファイルリスト出力"); } } }, rm { @Override public void execute(String... params) { if(params.length > 1) { System.out.println(params[1] + "の削除"); } } }, // NULL オブジェクト追加 UNDEFINED { @Override public void execute(String... params) { System.out.println("不正なコマンド " + params[0]); } }; public abstract void execute(String... params); }
- getCommandメソッド修正
public static Command getCommand(String cmd) { for(Command c : Command.values()) { if(c.name().equals(cmd)) { return c; } } return Command.UNDEFINED; }
- ロジック部修正
変更不要
それぞれの実装利点/欠点
null 返却
- 利点
- 結果が存在しないことを意図的に示せる。
- オブジェクトを作成しないため、メモリ領域の節約になる。
- 欠点
例外発生
- 利点
- 欠点
NULL オブジェクト返却
- 利点
- null チェックが必要ないため、利用者のコードがシンプルになる。
- 関連した処理に格納されるため、コードが見やすくなる
- 利用者のコードが複数あっても、お決まりの処理の記述が其々で不要なため、冗長性がなくなり保守性が向上する。
- 欠点
- 事前に定義する必要がある。
まとめ
null 返却の利点は基本的に大きな利点にならないため、意図的に null を返す場合は少ない。
また、null を返すことは思わぬバグを生む可能性があるため余程の意図がない限り行うべきではない。
基本的には、NULL オブジェクトで実装する方が保守性が良くなる。
しかし、全メソッドに NULL オブジェクトを作成しても、多くの NULL オブジェクト定義ができるだけで、あまり効果はない。
つまり、それほど重要でない処理にまでこのような実装をする必要はない。
例外を返す場合、その処理が「本質的にどういう処理ものであるべきか」に基づいて決定すべき。
その処理が仕様として、起こりえなかったり・明らかに不自然であれば、例外で実装するのが良い。
例外仕様設計 - Status Code 303 - See Other
また、異常時に返す値・その意図をドキュメントに示す方が良い(特に null を返す場合)。
そして、これらを意識することによって複数人開発においては以下の利点が見込まれる。
- バグの少ない品質の良いコード
- デバッグに要する時間が少ない保守面で優れたコード
- 開発者に異常系を意識したコーディングの促進
一通りコードが書けるようになった人は、次はメソッド仕様として
このようなことを考えてコーディングできるようにしてみよう!