<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>bash on Marketechlabo</title><link>https://www.marketechlabo.com/tags/bash/</link><description>Recent content in bash on Marketechlabo</description><generator>Hugo -- gohugo.io</generator><language>ja-jp</language><lastBuildDate>Mon, 21 Oct 2019 00:00:00 +0900</lastBuildDate><atom:link href="https://www.marketechlabo.com/tags/bash/index.xml" rel="self" type="application/rss+xml"/><item><title>bashスクリプトのエラー処理のベストプラクティス</title><link>https://www.marketechlabo.com/server-infrastructure/bash-batch-best-practice/</link><pubDate>Mon, 21 Oct 2019 00:00:00 +0900</pubDate><guid>https://www.marketechlabo.com/server-infrastructure/bash-batch-best-practice/</guid><description>
&lt;h2 id="データ処理バッチでシェルスクリプトは便利"&gt;データ処理バッチでシェルスクリプトは便利&lt;/h2&gt;
&lt;p&gt;データ処理などでバッチプログラムを書くことは多い。Pythonなどのプログラム言語を使って全部記述する方法もあるし、最近ではGUIのワークフローを描けるツールも出てきている。
ただシェルスクリプトは依然として強い。シェルスクリプトは概して動作が高速で、イレギュラー処理に対しても柔軟に対応できる。gcloudやawscliなどのコマンドを使って記述できるので、できないことはない。機能がなければコマンドをインストールすることも可能。困ったときにも確実にゴールにたどり着くメリットがある。プログラム言語だとライブラリの出来に依存する。AirflowやPrefect、Cloud Composerなどワークフロー系ツールは成熟しているが、細かいイレギュラー処理を扱うにはシェルスクリプトの方が適している場合がある。
便利なツールが出てきている時代ではあるが、シェルスクリプトを覚えておくのはおすすめである。バッチ処理ではエラーハンドリングが必須だが、bashではエラーハンドリングが難しい。そこでこの記事ではエラー処理を含めたbashスクリプトのベストプラクティスを紹介する。&lt;/p&gt;
&lt;h2 id="必要な処理の流れ"&gt;必要な処理の流れ&lt;/h2&gt;
&lt;h3 id="エラー処理の要件"&gt;エラー処理の要件&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;初期処理（init）&lt;/li&gt;
&lt;li&gt;メイン処理（main）
2.1. エラーが発生したらエラー処理（catch）
2.2. 任意のエラーを発生させることができる（raise）
2.3. ループ内処理ではエラーが発生しても次のループに進むだけ（continue）&lt;/li&gt;
&lt;li&gt;エラーの有無を問わずクリーンアップ処理（finally）&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;init&lt;span style="color:#f92672"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;try &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt; catch&lt;span style="color:#f92672"&gt;(&lt;/span&gt;e&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;()&lt;/span&gt; raise&lt;span style="color:#f92672"&gt;()&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt; finally &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;になるようなものをbashで実装する。&lt;/p&gt;
&lt;h3 id="実装方法"&gt;実装方法&lt;/h3&gt;
&lt;h4 id="シェルのエラー時の挙動をsetコマンドで指定する"&gt;シェルのエラー時の挙動を&lt;code&gt;set&lt;/code&gt;コマンドで指定する&lt;/h4&gt;
&lt;p&gt;スクリプトの冒頭で&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set -e -o pipefail
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;を実行する。&lt;code&gt;set&lt;/code&gt;はシェル自体の挙動の設定を行うコマンド。この中でエラー関連の引数は
&lt;code&gt;-e&lt;/code&gt;: エラーが発生したら（exit statusが0以外だったら）スクリプトの実行を終了する
&lt;code&gt;-o pipefail&lt;/code&gt;: パイプラインの途中でエラーが発生してもスクリプトの実行を終了する&lt;/p&gt;
&lt;h4 id="エラー処理まわりの3つの関数を設定"&gt;エラー処理まわりの3つの関数を設定&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raise()&lt;/code&gt;: エラーを発生させる。コマンドがエラーを出さない場合でも任意の条件でエラー扱いにするための関数。出力するエラーメッセージを引数として受け取る。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;catch()&lt;/code&gt;: エラー時の処理。いわゆるcatch。エラーの原因個所（行番号、関数／コマンド名）を特定できるようにするための引数を受け取る。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finally()&lt;/code&gt;: エラーの有無にかかわらず、最後に実行する関数。必ず最後に行う処理がなければ省略してもいい。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下はテンプレートとして使っていい（finally()はケースバイケースになるので必要に応じて実装）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# エラーを発生させる。exitでなくreturnで戻り値として1を返すことでエラートラップ処理に渡せるようにする。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; raise&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo $1 1&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# エラー時の処理。グローバル変数としてエラーバッファを用意しておく&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;err_buf&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; err&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Usage: trap &amp;#39;err ${LINENO[0]} ${FUNCNAME[1]}&amp;#39; ERR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$?
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lineno&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; func_name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;2&lt;span style="color:#66d9ef"&gt;:-&lt;/span&gt;main&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ログに出力するエラー。ここだけ書き換えればいい&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; err_str&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ERROR: [`date +&amp;#39;%Y-%m-%d %H:%M:%S&amp;#39;`] &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;SCRIPT&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;func_name&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;() returned non-zero exit status &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;status&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; at line &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;lineno&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;err_str&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; err_buf&lt;span style="color:#f92672"&gt;+=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;err_str&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# エラーの有無にかかわらず、最後に実行する関数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; finally&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="エラー処理を適用する開始地点でtrapコマンドを実行"&gt;エラー処理を適用する開始地点で&lt;code&gt;trap&lt;/code&gt;コマンドを実行&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;trap&lt;/code&gt;コマンドはシグナルが発生した時の挙動を指定するコマンド。&lt;strong&gt;エラーのシグナルが発生した時&lt;/strong&gt;の挙動を指定すればエラーハンドリングが可能になる。コマンドを実行した後で挙動が適用されるようになるので、エラー処理を行う対象のロジックの直前で実行する。という意味ではいわゆる&lt;code&gt;try()&lt;/code&gt;関数と同じ場所に差し込めばいい。&lt;/p&gt;</description></item></channel></rss>