tag:crieit.net,2005:https://crieit.net/tags/shell/feed 「shell」の記事 - Crieit Crieitでタグ「shell」に投稿された最近の記事 2023-10-20T20:06:09+09:00 https://crieit.net/tags/shell/feed tag:crieit.net,2005:PublicArticle/18622 2023-10-20T20:06:09+09:00 2023-10-20T20:06:09+09:00 https://crieit.net/posts/EOF EOF制御文字を削除する <pre><code>#末尾にEOF制御文字があれば、1行削除する if tail -n 1 ${DATA} | grep -q "^¥Z$"; then sed -i "s/^¥Z$//g" ${DATA} fi </code></pre> speasmen88 tag:crieit.net,2005:PublicArticle/17861 2021-12-17T09:13:56+09:00 2021-12-25T10:12:16+09:00 https://crieit.net/posts/bash-2 bash で標準出力と標準エラーを 2つのコマンドに出し分ける <p>本記事は <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">シェルスクリプトのカレンダー | Advent Calendar 2021 - Qiita</a> 17日目の記事だ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript" target="_blank" rel="noopener">シェルスクリプトのカレンダー | Advent Calendar 2021 - Qiita</a></p> <p>殆どカレンダーが埋まってなかったので、思いついたネタで埋めちゃえ埋めちゃえ。</p> <p>今回は、 bash 系列 (bash, zsh 等) の <a target="_blank" rel="nofollow noopener" href="https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html#lbBE">プロセス置換 (process substitution)</a> 機能の話だ。</p> <p>このプロセス置換は POSIX 互換の機能では無いため、以降の例は ash 系列 (busybox hush (ash), dash 等) では利用できない。</p> <h2 id="bash のプロセス置換"><a href="#bash+%E3%81%AE%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E7%BD%AE%E6%8F%9B">bash のプロセス置換</a></h2> <p>bash 系の プロセス置換 という機能と、 tee コマンドを組み合わせて、 同時に複数のコマンドにパイプできるらしい。</p> <p>詳しい説明を読んでも、正直頭がさっぱり追いつかないが…</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/angel_p_57/items/fd1d9a10e2f4aba17669" target="_blank" rel="noopener">Linuxでのプロセス置換 #Linux - Qiita</a></p> <p>要は、 引数部分でファイルを指定するべき場所で、コマンドの標準入出力を代用できる機能ということか。</p> <p>例えば、 "<code>cat <(ls ./a) <(ls ./b)</code>" とすれば、 "<code>ls ./a</code>" の内容と "<code>ls ./b</code>" の内容が連結されて出力されるし、<br /> "<code>command0 | tee >(command1) | command2</code>" とすれば、 command0 の標準出力が、 command1 の標準入力と、 command2 の標準入力両方に渡される。<br /> また、「ファイルの読み書きの代替」となるため、 "<code>command0 2> >(command1)</code>" のようにリダイレクト先のファイル名の替わりにプロセス置換を使えば、標準エラー<strong>だけ</strong>を command1 の標準入力に渡すことができる。</p> <p>ちょっとその尖った使い所を考えてみる。</p> <hr /> <p>例えばこんな、標準出力と標準エラーを吐き出すスクリプトがあったとしよう。</p> <pre><code class="bash">$ cat <<'EOF' > ./testecho.sh #!/bin/bash echo "stdout1" >&1 echo "stderr1" >&2 echo "stdout2" >&1 echo "stderr2" >&2 EOF $ chmod u+x ./testecho.sh </code></pre> <p>このスクリプトを実行し、 標準エラーだけ command2 に渡して、 標準出力は command1 に渡したい場合、 プロセス置換を使うと以下のようにできる。</p> <pre><code class="bash">$ ./testecho.sh 2> >(command2) | command1 # -&gt; 各コマンドの入力 # command1: # stdout1 # stdout2 # command2: # stderr1 # stderr2 </code></pre> <p>ここで更に、 標準出力と標準エラーの両方を command2 に渡して、 標準出力だけを command1 に渡したい場合、 ちょっと複雑になるが以下のようにして実現できる。</p> <pre><code class="bash">$ ./testecho.sh 2>&1 > >(tee >(command1)) | command2 # -&gt; 各コマンドの入力 # command1: # stdout1 # stdout2 # command2: # stdout1 # stderr1 # stderr2 # stdout2 </code></pre> <p>少し複雑なので分解して考えてみよう。</p> <p><a href="https://crieit.now.sh/upload_images/351d9041acf3d7f89470b18a3548b83961bb919f06ca5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/351d9041acf3d7f89470b18a3548b83961bb919f06ca5.png?mw=700" alt="bash_tee_stdout_and_err-00.png" /></a></p> <ol> <li>まずは <code>testecho.sh</code> のリダイレクト部分 (赤枠) を考える。 <ol> <li>リダイレクトを複数並べる場合は左から評価されるが、後ろに書いたリダイレクトで上書きされるような動きをするので、右から順番にリダイレクトされると考えるとわかりやすい。 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup><br /> このため、まずは (黄色下線部) の部分を見てみよう。<br /> この部分は、標準出力をファイルにリダイレクトしているだけ (つまり、只の "<code>> ファイルパス</code>") の表記だ。<br /> ただ、ファイルパスの代わりにプロセス置換 ("<code>>(cmd_list)</code>") が使われており、 標準出力が <code>tee</code> の標準入力へ書き込まれている。 <ol> <li>さらに、 <code>tee</code> のファイル出力もまた、プロセス置換を使って <code>command1</code> の標準入力に渡される。<br /> <strong>結果的に、 <code>testecho.sh</code> の標準出力だけが、 <code>command1</code> の標準入力に渡されることになる。</strong></li> <li>次に、 <code>tee</code> の標準出力のほう、 これは <code>./testecho.sh</code> を実行した標準出力に戻ってくる。</li> </ol></li> <li>さて、 <code>testecho.sh</code> のリダイレクトに話を戻すと、 その次のリダイレクト (水色下線部) の "<code>2>&1</code>" によって、 <code>tee</code> の標準出力と <code>./testecho.sh</code> の標準エラーが、標準出力側に統合される。</li> </ol></li> <li>最後に、 その統合された標準出力が、 パイプで <code>command2</code> に渡される。<br /> <strong>結果的に、 <code>testecho.sh</code> の標準出力と標準エラーの両方が、 <code>command2</code> の標準入力に渡されることになる。</strong></li> </ol> <p>ちなみに、 ./testecho.sh の標準出力と標準エラーの出力速度が早いと、上記の出力例のように command2 に渡される標準出力と標準エラーが順不同になってしまう。</p> <p>なお、以下のようにやっても同じ結果になるはずだ。</p> <pre><code class="bash">$ ./testecho.sh > >(command2) 2>&1 > >(tee >(command1)) </code></pre> <hr /> <h2 id="実用例"><a href="#%E5%AE%9F%E7%94%A8%E4%BE%8B">実用例</a></h2> <p>例えば、以下のようにすると、 コマンドの標準出力だけメールを出しつつ、メールの内容と 標準エラーの両方を journal に書き込む事ができる。</p> <pre><code>./testecho.sh 2>&1 > >(tee >(/usr/sbin/sendmail [email protected])) | /usr/bin/systemd-cat </code></pre> <p>ただ、どうせ journal に記録するなら、 <code>-t</code> オプションを使って、以下の 3 つを識別子で分けて記録したほうが良いかもしれない。</p> <ul> <li>./testecho.sh の標準エラー</li> <li>./testecho.sh の標準出力</li> <li>sendmail の標準出力&エラー</li> </ul> <pre><code>./testecho.sh 2> >(/usr/bin/systemd-cat -t cmderr) > >(tee >(/usr/bin/systemd-cat -t mailout /usr/sbin/sendmail -v [email protected]) | /usr/bin/systemd-cat -t cmdout) </code></pre> <p>うーん、 <strong>ここまで来ると初見で動作を理解できる気がしない。</strong></p> <p><strong>参考:</strong></p> <ul> <li>https://tellme.tokyo/post/2020/02/07/tee-command/</li> <li>https://www.greptips.com/posts/189/</li> </ul> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>記事の初稿でリダイレクトの参照順の説明が誤っていたので訂正。ただ、詳しい仕組みは難しくて説明しきれないのでググって。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> advanceboy tag:crieit.net,2005:PublicArticle/17814 2021-12-04T00:20:20+09:00 2021-12-04T00:20:20+09:00 https://crieit.net/posts/sed-0 置換ができない/複数ある場合に sed の終了コード0以外にする <p>本記事は、 <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">シェルスクリプト Advent Calendar 2021</a> の 4日目 の記事だ。<br /> そして、 <strong>且つ</strong> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/docker">docker Advent Calendar 2021</a> 4日目 の記事でもある。</p> <p>どちらのカレンダーもまだまだスッカスカなので、禁じ手で埋めにかかってしまった。</p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/official_images/">Docker 公式イメージ</a> などをベースにして、カスタムしてイメージをビルドして使おうとした際、 なるべくなら <code>/etc/apt/apt.conf.d/</code> 等のように、設定用の<strong>ファイルを追加</strong>して、ツール側がいい感じにマージして利用してくれるのが望ましい。<br /> しかし、 場合によってはやむを得ず、既存のファイルを <code>sed</code> コマンドなどで編集せざるを得ないこともあるだろう。</p> <p>カスタムイメージの Dockerfile をビルドする際に、当初は意図通り書き換えられていても、イメージが更新された結果、イメージのリビルド時にファイルの書き換えが意図しない結果となってしまう場合がある。 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p> <p>通常、 <code>sed</code> コマンドは、置換が発生してもしなくても、 終了コード 0 で終了する。<br /> このため、書き換えの成否にかかわらず、 docker build 時にエラーにならないため、コンテナ実行時に初めて置換が意図しない結果だったことに気づくことがある。</p> <p>そこで、<strong><code>sed</code> コマンドの書き換えで適切なパターンが見つからなかった</strong>場合に 0以外の終了コードを返し、<strong>ビルド時にエラー</strong>とする方法を考える。</p> <p>以下、 <code>sed</code> は GNU sed を前提とし、 "行頭の foo" を BARfooBAR に置き換える場合の例。</p> <h2 id="ひとつもヒットしなかったら終了コード16 のエラー"><a href="#%E3%81%B2%E3%81%A8%E3%81%A4%E3%82%82%E3%83%92%E3%83%83%E3%83%88%E3%81%97%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E7%B5%82%E4%BA%86%E3%82%B3%E3%83%BC%E3%83%8916+%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC">ひとつもヒットしなかったら終了コード16 のエラー</a></h2> <p>まずは、 書き換えるパターンが見つからなかった場合に、エラーコードを返す方法。</p> <pre><code class="bash">sed -e '/^foo/{s//BAR\0BAR/;h};$!b;p;x;/./Q;Q16' </code></pre> <p>参考: https://stackoverflow.com/a/15966279</p> <p>ざっとコマンドの流れを解説すると、以下のようになる。</p> <ol> <li>まず、 <a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#Regexp-Addresses">正規表現アドレス</a> で置換する行を選択する。</li> <li>ブロック <code>{}</code> を用いて、正規表現に一致する行について以下を実行する。 <ol> <li><a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#The-_0022s_0022-Command">s コマンド</a> で、<a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#regexp-extensions">後方参照</a>を使って FOObarBAR に置換する。<br /> <a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#Regexp-Addresses">空の正規表現 '//'は最後の正規表現のマッチを繰り返</a>すので、正規表現アドレスでマッチした "行頭の foo" が置き換えられる。</li> <li><a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#sed-commands-list">h コマンド</a> で、パターンスペース の内容をホールドスペース (sed 内のクリップボードみたいなもの) にコピーする。</li> </ol></li> <li><code>$!b</code> の部分は、最終行でなければ次のサイクルに移動する。 すなわち、以降のコマンドは<strong>最終行でのみ実行</strong>される。</li> <li><a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#The-_0022s_0022-Command">p コマンド</a> でパターンスペースの内容を出力にプリントする。</li> <li><a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#sed-commands-list">x コマンド</a> でパターンスペースの内容とホールドスペースをスワップする。<br /> <strong>パターンスペースの内容は結果的に</strong> 、1度でも最初の正規表現がヒットすればその文字列に、 最後まで一度も正規表現がヒットしなければ空っぽになる。</li> <li>最後、パターンスペースの内容が空でない (即ち、1度以上正規表現がヒットした) なら、 終了コード 0 で終了し、 そうでなければ 16 で終了する。</li> </ol> <h2 id="ヒットしなければ終了コード16, 2つ以上ヒットしたら終了コード32 のエラー"><a href="#%E3%83%92%E3%83%83%E3%83%88%E3%81%97%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E7%B5%82%E4%BA%86%E3%82%B3%E3%83%BC%E3%83%8916%2C+2%E3%81%A4%E4%BB%A5%E4%B8%8A%E3%83%92%E3%83%83%E3%83%88%E3%81%97%E3%81%9F%E3%82%89%E7%B5%82%E4%BA%86%E3%82%B3%E3%83%BC%E3%83%8932+%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC">ヒットしなければ終了コード16, 2つ以上ヒットしたら終了コード32 のエラー</a></h2> <p>さらに一歩踏み込んで、 書き換えるパターンが見つからない場合と、 2つ以上見つかってしまった場合<strong>両方</strong>で、エラーにする方法。</p> <pre><code class="bash">sed -e '/^foo/{s//BAR\0BAR/;x;/./Q32;g};$!b;p;x;/./Q;Q16' </code></pre> <p>基本的な動きは、ひとつもヒットしなかったパターンと同じだ。<br /> ただ、ブロック <code>{}</code> 内部のコマンドを以下のように変更して、複数ヒットした場合にエラーで終了している。</p> <ol> <li>まず、 正規表現アドレス で置換する行を選択する。</li> <li>ブロック <code>{}</code> を用いて、正規表現に一致する行について以下を実行する。 <ol> <li>s コマンド で、後方参照を使って FOObarBAR に置換する。</li> <li><a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#sed-commands-list">x コマンド</a> でパターンスペースの内容とホールドスペースをスワップする。<br /> <strong>パターンスペースの内容は結果的に</strong> 、以前の行でも正規表現がヒットすればその文字列に、 最後まで一度も正規表現がヒットしなければ空っぽになる。</li> <li>パターンスペースの内容が空でない (即ち、正規表現のヒットが2回目) なら、 終了コード 32 で<strong>エラー終了</strong>し、 そうでなければそのまま次へ。</li> <li><a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#sed-commands-list">g コマンド</a> でホールドスペースの内容をパターンスペースにコピーして戻す</li> </ol></li> <li>以降は「ひとつもヒットしなかったら…」と同じ</li> </ol> <p>なお、このコマンドは、 2つヒットした時点で出力が止まる。</p> <h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2> <p>モダンなプログラミング言語が軒並み型推論でコンパイル時にエラーとするように、 Dockerfile もビルド時にエラーにしてしまおう。</p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>カスタムイメージの Dockerfile ベースイメージを選択する際、基本的にはタグである程度絞っておくべきだが、それはさておき。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> advanceboy tag:crieit.net,2005:PublicArticle/17813 2021-12-04T00:00:04+09:00 2021-12-04T00:00:04+09:00 https://crieit.net/posts/sed-i-r-e-unmatched sed の ブロック {} 内で i, r, e コマンドを使うと "unmatched `{'" とエラーになる <p>本記事は、 <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">シェルスクリプト Advent Calendar 2021</a> の 3日目 の記事だ。<br /> 3日目が終わりそうになっても誰も書きそうにないので、最近 <code>sed</code> コマンドで ブロック <code>{}</code> を使っていたら、 "unmatched `{" というエラーにハマったので、そのメモ。</p> <hr /> <p><code>target.txt</code>:</p> <pre><code class="plain">foo bar foo bar foo </code></pre> <p><code>insert.txt</code>:</p> <pre><code class="plain">*** </code></pre> <p>上記のような、2つのファイルがあったとする。</p> <p><code>target.txt</code> ファイルに対して、 <a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#Regexp-Addresses">正規表現アドレス</a> で <code>bar</code> から始まる行を選択し、 その後ろに <a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#sed-commands-list">r コマンド</a> <code>insert.txt</code> のファイルの中身を挿入する。</p> <p>するとこんな結果になる。</p> <pre><code>$ sed -e '/^bar/rinsert.txt' target.txt foo bar *** foo bar *** foo </code></pre> <p>では、アドレス指定の後ろにブロック <code>{}</code> を追加し、以下のように bar が2回以上ヒットしたらエラーコード出して終了するようにしてみる。</p> <pre><code>$ sed -e '/^bar/{rinsert.txt;x;/./Q129;g}' target.txt sed: -e expression #1, char 0: unmatched `{' $ echo $? 1 </code></pre> <p>はい、別のエラーで失敗した。<br /> ちゃんと <code>{</code> と <code>}</code> の数はマッチしているのに……</p> <p>これは、 i, r, e などのコマンドは、 コマンドの終了区切りに改行が必須となっていて、 セミコロン (<code>;</code>) などで区切ろうとしても、 その文字もコマンドのオプションとして渡されてしまうためだ。 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p> <p>これを回避する場合は、 コマンドの後ろに改行を入れるか、 -e を使ってコマンド区切る必要がある。</p> <pre><code>$ sed -e '/^bar/{rinsert.txt' -e 'x;/./Q129;g}' target.txt foo *** bar foo *** $ echo $? 129 </code></pre> <pre><code>$ sed -e '/^bar/{rinsert.txt' -e 'x;/./Q129;g}' <<EOF > foo > bar > foo > foo > EOF foo bar *** foo foo $ echo $? 0 </code></pre> <p>様々な区切り文字でつかわれるので忘れがちだけど、セミコロンをファイル名にすることだって、できるもんな。<br /> そう考えれば納得。</p> <p>ちなみに、同様の振る舞いをするコマンド ("Commands Requiring a newline") は、以下の通り。</p> <ul> <li><code>a,c,i (append/change/insert)</code></li> <li><code># (comment)</code></li> <li><code>r,R,w,W (reading and writing files)</code></li> <li><code>e (command execution)</code></li> <li><code>s///[we] (substitute with e or w flags)</code></li> </ul> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://superuser.com/questions/456246/sed-weirdness-unmatched">command line - sed weirdness, unmatched { - Super User</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.gnu.org/software/sed/manual/sed.html#Commands-Requiring-a-newline">sed, a stream editor #3.8.1 Commands Requiring a newline - GNU sed online manual</a><br /> <a target="_blank" rel="nofollow noopener" href="https://kuma35.github.io/sed47doc-jp/sed.html#Commands-Requiring-a-newline">ストリームエディタ sed #3.8.1 sedコマンドに改行が必要な時 - GNU sed オンラインマニュアル</a> <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> advanceboy 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/15475 2019-10-12T12:27:26+09:00 2019-10-12T12:27:26+09:00 https://crieit.net/posts/IPA IPAのネットワークスペシャリスト試験の過去問一括ダウンロードスクリプト <p>IPAのネットワークスペシャリスト試験の過去問を一括ダウンロードするスクリプト。<br /> 平成16年~平成20年は、午前がまとまっているので、午後の問題のみ。</p> <h3 id="スクリプトはこんな感じ"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">スクリプトはこんな感じ</a></h3> <pre><code class="shell">#!/bin/bash # ***************************************************************************** # * ネットワークスペシャリスト試験の過去問一括ダウンロードスクリプト # ***************************************************************************** first_year=16 # 平成16年から last_year=30 # 平成30年まで base_url='https://www.jitec.ipa.go.jp/1_04hanni_sukiru' dl_dir='dist' dl_pdf() { year_label="${1}h${2}" fname="$3" url="${base_url}/mondai_kaitou_${year_label}_2/${year_label}a_${fname}.pdf" echo "#### DOWNLOAD: ${url}" wget ${url} -P "./${dl_dir}" sleep 1 # 1秒待つ } rm -rf ${dl_dir} mkdir ${dl_dir} for i in `seq ${first_year} ${last_year} | sort -r`;do year="$((1988 + i))" year2="${i}" echo "${year} - ${year2}" if [ $year2 -gt 21 ]; then dl_pdf "$year" "$year2" 'nw_am2_qs' dl_pdf "$year" "$year2" 'nw_am2_ans' dl_pdf "$year" "$year2" 'nw_pm1_qs' dl_pdf "$year" "$year2" 'nw_pm1_ans' dl_pdf "$year" "$year2" 'nw_pm1_cmnt' dl_pdf "$year" "$year2" 'nw_pm2_qs' dl_pdf "$year" "$year2" 'nw_pm2_ans' dl_pdf "$year" "$year2" 'nw_pm2_cmnt' elif [ $year2 -gt 18 ]; then dl_pdf "$year" "$year2" 'nw_pm1_qs' dl_pdf "$year" "$year2" 'nw_pm1_ans' dl_pdf "$year" "$year2" 'nw_pm1_cmnt' dl_pdf "$year" "$year2" 'nw_pm2_qs' dl_pdf "$year" "$year2" 'nw_pm2_ans' dl_pdf "$year" "$year2" 'nw_pm2_cmnt' else dl_pdf "$year" "$year2" 'nw_pm1_qs' dl_pdf "$year" "$year2" 'nw_pm1_ans' dl_pdf "$year" "$year2" 'nw_pm2_qs' dl_pdf "$year" "$year2" 'nw_pm2_ans' fi done </code></pre> <p>こんな感じで実行すると...</p> <pre><code class="console">$ bash download_pdf_nw.sh 2>&1 </code></pre> <p>こんな感じにdist配下にPDFをダウンロードしてきます。</p> <p><img width="457" alt="スクリーンショット 2019-10-12 12.22.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/576e7a19-7547-77f1-446d-750ae25aa018.png"></p> <p>あとは、好きなのを印刷すればOK(<em>´ω`</em>)</p> <p>システムアーキテクトやデータベーススペシャリスト版はこちら。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2018/10/26/155638">システムアーキテクト試験の過去問一括ダウンロードスクリプト - くらげになりたい。</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/04/07/130258">データベーススペシャリスト試験の過去問一括ダウンロードスクリプト - くらげになりたい。</a></p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15473 2019-10-11T01:39:47+09:00 2019-10-11T01:39:47+09:00 https://crieit.net/posts/6417df54e9cc7b5492fe9b8d2af9e43e パイプで受け取った標準入力を列ごとに読みこんで配列っぽく処理したい <p>最近、古いシェルスクリプトをリファクタリングしたので、そのときに使ったテクニックをここに残しておく。</p> <p>コマンドをパイプでつないで、最終的に出力を行単位で読み込みたいときがある。例えば、以下の<code>ls</code>コマンドの出力から一番ファイルサイズが大きいやつを選びたいとする。</p> <pre><code class="console">$ ls /tmp/ total 28 ... drwx------ 2 root wheel 64 10 8 01:25 KSDownloadAction.dFCyHsIUZR -rw-r--r-- 1 root wheel 0 10 9 23:35 wetrsmuoutpDmR5aR -rw-r--r-- 1 root wheel 0 10 9 23:36 wetrsmuoutpalIXOW ... </code></pre> <p>レガシー(というかマナーが悪い)なコードだとこんな感じになる。</p> <pre><code class="sh">#!/usr/bin/env bash set -eu OLD_IFS="${IFS}"; IFS=$'\n' max_size=0 max_filename= ls_outpu="$(\ls -l /tmp/ | sed -e '1d' | grep -v '^d')" for output in ${ls_outpu} do filesize="$(echo "${output}" | awk '{print $5}')" filename="$(echo "${output}" | awk '{print $9}')" if [ "${filesize}" -gt "${max_size}" ]; then max_size="${filesize}" max_filename="${filename}" fi done IFS="${OLD_IFS}" </code></pre> <p>やりたいことはタイトルにあるように「パイプで受け取った標準入力を列ごとに読みこんで配列っぽく処理したい」なんだけど、上のコードはいくつか良くない点がある。</p> <ol> <li><code>IFS</code>を一時的に置き換えている。</li> <li>空白区切りの行から特定位置の文字列を取り出すために<code>awk</code>を使っている。</li> <li><strong>数字</strong>を比較している。</li> </ol> <p>1つずつ説明する。</p> <h2 id="問題点と解決法"><a href="#%E5%95%8F%E9%A1%8C%E7%82%B9%E3%81%A8%E8%A7%A3%E6%B1%BA%E6%B3%95">問題点と解決法</a></h2> <h3 id="IFSを一時的に置き換えている。"><a href="#IFS%E3%82%92%E4%B8%80%E6%99%82%E7%9A%84%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%81%84%E3%82%8B%E3%80%82">IFSを一時的に置き換えている。</a></h3> <p>これは古いシェルスクリプトだとよく見かけるけど、このやり方はメンテナンスが非常にやりにくくなり、本当に危ないのでやめたほうが良い。<br /> まず、<code>IFS</code>はshellにおいて、文字列の<em>区切り</em>を示すもの。これを変えることで、文字列の区切りを変えることが出来る。</p> <p>通常、<code>IFS</code>は半角スペース、タブ文字、改行の3つである。ここでは<code>ls -l</code>の出力を行ごとに読み込むために文字列の区切りを改行区切りにしている(そうしなければ、出力を1行ごとに列単位で扱うことになってしまう)。そして処理が完了したら<code>IFS</code>を元に戻している。</p> <p>これ、最初に書いた人は良いけど、別の人がコードを保守するときに、この部分を見落としがちなのでバグが発生しやすくなるんだよね。コードを読む人は途中で<code>IFS</code>が変わったことを常に意識しながら読むことを強いられるので、コードの保守性が著しく悪くなる。</p> <p>よって、<code>IFS</code>の置き換えは良くないのでやめましょう。ではどうすれば良いのかと言うと、ヒアドキュメントを使って<code>read</code>で読み込めば良い。</p> <pre><code class="sh">while read line do ... done <<< "$(ls -l /tmp/ | sed -e '1d' | grep -v '^d')" </code></pre> <p>すこし見づらくなるけど、こうすることで<code>IFS</code>の置き換えが不要になり、行ごとに変数<code>line</code>の中に文字列が格納されるようになる。</p> <p>もしかしたらこうしたほうが良いかも?と思う人がいるかもしれない。</p> <pre><code class="sh">ls -l /tmp/ | sed -e '1d' | grep -v '^d' | while read line do ... done </code></pre> <p>べつにこれでも良いのだけど、パイプで繋がれた処理はサブシェルで起動するということを思い出してほしい。今回のようにwhileのループ文の中で、事前に定義した変数を読み込みたい場合、つまり、以下のような場合、</p> <pre><code class="sh">max_size=0 max_filename= ls -l /tmp/ | sed -e '1d' | grep -v '^d' | while read line do filesize="$(echo "${output}" | awk '{print $5}')" filename="$(echo "${output}" | awk '{print $9}')" if [ "${filesize}" -gt "${max_size}" ]; then max_size="${filesize}" max_filename="${filename}" fi done </code></pre> <p>ループ文中の<code>max_size</code>と<code>max_filename</code>は先に定義したものとは別の変数として扱われる。その上、サブシェルなのでループ文を抜けると、その中にあった変数たちは参照できなくなる。なぜなら、サブシェルはメインのシェルとは違う世界なので。よって、パイプでつなぐのでなく、ヒアドキュメントで入力を受け取るようにしている。</p> <p>もちろん、サブシェルで起動しても問題なければパイプで繋げばいいと思うけど、ヒアドキュメントを使えばサブシェルの起動を1つ減らせるので、リソースやパフォーマンスの面からもオススメしたいやり方だったりする。</p> <h3>空白区切りの行から特定位置の文字列を取り出すために<code>awk</code>を使っている。</h3> <p><code>awk</code>って便利なのでよく使われているんだけど、大抵の場合、空白切りの文字列の特定の位置の文字を取り出すときに使われることが多い。手元のコマンドラインで使うとかだったら良いんだけど、データ処理とかバッチ処理のスクリプトで使っているとしたら、なるべく次に紹介する方法で置き換えたほうが良い。<code>awk</code>は重いし、思わず色々awkの中に処理を詰め込みがちになってしまうから。</p> <p>オススメのリファクタリングとしては、<code>set</code>を使うこと。<code>set</code>というのは、よくシェルスクリプトの冒頭で<code>set -eu</code>とかやると思うんだけど、その<code>set</code>。これはbuilt-in関数なので<code>awk</code>と違って別途にプロセスがforkされることがなくて軽い。</p> <p>で、どういうふうに<code>set</code>を<code>awk</code>の代わりに使うのかというと、以下のようになる。</p> <pre><code class="sh">... set -- ${output} filesize="${5}" filename="${9}" ... </code></pre> <p>これだけ。スッキリしてわかりやすくなったと思う。解説は<code>man set</code>で見れば良いと思うけど、ここに引用しておく。</p> <blockquote> <p>Any arguments remaining after option processing are treated as values for the positional parameters and are assigned, in order, to $1, $2, ... $n.</p> </blockquote> <p><code>--</code>については以下の引用を参考。<code>grep</code>とかでもよく使うので知っている人は多いと思う。</p> <blockquote> <p>-- If no arguments follow this option, then the positional<br /> parameters are unset. Otherwise, the positional parame-<br /> ters are set to the args, even if some of them begin with<br /> a -.</p> </blockquote> <p>つまり、<code>set -- foo bar baz</code>とやると、それぞれが<code>$1</code>, <code>$2</code>, <code>$3</code>に格納される。Pythonで言うところの<code>*args</code>みたいな可変長引数みたいな感じで位置引数として利用することが出来る。これにより、もう<code>awk</code>を使ってわざわざ<code>echo</code>してパイプで…みたいなことをしなくて済む(もちろん<code>IFS</code>を変更していないことが前提である)。</p> <p>副作用としては、シェルスクリプトに渡された位置引数を上書きすることになるので、そういった引数は事前に別の変数に格納しておくのを忘れないこと。</p> <h3><strong>数字</strong>を比較している</h3> <p>これはなんのこっちゃと思うかもしれないけど、<code>-gt</code>や<code>-ne</code>は本来は<strong>数値</strong>を比較するためのものであって<strong>数字</strong>を比較するものではない。つまり、文字列のままで数字を扱うのは危ないのでやめましょうということ。ではどうするかというと、以下のようにすれば良い。</p> <pre><code class="sh"> if [ $((filesize)) -gt $((max_size)) ]; then </code></pre> <p>シェルスクリプトで足し算とかするときに<code>$((foo + bar))</code>みたいなことをするとおもんだけど(もし<code>expr</code>を使っているなら即刻書き換えましょう)、上のようにすることで数字を数値にすることが出来る。これの何が良いかと言うと、もし変数の中身がアルファベットのような数字ではない文字だった場合はゼロとして扱われる。</p> <pre><code class="console">$ a="aaa" $ echo $((a)) 0 </code></pre> <p>状況によるが、変数が数字でなければゼロとして扱って差し支えない場合はこのようにすることで余計な数字判定の式を省くことが出来る。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ということで、ここまでの内容をまとめると以下のようになる。</p> <pre><code class="sh">max_size=0 max_filename= while read -r line do set -- ${line} filesize="${5}" filename="${9}" if [ "$((filesize))" -gt "$((max_size))" ]; then max_size="${filesize}" max_filename="${filename}" fi done <<< "$(\ls -l /tmp/ | sed -e '1d' | grep -v '^d')" </code></pre> <p>行数はほとんど変わらないけど、最初の方と比べるとわかりやすくなったかなと思う。特別な処理とかないし。ちなみに、もっとも大きいファイル名を取得するだけなら<code>ls -S | head -1</code>で一発で取れるので、わざわざこんなことをしなくて良い。</p> shige tag:crieit.net,2005:PublicArticle/15466 2019-10-08T16:56:35+09:00 2019-10-08T16:56:35+09:00 https://crieit.net/posts/Nuxt-js-Vue Nuxt.jsで未利用のVueコンポーネントを探すシェルスクリプト <p>最近Nuxt.jsで<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webアプリ</a>を作ってるけど、<br /> 度重なる改修でVueコンポーネントが乱立。。</p> <p>使ってないのもたくさんありそうなので、調べるスクリプトを作ってみた。</p> <h3 id="スクリプトはこんな感じ"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">スクリプトはこんな感じ</a></h3> <pre><code class="sh">#!/bin/bash # vueコンポーネントの一覧を取得 FILES=`find components -name "*.vue"` for i in $FILES; do # 全体からコンポーネントをインクルードしている行の数を取得 NUM=`grep -r "$i" * | wc -l | sed -e "s:[^0-9]*::g"` # ファイル名と見つけた件数を表示 echo "** ${NUM}: ${i}" # grepした結果を表示(確認用) grep -r "$i" * echo "" done </code></pre> <p>こんな感じで出力されるので、<code>** 0:</code>で始まるコンポーネントを削除していけばOK。</p> <pre><code class="text">** 1: components/Hero.vue pages/index.vue:import Hero from "~/components/Hero.vue"; ** 0: components/OgpUserStock.vue ** 0: components/Card.vue ** 3: components/atom/TotalNumBooks.vue pages/clip/_uid.vue:import TotalNumBooks from "~/components/atom/TotalNumBooks.vue"; pages/read/_uid.vue:import TotalNumBooks from "~/components/atom/TotalNumBooks.vue"; pages/stack/_uid.vue:import TotalNumBooks from "~/components/atom/TotalNumBooks.vue"; </code></pre> <p>確認用にgrepの結果を出しているけど、コメントアウトしたり、<br /> NUMが0件のときは、結果を表示しないようにすればいい感じ。</p> <p>もしくは、<code>| grep "** 0:"</code>で結果をgrepするとかでもいいかも。</p> <h3 id="appとかディレクトリを変えてる場合"><a href="#app%E3%81%A8%E3%81%8B%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%82%92%E5%A4%89%E3%81%88%E3%81%A6%E3%82%8B%E5%A0%B4%E5%90%88">appとかディレクトリを変えてる場合</a></h3> <p>こんな感じにapp配下とかに場所を変えている場合は、</p> <pre><code>app/ pages/ components/ </code></pre> <p>こんな感じで、findする場所を変えればOK♪</p> <pre><code class="sh">#!/bin/bash FILES=`find app/components -name "*.vue" | sed 's:app/::g'` for i in $FILES; do NUM=`grep -r "$i" app/* | wc -l | sed -e "s:[^0-9]*::g"` echo "** ${NUM}: ${i}" grep -r "$i" app/* echo "" done </code></pre> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/14492 2018-07-30T06:37:43+09:00 2018-12-11T10:28:14+09:00 https://crieit.net/posts/Windows-Web Windowsで快適なWebシステム開発を行えるか検証中 <p>僕はここ数年Linux MintというUbuntuベースのOSでWebシステム開発と日常の趣味活動の全てをまかなっていました。</p> <p>PCの劣化に伴い新しいPCを購入したのですが、せっかくなのでWindows10 Proそのままでどこまで不便なくやっていけるか試してみました。</p> <h2 id="特に気になっていたところ"><a href="#%E7%89%B9%E3%81%AB%E6%B0%97%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%84%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D">特に気になっていたところ</a></h2> <ul> <li>快適なシェル生活が可能か</li> <li>Dockerは問題なく使えるか</li> <li>ファイルの改行コードや属性など</li> </ul> <h2 id="シェル"><a href="#%E3%82%B7%E3%82%A7%E3%83%AB">シェル</a></h2> <p>Linuxの場合シェルはデフォルトでBashのため、あらゆることを何も深く考えずにスムーズに使うことができました。Windowsでもなるべく快適に使っていきたいところです。</p> <h3 id="シェルを使い分ける"><a href="#%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%92%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91%E3%82%8B">シェルを使い分ける</a></h3> <p>Windows10の場合は下記のシェルが利用できます。</p> <ul> <li>デフォルトのPowerShell</li> <li>Git Bash(Gitインストール時にインストールされる)</li> <li>Windows Subsystem for LinuxのBash</li> </ul> <h3 id="PowerShell"><a href="#PowerShell">PowerShell</a></h3> <p>PowerShellは現在はWindowsのデフォルトのシェルになっています。使い方を見てみると、何やらよく分からない長い名前のコマンドがたくさん用意されているようです。例えばフォルダの移動は<code>Set-Location</code>など。</p> <p>ただ、様々なコマンドにエイリアスが用意されていて、Linuxと同じコマンドも結構あります。例えば<code>ls</code>, <code>cd</code>, <code>cat</code>等は普通に使えます。</p> <p>他にもTabキーでパスを保管できたり、<code>Ctrl+r</code>でBashのようにコマンド履歴から検索したりすることができ、わりと使い勝手は悪くないです。あとは下記のURLにもあるように、設定を行えばGitの補完などもできるようになったりするようです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://git-scm.com/book/ja/v2/Appendix-A:-その他の環境でのGit-PowershellでGitを使う">Git - PowershellでGitを使う</a></p> <p>僕も入れてみましたが、多分公式のGitHubの説明どおりに入れた方が良いと思います。色々変なやり方をするとエラーが出まくります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dahlbyk/posh-git">dahlbyk/posh-git: A PowerShell environment for Git</a></p> <p>インストールしてうまく動かなかったらPSModulePathという環境変数に、インストールされた自分のドキュメントフォルダ以下の<code>WindowsPowerShell\Modules</code>のパスを入れておきましょう。</p> <h3 id="Windows Subsystem for Linux"><a href="#Windows+Subsystem+for+Linux">Windows Subsystem for Linux</a></h3> <p>通称WSLです。WindowsでUbuntu等のLinux環境を動作させることができます。Ubuntuを入れればUbuntuと全く同じコマンドを使うことができます。apt等も使用できます。</p> <p>PowerShell上で<code>wsl</code>と実行すれば開くことができますし、レジストリの設定をすればエクスプローラでォルダを右クリックして実行することなどもできます。WindowsにあるソフトウェアよりLinuxのソフトウェアを直で使った方が使い勝手が良い、という場合にかなり便利です。</p> <h3 id="Git Bash"><a href="#Git+Bash">Git Bash</a></h3> <p>Git BashはWindowsでGitをインストールすると同時にインストールされているものです。Windows上で気軽にBashを使うことができ非常に便利です。</p> <p>Bashなら前述したWSLのBashを使えばいいのでは? と思うかもしれませんが、WSLの方は完全に環境が独立してしまっているため、使いづらいところがあったりします。Git BashはWindowsの環境を共有していますので、Windowsのプログラムも実行できますし、パスや設定等もWindowsのものを共有していますし、何気に結構便利です。</p> <p>このようにPowerShell, WSL, Git Bashを適切に使い分けていくとなかなか快適なシェル生活を過ごすことができます。</p> <h2 id="ソフトウェアなど"><a href="#%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AA%E3%81%A9">ソフトウェアなど</a></h2> <h3 id="Windowsで使えるソフトウェア"><a href="#Windows%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2">Windowsで使えるソフトウェア</a></h3> <p>Windowsだとやはりソフトウェアが充実しているので、何も考えずに色々使えるので安心です。Linux Mintの時はGitを操作する時はGitとTigとVisual Studio CodeのGit操作をそれぞれ使い分けていましたが、Windowsだと普通にSourceTreeが使えるので安心です。(ただ、何気にLinux MIntの時もその組み合わせだとむちゃくちゃ快適ではあったため特にSourceTreeを使う必要性は感じませんでした)</p> <p>あとはWeb開発に関係ないですがちょっとしたゲームやソフトウェアをあまり深く考えずインストールできるというのはやはり安心できるところです。</p> <h3 id="Docker"><a href="#Docker">Docker</a></h3> <p>Windows10もPro版であればDockerが使えます。時々うまく動かなくなったり回線が繋がらなくなったりする時があるのでLinuxの時ほど安定しているとは言い難いですが、特に不便はなく使えています。</p> <p>以前Nortonを数年分買っていてまだ使えたためとりあえず入れていますが、DockerへのSamba接続を許可しないとvolumeが共有できなかったり、というところはあるのでちょっと気をつける必要があります。</p> <p>Pro版でない人も、最近だとWSLの方でDockerが使える、といった情報があるようです。</p> <h3 id="言語env系"><a href="#%E8%A8%80%E8%AA%9Eenv%E7%B3%BB">言語env系</a></h3> <p>rbenvとかpyenvとかはWSLに入れました。Windowsだと対応していなかったりするため、直でUbuntu側に入れて使った方が良さそうでした。</p> <p>あとはnvmなども入れています。一度Windowsに入れていたのですが、インストールするパッケージによってはビルド時にVisual Studioをインストールしていないとビルドできないものなどがあり、そんな重いものはインストールしたくなかったのでWSL側に入れました。</p> <p>ただ、WSLに入れてしまうとエディタ連携などができないため、サーバーサイド系の言語はとりあえずWindows側にも最新のバージョンのものを入れておく、等が必要だったりします。そのため容量には少し気をつけなければなりません。Dockerだけでなんとかするよ、という人は何もインストールしなくて良いのかもしれませんが、ちょっとあれこれ全部満たすには面倒そうな気がしました。</p> <h3 id="Visual Studio Code"><a href="#Visual+Studio+Code">Visual Studio Code</a></h3> <p>VSCodeはOS関係なく使えるので別に何も問題はないのですが、Windowsだとデフォルトの改行コードの設定がCRLFになっています。LFに設定を変えておきましょう。</p> <h3 id="Git"><a href="#Git">Git</a></h3> <p>Gitも属性周りがちょっと怖いので、下記などを設定しておきましょう。</p> <pre><code class="sh">git config core.filemode false </code></pre> <h3 id="Windowsメニューのランチャー"><a href="#Windows%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%81%AE%E3%83%A9%E3%83%B3%E3%83%81%E3%83%A3%E3%83%BC">Windowsメニューのランチャー</a></h3> <p>Windowsキーを押すとWindowsメニューが出てきますが、そこでそのまま文字入力をはじめると、実行したいアプリケーション等を適当に検索してそのまま実行すること等ができます。デフォルトでここまでできるとはあまりにも快適でびっくりしました。これはかなりプラス評価でした。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>色々書きましたが、とにかくシェルをいかに使っていくかがWindowsでの快適なWeb開発生活をおくれるかどうかの鍵になってくると思います。PowerShellだけだったら多分Windowsを使うという選択肢はなかったと思います。今のところLinuxの時とさほど変わらない快適度は保っていると思います(まだ新しいPCの方が高速なのでそれだけでも非常に快適)。</p> <p>とはいえこれからもまた色々問題は出てくるかもしれません。実際に使い始めて数日はnpmでビルド失敗エラーが出たりDockerのvolumeが連携されなかったりで挫けそうになっていました。また色々と試していきます。</p> だら@Crieit開発者