WebMockとHTTPartyで request spec にハマった話
久しぶりの更新になります。 今年はいろいろアウトプットできるといいですね。。。
今回は仕事沼ったことを、軽く、雑に、ウォームアップ感覚で書いていきたいと思います。
実装したAPIに request specを書くためとき、処理の中で外部のAPIにリクエストを送ることがあるかと思います。 specの中で実際にリクエストを飛ばされると困るし、テストしずらいので webmock という便利なgemが用意されています。
webmockを使うことによってstubしたurlに対して、どんなレスポンスを返すか指定することができます
その上で今回はHTTPartyを使用してリクエストを送る想定で進めます。
例えばコントローラーにこんな処理があったとします(とても雑に)
def index response = HTTParty.get("https://somesampleapi.com/posts/1") render json: response, status: :ok end
これに対してこんな感じにstub_requestしたテストを書くことが可能です。
let(:exepcted_response) do { "userId": 1, "id": 1, "title": "hogehoge", "body": "fugafuga" } end it "外部からレスポンスを取得できること" do stub_request(:get, "https://somesampleapi.com/posts/1") .to_return(status: 200, body: exepcted_response.to_json) get posts_url expect(response).to have_http_status(:ok) expect(response.body).to eq exepcted_response.to_json end
しかし、resopnse.body がなぜか空のhashを返してました。
Failure/Error: expect(response.body).to eq exepcted_response.to_json expected: "{\"userId\":1,\"id\":1,\"title\":\"hogehoge\",\"body\":\"fugafuga\"}" got: "{}"
controllerをbinding.pryで覗くと以下のようになっていました
def index response = HTTParty.get("https://somesampleapi.com/posts/1") binding.pry render json: response, status: :ok end [10] pry(#<PostsController>)> response.class => HTTParty::Response [11] pry(#<PostsController>)> response.to_json => "{}" [12] pry(#<PostsController>)> response => "{\"userId\":1,\"id\":1,\"title\":\"hogehoge\",\"body\":\"fugafuga\"}"
renderにresopnseを渡すと、to_jsonが内部で自動で呼ばれた気がします(うろ覚え。。) なのでrender で to_jsonされた結果空のハッシュが返ってくるようです。
解決方法
結果、webmockの方に解決方法書いてありました
Set appropriate Content-Type for HTTParty's parsed_response.
stub_request(:any, "www.example.com").to_return body: '{}', headers: {content_type: 'application/json'}
ヘッダーコンテンツの中身しっかり書いておいてね〜という内容でした。 ということで、stub_requstを修正して、もう一度responseの中身を確認してみました。
stub_request(:get, "https://somesampleapi.com/posts/1") .to_return(status: 201, body: exepcted_response.to_json, headers: {content_type: "application/json"})
[1] pry(#<PostsController>)> response => {"userId"=>1, "id"=>1, "title"=>"hogehoge", "body"=>"fugafuga"} [2] pry(#<PostsController>)> response.class => HTTParty::Response [4] pry(#<PostsController>)> response.to_json => "{\"userId\":1,\"id\":1,\"title\":\"hogehoge\",\"body\":\"fugafuga\"}"
今回は to_json
で空が入ってこなかったですね。 なのでspec側も動くはず
Finished in 0.07161 seconds (files took 1.6 seconds to load) 1 example, 0 failures
🙌 🙌 🙌 🙌
WebMock側かHTTParty側の問題の切り分けは必要でしたが、結局ドキュメントを先に見ていればある程度早く解決できたかもしれないです。 Official Document を真っ先にみよう!
今回は調べきれてないですが、 HTTPartyの挙動も気になりますなぁ。。
久しぶりに書いてみて、用語の意味が曖昧だったり、説明しきれてない部分、自分の理解してない部分が書いていてどんどん出てきました。これ良い学習になるな!と思いつつも、ガチガチにやるとリタイアしそうなので、最初のうちは軽く、ゆるく、長くできるようなスタンスで進めたいな(願望
サイトにお問い合わせFormが簡単に設置できるサービスをリリースしました
まずはじめに
このたびF-FORM
という自作アプリをリリースしました。
F-FORM
フィヨルドブートキャンプの最終課題の自作アプリをつくる
の一環として作成しました。それについてのまとめ記事です。
どんな人が作ったの?
まず簡単に自己紹介をしたいと思います。私は現在フィヨルドブートキャンプというプログラマーとして就職を目指せるだけのスキルを身につけること
を目標としたスクールに1年弱通っています。
それ以前は独学でしばらく勉強をしてwebエンジニアとして短期間バイトしたことがあります。
ブートキャンプに参加する前はRailsとJavaScriptだけチョットワカルぐらいでその他についてはほとんどわからない状態でした。
現在は勉強した甲斐もあり、当時と比べて1歩踏み込んだ知識や考え方を身に付けることができたと思います。
何を作ったの?
今回はF-FORM
というフォーム作成サービスを作りました。サイト情報を登録すると、自分のサイトにHTMLでフォームを設置すれば、内容をメールで転送できるサービスです。フォームの項目もアプリ側での設定は必要なく、HTML側でinputを追加すれば簡単に項目を追加することができます。
またフォームにCSSを当てることができます。自分のサイトに合わせて見た目を自由にカスタマイズすることが可能です。
他にも場面によって使いやすくするために、JavaScriptでのフォームチェック、reCAPTCHAによるスパムチェックを実装しました。
どんな経緯で?
作るアプリとして最初はいろんなアイデアがあったものの、どれも問題定義が曖昧だったので、作る必要性のあるアプリが思いつきませんでした。
- 部屋のモノを登録して整理できるアプリ → なぜ登録すると整理できるようになるのか
- タスクをコミュニティ内で共有するアプリ → Trelloでできるのでは?
- ジャズのカヴァー曲を一括でまとめるアプリ → ジャズの知識不足でどうやったら便利なアプリになるかわからない
悩んでるところにメンターの駒形さんから「こんなフォームアプリがあったらいいなぁ」というお話を聞き、私もそれが解決できたら素晴らしいと思ったので、今回はメンターにヒアリングしながらアプリを作る擬似請負の形で進めました。
どんな問題を解決したかったの?
お問い合わせ用のフォームを作るのって案外大変だと思います。それを解決するためにフォーム設置型サービスは既にいろいろあると思います。
しかし、内容を転送するだけ
というシンプルなサービスがなかなかありませんでした。また、それに合わせてCSSを当ててデザインを自由にカスタマイズできるようにしたら便利だと思いました。フォームは簡単に作れるけど、埋め込んでカスタマイズするのがちょっと大変だったりします。
なので今回は「フォーム内容をシンプルに返すもの」
「CSSでデザインを設定できるもの」
の2つをテーマに取り組みました。
開発概要
実装環境
本アプリはRuby on Rails6で実装しました。デプロイ先は管理が簡単にできるHerokuを選びました。メール関係はHerokuで使えるsendgridに一任。
JavaScript周りは、フォームのフロント側のフォームチェックのためにJavaSciprtファイルが1個公開しており、クリップボード機能など細かいところで一部JavaSciptが使われてます。 (公開後にチェックするJavaScriptは別レポジトリで独立して管理したほうが良さそうと思いました。。。)
デザイン関係は時間を節約するためにbulmaを使って大部分を実装しました。細かい調整部分のみCSSを追加。
かかった期間
作るものが決まってから完成まで約3ヶ月かかりました。最初のうちはスクラム開発のプラクティスと被り、(turbolinksと格闘しながら)なかなか時間が取れない状況が続きました。
1ヶ月半は開発しながら実装に必要な情報収集したり(特にスパム周り)、試作品を作って動作のテストをしていました。残りの1ヶ月半で集中的に本アプリの実装に取り掛かかり、完成へ!
大変だったこと - 挑戦したこと
今回のアプリ実装で一番苦労したものと、個人的にちょっと挑戦的だったものを書きたいと思います。
大変 - スパム対策
今回の実装で一番悩んだのがスパム対策をどうするかでした。そもそも何をどうやってスパムを防げばいいのかがわからない状態からスタート。
いろいろ調べていくうちに、スパムに対していろんな対策やサービスがあることを知りました。
Honeypot Captcha
Honeypot Captchaとはスパムを対策するための手段のひとつです。
スパムボットはHTMLから<input>
を探し出して自動入力して送信するという動きをするそうです。なのでそれを逆手にとって、入力されていたらそのフォームリクエストは無効
というトラップを仕掛けます。
具体的な方法としては、HTML側にトラップのinputを用意します。そのinputはJavaScriptでdisplay: none
などを使って善意のユーザーには見えないように設定します。そしてサーバー側で、もしそのinputに何か値が入ってればそのリクエストは無効になるように設定します。善意のユーザーはフォームが見えないので入力することはおそらくないので、もし入力があればエラーを返すなり対処すればリクエストはそこで止まります。
Railsではこれを簡単に実装できるようにGemがあったりします。
ただ、最近のスパムボットには賢いものも出回ってるそうで、効果が本当にあるのか疑問が残ります。簡単に実装できるという点で有用だと思いますが、機械学習などによって将来的に完全に無効化されるとユーザーの手間が増えるだけなので、今回は見送りました。
アンチスパムサービス
次に探したのがスパムを見つけてくれるサービスでした。Wordpressにはakismetというスパムサービスがあるとお聞きし、それに似たサービスがないか調べました。その結果cleantalk
というサービスにたどり着きました。
Anti Spam Plugin - Spam Protection by Cleantalk
このサービスはweb版のakismetみたいな感じで、独自でメールアドレスとIPを収集してスパム情報を蓄えてます。このサービスではAPIでリクエスト情報をチェックし、スパムに該当するかレスポンスを返してくれます。
大変便利で、しかも安い金額で対策してくれるのですが、スパム情報を外部が握っている状態になってしまうのが不安でした。例えば全く関係人がたまたまそのサービスにスパム認定されていたら、本人はどんな理由で問い合わせができないかわからず、困った状態になってしまいます。今回の場合、もしもエンドユーザーがこのような状況になったら対応が難しいと考えました。 もちろんcleantalk側はそのようなことが起きないように細心の注意を払ってると思いますが、今回は万一を考えて、こちらも採用しませんでした。
reCAPTCHA
最終的に採用したのがgoogleが提供しているキャプチャreCAPTCHA
でした。
実は最初からこれを使うことも考えていたのですが、キャプチャ認証がめんどかったり、ユーザーにreCAPTCHAのアカウントを作る必要がありそうなので敬遠していました。
reCAPTCHAにはv2,v3の2つが用意されており、v3に関してはキャプチャ認証をしなくても、google独自の技術でユーザーがボットかどうかを判定してくれます。ただし、正常に動かすにはreCAPTACHAを置くサイトの全てのページにreCAPTCHAのスクリプトを置くことが推奨されること、ボットか人間かどうかをスコアで出すので一律で適切な値の把握が難しく、今回はv3での実装は向いてないと判断しました。
最終的に確実にスパム対策すること、そして間違った判定を回避するには今回キャプチャ機能を外すことは難しいと考えました。ただ、ドメイン認証で正式なリクエストかどうかを判断する仕組みをバックエンド側で実装できることがわかったので、ユーザー側がキャプチャのサイトキーとシークレットキーを登録しなくてもこの機能を使えるようにできました。
キャプチャ認証という手間がかかるものの、今回のケースでは一番リスクが少ないと思ったのでreCAPTCHAを採用しました。
挑戦 - メール変更の認証機能追加
もしユーザーが誤って間違ったアドレスに変更した場合、フォーム内容が届かないと言う悲惨なシチュエーションが考えられます。deviseだとreconfirmable
を有効にすれば良いのですが、今回採用したsorceryにはその実装が見当たらず。。。それを防ぐためにも、今回はメールアドレスを変更した場合、新アドレスを認証するまでは旧アドレスを使うような実装をしました。
フロー自体はパスワードリセットと同じでユニークなidがついたURLをメールアドレスに記載し、idからユーザーを見つけて処理をすれば良いと思いました。ただ「idはどう作るのだろう?」と悩み、sorceryとdeviseのコードを参照しました。そしたらdeviseのトークンを作るコードが非常に参考になったので、それを元にトークンを作るクラスを作成しました。
rawとencodedのトークンペアができたので、あとはパスワードリセットと同じ要領でトークンが有効の場合のみ処理をするように実装できました。 全体でみると難しそうな機能だなぁと思いつつ、やりたいことを分解すればどんな機能が必要か、その機能を実装するにはどんな仕組みやコードが必要かの道筋が見えたような気がします。
最後に
アプリ開発に際し、最後までアドバイスをくださった駒形さんと町田さんには感謝しかありません。本当にありがとうございました!
まだまだ改善しないといけない点、あったら良いなぁという機能が山のようにあります。特に町田さんによるデザインレビュー、駒形さんによるコードレビューで指摘された点を優先的に直す計画をしております。
より良いサービスになるよう今後ちょっとずつ改善していきたいと思います。
(さきに「ふりかえり」を書いてしまい、今回の開発の反省や気づきはそちらに追記していこうと思います)
フィヨルドブートキャンプの自作アプリを終えて
フィヨルドブートキャンプ最後の課題である「自作アプリを作る」が無事完成し、最後のステップとしてアプリの内容とその振り返りをまとめました。
どんなサービスを作ったのか
今回はF-FORM
というアプリを作成しました。
このアプリはドメインなどのサイト情報を入力することによって、問い合わせ内容をメールに転送するフォームを設置できるアプリです。
項目を増やす場合はアプリ側で設定が必要なく、HTMLでinputを増やせば項目を追加でき、CSSを追加すればフォームにデザインをあてることも可能です。 フォーム設置型のサービスは他にも存在しますが、機能がシンプルかつデザインをカスタマイズできるものはなかなかないので今回実装に取り組みました。 (デモサイトは こちら )
どんな経緯で作成したのか
フィヨルドブートキャンプは最終課題に自作アプリを作るというものがあるのですが、Getting Realに合わせたアイデアがなかなか思いつきませんでした。
そこで今回は駒形さん(メンターの方)の作りたいモノからアプリを作ることにしました。
"フォームを実装するのは地味にめんどくさく、メールの転送機能だけしてくれるシンプルなサービスが欲しい" というのが駒形さんの要望でした。また私自身も過去にフォームを埋め込む作業で痛い目を見ているので、フォームアプリをシンプルに設置できるのは魅力的だと思い、今回擬似的な請負ということで取り組みました。
アプリ概要
サイトのドメイン情報を入力することによってフォーム内容を転送するエンドポイントURLを作成します。そのURLへフォームの内容を送ることによってメールに情報が転送されます。自サイトにformのHTMLを貼り付ければ簡単にフォームを設置することが可能です。 CSSはこちらで用意したサンプルなので、自分のサイトに合わせてデザインを調整することもできます。
また以下の機能を使えば、自分の状況にあったフォームを作成できるように実装しました。
- リダイレクト先を設定できる
- reCAPTCHAを簡単に利用できる
- JavaScriptで簡単なvalidationが可能
*注 フォーム内容は受け付けたことを残すためにデータベース上に保存されますが、ユーザーは内容を見ることができません。将来的にユーザーもアプリ上で確認できるようにできたら便利かもと考えたりしてます。
自作アプリ開発を振り返ってみて
改めて書き出してみると当たり前の内容しか書いてないと思いましたが、個人的に今後忘れたくない項目として書き残したいと思います。
「他者の欲しい」の把握は難しい
今回は請負を擬似的にやってみたのですが、プロダクトオーナーの要望を汲み取る
、プロダクトオーナーが必要な機能の提供と調整
、プロダクトオーナーのためかエンドユーザーのためか
を考えされられました。
大まかな流れは共有していたし、細かい事項も聞いていたので完成イメージが大きくズレるとは思いませんでした。しかし、開発しているうちに「こんな機能があった方が便利」という提案をしましたが、「シンプルさ優先」ということで実装が見送られました。
初期段階でイメージが合致していても途中から本質を見失ったり、本当に実装したいものは何かが揺らいだので、頻繁に確認を取ったり、開発途中のデモをしっかり行ってお互いの認識のズレをなくすのは重要だと感じました。
作る前に小さく実装してみることは大事
実装するアプリの概要が決まり、なんとなく実装する方法は思いついていたのですが、本当に動作するか全く確証がありませんでした。特に今回はサーバー間でやりとりがあったため、実装できないのでは?という不安もありました。
そこでメインとなる機能を最低限実装したアプリとそれを試すためのサブアプリを作り、herokuにデプロイして実際に動きを確認してから開発に取り込みました。
複雑な機能を実装する場合、汚かったり壊れても問題ない小さなプロトタイプでいろいろ実験して内容を把握した方が開発しやすいように感じました。試作を作る分時間がかかるのですが、複雑な実装をする場合、理解が浅いとどこがうまくいってないのか把握しにくく、より時間がかかるように思いました。
自信を持てないうちは小さく動かしてみる、地味に重要だと思います。
この先大きなプロジェクトに複雑な機能を実装する機会があれば、最小の単位でまず組み立てて理解を深めたり、問題がないかしっかり検討しながら実装したいと思いました。
進捗の視覚化(issueポイントは重要)
スクラムを回す時にissueにポイントを振り分けることを学んだのですが、一人で開発するから特に必要ないと軽い気持ちでポイントを振りませんでした。
その結果以下の2点が曖昧になり、進捗管理が難しくなりました。
- 毎週どのくらいの開発ペースが出ていたかわからない
- プロジェクトがいつ終わるのか算出できない
なんとなくは把握していたものの、データが残っていないので本当に効率よく開発できていたのか今となっては確証が持てません。
特に2番目に関しては途中から振ってないことに後悔しました。
計画通りに実装できればの終了目安はありましたが、実装するうちに「これを考慮し忘れた」「この実装に思った以上に細かいタスクが多かった」など変更が結構ありました。その時、どれくらいタスクが増えたか、どのくらい期間が伸びるのかを全く把握できてないことに気づきました。その結果いつに終わるのかが明確に出せず、無駄な不安や悩みが増えたように思います。
最初からしっかり数値を出しておけば、少なくとも今どれくらい進んでいる(遅れている)かを数字で把握することができ、開発が順調に進んでいるのか判断できたのではと思います。
今後実装したい機能
JavaScriptをテストできるようにしたい
- 手動で毎回テストが大変。フロントエンドでテストをして、どう1つのファイルにまとめるか考える
ブロックリストの作成
- slackなどの外部サービスとの連携
- メールサーバーの仕組みを学んで、実働できるサーバーを整備
- sendgridだと送信に上限があるので、メールの仕組みから勉強して、ベストな運用環境を考えたい(確実に届かないとまずいため)
最後に
今回の開発を通して、自分ができること、できてないこと、何が重要なのかを少し掴めたような気がしました。特に認識のすり合わせ
リソースの管理
が自分の中で今後課題となりそうです。
またGetting Real
の考えをもっと深く理解したいと思いました。先日のミーティングで、自社開発でも受託開発でも、具体的な問題に適した実装をするサイクルが繰り返されるとお聞きしました。
私自身問題を具体的なレベルに落とし込む
ことが苦手なので、Getting Realをしっかり読み直して、問題発見とその解決方法、それがベストで本当に問題が解決できているのかを日常レベルで意識できるようにしたいと思います。
最後に、卒業まで全力でサポートしていただいた町田さん、駒形さんをはじめとするフィヨルドブートキャンプ関係者の方々に感謝申し上げます。フィヨルドブートキャンプのおかげでエンジニアとして自信を持てる第一歩を踏むことができそうです。
約10ヶ月間大変お世話になりました。
ありがとうございました!!
JavaScriptでのメソッド定義備忘録
忘れそうなのJavaScript Primer 第8章 関数と宣言のメモ
オブジェクトのプロパティである関数をメソッドと呼ぶ。 関数とメソッドは機能的に違いはない。JavaScript Primerでは区別するために、オブジェクトのプロパティである関数はメソッドと定義している。
const obj = { method1: function () { return "functionメソッドでmethod1を定義" }, method2: () => { return "arrow functionでmethod2を定義" } } console.log(obj.method1()) console.log(obj.method2())
なお、ES2015からメソッドを短縮して書くことができる。 クラスメソッドでも共通の書き方なので、メソッドを定義する場合は短縮記法に統一した方が良いらしい。
const obj = { method1() { return "短縮記法で書いてみた" } } console.log(obj.method1())
たしかVue.jsで「なんでメソッドの書き方違うんだろう?」と昔疑問に思ったけど、短縮してただけだったのか。。。
参考
lvh.meの備忘録
ローカルでサブドメインからRailsサーバーにアクセスしたく、lvh.me
を使うところまでは覚えていたんだ。。。
だが記憶が曖昧で「何をどう設定するんだっけ?」と謎の悩みが出たので自分用にまとめておく。
lvh.meとは?
全く難しいことはなく、ただ単にローカル環境で127.0.0.1(ローカルホスト)へ向かうドメイン。
localhost:3000
にrailsサーバーを動かせば、http://lvh.me:3000
でアクセスできるし、サブドメインをつけてhttp://hogehoge.lvh.me:3000
でもアクセスが可能(設定必要あり)
Rails6では設定が必要
Rails6からはドメインをホワイトリストに入れないとアクセスできない。 以下のエラーを吐き出す。
Blocked host: lvh.me To allow requests to lvh.me, add the following to your environment configuration: config.hosts << "lvh.me"
DNSリバインディグ攻撃を防ぐために追加されたらしい。 なので、development.rbに次のように追加すれば問題なく動く。
#development.rb Rails.application.config.hosts << 'lvh.me'
lvh.meについてはスッキリ。当時は別のことも絡み、いろいろごっちゃになってたらしい。nginx
と組み合わせた記憶があるので、nginxあたりの知識が怪しい可能性が。
逆に学ぶことが増えていい!
参考:
Rails 6 にしたら Blocked host: というエラーが発生した場合の対処 - Lonely Mobiler
5年前のHeroku + Rails お遊びアプリがノーメンテで動いてた
どうも、全然更新してないブログです。。
フィヨルドブートキャンプは続いており、卒業間近の段階にきてます。
チーム開発プラクティスに入りissueをこなしたり、自作アプリを作るプラクティスに物凄く慌てふためき中です。
学んだこともちょくちょくあるので、ちょっとずつアウトプットしたいなぁと思いつつ、願望で止まってます。。
さて、今回は5年前に初めて作ったアプリが消えるので、なんとなく記録として残したいと思って書いた雑記です。技術的な話は一切なく中身もない内容ですが、もし興味あれば、ゆるい感覚で読んでいただければと思います。
5年前、エンジニアを目指して簡単なアプリを作ろうとした、とある未熟なエンジニアがいました。彼は当時あまりにも未熟な故、クソの名がつくkuso_bbsという簡単な掲示板を作り出します。機能は至って簡単、トピックを作ってレスを投稿できること。
彼はRubyをあまり理解せず、Rails4で意気揚々と掲示板作りに励み、苦戦しながらもなんとかHerokuにデプロイすることに成功しました。デプロイできた時は、それはそれはとても感動したそうです。
この勢いでポートフォリオアプリを作って就職するぜ!と意気込んだ彼ですが、このあと挫折したり、数年間人生迷走したり、2020になってもエンジニアになれてないのは、この時まだ知らなかったのです。
がんばれ未熟エンジニア!
と、こんな経緯でHerokuにRails4のアプリをデプロイしました。
(UIボロボロ。。)
このアプリなのですが、Herokuのサーバー整理で5月にサーバーが稼働停止してしまいます。もちろん突然ではなく、去年から「サーバー停止するので移行してください」という通知は来てたみたいです。(気づいたの今年入ってから)
まぁサーバーを稼働させるのはボランティアじゃないですからねぇ。。
初めてのアプリはネットの海から消えるのを見守ります。
それよりも、自分ですら忘れていた5年前のアプリがメンテなしで問題なく動くことに驚きました。さすがHeroku!そしてkusoなコードでも5年動くRailsすごい!
そもそもサービスとしてアクセスが0なのでエラーが起きる可能性は低いですが、それでも問題なく動く環境を提供してくれていたHerokuのサーバー、アプリ層として働いてくれたRails(Ruby)は素晴らしいと思いました。
次作るサービスはこの二つ(+α)で作る予定です。
ネットから自分のモノが消えるのは心惜しいですが、時代は変化していくもの、そして自分もそろそろ変革していくのだ。。
と、考えに耽る未熟エンジニアであった。
ターミナルとは?黒い画面をもう一度
こんばんは、hasehiro25です。
突然ですが、FJORD BOOT CAMPに参加しました! 近日簡単なブログや自分の紹介記事を書こうと思います。
今回はターミナルについてのプラクティスがあったので気になった部分をまとめてみました。 自分では「コマンドなんて使えれば大丈夫でしょ」ぐらいの気持ちでしたが、学び直すと仕組みが非常に重要な部分だと知りました。
やる前の筆者の知識
- ls, pwd, cd, など基本的なコマンドは使える。
- コマンドは知ってるが、仕組みは全くわからない
環境
- macOS Mojave 10.14.6
コマンドはどこにある?
ls
やpwd
をターミナルに打ち込めばそのコマンドに合った出力が返ってきます。
自分はこれまでコマンドはターミナルの機能の一部であり、ターミナル内で全て完結すると思っていました。
しかし、調べていくとターミナルそのものにはそんな機能がなく、ファイルの入力を受け付け、それを実行し、その結果を出力しているだけでした。 では実際の処理はどこに書いてあるのでしょうか?
bin/ls
、このコマンドを先に学んでいればそんな悩みなかったのかもしれません…。
ls
は/bin
ディレクトリのls
を実行していることがわかりました。
つまり今まで実行していたコマンド全て、どこかに置いてあるファイルを実行していただけだったのです。
rails
やgit
などデフォルトのmacにないものはどこかに何かあるんだろうなー、ぐらいには思ってましたが、まさか全てが同じ仕組みで実行されてるとは思いませんでした。
またbin/ls
ではなくls
で実行できるのは$PATH
が通ってるからだとわかりました。
$ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/hase/bin
:を区切りとして、それぞれのディレクトリがコマンド検索パスとして格納され、実行時に格納されたディレクトリを探すためファイル名だけで実行できるようになってます。
(ちなみにenv
で環境変数の一覧が見れます)
homebrewのインストールコマンド
homebrewはmacos用の非常に優秀なパッケージマネージャーです。
パッケージマネージャーとは、例えばソフトAを動かすのにソフトBとソフトCが必要だったりします。また、ソフトCにバージョンアップが入ったのでアップデートしたところ、ソフトAが対応していなかったのでソフトAが動かなくなってしまう可能性もあります。
このようにお互い依存し合ってるソフトを管理してくれるのがパッケージマネージャーです。
今回は公式ホームページに乗ってるhomebrewインストールコマンドが何をしているのか見てみました。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
これをターミナルにが貼り付ければhomebrewを簡単にインストールすることができます。(初めてやった時は何も考えず貼り付けました)順番にみていきます。
まずusr/bin/ruby -e
。これはusr/bin
にあるruby
を実行するように指定してます。ruby -e
でもrubyは実行できますが、どのruby
を使っているのか環境によって違う可能性があるため、macのデフォルトで入ってるruby
を使うように指定してるんじゃないかと思います。-e
はその後に続くコードを実行してくれます。
$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)
curl
は ”Client for URLs” の略で、ネットからデータをダウンロードするコマンドです。なのでurl先からデータをダウンロードし、それをruby -e
で実行してます。
-fsSL
はcurlに対するオプションになります。
-f
サーバーからのエラーレスポンスに対して何も表示しないオプション。何も設定しないとhtmlをかえすため、それを表示しない。
-s
ダウンロードの進捗状況を表示しない
-S
s
で進捗状況は表示しないが、エラー表示をしたい場合、それを出力するオプション。基本s
と一緒に使う。
-L
もしダウンロードしようとしているアドレス先にリダイレクトが設定されている場合、リダイレクトを実行してダウンロードするように設定。curlのデフォルトだとリダイレクトは実行されない。
分解してみるとコマンドそのものはやってることがシンプルということがわかりました。またman curl
でマニュアルを見れることを初めて知りました。(内容は英語です)
#! (シバン)
ソースコードをみてるとたまに見かける#!
、よくわからないし、何かのおまじないというのはなんとなくわかってました。が、めちゃくちゃ重要な1行だと知りました。簡単なスクリプトを実行してみます。
/Users/hase/say-hello #!/usr/bin/ruby puts 'HELLO!!!' $ /Users/hase/say-hello HELLO!!!
#!
があると、それ以降の部分(/usr/bin/ruby)に2行目以降のデータ(puts 'HELLO!!!!)を送ります。
この場合/usr/bin/rubyへputs HELLO!!!!
を送りターミナルに出力されます。
もし#!/usr/bin/ruby
を削除した場合、putsが何なのかわからないと怒られます。
/Users/hase/say-hello puts 'HELLO!!!' $ /Users/hase/say-hello /Users/hase/say-hello: line 1: puts: command not found
まとめ
今までなんとなくコマンドを使ってましたが、実際の仕組みを見て謎が一つ解けたような気分になりました。いろんなところで見かけたり、使うので早めに学び直せてよかったです。
もし間違ってる部分があればご指摘頂けますと幸いです。