tag:crieit.net,2005:https://crieit.net/tags/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88/feed 「シェルスクリプト」の記事 - Crieit Crieitでタグ「シェルスクリプト」に投稿された最近の記事 2021-12-21T08:00:24+09:00 https://crieit.net/tags/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88/feed tag:crieit.net,2005:PublicArticle/17878 2021-12-21T07:40:58+09:00 2021-12-21T08:00:24+09:00 https://crieit.net/posts/Bash シェルスクリプト(Bashスクリプト)でかんたんな自作言語のコンパイラを書いた <p>シェルスクリプト Advent Calendar 2021<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">https://qiita.com/advent-calendar/2021/shellscript</a><br /> の20日目の記事です。</p> <p><a href="https://crieit.now.sh/upload_images/d022495e2d320868f0b13d07c98d3f9861b66f943faf6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d022495e2d320868f0b13d07c98d3f9861b66f943faf6.png?mw=700" alt="image" /></a></p> <p>Bashがある環境ならどこでも自作言語で書いたプログラムをコンパイルできるようになりました。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-bash">https://github.com/sonota88/vm2gol-v2-bash</a></p> <p>いろいろと雑なのですが、アドベントカレンダーの期日があるのでとりあえず公開します。</p> <p>サイズはこんな感じ。</p> <pre><code>$ wc -l {lexer,parser,codegen}.sh lib/*.sh 220 lexer.sh 655 parser.sh 525 codegen.sh 42 lib/common.sh 193 lib/json.sh 284 lib/utils.sh 1919 合計 </code></pre> <h1 id="動かし方の例"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9%E3%81%AE%E4%BE%8B">動かし方の例</a></h1> <pre><code class="bash">echo ' func add(a, b) { return a + b; } func main() { var one = 1; var result; call_set result = add(one, 2); } ' | ./lexer.sh | ./parser.sh | ./codegen.sh # ↓アセンブリが出力される call main exit label add push bp cp sp bp cp [bp:2] reg_a push reg_a cp [bp:3] reg_a push reg_a pop reg_b pop reg_a add_ab cp bp sp pop bp ret label main push bp cp sp bp sub_sp 1 cp 1 reg_a cp reg_a [bp:-1] sub_sp 1 cp 2 reg_a push reg_a cp [bp:-1] reg_a push reg_a _cmt call~~add call add add_sp 2 cp reg_a [bp:-2] cp bp sp pop bp ret # (snip) </code></pre> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></p> <p>Bashスクリプト版のベースになっているバージョンは <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/62">tag:62</a> のあたり</p> <p><自作言語処理系の説明用テンプレ></p> <p>自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。</p> <ul> <li>小規模: コンパイラ部分は 1,000 行程度</li> <li>pure Ruby / 標準ライブラリ以外のライブラリ不要</li> <li>x86風の自作VM向けにコンパイルする</li> <li>ライフゲームのために必要な機能だけ <ul> <li>変数宣言、代入、反復、条件分岐、関数定義</li> <li>演算子: <code>+</code>, <code>*</code>, <code>==</code>, <code>!=</code> のみ(優先順位は <code>(</code> <code>)</code> で明示)</li> <li>型なし(値は符号付き整数のみ)</li> </ul></li> <li>作ったときに書いた備忘記事 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">Rubyでかんたんな自作言語のコンパイラを作った</a></li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/vm2gol_v2_additional_features">本体には含めていない後付けの機能など</a> <ul> <li>真偽値リテラル / break / if/else / 単項マイナス / Racc などを使って書いたパーサの別実装</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">Ruby 以外の言語への移植</a> <ul> <li>コンパイラ部分のみ</li> <li>Python, Java, TypeScript など、2021-12-19 の時点では 20言語</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">セルフホスト版</a></li> <li>製作過程を知りたい場合は<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">製作メモ</a>を見てください</li> </ul> <p><説明用テンプレおわり></p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <h2 id="文字列"><a href="#%E6%96%87%E5%AD%97%E5%88%97">文字列</a></h2> <p>下記の記事のおかげでなんとかなりました。ありがとうございます 🙏</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/richmikan@github/items/6b763519a28b4ce40031">シェルスクリプトはバイナリを扱えない。さてどうしよう…… - Qiita</a></p> <p>バイト単位で16進数表現との相互変換さえできてしまえば、あとは煮るなり焼くなり思いのまま、です。</p> <h2 id="データ構造"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0">データ構造</a></h2> <pre><code class="json">[ 123, "fdsa", [ 11, "FDSA", [] ] ] </code></pre> <p>のような、リストの要素として整数、文字列、リストを持たせることができる再帰的なデータ構造が必要です。Bashスクリプトの配列は単純な値を入れる分にはいいのですが、ネストしたリストを扱うことができなさそうでした。</p> <p>(※ いつもならこういうことをやりたくなった段階で別の言語で書くことを検討しますが、今回は Bashスクリプトで書くこと自体が目的です)</p> <p>そこで、ひとまず下記のようにしました。もっと良いやり方がある気がします。</p> <p>リストの要素にあたるデータの表現はこんな感じ。</p> <pre><code class="bash"># 整数 "int:-123" # 文字列 "str:fdsa" # リスト "list:1" </code></pre> <ul> <li>先頭に型を表すタグを付けた1行の文字列</li> <li>文字列の値(<code>str:...</code> の <code>...</code> の部分)は改行を含まない(レキサで捨てる)ため、改行についての考慮は不要</li> <li><code>list:</code> の後に続く数字はリストID</li> </ul> <p>たとえば <code>["+", 1, -2]</code> というリストは次のように3行の文字列で表せます。1行がリストの要素1つに対応します。</p> <pre><code class="bash">"str:+ int:1 int:-2 " </code></pre> <p>この文字列をグローバルな配列変数に入れて管理する形にしました。要素のインデックスがリストIDに対応します。</p> <p>たとえば、入れ子のあるリスト <code>["+", 1, ["*", 2, 3]]</code> は次のようになります。</p> <pre><code class="bash"># GLOBAL[0] "str:* int:2 int:3 " # GLOBAL[1] "str:+ int:1 list:0 " </code></pre> <p>パフォーマンスの問題は別途ありますが、要するに下記のような操作ができるインターフェイスが用意できればOK。</p> <ul> <li>リストの要素数を求める</li> <li>リストからn番目の要素を読み書きする</li> <li>リストの要素の型を判別する</li> <li>リストの要素の値を取りだす</li> </ul> <h2 id="関数からの値の返却"><a href="#%E9%96%A2%E6%95%B0%E3%81%8B%E3%82%89%E3%81%AE%E5%80%A4%E3%81%AE%E8%BF%94%E5%8D%B4">関数からの値の返却</a></h2> <p>関数 <code>myfunc</code> から標準出力に出力して <code>result="$(myfunc)"</code> のようにコマンド置換で受け取る方法だと不都合な点が2つあります。</p> <ul> <li>(1) 末尾の改行の有無を制御しにくい <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ko1nksm/items/7c4040c6b6f3fac14db7">シェルスクリプトにはコマンド出力を変数に入れると末尾の改行が全部消えてしまう罠がある! - Qiita</a> に書かれている通り</li> </ul></li> <li>(2) 呼び出した関数内でグローバル変数を更新できない <ul> <li>コマンド置換だとサブシェルになるので</li> </ul></li> </ul> <p>ちょっと悩みましたが、結局下記の方式に落ち着きました。</p> <ul> <li>関数からの戻り値の受け渡し用のグローバル変数 <code>RV1</code> を用意する</li> <li><code>$(...)</code> で囲まずに普通に関数を呼び出す</li> <li>関数側では、返したい値を <code>RV1</code> に代入する</li> <li>呼び出し元では、関数呼び出しの直後に <code>RV1</code> から移し替える</li> </ul> <p>単純な例として、文字列を2つ受け取って連結した文字列を返す関数。</p> <pre><code class="bash"># return value RV1= myfunc() { RV1="${1}${2}" } myfunc "foo" "bar" retval="$RV1" </code></pre> <p>こうですね。単にこのパターンで書けばよいだけなので頭は使わなくてよいです。無心に書いていくとコンパイラができあがります。並行処理とかやってないですし、関数から戻った直後で受け取る約束さえ守っていれば問題は起こりません。</p> <p>全然スマートじゃなくてなんじゃこりゃって感じのソリューションですけど、この方法にも良いところがあって、<code>RV2</code>, <code>RV3</code>, ... と増やすことで複数の値をいくらでも返すことができます。あと、コマンド置換よりはオーバーヘッドが小さいかもしれません(未確認)。</p> <p>関数の中でグローバル変数を書き換える実際の例としてはリストの生成処理があります。ヒープにリスト用の領域を確保して、リストを指し示すポインタを返すようなイメージです。</p> <pre><code class="bash">new_list() { new_gid local self_=$RV1 GLOBAL[$self_]="" RV1=$self_ } new_list list1_=$RV1 </code></pre> <p>Ruby で書くとしたらこんな感じ。</p> <pre><code class="ruby">def new_list self_ = new_gid() # 新しいインデックスを採番 $GLOBAL[self_] = "" return self_ end list_ = new_list() </code></pre> <hr /> <p>というわけで、かんたんなコンパイラを書くことにより、Bashスクリプトにおける</p> <ul> <li>文字列処理</li> <li>データ構造(入れ子のあるリスト)</li> <li>関数からの値の返却</li> </ul> <p>についてのノウハウが新たに得られました。めでたしめでたし。</p> <h1 id="この記事を読んだ人は(たぶん)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%9F%E3%81%B6%E3%82%93%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(たぶん)こちらも読んでいます</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1482e236054ad3edfdf5">Perlでかんたんな自作言語のコンパイラを書いた</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/54c65e97aa563009137e">なでしこ3でかんたんな自作言語のコンパイラを書いた</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">素朴な自作言語のコンパイラをLibreOffice Basicに移植した</a></p> sonota486 tag:crieit.net,2005:PublicArticle/15663 2020-01-04T00:49:50+09:00 2020-01-04T00:55:19+09:00 https://crieit.net/posts/e-x シェルスクリプト内のエラーで実行を停止する「-e」と、実行コマンドを出力する「-x」オプションを使ったシェルスクリプトの開発 <p>「ソフトウェアデザイン2020年1月号」を読んでいるのですが、その中に次の一節がありました。</p> <blockquote> <p>GitLab のCI に関する部分は.gitlab-ci.yml に書き、単独でも実行できる内容はシェルにまとめて、scriptで呼び出すのがお勧めです。<br /> そうすると、.gitlab-ci.ymlに必要な事項だけがまとまって見やすくなるはずです。<br /> しかし、シェルスクリプトにまとめてしまうと実行経過が見えなくなったりするので、シェルスクリプト内で実行過程を標準出力に出力するか、<strong>シェルスクリプトのshebang注C に「set -x」または<br /> 「set -xe」を付ける</strong>ことをお勧めします。</p> </blockquote> <p>GitLab特集の記事ですが、<strong>CI上で実行するシェルスクリプトには、「set -x」または「set -xe」を付けよう</strong>という内容です。</p> <p>なぜ「set -x」または「set -xe」なのか、少し深堀ってみます。<br /> これが理解できるようになると、CI以外の環境であっても、重要なオプションであることがわかります。</p> <p>なお本記事では、「/bin/bash」を使用しております。</p> <h2 id="「-e」オプション(bash -e / set -e)"><a href="#%E3%80%8C-e%E3%80%8D%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%EF%BC%88bash+-e+%2F+set+-e%EF%BC%89">「-e」オプション(bash -e / set -e)</a></h2> <blockquote> <p>errexit -e:パイプやサブシェルで実行したコマンドが1つでもエラーになったら、直ちにシェルを終了する<br /> <a target="_blank" rel="nofollow noopener" href="https://www.atmarkit.co.jp/ait/articles/1805/10/news023.html">https://www.atmarkit.co.jp/ait/articles/1805/10/news023.html</a> より</p> </blockquote> <p>シェルスクリプトを実行する際に「-e」を追加すると、エラーが起きたコマンドの時点でスクリプトが終了するようになります。</p> <pre><code class="sh">#!/bin/bash -e # 存在しないファイルを閲覧しようとする cat ./test1 # 以下は実行されない cat ./test2 cat ./test3 </code></pre> <pre><code class="sh">$ /bin/bash -e test.sh cat: ./test1: No such file or directory $ echo $? 1 # 1が出力されるため、test.shが成功しなかったことがわかります </code></pre> <p>または「set -e」を使っても、同様にエラーが発生した時点で処理を止めることができます。</p> <pre><code class="sh">#!/bin/bash # -o:シェルオプションを有効にする set -e # 存在しないファイルを閲覧しようとする cat ./test1 # 以下は実行されない cat ./test2 cat ./test3 # +o:シェルオプションを無効にする(-oによる設定を打ち消す) set +e </code></pre> <h3 id="「-e」オプションを使わない場合"><a href="#%E3%80%8C-e%E3%80%8D%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88">「-e」オプションを使わない場合</a></h3> <p>自力で頑張る場合は、コマンドの終了ステータスを判定することによって、制御が可能です。</p> <pre><code class="sh">#!/bin/bash cat ./test1 # コマンドの終了ステータスが「0」以外であれば、コマンドは成功していない cmdstatus=$? if [ $cmdstatus -ne 0 ]; then echo "command error!!" # 必要なら、ここで異常系フローの後処理ができる # 実行を終了させる exit $cmdstatus fi # 以下は実行されない cat ./test2 cat ./test3 </code></pre> <h2 id="「-x」オプション(bash -x / set -x)"><a href="#%E3%80%8C-x%E3%80%8D%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%EF%BC%88bash+-x+%2F+set+-x%EF%BC%89">「-x」オプション(bash -x / set -x)</a></h2> <blockquote> <p>xtrace -x: トレース情報として、シェルが実行したコマンドとその引数を出力する。情報の先頭にはシェル変数PS4の値を使用<br /> <a target="_blank" rel="nofollow noopener" href="https://www.atmarkit.co.jp/ait/articles/1805/10/news023.html">https://www.atmarkit.co.jp/ait/articles/1805/10/news023.html</a> より</p> </blockquote> <p>シェルスクリプトを実行する際に「-x」を追加すると、シェルが実行したコマンドを出力します。</p> <pre><code class="sh">#!/bin/bash -x cat ./test1 cat ./test2 cat ./test3 </code></pre> <pre><code class="shell-session">$ /bin/bash -x test.sh + cat ./test1 cat: ./test1: No such file or directory + cat ./test2 cat: ./test2: No such file or directory + cat ./test3 cat: ./test3: No such file or directory </code></pre> <p>スクリプト内に「set -x」を記述する場合は、トレースしたい箇所の前後に追加します。</p> <pre><code class="sh">#!/bin/bash set -x cat ./test1 cat ./test2 cat ./test3 set +x </code></pre> <h2 id="「-ex」は、シェルスクリプトのデバッグで役に立つ"><a href="#%E3%80%8C-ex%E3%80%8D%E3%81%AF%E3%80%81%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E3%81%A7%E5%BD%B9%E3%81%AB%E7%AB%8B%E3%81%A4">「-ex」は、シェルスクリプトのデバッグで役に立つ</a></h2> <p>これら2つのオプションは、組み合わせて使うことができます。</p> <p>「-e」オプションによりエラーが起きた時点でスクリプトが終了しますが、「-x」オプションにより実行コマンドが逐一出力されるため、開発中やデバッグ時はとくに便利です。</p> <pre><code class="sh">#!/bin/bash -ex # 存在しないファイルを閲覧しようとする cat ./test1 # 以下は実行されない cat ./test2 cat ./test3 </code></pre> <pre><code class="sh">$ /bin/bash -ex test.sh + cat ./test1 cat: ./test1: No such file or directory # 「cat ./test1」でエラーが起きて失敗したことが、ひと目でわかる </code></pre> <h3 id="CI上で実行されるシェルスクリプトのデバッグ"><a href="#CI%E4%B8%8A%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%82%8B%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E3%83%87%E3%83%90%E3%83%83%E3%82%B0">CI上で実行されるシェルスクリプトのデバッグ</a></h3> <p>CI上でシェルスクリプトを動かす場合、基本的には自分の手の届かない範囲でスクリプトが実行さます。</p> <p>でもシェルスクリプトの実行にデバッグオプションを付けておけば、CIのログを確認することで、スクリプトの実行確認やエラーの追跡がとてもやりやすくなります。</p> <h2 id="さいごに"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">さいごに</a></h2> <p>アプリケーションのプログラミングでは、「try〜catch」をはじめとするエラー制御が必要であるという認識は普及しています。</p> <p>一方で、<strong>シェルスクリプトにおいてもエラー処理や異常系を考慮</strong>して開発する必要があります。<br /> これは意外と抜け落ちやすいので、忘れないようにしましょう(・・という戒めを込めた、自分向けの記事だったりします)。</p> <p>ちなみに「ソフトウェアデザイン2020年1月号」にはGitLab特集があるのですが、私もGitLabは好きです。<br /> 実際に業務で使ってますが、CIがとても書きやすいです。</p> このすみ tag:crieit.net,2005:PublicArticle/15228 2019-07-12T19:35:42+09:00 2019-07-12T19:35:42+09:00 https://crieit.net/posts/apache-configtest-Syntax-OK apacheのconfigtestは"Syntax OK"でもエラー扱い <h2 id="最初に。ごめんなさい"><a href="#%E6%9C%80%E5%88%9D%E3%81%AB%E3%80%82%E3%81%94%E3%82%81%E3%82%93%E3%81%AA%E3%81%95%E3%81%84">最初に。ごめんなさい</a></h2> <p>タイトル盛りました。<br /> 正確には、<br /> <strong>apacheのconfigtestは"Syntax OK" を標準エラー出力する。戻り値はちゃんと0が返る。</strong><br /> です。</p> <p>調べてもあまり情報出てこなかったし、誰も気にしていないんだろうけど。</p> <h2 id="確認したバージョン"><a href="#%E7%A2%BA%E8%AA%8D%E3%81%97%E3%81%9F%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3">確認したバージョン</a></h2> <pre><code>Server version: Apache/2.2.34 (Unix) </code></pre> <pre><code>Server version: Apache/2.4.33 (Unix) </code></pre> <h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2> <p>定期的に(結構な頻度で)apacheのエイリアスを増やさなきゃいけない不思議な環境がありまして。<br /> いい加減、手作業がダルいので、シェルで自動化しようと考えまして。</p> <pre><code class="sh"># 色々処理をごにょごにょ doSomething1 doSomething2 doSomething... service httpd configtest >> logfile RET=$? echo $RET >> logfile if [ ${RET} = 0 ]; then service httpd graceful fi </code></pre> <p>(今、そらで書いたのでだいぶ怪しい)</p> <p>で、動かして見たらconfigtestのところがログになかった。<br /> あれ???ってなって、構文ミスったかな? とか、いろいろ見直した。<br /> 最終的にやっと気が付いて、</p> <pre><code class="sh"># 色々処理をごにょごにょ doSomething doSomething2 doSomething... service httpd configtest >> logfile 2>&1 RET=$? echo $RET >> logfile if [ ${RET} = 0 ]; then service httpd graceful fi </code></pre> <p>これでちゃんとログに出た。</p> <h2 id="ちょっと待って"><a href="#%E3%81%A1%E3%82%87%E3%81%A3%E3%81%A8%E5%BE%85%E3%81%A3%E3%81%A6">ちょっと待って</a></h2> <p>Q. これってapacheの仕様うんぬんの前に……。<br /> A. はい。何らかのエラー出力があった場合もログに出ないので、最初のプログラムはただの僕が作ったバグです。</p> <h2 id="まあ"><a href="#%E3%81%BE%E3%81%82">まあ</a></h2> <p>戻り値はちゃんと出し分けてるのに(当たり前だ)<br /> 出力は出し分けずに標準エラー出力で統一ってのも、なんか不思議な気はする。<br /> まあ、だいたい2>&1するし、実害はないんだけど。</p> hammhiko