https://yakst.com/ja/tags
Yakst - shell-scripting
2017-06-18T21:02:17+09:00
Yakst
https://yakst.com/ja/posts/2929
2015-09-28T12:47:47+09:00
2017-06-18T21:02:17+09:00
Bashのよくある間違い
<p>原文は2015年8月22日時点のものを利用しており、それ以降に追記、更新されている可能性があります。
本翻訳は原作者の許可を得て公開されています。 Thanks for <a href="http://mywiki.wooledge.org/GreyCat">GreyCat</a>!</p>
<hr>
<p>このページはBashプログラマーが陥りがちなよくあるエラーについてまとめました。以下の例は全てなんらかの欠陥があります。
<a href="http://mywiki.wooledge.org/Quotes">クオートをいつも使い</a>、どんな理由があっても<a href="http://mywiki.wooledge.org/WordSplitting">単語分割</a>を使わなければ、多くの落とし穴からあなた自身を守ることができます!単語分割はクオート表現をしない場合にはデフォルトでオンになっている、Bourneシェルから継承された壊れたレガシーな設計ミスです。落とし穴の大半はクオートされていない展開になんらか関連し、単語分割しその結果をグロブします。</p>
<h3>1. for i in $(ls *.mp3)</h3>
<p><a href="http://mywiki.wooledge.org/BASH">BASH</a>プログラマーたちがループを書く際にもっとも犯しがちなよくあるミスは以下のような感じです。:</p>
<pre><code class="bash">for i in $(ls *.mp3); do # 間違いです!
some command $i # 間違いです!
done
for i in $(ls) # 間違いです!
for i in `ls` # 間違いです!
for i in $(find . -type f) # 間違いです!
for i in `find . -type f` # 間違いです!
files=($(find . -type f)) # 間違いです!
for i in ${files[@]} # 間違いです!
</code></pre>
<p><a href="http://mywiki.wooledge.org/CommandSubstitution">コマンド置換</a> はどの種類のものもクオート無しで使ってはいけません!</p>
<p>ここには2つの主な問題があります。クオート無しでの展開の出力を引数としてsplitして用いること、そしてlsの出力をパースすること -- それらの使い方の出力は決してパースされていないはずです。</p>
<p>何故って?これはファイル名にスペースが含まれていた時に<a href="http://mywiki.wooledge.org/ParsingLs">壊れる</a>からです。もう1つの理由として、 <code>$(ls *.mp3)</code> コマンド置換の出力が<a href="http://mywiki.wooledge.org/WordSplitting">単語の分割</a>になるからです。<code>01 - Don't Eat the Yellow Snow.mp3</code>というファイルがカレントディレクトリにあると想定しましょう、forループはファイル名を区切った結果の各単語を<code>01</code>, <code>-</code>, <code>Don't</code>, <code>Eat</code> ... というようにイテレーションします。</p>
<p>ありうる更に悪いこととして、前の単語をsplitしていって得られた文字列ではパス名展開が発生します。例えば、<code>ls</code>が<code>*</code>を含む文字出力を発生させ、それを含む文字がパターンとして認識され、それとマッチする全てのファイル名のリストと置換されます。</p>
<p>また、ダブルクオート(<code>"</code>)を置換することもできません:</p>
<pre><code>for i in "$(ls *.mp3)"; do # Wrong!
</code></pre>
<p>これは <code>ls</code> の全体の出力を1語として扱うようにしてしまいます。それぞれのファイルの名前をイテレーションする代わりに、ループは全てのファイル名をくっつけた文字列を <code>i</code> として一回だけ実行されます。</p>
<p>上記に加え、 <code>ls</code> コマンドのこの使い方は必要でないことは明らかです。この外部コマンド(訳者注: <code>ls</code> コマンド)の出力は人間が読むことを特に想定しており、スクリプトによりパースすることを想定していません。さて、これを行う正しい方法はなんでしょうか?</p>
<pre><code>for i in *.mp3; do # Better! and...
some command "$i" # ...always double-quote expansions!
done
</code></pre>
<p>BashのようなPOSIXシェルは<a href="http://mywiki.wooledge.org/glob">グロブ</a>機能がこの目的、つまりシェルがファイル名マッチングのリストにパターンを展開することをできるようにするために、特化して実装されています。外部ユーティリティの結果を解釈する必要はありません。グロビングは展開の最終ステップであり、 各 <code>*.mp3</code> パターンマッチが正しく単語分割を展開し、クオートなしの展開の影響を受けません。もしファイルを再帰的に処理する必要があるなら、 <a href="http://mywiki.wooledge.org/UsingFind">find(1)を使う (原文:UsingFind)</a> のページを参照してください。</p>
<p>ここで問題です。もし <code>*.mp3-files</code> がカレントディレクトリになかったなら何がが起きるでしょうか?その場合 <code>i="*.mp3"</code> というものが <code>i</code>に挿入された状態で<code>for</code> ループは一回だけ実行されますが、これは期待された結果ではありません!マッチしたファイルのあるなしをテストすることがワークアラウンドです:</p>
<pre><code># POSIX
for i in *.mp3; do
[ -e "$i" ] || continue
some command "$i"
done
</code></pre>
<p>単純にクオートを使用し、<a href="http://mywiki.wooledge.org/WordSplitting">単語分割</a> をどんな理由があっても使わなければ、これらのよくある間違いを防ぐことができるでしょう。単語分割はあなたがクオート展開をしない場合に、デフォルトで備わっている不便な、壊れている仕様です(Bourneシェルから引き継がれました。)。落とし穴のほぼ大半は、クオートなし展開と何らかの関連があり、続いて単語分割、そしてグロビングがあります。ほかのこのテーマにおけるバリエーションは単語分割の不正な利用とファイルの行を読み込む <code>for</code> ループです。これは間違っています!それらの行はファイル名で、2倍、(もしくは3倍かも)です。</p>
<p>上のloop本文の <code>$i</code> の周りのクオートに注目してください。これは2つめの落とし穴にあなたを導きます。</p>
<h3>2. cp $file $target</h3>
<p>この上に表示されているコマンドの何が間違いでしょうか?もしあなたが <code>$file</code> と <code>$target</code> がスペースもしくはワイルドカードのどちらも持っていないことを先に知っていれば、何もないでしょう。しかし、展開の結果は<a href="http://mywiki.wooledge.org/WordSplitting">単語分割</a>と<a href="http://mywiki.wooledge.org/glob">パス名展開</a>となります。ダブルクオートされたパラメータは必ず展開されます。</p>
<pre><code class="bash">cp -- "$file" "$target"
</code></pre>
<p>ダブルクオートなしでは <code>cp 01 - Don't Eat the Yellow Snow.mp3 /mnt/usb</code> のようなコマンドで次のような結果がエラーとなり返るでしょう。</p>
<p><code>cannot stat `01': No such file or directory</code></p>
<p><code>$file</code>がその中にワイルドカード ( <code>*</code>, <code>?</code>, <code>[</code> )を含んでおり、そこにそれらにマッチするファイルがあった場合、それらは展開されます。 <code>-</code> から始まる <code>$file</code>の中身がある場合だけは、ダブルクオートがあっても <code>cp</code> はあなたがコマンドラインオプションを与えたと考えてしまいます(下の落とし穴 #3 を見て下さい。)。 </p>
<p>特に変数が複数のファイル名を含む場合には、パラメータ展開をクオートで囲うことは慣習的でベストプラクティスです。たとえどんな一般的でない状況でも変数の中身(が問題ないこと)を保証できます。経験を積んだスクリプト作成者は、コードの文脈上、明らかにパラメータの中身が安全なことを保証されている数少ない場合を除いて、いつもクオートを使います。上級者はタイトルの <code>cp</code> コマンドは間違っているとほぼ考えるでしょう。</p>
<h3>3. ダッシュから始まるファイル名</h3>
<p>ダッシュ ( <code>-</code> )から始まるファイル名は様々な問題を起こします。展開されたリストの中に <code>*.mp3</code> のようなグロブがソートされ (現在の<a href="http://mywiki.wooledge.org/locale">ロケール</a>に基づき)、そして ダッシュ( <code>-</code> )は多くのロケールで文字の前にソートされます。リストはその後いくつかのコマンドに渡されますが、<code>-filename</code>というものをオプションとして誤った解釈される可能性があります。これに対しては、2つの主な解決方法があります。</p>
<p>1つめの解決法は <code>--</code> をコマンド(<code>cp</code>のような)とその引数の間に入れることです。これはオプションとしてのスキャンを止めさせて、いい感じにしてくれます。</p>
<pre><code class="bash">cp -- "$file" "$target"
</code></pre>
<p>この方法には潜在的な問題があります。オプションとして解釈され得るコンテキストの中で全てのパタメータの使用に際し、<code>--</code> を挿入したことを確かめる必要があり、<code>--</code>はとても見失いやすく、非常にコマンドを冗長にしてしまう可能性があることです。</p>
<p>ほとんどのよく書かれるオプションをパースするライブラリはこれを理解し、そしてプログラムはこれらの機能を正しく扱い、無料でこの機能を継承しているはずです。しかしながら、このオプションの終わりを認識することは結局のところアプリケーション依存です。いくつかのプログラムはオプションを手動でパースし、もしくは誤って解釈し、もしくはこれを認識できないサードパーティーの貧弱なオプションを使っているでしょう。標準的なユーティリティについても、POSIXにより記述されたいくつかの例外があるはずです。<code>echo</code>がその1つの例です。</p>
<p>他の方法は相対パスもしくは絶対パスを用いて、ファイル名がディレクトリから始まっていることを確認する方法です。</p>
<pre><code>for i in ./*.mp3; do
cp "$i" /target
...
done
</code></pre>
<p>このケースでは、ファイル名が<code>-</code>から始まっていたとしても、グロブは変数の値が<code>./-foo.mp3</code>のようなものを含んでいるか確認します。これは<code>cp</code>コマンドに関わるところでは、完璧に安全でしょう。</p>
<p>最終的には、全ての結果が同じプリフィックスを持つことを確かめ、<code>loop</code>本文の中で数回変数を使うだけなら、シンプルに展開を用いてプリフィックスを結合することができます。これは、生成した各単語のためにいくつかの余分な文字を格納する際に、理論的に節約をすることが出来ます。</p>
<pre><code>for i in *.mp3; do
cp "./$i" /target
...
done
</code></pre>
<h3>4. [ $foo = "bar" ]</h3>
<p>これは落とし穴#2と非常に似ているケースです。しかし、非常に重要なことなので繰り返します。この上の例では、クオートが間違った場所にあります。Bashにおいて文字列をクオートする必要はありません(メタ文字もしくはパターン文字を含まない限り)。しかし、もしスペースやワイルドカードを含むことがあるかないか確かで無いときは、変数をクオートすべきでしょう。</p>
<p>この例はいくつかの理由で壊れえます:</p>
<p>もし<code>[</code>の中の変数が存在しなかった場合、もしくはブランクであった場合、<code>[</code>コマンドは次のように閉じられることになります。</p>
<pre><code>[ = "bar" ] # 間違っています!
</code></pre>
<p>そして、次のエラーを発するでしょう。</p>
<pre><code>unary operator expected. (The = operator is binary, not unary, so the [ command is rather shocked to see it there.)
</code></pre>
<p>もし変数が内部に空白を含むなら、次のように<code>[</code>コマンドがその中身を見る前に2つの単語に分割されます。</p>
<pre><code>[ multiple words here = "bar" ]
</code></pre>
<p>これがあなたには大丈夫なようにみえるかもしれませんが、<code>[</code>が係る限りシンタックスエラーです。
正しい書き方は次の通りです:</p>
<pre><code># POSIX
[ "$foo" = bar ] # Right!
</code></pre>
<p>POSIXの<code>[</code>は渡された引数の数に応じてその作用を決定するので、<code>$foo</code>が<code>-</code>で始まる場合でも、POSIX準拠の実装で正常に動作します。古いシェルのみはこれに伴う問題がありますが、新しいコードをを書くときには心配する必要はありません。(以下の<code>x"$foo"</code>ワークアラウンドを参照して下さい)</p>
<p>Bashや他のkshのようなシェルでは、<code>[[</code>を使うさらなる代替案があります。</p>
<pre><code># Bash / Ksh
[[ $foo == bar ]] # 正しいです!
</code></pre>
<p>グロビングや単語分割がないため、<code>[[ ]]</code>の中にある<code>=</code>の左辺をクオートする必要はありません、また空の変数も正しく扱われます。一方、クオートすることはなにも傷をつけることはありません。<code>[</code>や<code>test</code>とは異なり、同様に<code>==</code>を利用することもできます。しかし<code>[[</code>を使った比較は、ただの文字列比較ではなく、右辺の文字列に対してパターンマッチングを行います。右辺を文字列とするには、パターンマッチのコンテキストの中で特殊な意味を持つ文字列が使われている時にはクオートをする必要があることを覚えておいて下さい。</p>
<pre><code># Bash / Ksh
match=b*r
[[ $foo == "$match" ]] # いい感じです!クオートしないことでb*rのパターンに対してもマッチします。
</code></pre>
<p>以下のようなコードを見たこともあるでしょう:</p>
<pre><code># POSIX / Bourne
[ x"$foo" = xbar ] # Ok, but usually unnecessary.
</code></pre>
<p><code>x"$foo"</code> ハックはコードを<code>[[</code>がなくもっと原始的なものである<code>[</code>がある非常に古いシェルで実行するのに必要で、もし<code>$foo</code>が<code>-</code>から始まっていると混乱を招きます。古いシステムに関していうと、<code>[</code>は<code>-</code>ではじまる<code>=</code>の右辺の中身であろうがなかろうが気にしません。ただ文字列的に扱います。左辺だけさらなる注意が必要です。</p>
<p>このワークアラウンドが必要なシェルはPOSIXに従っていないものと覚えておいて下さい。Heirloom Bourneシェルでさえこれは必要ありません。(おそらくBourneシェルのPOSIXでないクローンは広くシステムシェルとして使われています。)このような過剰なポータビリティは非常にレアな要件であり、コードを見づらくするでしょう。(そして見難いでしょう。)</p>
<h3>5. cd $(dirname "$f")</h3>
<p>これもまた別の<a href="http://mywiki.wooledge.org/Quotes">クオート</a>エラーです。変数展開とともに、<a href="http://mywiki.wooledge.org/CommandSubstitution">コマンド置換</a>の結果が<a href="http://mywiki.wooledge.org/WordSplitting">単語分割</a>と<a href="http://mywiki.wooledge.org/glob">パス名展開</a>となります。よって、コレに対しては次のようにクオートを行うべきです。</p>
<pre><code>cd -P -- "$(dirname -- "$f")"
</code></pre>
<p>ここで明確でないのはどうクオートをネストするかです。Cプログラマーはこれを最初と2番目のダブルクオートをまとまったグループとして期待し、そして3つ目と4つ目も同様です。しかしBashにおいてその限りではありません。Bashはダブルクオートを1つのペアとしてコマンド置換の中で扱い、置換の外側のダブルクオートを別のペアとして扱います。</p>
<p>別の方法でこれを表現すると、parserはコマンド置換をネストレベルとして扱い、その内側のクオートは外側のクオートとは別のものとして隔離されます。</p>
<h3>6. [ "$foo" = bar && "$bar" = foo ]</h3>
<p><code>&&</code> を <a href="http://mywiki.wooledge.org/BashFAQ/031">古い</a> <code>test</code> (もしくは <code>[</code> )コマンドの中で使うことはできません。Bashのバーサーは、<code>[[ ]]</code> もしくは <code>(( ))</code> の外側の <code>&&</code> について、 <code>&&</code> の前後においてコマンドを2つのコマンドに分割します。</p>
<pre><code>[ bar = "$foo" ] && [ foo = "$bar" ] # Right! (POSIX)
[[ $foo = bar && $bar = foo ]] # Also right! (Bash / Ksh)
</code></pre>
<p>(落とし穴#4で紹介した伝統的な理由により<code>[</code>の中の変数と定数を逆にしています。また、<code>[[</code>のケースも同様に逆にしていますが、パターンとして解釈することを防ぐために展開の際にはクオートが必要です。)同じことは<code>||</code>にも当てはまります。どちらの場合もかわりに、<code>[[</code>、もしくは2つの<code>[</code>コマンドを使って下さい。</p>
<p>これを避けるには次の通りにします:</p>
<pre><code>[ bar = "$foo" -a foo = "$bar" ] # Not portable.
</code></pre>
<p><code>-a</code>, <code>-o</code>, そして<code>(</code>と<code>)</code>(グルーピング)などのバイナリの演算子はPOSIX標準へのXSI拡張です。
これら全ては、POSIX-2008ではobusolute (廃止予定)としてマークされます。これらは新しいコードでは使用すべきではありません。<code>[ A = B -a C = D ]</code> もしくは <code>-o</code>の実践的な問題の1つはPOSIXが4つ以上の引数と<code>test</code>もしくは<code>[</code>コマンドの結果を<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">明示しないこと</a>です。これはおそらく、ほとんどのシェルで動作しますが、それを頼りにすることはできません。POSIXシェルのためにコードを書く必要がある場合、2つの<code>test</code>または<code>&&</code>演算子で区切られた<code>[</code>コマンドを代わりに使用する必要があります。</p>
<h3>7. [[ $foo > 7 ]]</h3>
<p>ここでは複数の問題があります。まず、 <code>[[</code><a href="http://mywiki.wooledge.org/BashFAQ/031">コマンド</a>は、<a href="http://mywiki.wooledge.org/ArithmeticExpression">算術式</a>を評価するためにのみ使用すべきではありません。サポートされているテスト演算子のいずれかを含むテスト式のために使用されるべきです。技術的にはあなたが<code>[[</code>演算子のいくつかを使用して計算を行うことができますが、それだけでどこかの式で非数値演算テストのいずれかの演算子と一緒にこれを行うには意味があります。数値比較(または任意の他のシェルの算術演算)を行いたいだけである場合には、<code>(( ))</code>を使用することがはるかに優れています:</p>
<pre><code># Bash / Ksh
((foo > 7)) # Right!
[[ foo -gt 7 ]] # Works, but is pointless. Most will consider it wrong. Use ((...)) or let instead.
</code></pre>
<p>もしあなたが<code>[[ ]]</code>の内側で<code>></code>演算子を使うなら、それは文字列比較として扱われ、(ロケールの順序で照合します)、整数型の比較ではありません。これは時には動きますが、あなたが少なくとも期待した時には失敗するでしょう。もし<code>></code>演算子を<code>[ ]</code>のなかで使うケースはもっと悪いケースです。出力リダイレクトになります。ディレクトリ内部の7と名付けられたファイルを受け取り、テストは<code>$foo</code>が空でない限り成功してしまいます。</p>
<p>もし厳密なPOSIX準拠が必要で、<code>((</code>が使えないなら、古い方式の<code>[</code>を使うことが正しい代替案です。</p>
<pre><code># POSIX
[ "$foo" -gt 7 ] # Also right!
[ $((foo > 7)) -ne 0 ] # POSIX-compatible equivalent to ((, for more general math operations.
</code></pre>
<p><code>test ... -gt</code>コマンドは<code>$foo</code>が<a href="http://mywiki.wooledge.org/BashFAQ/054">整数型でないと</a>興味深いことになりうるということを覚えておいて下さい。そのため、正しいクオートをするところがパフォーマンスと、いくつかのシェルであり得るあいまいな副作用の可能性を低減するために単一の単語に引数を閉じ込めること以外にそんなにありません。</p>
<p>もし算術式への入力(<code>((</code>もしくは<code>let</code>を含む)もしくは数値比較に係る<code>[</code>テスト表現が確認出来ないのであれば、必ずいつも評価表現の前に<a href="http://mywiki.wooledge.org/BashFAQ/054">入力をバリデーション</a>しなければなりません。</p>
<pre><code># POSIX
case $foo in
*[^[:digit:]]*)
printf '$foo expanded to a non-digit: %s\n' "$foo" >&2
exit 1
;;
*)
[ $foo -gt 7 ]
esac
</code></pre>
<h3>8. grep foo bar | while read -r; do ((count++)); done</h3>
<p>上のコードはひと目見ただけではOKなように見えますよね?確かに、ただの <code>grep -c</code> の貧相な実装ですが、これはシンプルな例になることを意図されているからです。別の<a href="http://mywiki.wooledge.org/SubShell">SubShell</a>の中でそれぞれのコマンドが並列で実行されるため、 <code>count</code> を変えることは<code>while</code> ループの外に移譲されていません。この動作は様々な点でほぼすべてのBash初心者を驚かせます。</p>
<p>POSIXはsubshell内で評価された並列の最後の要素について指定をしません。</p>
<p>POSIXは、パイプラインの最後の要素はサブシェルで評価されているかどうかを指定しません。ksh バージョン93、または Bash バージョン4.2以上で <code>shopt -s lastpipe</code> を実行した場合、 <code>while</code> ループをこの例のようにオリジナルのシェルプロセスで実行し、何の副作用なしに有効となります。そのため、ポータブルなスクリプトは、いずれの動作に依存しないような方法で記述する必要があります。</p>
<p>この問題、もしくは類似の問題へのワークアラウンドは、<a href="http://mywiki.wooledge.org/BashFAQ/024">Bash FAQ #24</a>を参照して下さい。ここに書くには長すぎるので・・・。</p>
<h3>9. if [grep foo myfile]</h3>
<p>多くの初心者は<code>[</code>もしくは<code>[[</code>のすぐ前に<code>if</code>キーワードがある非常に一般的なパターンをみて<code>if</code>ステートメントについて誤った直感をもつことでしょう。このことは<code>[</code>が、C言語の<code>if</code>ステートメントで使われる丸括弧のように、あたかも<code>if</code>ステートメントのシンタックスの一部であるかのように確信させます。</p>
<p>これはそのようなものではありません!<code>if</code>はコマンドを取るのです。<code>[</code>はコマンドで、<code>if</code>ステートメントのシンタックスマーカーではありません。これは<code>]</code>が最後の引数で有る必要を除いて、<code>test</code>コマンドと等価です。</p>
<pre><code># POSIX
if [ false ]; then echo "HELP"; fi
</code></pre>
<pre><code>if test false; then echo "HELP"; fi
</code></pre>
<p>上記の2つは等価です。どちらも引数 <code>false</code>が空ではないことを確認します。どちらの場合も <code>HELP</code> はいつも表示され、シェルシンタックスについて他のプログラム言語から類推したプログラマは驚かされるでしょう。</p>
<p><code>if</code>ステートメントのシンタックスは次の通りです:</p>
<pre><code>if COMMANDS
then <COMMANDS>
elif <COMMANDS> # optional
then <COMMANDS>
else <COMMANDS> # optional
fi # required
</code></pre>
<p>繰り返しますが、 <code>[</code> はコマンドです。他の通常のシンプルなコマンド同様引数を取ります。 <code>if</code> は他のコマンドを含んで構成されるコマンドです、 <code>[</code> はこのシンタックスそのものではありません!</p>
<p>Bashがビルトインコマンドである <code>[</code> をもっており、 <code>[</code> は <code>]</code> と何も特別な関係は無いということを知っています。Bashはただ <code>]</code> を <code>[</code> の引数として渡し、スクリプトをより良く見せるためだけに作られた最後の引数 <code>]</code> を必要とします。</p>
<p>ゼロ以上のオプションの<code>elif</code>のセクション、 1オプションの<code>else</code>節があるかもしれません。</p>
<p><code>if</code>を構成するコマンドは2つもしくはそれ以上のコマンドのリストを含み、<code>then</code>, <code>elif</code>, もしくは<code>else</code>によりそれぞれ区切られ、<code>fi</code>というキーワードにより終了されます。最初の節の最後のコマンドの終了ステータスとそれぞれの次の<code>elif</code>節がそれぞれどの<code>then</code>節が評価されるかを決定します。また別の<code>elif</code>は<code>then</code>節の1つが実行されるまでに評価されます。もし評価される<code>then</code>節がないなら、<code>else</code>節が取られ、もしくは<code>else</code>が与えられていなければ<code>if</code>ブロックは完了し、<code>if</code>コマンド全体で0 (true)を返します。</p>
<p>もし<code>grep</code>コマンドの出力にもとづき何かを判断したいなら、丸括弧や鍵括弧、バッククオートや他のどんなシンタックスにおいても囲う必要はありません!こんな感じに、ただ<code>if</code>のあとに<code>grep</code>をコマンドとして使うだけで良いのです。</p>
<pre><code>if grep -q fooregex myfile; then
...
fi
</code></pre>
<p>もし<code>grep</code>が<code>myfile</code>の行でマッチするなら、終了コードは0(true)になり、<code>then</code>節が実行されます。そうでない場合、つまりマッチするものがない場合には、<code>grep</code>は0でないコードを返し、<code>if</code>コマンド全体は0となります。</p>
<p>以下も参照して下さい。</p>
<ul>
<li><a href="http://wiki.bash-hackers.org/syntax/ccmd/if_clause">BashGuide/TestsAndConditionals</a></li>
</ul>
<h3>10. if [bar="$foo"]; then ...</h3>
<pre><code>[bar="$foo"] # 間違いです
[ bar="$foo" ] # これも同様に間違いです!
</code></pre>
<p>前の例で説明したように、<code>[</code>はコマンドなのです(そのことは<code>type -t [</code>もしくは<code>whence -v [</code>でわかります)。
他のシンプルなコマンドのように、Bashはコマンドにスペースが続くこと、その後に1つめの引数、その後別のスペース・・・等を期待します。スペースを入れることなしにすべてのものを一緒に実行することはできません!
ここに正しい例方法があります:</p>
<pre><code>if [ bar = "$foo" ]; then ...
</code></pre>
<p><code>bar</code>, <code>=</code>, <code>"$foo"</code> の展開結果、 そして <code>]</code> は <code>[</code> コマンドにとってそれぞれ別の引数です。それぞれの引数のペアの間にはスペースがなくてはならず、それによりShellはそれぞれの引数の始まりと終わりがどこなのか知ることができます。</p>
<h3>11. if [ [ a = b ] && [ c = d ] ]; then ...</h3>
<p>さぁ、またです。<code>[</code>はコマンドです。<code>if</code>とC言語のような条件式の類の間に位置するシンタックスマーカーではありません。また、グルーピングでもありません。C言語のような<code>if</code>コマンドをとることは出来ませんし、括弧を<code>[]</code>で置き換えるだけでそれをBashコマンドにすることもできません!</p>
<p>もし合成条件式を表現したいのであれば、次のようにしてください:</p>
<pre><code>if [ a = b ] && [ c = d ]; then ...
</code></pre>
<p><code>if</code> のあとに <code>&&</code>(論理的AND、短縮された評価式)演算子でつなげられた2つのコマンドがあることを覚えておいて下さい。これは次のものと全く同じです:</p>
<pre><code>if test a = b && test c = d; then ...
</code></pre>
<p>もし最初の <code>test</code> コマンドが <code>false</code> を返すなら、 <code>if</code> ステートメントの本文は何も入力されません。 <code>true</code> を返すときには、2番目の <code>test</code> コマンドが実行されます。そしてtrueを返す場合には<code>if</code>ステートメントの本文は入力されます。(Cプログラマーは <code>&&</code> に慣れているでしょう。Bashも同じ短縮された評価式を用います。同様に<code>||</code>も<code>OR</code>演算の短縮された評価式です。)</p>
<p><code>[[</code>キーワードは<code>&&</code>の利用を許可するので、次のように書くことも可能です:</p>
<pre><code>if [[ a = b && c = d ]]; then ...
</code></pre>
<p>評価演算子と一緒に使われるtestについては落とし穴#6も参照して下さい。</p>
<h3>12. read $foo</h3>
<p><code>read</code>コマンドでは変数名の前に$を使うことはできません。もし<code>foo</code>という変数にデータを入れたいなら、次のようにすべきです:</p>
<pre><code>read foo
</code></pre>
<p>もしくは、より安全に以下のようにします:</p>
<pre><code>IFS= read -r foo
</code></pre>
<p><code>read $foo</code> は入力の行を読み込もうとし、それを<code>$foo</code>の中にある名前に代入しようとします。これはあなたが<code>foo</code>を他の変数に参照されるよう意図する場合には有用です。しかしながらほとんどのケースではこれは単純にバグです。</p>
<h3>13. cat file | sed s/foo/bar/ > file</h3>
<p>ファイルから読み込み、書き込みを同じパイプライン上で行うことは出来ません。そのパイプラインが何をするかに依存し、ファイルは上書きされ得ります (0バイトに、もしくはあなたのOSのパイプラインバッファのサイズと同じバイト数になります。)、もしくはそれが利用可能なディスク・スペースをいっぱいになるまで大きくなり得ますし、OSのファイルサイズの制限に達したり、クオータ制限に達する等するでしょう。</p>
<p>もし安全にファイルに現行を加えたいなら、ファイルの終わりに追記する以外に、テキストエディタを使って下さい。</p>
<pre><code>printf %s\\n ',s/foo/bar/g' w q | ed -s file
</code></pre>
<p>もしテキストエディタではできない何かを行う場合には、どこかの時点で作られたテンポラリファイルが必要です。(*)例として、次の例は完全にポータブルです:</p>
<pre><code>sed 's/foo/bar/g' file > tmpfile && mv tmpfile file
</code></pre>
<p>以下の例はGNU sed 4.X系でのみ動作します:</p>
<pre><code>sed -i 's/foo/bar/g' file(s)
</code></pre>
<p>これもテンポラリファイルを作成し、トリッキーにリネームと同じことをします。透過的にそれを処理するだけです。</p>
<p>以下の代替コマンドは perl 5.X系を必要とします(もしかしたらGNU sed 4.X系でも使えるかもしれません。)</p>
<pre><code>perl -pi -e 's/foo/bar/g' file(s)
</code></pre>
<p>ファイルの中身を置換することの詳細は、<a href="http://mywiki.wooledge.org/BashFAQ/021">Bash FAQ #21</a>を参照して下さい。</p>
<p>(*) : <a href="https://packages.debian.org/sid/moreutils">moreutils</a>のspongeコマンドでは、そのマニュアルの中で次の例を利用しています:</p>
<pre><code>sed '...' file | grep '...' | sponge file
</code></pre>
<p><code>mv</code> コマンドの不可分性に加えテンポラリファイルを用いるより、このバージョンは全てのデータをファイルを開き、書き込む前に”浸し”ます(実際のマニュアル内の表現です!)。処理時点においてオリジナルファイルのコピーがディスク上にないため、このバージョンはプログラムが書き込み演算中にクラッシュした場合にはデータロスを起こします。</p>
<p>テンポラリファイルと<code>mv</code>を用いることは、電源断やシステムクラッシュの際にもほんの少ないリスクのみがあります。電源断の際も新しいファイルもしくは古いファイルのどちらかが残るように100%確実にするには、 <code>mv</code> の前に <code>sync</code> する必要があります。</p>
<h3>14. echo $foo</h3>
<p>この比較的害がないように見えるコマンドはとてつもない混乱を生じさせます。なぜなら<code>$foo</code>が<a href="http://mywiki.wooledge.org/Quotes">クオート</a>されておらず、<a href="http://mywiki.wooledge.org/WordSplitting">単語分割</a>を生じるだけでなく、ファイルの<a href="http://mywiki.wooledge.org/glob">グロブ</a>も生じるからです。これはBashプログラマー達を彼らの変数が間違った値を含んでいたと勘違いさせます。たとえ実際は値に問題がなかったとしても。単語分割もしくはファイル名展開は何が起こっているかの見通しを混乱させます。</p>
<pre><code>msg="Please enter a file name of the form *.zip"
echo $msg
</code></pre>
<p>このメッセージは2つの単語に分割され、どんなグロブも展開されます、例えば <code>*.zip</code>のようなものも。あなたのユーザがこのメッセージを見た時、どう思うでしょうか。</p>
<pre><code>Please enter a file name of the form freenfss.zip lw35nfss.zip
</code></pre>
<p>デモをお見せします:</p>
<pre><code>var="*.zip" # var contains an asterisk, a period, and the word "zip"
echo "$var" # writes *.zip
echo $var # writes the list of files which end with .zip
</code></pre>
<p>事実、ここでは<code>echo</code>コマンドは絶対的な安全性がある状態で使われることはできません。例としてもし変数が<code>-n</code>を含んでいる場合、<code>echo</code>はそれを表示されるデータとしてではなく、オプションとして解釈します。変数の値を表示する最も絶対的に確実な方法は<code>printf</code>を用いることです。</p>
<pre><code>printf "%s\n" "$foo"
</code></pre>
<h3>15. $foo=bar</h3>
<p><code>$</code>を変数名の前につけて変数を代入することはできません。Perlではないのですから。</p>
<h3>16. foo = bar</h3>
<p>変数に値を代入する際にスペースを<code>=</code>の周りにいれることはできません。Cではないのです。
あなたが<code>foo = bar</code>と書いた時、shellはそれらを3つの単語として分割します。最初の単語fooはコマンド名として認識されます。2つ目と3つ目はそのコマンドへの引数になります。</p>
<p>同じように、次の例も間違っています:</p>
<pre><code>foo= bar # 間違いです!
foo =bar # 間違いです!
$foo = bar; # 完全に間違っています!
foo=bar # 正しいです。
foo="bar" # もっと正しいです。
</code></pre>
<h3>17. echo <<EOF</h3>
<p>ここでの文書はスクリプトでテキストデータの大きなブロックを埋め込むための便利なツールです。これはスクリプト中のテキストの行をコマンドの標準出力へリダイレクトします。残念ながら、<code>echo</code>は標準入力より読み込むことができません。</p>
<pre><code># この例は間違っています:
echo <<EOF
Hello world
How's it going?
EOF
# これがあなたがしようとしたことです:
cat <<EOF
Hello world
How's it going?
EOF
# Or, use quotes which can span multiple lines (efficient, echo is built-in):
echo "Hello world
How's it going?"
</code></pre>
<p>クオートをこのように使うのは問題ありません、すべてのシェルできちんと動きます。しかし行のまとまりをスクリプトへ落とすことは出来ません。最初と最後の行にシンタックスのマークアップがあります。もしシェルシンタックスから触らない行がある場合には、もしくは <code>cat</code> コマンドをspawnしたくないなら、ここに代替案があります:</p>
<pre><code># 代替手段として printf を用いる(これも有効であり、printfはビルトインコマンドです)
printf %s "\
Hello world
How's it going?
"
</code></pre>
<p><code>printf</code> の例の中で、最初の行の <code>\</code> はテキストブロックの始まりに余計な改行を加えることを防ぎます。最終行には改行が入ります(最後のクオートは改行と一緒にあるためです。)。 <code>printf</code> フォーマットの引数の <code>\n</code> を使わないことは <code>printf</code> が最後に余計な行を追加することを防ぎます。 <code>\</code>トリックはシングルクオート内では動作しません。もしテキストのかたまりの周りでシングルクオートを使いたい/使う必要が有るときには、2つ選択肢があります。どちらも必要なシェルシンタックスをあなたのデータに紛れ込ますことです:</p>
<pre><code>printf %s \
'Hello world
'
printf %s 'Hello world
'
</code></pre>
<h3>18. su -c 'some command'</h3>
<p>このシンタックスはほとんど正しいです。問題は、多くのプラットフォームにおいて <code>su</code>は<code>-c</code>引数を取りますが、これは望んだ結果ではありません。OpenBSDの例を見てみましょう。</p>
<pre><code>$ su -c 'echo hello'
su: only the superuser may specify a login class
</code></pre>
<p><code>-c 'some command'</code> をシェルに渡すには、ユーザ名を <code>-c</code>の前に付ける必要があります。</p>
<pre><code>su root -c 'some command' # Now it's right.
</code></pre>
<p>このコマンドを使った際には、 <code>su</code> は <code>root</code> というユーザ名があるとみなします、しかしShellにコマンドを後で渡したい場合にはこれは失敗します。このケースにおいては、必ずユーザ名を提供する必要があります。</p>
<h3>19. cd /foo; bar</h3>
<p>もし<code>cd</code>コマンドからのエラーを確認しない場合、<code>bar</code>は間違った場所で実行されて終了するでしょう。例としてもし<code>bar</code>が<code>rm -f *</code>であれば、これは大変な災害を引き起こしかねません。</p>
<p><code>cd</code>コマンドからのエラーを必ずいつも確認することが必要です。最もシンプルなこれを実施する方法は次の通りです:</p>
<pre><code>cd /foo && bar
</code></pre>
<p>もし<code>cd</code>の後に1つ以上コマンドがある場合には、これが望ましいでしょう:</p>
<pre><code>cd /foo || exit 1
bar
baz
bat ... # Lots of commands.
</code></pre>
<p><code>cd</code>はディレクトリのの変更の失敗を"bash: cd: /foo: No such file or directory"のような標準エラー出力メッセージと共に知らせてくれます。しかし、もし標準出力に任意のメッセージを追加したいなら、次のようにコマンドのグループ分けが使えます。</p>
<pre><code>cd /net || { echo "Can't read /net. Make sure you've logged in to the Samba network, and try again."; exit 1; }
do_stuff
more_stuff
</code></pre>
<p><code>{</code>と<code>echo</code>の間にスペースが必要なこと、そして<code>}</code>で閉じる前に<code>;</code>が必要なことを覚えておいて下さい</p>
<p>0でないコードを返したいずれかのコマンドでスクリプトを途中停止するために<a href="http://mywiki.wooledge.org/BashFAQ/105">"set -e"</a>を有効にするのを好む人もいますが、これを正しく使うのは少しトリッキーです。(多くの有名なコマンドは致命的として扱ってほしくないであろうWarningに対し0でないコマンドを返すためです。)</p>
<p>ところで、多くのBashスクリプトの内部でディレクトリ変更をしているなら、Bashの<code>pushd</code>,<code>popd</code>,<code>dirs</code>に関するヘルプを必ず読んで下さい。おそらくあなたの書いた<code>cd</code>および<code>pwd</code>を管理する全てのコードは完全に必要ないもののはずです。</p>
<p>これを語るにはこれを見てから次と比較してみてください:</p>
<pre><code>find ... -type d -print0 | while IFS= read -r -d '' subdir; do
here=$PWD
cd "$subdir" && whatever && ...
cd "$here"
done
</code></pre>
<p>比較対象です:</p>
<pre><code>find ... -type d -print0 | while IFS= read -r -d '' subdir; do
(cd "$subdir" || exit; whatever; ...)
done
</code></pre>
<p><a href="http://mywiki.wooledge.org/SubShell">SubShell</a>を強制することで<code>cd</code>はSubShellの中でだけ発生します。ループの次のイテレーションには、<code>cd</code>の成功失敗にかかわらず通常の場所に戻ります。手動で戻る必要はなく、また終わらない他の条件式の使用を防ぐ<code>&&</code>ロジックの繰り返しで詰まることもありません。Subshellバージョンはシンプルかつ綺麗です。(少しだけ遅いにもかかわらずです。)</p>
<h3>20. [ bar == "$foo" ]</h3>
<p><code>==</code> 演算子は <code>[</code>コマンドで有効ではありません。代わりに、<code>=</code>もしくは<code>[[</code>を使って下さい。</p>
<pre><code>[ bar = "$foo" ] && echo yes
[[ bar == $foo ]] && echo yes
</code></pre>
<h3>21. for i in {1..10}; do ./something &; done</h3>
<p><code>;</code> を <code>&</code> の後に使うことは出来ません。異例な <code>;</code> を全体から取り除いてください。</p>
<pre><code>for i in {1..10}; do ./something & done
</code></pre>
<p>もしくは次のようにします。</p>
<pre><code>for i in {1..10}; do
./something &
done
</code></pre>
<p><code>&</code> は、<code>;</code> のように、コマンドを終了させる役割として機能しています。これら2つを混在させることはできません。</p>
<p>一般的に、<code>;</code> は改行で置き換えられます、しかし全ての改行が <code>;</code>で置き換えられるわけではありません。</p>
<h3>22. cmd1 && cmd2 || cmd3</h3>
<p><code>&&</code>や<code>||</code>を<code>if ... then ... else ... fi</code>のショートカットシンタックスとして用いる人がいます。多くの場合において、これは完全に安全です:</p>
<pre><code>[[ -s $errorlog ]] && echo "Uh oh, there were some errors." || echo "Successful."
</code></pre>
<p>しかしながら、一般的にこの構成は完全に<code>if ... fi</code>と等価なわけではありません。<code>&&</code>もまた終了ステータスを生成した後にコマンドが来るためです。もしその終了ステータスが "true" (0) でなかった場合には、<code>||</code>の後のコマンドも無効にされます。例としては次の通りです:</p>
<pre><code>i=0
true && ((i++)) || ((i--))
echo $i # Prints 0
</code></pre>
<p>ここでは何が起きたでしょうか?<code>i</code>が1になるべきに見えますが、0で終了しています。何故でしょうか。それは<code>i++</code>と<code>i--</code>が両方とも実行されたからです。<code>((i++))</code>コマンドは終了ステータスを持ち、そしてその終了ステータスは括弧の中のC言語のような評価の表現から、届けられます。この表現の値は0(<code>i</code>の初期値)で、C言語ではint型の<code>0</code>はfalseとして捉えられます。つまり、<code>((i++))</code> (<code>i</code>が0のとき)終了ステータスは1となり(つまりfalse)、従って<code>((i--))</code>コマンドも同様に実行されるのです。</p>
<p>これは前置インクリメント演算子を用いた場合には、<code>++i</code>からの終了ステータスがtrueとなるため、発生しません。</p>
<pre><code>i=0
true && (( ++i )) || (( --i ))
echo $i # Prints 1
</code></pre>
<p>しかしこれは例のポイントを見失っています。これは単なる偶然により動作し、そしてもし<code>y</code>が失敗する可能性があるなら、<code>x && y || z if y</code>に頼ることはできません!(この例ではもし<code>i</code>を0に替えて<code>-1</code>で初期化したなら失敗します。)</p>
<p>もし安全性が必要なら、もしくはこれがどう動くか確かでないのなら、もしくはどこかの節の何かが明確でないなら、シンプルに<code>if ... fi</code>シンタックスをプログラム内部で利用して下さい。</p>
<pre><code>i=0
if true; then
((i++))
else
((i--))
fi
echo $i # Prints 1
</code></pre>
<p>この節はBourne Shellにも当てはまり、それを示すコードがこれになります:</p>
<pre><code>true && { echo true; false; } || { echo false; true; }
</code></pre>
<p>出力は"single"という一行の代わりに、"true"と"false"の2行になります。</p>
<h3>23. echo "Hello World!"</h3>
<p>ここにおける問題は、対話型Bashシェルにおいて、次のようなエラーが発生することです。</p>
<pre><code>bash: !": event not found
</code></pre>
<p>これは対話型シェルのデフォルト設定においてはBashはcsh-styleのコマンド履歴展開を <code>!</code> を用いて行うために発生します。これはシェルスクリプトにおける問題ではなく、対話型シェルのみにおいて発生する問題です。</p>
<p>残念ながら、これが動かないことを"直す"方法は次の通りです:</p>
<pre><code>$ echo "hi\!"
hi\!
</code></pre>
<p>最も簡単な解決方法は<code>histexpand</code>オプションを設定しないことです。これは <code>set +H</code> もしくは <code>set +o histexpand</code>にて実施できます。</p>
<p>さて、ここで問題です。なぜ<code>histexpand</code>はシングルクオートより優先されるのでしょうか。
私は曲のファイルを操作するとき、個人的にこの問題に遭遇しました。</p>
<pre><code>mp3info -t "Don't Let It Show" ...
mp3info -t "Ah! Leah!" ...
</code></pre>
<p>シングルクオートを使うことは、曲名にアポストロフィーを含むすべての曲のせいで非常に不便です。
ダブルクオートを使うとコマンド履歴展開の問題に当たることになります。(そしてファイルがアポストロフィーおよび<code>!</code>の両方を含んでいることを想像してみてください。クオートによる区切りは酷いことになります。)私は実際にコマンド履歴展開を使ったことがないので、個人的な好みは <code>~/.bashrc</code> の中でコマンド履歴展開を無効化することです。</p>
<p>この場合以下のコマンドへ有効です:</p>
<pre><code>echo 'Hello World!'
</code></pre>
<p>もしくは</p>
<pre><code>set +H
echo "Hello World!"
</code></pre>
<p>もしくは</p>
<pre><code>histchars=
</code></pre>
<p>多くの人達はシンプルに<code>~/.bashrc</code>の中で <code>set +H</code> もしくは <code>set +o histexpand</code> を設定しコマンド履歴展開を無効にすることを選択するでしょう。これは個人的な好みなので、どの方法があなたにあっているか選ぶべきでしょう。</p>
<p>他の解決方法としては以下もあります:</p>
<pre><code>exmark='!'
echo "Hello, world$exmark"
</code></pre>
<h3>24. for arg in $*</h3>
<p>Bash(すべてのBourneシェル)は位置パラメータのリストを1つづつ参照するための特別なシンタックスをもっています、そして<code>$*</code>はそれではありません。また<code>$@</code>もそうではありません。どちらもスクリプトのパタメータの中で単語のリストへ展開し、別の単語としてそれぞれのパラメータへ展開しません。</p>
<p>正しいシンタックスは次の通りです:</p>
<pre><code>for arg in "$@"
# Or simply:
for arg
</code></pre>
<p>位置パラメータでループすることがスクリプト中でつぎのようにすることが一般的です、<code>for arg</code>はデフォルトで<code>for arg in "$@"</code>のような感じです。
ダブルクオートされた<code>"$@"</code>はそれぞれの変数を1つの単語として扱う(もしくは1つのループイテレーションとして扱う)特殊な魔法です。これは少なくとも99%の場合使うべきでしょう。</p>
<p>ここに例があります:</p>
<pre><code> # Incorrect version
for x in $*; do
echo "parameter: '$x'"
done
</code></pre>
<pre><code> $ ./myscript 'arg 1' arg2 arg3
parameter: 'arg'
parameter: '1'
parameter: 'arg2'
parameter: 'arg3'
</code></pre>
<p>これは以下のように書く必要があります:</p>
<pre><code># 正しいバージョンです
for x in "$@"; do
echo "parameter: '$x'"
done
$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg 1'
parameter: 'arg2'
parameter: 'arg3'
</code></pre>
<h3>25. function foo()</h3>
<p>これはいくつかのShellでは動作します、しかし一部ではそうではありません。関数を定義する際にキーワード関数を<code>()</code>と結合するべきではありません。</p>
<p>Bash(少なくともいくつかのバージョンにおいて)は2つを混合することを許容しています。ほとんどのShellはこれを受け入れません。(例としてzsh 4.x 系もしくはおそらくそれ以上のバージョンもです。) いくつかのShellは上の<code>foo</code>関数を許容しますが、移植性の最大化のために、次のようなものを使うべきでしょう:</p>
<pre><code>foo() {
...
}
</code></pre>
<h3>26. echo "~"</h3>
<p>チルダの展開は <code>~</code>がクオートされていない時のみ有効です。この例においてはechoは <code>~</code> をユーザのホームディレクトリのパスではなく、<code>~</code>として標準出力へ書き出します</p>
<p>クォートされたパスのパラメータでユーザホームディレクトリの相対パスを表現するには<code>~</code>ではなく <code>$HOME</code> を 使うべきです。例えば <code>$HOME</code> が <code>/home/my photos</code>の場合を例として考えてみましょう。</p>
<pre><code>"~/dir with spaces" # "~/dir with spaces"として展開
~"/dir with spaces" # "~/dir with spaces"として展開
~/"dir with spaces" # "/home/my photos/dir with spaces"として展開
"$HOME/dir with spaces" # "/home/my photos/dir with spaces"として展開
</code></pre>
<h3>27. local varname=$(command)</h3>
<p>ローカル変数を関数のなかで宣言するとき、 <code>local</code> はコマンドとして自分の権限に基づき振る舞います。これは奇妙にも他の行と影響しあうことがあります。例えば、コマンド置換の終了ステータス ( <code>$?</code> )を取得したいとしても、できません。 <code>local</code> の終了ステータスがそれを置き換えてしまいます。</p>
<p>次のように、別のコマンドを使うのがベストです。</p>
<pre><code> local varname
varname=$(command)
rc=$?
</code></pre>
<p>次の落とし穴では、シンタックスに関する別の問題を説明します。</p>
<h3>28. export foo=~/bar</h3>
<p><a href="http://mywiki.wooledge.org/TildeExpansion">チルダの展開</a>(ユーザ名と一緒に、もしくはユーザ名なしで)はチルダが文字列の最初、もしくはスラッシュの後、もしくはチルダのみの時だけ発生することが保証されています。また、 <code>=</code> による変数への代入の直後にチルダが現れるときにも保証されています。</p>
<p>しかしながら、 <code>export</code> もしくは <code>local</code>コマンドは代入を構成しません。つまり、いくつかのシェル(Bashのような)は <code>export foo=~/bar</code> はチルダの展開が行われます、いくつかのシェル(dash等)では行われません。</p>
<pre><code> foo=~/bar; export foo # Right!
export foo="$HOME/bar" # Right!
</code></pre>
<h3>29. sed 's/$foo/good bye/'</h3>
<p><a href="http://mywiki.wooledge.org/Quotes">シングルクオート</a>の中では、 <code>$foo</code>のようなbashパラメータ展開は展開されません。それこそがシングルクオートの目的で、 <code>$</code> のような文字をシェルから保護するためにあります。</p>
<p>クオートをダブルクオートに変更してみましょう:</p>
<pre><code> foo="hello"; sed "s/$foo/good bye/"
</code></pre>
<p>しかしダブルクオートを使う場合更にエスケープが必要になる場合があることを覚えておいて下さい。詳細は<a href="http://mywiki.wooledge.org/Quotes">クオート</a>のページを参照してください。</p>
<h3>30. tr [A-Z] [a-z]</h3>
<p>ここには(少なくとも)3つ間違っている点があります。最初の問題は <code>[A-Z]</code>と<code>[a-z]</code>がシェルには<a href="http://mywiki.wooledge.org/glob">グロブ</a>のようにみえることです。もしカレントディレクトリに一文字のファイル名のファイルがないなら、このコマンドはあっているようにみえるでしょう。しかしやってみると、実行結果は失敗します。おそらく週末午前3時に。(訳者注:週末の午前3時にやってしまう、というぐらいケアレスミスということです)</p>
<p>2つ目の問題は<code>tr</code>の必ずしも正しい書き方では無いということです。これは実際には<code>[</code>を<code>[</code>に変換します。<code>A-Z</code>を<code>a-z</code>に変換し、<code>]</code>を<code>]</code>にも変換します。つまり、ブラケット(<code>[]</code>)は必要なく、最初の問題も発生しないのです。</p>
<p>3つめの問題はロケール依存で、<code>A-Z</code>もしくは<code>a-z</code>は期待した通りの26文字ASCII文字列を与えないことがあることです。実際、いくつかのロケールでは、<code>z</code>はアルファベットの中間の文字です!この解決法は何をしたいかに依存します:</p>
<pre><code> # 26文字ラテン配列を変更したい場合にはこれを使って下さい
LC_COLLATE=C tr A-Z a-z
# ユーザが期待しているものに近いロケール依存の変換をしたい場合にはこれを使って下さい
tr '[:upper:]' '[:lower:]'
</code></pre>
<p>2番目のコマンドにおいては<a href="http://mywiki.wooledge.org/glob">グロブ</a>防ぐためにクオートが必要です。</p>
<h3>31. ps ax | grep gedit</h3>
<p>ここでの根本的な問題は動作しているプロセスの名前は本質的に信頼できないということです。複数の正当なgeditのプロセスがあるかもしれません。何か他のものがgeditのとしての偽装をしているかもしれません。(実行したコマンドの報告されている名前の変更は簡単です。)これに対する本当の答えは、<a href="http://mywiki.wooledge.org/ProcessManagement">プロセス管理</a>を参照してください。</p>
<p>つぎのものは素早く直せますが、汚い方法です。</p>
<p>(例として)geditのPIDを検索し、多くの人達は次のようにします。</p>
<pre><code>$ ps ax | grep gedit
10530 ? S 6:23 gedit
32118 pts/0 R+ 0:00 grep gedit
</code></pre>
<p>これは、<a href="http://mywiki.wooledge.org/RaceCondition">競合条件</a>によっては、結果として多くの場合<code>grep</code>自身を結果とします。<code>grep</code>を取り除くには、次のようにします。</p>
<pre><code>ps ax | grep -v grep | grep gedit # will work, but ugly
</code></pre>
<p>別の方法として、これを使います。</p>
<pre><code>ps ax | grep '[g]edit' # quote to avoid shell GLOB
</code></pre>
<p>これは<code>grep</code>が一度評価された<code>gedit</code>を探し、<code>[g]edit</code>をプロセステーブルから無視します。</p>
<p>GNU/Linuxにおいては、<code>-C</code>パラメータがコマンド名でフィルタする代わりに使用できます。</p>
<pre><code>$ ps -C gedit
PID TTY TIME CMD
10530 ? 00:06:23 gedit
</code></pre>
<p>しかし<code>pgrep</code>を使うときにはこの悩ましさはありません。</p>
<pre><code>$ pgrep gedit
10530
</code></pre>
<p>2段階目においては、PIDは多くの場合<code>awk</code>もしくは<code>cut</code>により抽出されます。</p>
<pre><code>$ ps -C gedit | awk '{print $1}' | tail -n1
</code></pre>
<p>しかし<code>ps</code>の山ほどあるパラメータの一部として扱う事ができます。</p>
<pre><code>$ ps -C gedit -opid=
10530
</code></pre>
<p>もしあなたが1992年で止まっており、<code>pgrep</code>を使わないなら、代わりにすでに古代のものであり、破棄された、非推奨の<code>pidof</code>を代わりに使うことができます。</p>
<pre><code>$ pidof gedit
10530
</code></pre>
<p>そしてもしプロセスをkillするのにPIDが必要なら、<code>pkill</code>はあなたにとって興味深いものでしょう。しかし覚えておいて下さい。例えば<code>pgrep</code>もしくは<code>pkill</code>を<code>ssh</code>する場合、それは<code>sshd</code>という名前のプロセスをみつけ、それを終了したくないことでしょう。</p>
<p>残念ながら幾つかのプログラムはその名前で始まらないものもあり、例えばfirefoxは多くの場合firefox-binで始まりますが、<code>ps ax | grep firefox</code>で探す必要があるでしょう。もしくは、<code>pgrep</code>にいくつかパラメータを足すことで関連付けることも可能です。</p>
<pre><code>$ pgrep -fl firefox
3128 /usr/lib/firefox/firefox
7120 /usr/lib/firefox/plugin-container /usr/lib/flashplugin-installer/libflashplayer.so -greomni /usr/lib/firefox/omni.ja 3128 true plugin
</code></pre>
<p><a href="http://mywiki.wooledge.org/ProcessManagement">プロセス管理</a>も真剣に読んで下さい。</p>
<h3>32. printf "$foo"</h3>
<p>これはクオートの誤りではありませんが、文字列形式のエクスプロイトが原因です。もし<code>$foo</code>があなたの確実に制御出来る範囲でなければ、変数の中の<code>\</code>もしくは<code>%</code>文字が望まない動きをするでしょう。</p>
<p>必ずフォーマット文字列を与えて下さい:</p>
<pre><code>printf %s "$foo"
printf '%s\n' "$foo"
</code></pre>
<h3>33. for i in {1..$n}</h3>
<p><a href="http://mywiki.wooledge.org/BashParser">Bashのパーサー</a>は<a href="http://mywiki.wooledge.org/BraceExpansion">括弧の展開</a>を他のどんな展開や置換のよりも前に行います。したがって、括弧の展開のコードは<code>$n</code>のような表記(数値ではない)をみて、数字の配列へ中括弧を展開しないのです。これはこれは括弧の展開を実行時にのみわかる大きさのリストを作ることに使うことが出来ないことにほぼ等しいです。</p>
<p>代わりにこうしてください:</p>
<pre><code>for ((i=1; i<=n; i++)); do
...
done
</code></pre>
<p>この整数を通してのシンプルなイテレーションの場合、<code>for</code>ループの演算は、ほとんどの場合、括弧の展開が遅くなる得ることがあったり、またすべての引数を事前に展開され、不必要にメモリを消費するので、そもそもブレース展開よりも優先されます。</p>
<h3>34. if <a href="depending%20on%20intent">[ $foo = $bar ]</a></h3>
<p><code>[[</code> (<a href="http://mywiki.wooledge.org/BashFAQ/031">http://mywiki.wooledge.org/BashFAQ/031</a>) の中の<code>=</code>演算子の右辺がクオートされていない場合、Bashは文字列としてそれを扱う代わりに、<a href="http://mywiki.wooledge.org/glob">パターンマッチ</a>を行います。そして、上のコードの中では、<code>bar</code>が<code>*</code>を含んでいた場合、結果はいつでもtrueになります。文字列が等価であることを評価したいのであれば、右辺はクオートされるべきです。</p>
<pre><code>if [[ $foo = "$bar" ]]
</code></pre>
<p>もしパターンマッチを行いたい場合には、パターンを含む右辺であるとわかるような変数名にしたほうが懸命でしょう。もしくはコメントを使うかです。</p>
<p>これは同様に<code>=~</code>の右辺をクオートした場合には正規表現マッチングよりも単純な文字列比較となることを強制し、指摘する価値があります。</p>
<h3>35. if [[ $foo =~ 'some RE' ]]</h3>
<p><code>=~</code> 演算子の右辺のクオートは<a href="http://mywiki.wooledge.org/RegularExpression">正規表現</a>ではなく、文字列となってしまうことがあります。もし複雑だったり長い正規表現を使いたい、そして <code>\</code> エスケープだらけになることを避けたいなら、変数に格納してしまいましょう。</p>
<pre><code>re='some RE'
if [[ $foo =~ $re ]]
</code></pre>
<p>これは Bashバージョンの違いによる <code>=~</code> の仕様差分のワークアラウンドとしても動作します。変数を使うことは、微妙かつ面倒ないくつかの問題を避ける事にもなるのです。</p>
<p>同じ問題は <code>[[</code> の中のパターンマッチングでも発生します。</p>
<pre><code>[[ $foo = "*.glob" ]] # Wrong! *.glob is treated as a literal string.
[[ $foo = *.glob ]] # Correct. *.glob is treated as a glob-style pattern.
</code></pre>
<h3>36. [ -n $foo ] or [ -z $foo ]</h3>
<p><code>[</code> コマンドを使うとき、それぞれの置換を必ずクオートする必要があります。しない場合には、 <code>$foo</code> は1語ではなく0語もしくは42語、もしくはいくつかの語として展開され得て、シンタックスを破壊します。</p>
<pre><code>[ -n "$foo" ]
[ -z "$foo" ]
[ -n "$(some command with a "$file" in it)" ]
# [[ doesn't perform word-splitting or glob expansion, so you could also use:
[[ -n $foo ]]
[[ -z $foo ]]
</code></pre>
<h3>37. [[ -e "$broken_symlink" ]] が壊れたシンボリックリンクがあっても1を返す</h3>
<p>testはシンボリックリンクをたどり、シンボリックリンクが壊れていた場合(例えば存在しないファイルを指している、等)には <code>test -e</code> の結果はシンボリックリンクが存在していたとしても1を返します。</p>
<p>このワークアラウンド(もしくは事前に防ぐには)次のようにすべきでしょう。</p>
<pre><code>[[ -e "$broken_symlink" || -L "$broken_symlink" ]]
</code></pre>
<h3>38. ed file <<<"g/d{0,3}/s//e/g" fails</h3>
<p>ここでの問題は <code>ed</code> が <code>\{0,3\}</code> に対し0を受け入れない事により生じます。
下のコードは動くことを確認できるはずです。</p>
<pre><code>ed file <<<"g/d\{1,3\}/s//e/g"
</code></pre>
<p>POSIXが0を連続数の最低値として受け入れるはずのBRE( <code>ed</code> により使われる正規表現)で書いていてもこれが発生することは覚えておいて下さい。(<a href="http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_06">5節</a>を参照して下さい)</p>
<h3>39. expr sub-string fails for "match"</h3>
<p>多くの場合これはこれはとても合理的に動きます。</p>
<pre><code>word=abcde
expr "$word" : ".\(.*\)"
bcde
</code></pre>
<p>しかし <code>word</code> が <code>match</code>であった場合には失敗します。</p>
<pre><code>word=match
expr "$word" : ".\(.*\)"
</code></pre>
<p>問題は"match"がキーワードであることです。(GNUだけですが)解決方は<code>+</code>を接頭語にすることです。</p>
<pre><code>word=match
expr + "$word" : ".\(.*\)"
atch
</code></pre>
<p>もしくは、あなたも知る通り、<code>expr</code>を使うことをやめることです。<a href="http://mywiki.wooledge.org/BashFAQ/073">パラメーター展開</a>を使えば<code>expr</code>でできることは何でもできます。そこまでしたやろうとしていることは何ですか?単語の最初の文字を削除しますか?それはPOSIXシェルPEを使用することで、または拡張サブストリングで行うことができます。</p>
<pre><code>$ word=match
$ echo "${word#?}" # PE
atch
$ echo "${word:1}" # SE
atch
</code></pre>
<p>真剣に、SolarisのPOSIX準拠でない<code>/bin/sh</code>を使っていない限り`<code>expr</code>を使う言い訳はありません。それは外部プロセスであり、内部文字列操作より遅いのです。そして誰も使っていないので、誰も何をしているのか解りませんし、あなたのコードは難読化しメンテナンスすることが難しくなるでしょう。</p>
<h3>40. UTF-8環境におけるスクリプト、そしてByte-Order Marks (BOM)</h3>
<p>一般的に、UnixのUTF-8テキストはBOMを使いません。プレーンテキストのエンコードはロケール、MIMEタイプ、もしくは他のメタデータにより決定されます。BOMの存在は人間に読めるUTF-8にダメージを与えません、</p>
<p>通常BOMの存在は人間だけによる読み取りのためのUTF-8ドキュメントを傷つけませんが、スクリプト、ソースコード、設定ファイルなどの自動化プロセスによって解釈されるテキストファイルにおいては、問題となります。(多くの場合、シンタックス的には規約違反です。)BOMで始まるファイルは、MS-DOSの改行を持つものと同等に外のものとして考慮すべきです。</p>
<p>シェルスクリプトにおいては、次のページでは以下のように示されています。
"UTF-8は8ビット環境で透過的に使用される場合、先頭に特定のASCII文字を含むとき、例えばUnixのシェルスクリプトの先頭に<code>#!</code>の使用などがある、どんなファイルフォーマットもしくはプロトコルであってもBOMの使用はそれに干渉するでしょう。"</p>
<p>引用元: <a href="http://unicode.org/faq/utf_bom.html#bom5">http://unicode.org/faq/utf_bom.html#bom5</a></p>
<h3>41. content=$(<file)</h3>
<p>この表現には何も間違っているところはありません、しかしコマンド置換が最後の改行を削除することに気づくはずです。(<code>...</code>, <code>$(...)</code>, <code>$(<file)</code>, <code>`<file`</code>, <code>${ ...;</code> の全てです。)これは多くの場合、重要ではないかあっても望ましいものですが、行末にあるすべての存在し得る改行文字を含むリテラルの出力を保持しなければならない場合は、出力が改行文字を含むものを含んでいたか、またいくつ含んでいたのかについて知る方法はなく、トリッキーです。一つの醜いながらも使用可能な回避策は、コマンド置換の内部に接尾辞を追加して、外側でそれを削除することです:</p>
<pre><code>absolute_dir_path_x=$(readlink -fn -- "$dir_path"; printf x)
absolute_dir_path=${absolute_dir_path_x%x}
</code></pre>
<p>さらにポータブルでないですが、見やすい方法としては、<code>read</code>を空のデリミタと共に使用することです。</p>
<pre><code># Ksh (or bash 4.2+ with lastpipe enabled)
readlink -fn -- "$dir_path" | IFS= read -rd '' absolute_dir_path
</code></pre>
<p>この方法のデメリットは、読み込まれつストリームの一部だけがコマンド出力がNULバイトであっても<code>read</code>がいつもfalseを返すことです。コマンドの終了ステータスを取得する唯一の方法は<code>PIPESTATUS</code>を通すことだけです。<code>read</code>がtrueを返すように強制するよう、<code>pipefail</code>を利用し、内部的にNULバイトを出力することもできます。</p>
<pre><code>set -o pipefail
{ readlink -fn -- "$dir_path"; printf '\0x'; } | IFS= read -rd '' absolute_dir_path
</code></pre>
<p>これは、ポータビリティがない方法で、bashがpipefailとPIPESTATUSの両方をサポートしており、ksh93はpipefailだけをサポートし、最近のバージョンのmkshだけがpipefailをサポートして、以前のバージョンではPIPESTATUSのみをサポートしています。 加えて、NULバイト文字で<code>read</code>が止まるようにksh93の最新版が必要です。</p>
<h3>42. for file in ./* ; do if [[ $file != <em>.</em> ]]</h3>
<p>プログラムがファイル名解釈することを防ぐ方法はパス名を利用することです。(落とし穴#3を参照して下さい。) カレントディレクトリ配下のファイルには、ファイル名は相対的パス名として<code>./</code>接頭辞がついているでしょう。</p>
<p>しかしながら<code>*/*</code>のようなパターンの場合、<code>./filename</code>という形の文字列にマッチしてしまうため、問題が発生します。シンプルな場合、直接グロブを使い望ましいマッチを生成できるでしょう。しかしながらもしパターンマッチのステップが必要な場合には (例えば、結果がすでに処理され配列に保持されており、フィルタされる必要がある等) パターンを考慮し接頭辞を取る、つまり <code>[[ $file != ./*.* ]]</code> もしくはマッチよりパターンを削除することで解決できるでしょう。解決できます。</p>
<pre><code># Bash
shopt -s nullglob
for path in ./*; do
[[ ${path##*/} != *.* ]] && rm "$path"
done
</code></pre>
<pre><code># Or even better
for file in *; do
[[ $file != *.* ]] && rm "./${file}"
done
</code></pre>
<pre><code># Or better still
for file in *.*; do
rm "./${file}"
done
</code></pre>
<p>他の可能性は<code>--</code>引数と共にオプションを終了することです。(繰り返しになりますが、落とし穴#3で言及されています。)</p>
<pre><code>shopt -s nullglob
for file in *; do
[[ $file != *.* ]] && rm -- "$file"
done
</code></pre>
<h3>43. somecmd 2>&1 >>logfile</h3>
<p>これはリダイレクションの係る通常のミスからは程遠いですが、ターミナルに標準エラーがいまだ表示されることを理解せず標準出力と標準エラーをファイルもしくはパイプに渡したい人により行われがちです。もしこれに困惑するなら、おそらくあなたはどのようにしてリダイレクションするのかもしくはファイルディスクリプタがそのためにどのように動くのかわかっていないのでしょう。リダイレクションはコマンドが実行される前に左辺で評価されます。この意味的に不正なコードは、本質的には次を意味します 「まず、標準出力が現在ポイントしているところ(TTY )に、標準エラーをリダイレクトし、その後に標準出力をログファイルにリダイレクトする。」これはすでに過去のことです。標準エラーは、すでにttyに渡されています。代わりに以下を使用してください。</p>
<pre><code>somecmd >>logfile 2>&1
</code></pre>
<p><a href="http://mywiki.wooledge.org/BashFAQ/055">もっと詳しい説明</a>については、<a href="http://wiki.bash-hackers.org/scripting/copydescriptor">コピーデスクリプタの説明</a>と<a href="http://mywiki.wooledge.org/BashGuide/InputAndOutput#Redirection">BashGuideのredirection</a>を参照して下さい。</p>
<h3>44. cmd; (( ! $? )) || die</h3>
<p><code>$?</code> は前のコマンドのステータス詳細を取得する必要があるときにのみ必要です。もし成功もしくは失敗(もしくは返り値が0でないステータス)をチェックしたいだけなら、 単純に <code>test</code> コマンドを使いましょう。</p>
<pre><code>if cmd; then
...
fi
</code></pre>
<p>選択肢のリストに対して終了時のステータスをチェックすると、次のようなパターンになるでしょう。</p>
<pre><code>cmd
status=$?
case $status in
0)
echo success >&2
;;
1)
echo 'Must supply a parameter, exiting.' >&2
exit 1
;;
*)
echo "Unknown error $status, exiting." >&2
exit "$status"
esac
</code></pre>
<h3>45. y=$(( array[$x] ))</h3>
<p><a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_04">算術展開</a>のPOSIXシンタックス(パラメータ展開の後のコマンド置換の展開を呼び出します)により、算術展開のSubscript内部の配列の展開はコードインジェクションとエクスプロイトが発生し得ます。</p>
<p>非常に紛らわしい単語ですね。ここにどう壊れるかの例があります:</p>
<pre><code>$ x='$(date >&2)' # リダイレクションは全てに対し発生するように見えます
$ y=$((array[$x])) # 配列にも存在している必要はありません
Mon Jun 2 10:49:08 EDT 2014
</code></pre>
<p>同様に<code>$x</code>をクオートすることは助けになりません:</p>
<pre><code>$ y=$((array["$x"]))
Mon Jun 2 10:51:03 EDT 2014
</code></pre>
<p>これを動かす2つの技があります:</p>
<pre><code class="bash"># 1. $xを事前に展開されないようにエスケープします
$ y=$((array[\$x]))
</code></pre>
<pre><code class="bash"># 2. 完全な ${array[$x]} シンタックスを利用します
$ y=$((${array[$x]}))
</code></pre>
<h3>46. read num; echo $((num+1))</h3>
<p>算術式としてコードを挿入できるように、 <code>num</code> の前に入力バリデーションをしてください。 (詳細は <a href="http://mywiki.wooledge.org/BashFAQ/054">BashFAQ/054</a> を参照して下さい。)</p>
<pre><code>$ echo 'a[$(echo injection >&2)]' | bash -c 'read num; echo $((num+1))'
injection
1
</code></pre>
<h3>47. IFS=, read -ra fields <<< "$csv_line"</h3>
<p>見てわかるかもしれませんが信じられないことにPOSIXはIFSを区切り文字として扱うことが必要です。これの意味するところは、この例では入力文字のサイドに空のフィールドがあった場合、それは破棄されるということです。</p>
<pre><code>$ IFS=, read -ra fields <<< "a,b,"
$ declare -p fields
declare -a fields='([0]="a" [1]="b")'
</code></pre>
<p>空のフィールドはどこにいったのでしょうか。歴史的な理由により"食べられて"しまったのです。(なぜからいつもそのようにしてきたから、です。)この動作はBashだけのユニークなものではありません。全てのPOSIX準拠のシェルがこうします。空でないフィールドはおそらくスキャンされるでしょう。</p>
<pre><code>$ IFS=, read -ra fields <<< "a,b,c"
$ declare -p fields
declare -a fields='([0]="a" [1]="b" [2]="c")'
</code></pre>
<p>さぁ、そのようにこのナンセンスなものを回避しましょう。すでにわかったように、入力文字列の最後に、IFSの文字を追加することでスキャンが動作するように強制できます。末尾に空のフィールドがあった場合、それがスキャンされるように、余分なIFSの文字は、それを「削除」します。後続の空でないフィールドがあった場合は、IFSの文字が削除された新しい空のフィールドを作成します。</p>
<pre><code>$ input="a,b,"
$ IFS=, read -ra fields <<< "$input,"
$ declare -p fields
declare -a fields='([0]="a" [1]="b" [2]="")'
</code></pre>
yutah-3
https://yakst.com/ja/posts/2545
2015-08-31T13:45:44+09:00
2017-06-18T21:02:17+09:00
The art of command line (日本語訳)
<p>原文のコミット <a href="https://github.com/jlevy/the-art-of-command-line/blob/bb0c38c0899339e836c37eead4a9534b06c56662/README.md">bb0c38c0899339e836c37eead4a9534b06c56662</a></p>
<h3>The Art of Command Line</h3>
<ul>
<li><a href="#meta">メタ情報</a></li>
<li><a href="#basics">基本</a></li>
<li><a href="#everyday-use">日常的に使うもの</a></li>
<li><a href="#processing-files-and-data">ファイルとデータの処理</a></li>
<li><a href="#system-debugging">システムのデバッグ</a></li>
<li><a href="#one-liners">ワンライナー</a></li>
<li><a href="#obscure-but-useful">目立たないが便利なもの</a></li>
<li><a href="#more-resources">さらなるリソース</a></li>
<li><a href="#disclaimer">免責事項</a></li>
</ul>
<p><img src="https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/cowsay.png" alt="curl -s 'https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README.md' | egrep -o '`\w+`' | tr -d '`' | cowsay -W50"></p>
<p>コマンドラインで流れるように操作ができるということは、軽く見られたり他人から理解されないスキルだとみなされることもあるでしょう。しかしそのスキルは、明らかにかすぐ分かるようかは問わず、エンジニアとしてのあなたの柔軟性や生産性を改善してくれるものです。ここでは、Linuxでコマンドラインを使う上で便利だと思ったメモやTipsの数々を挙げてみます。あるものは基礎的ですが、非常に詳しいもの、洗練されたもの、曖昧なものもあります。このページはそんなに長いものではないですが、ここに書いてあることの全てを使ったり思い出すことができれば、かなり詳しくなれるでしょう。</p>
<p>ここに書いてあることの多くは、<a href="http://www.quora.com/What-are-some-lesser-known-but-useful-Unix-commands">元々</a><a href="http://www.quora.com/What-are-some-time-saving-tips-that-every-Linux-user-should-know">Quora</a>に<a href="http://www.quora.com/What-are-the-most-useful-Swiss-army-knife-one-liners-on-Unix">書かれて</a>いたものが多いですが、私よりももっと優れた人たちがすぐに改善案を出すことができるGithubに置くのがよいのではと思ったのです(訳注 : 原文はGithub上にあります)。間違いやもっとこうした方がよいという点があれば、イシューを登録するかプルリクエストをください!(もちろん、メタ情報の項や既存のプルリクエスト、イシューをまず確認してください)</p>
<h4>メタ情報</h4>
<p>対象 :</p>
<ul>
<li>このガイドは、初心者向けでも経験者向きでもあります。幅広く(書いてあることは全て重要)、かつ明確で(多くのケースに対して具体的な例を付ける)、そして簡潔(他の場所で見つけられるような重要でないことや脱線したことは省く)であることをゴールにしています。各項目は、多くの場面において必須であるか、他の方法に比べて劇的に時間を節約してくれるでしょう。</li>
<li>Linux向けに書いています。多くはMacOS(あるいはCygwin)でも使えますが、全部ではありません。</li>
<li>インタラクティブなBashを使うことを想定していますが、多くの項目は他のシェルやBashのスクリプトでも使えるでしょう。</li>
</ul>
<p>注意 :</p>
<ul>
<li>1ページ内に収めるために、内容には暗黙的に書かれていることがあります。ここで取りかかりを知ったりコマンドが分かれば、詳細をどこかで調べたりするくらいはできるでしょう。新しいプログラムをインストールするには、<code>apt-get</code>/<code>yum</code>/<code>dnf</code>/<code>pacman</code>/<code>pip</code>/<code>brew</code>(どれか適したもの)を使いましょう。</li>
<li>コマンドやオプション、パイプを分解して理解する手助けに、<a href="http://explainshell.com/">Explainshell</a>を使おう。</li>
</ul>
<h4>基本</h4>
<ul>
<li><p>基本的なBashを学ぼう。実際のところ、<code>man bash</code>は結構簡単に理解できるしそんなに長くないので、これで一通りのことは分かる。それ以外のシェルもよいが、Bashは強力だし、常に使用可能であるという利点もある(自分のPCに入れてしまったと言ってzshやfishなど<em>だけ</em>を学んでしまうと、既存のサーバを触らなくてはならない時などに制約が出てしまう)。</p></li>
<li><p>テキストエディタのどれか最低1つに習熟しよう。ターミナル内で適当にものを書くにあたって他に全く代替品がないという点で、理想的にはVim(<code>vi</code>)がよいだろう(通常はEmacsや高機能なIDEや最新のかっこいいエディタをメインに使っていたとしても)。</p></li>
<li><p><code>man</code>でのドキュメントの読み方を知ろう(知りたがりのために書くと、<code>man man</code>でセクション番号が分かる。例えば1は「一般的な」コマンド、5はファイルやそのお作法、8は管理についてといった具合)。<code>apropos</code>でmanページを探そう。コマンドによっては実行可能ファイルではなくBashのビルトインコマンドであることを理解し、<code>help</code>や<code>help -d</code>でヘルプが見られることを知ろう。</p></li>
<li><p><code>></code>や<code><</code>、<code>|</code>を使ったパイプによる入出力のリダイレクションを学ぼう。stdout(標準出力)とstderr(標準エラー出力)を学ぼう。</p></li>
<li><p><code>*</code>(または<code>?</code>や<code>{</code>...<code>}</code>)を使ったファイルグロブ展開、クォーテーション、ダブルクォート<code>"</code>とシングルクォート<code>'</code>の違いを学ぼう(詳しくはこの後の変数展開の項を参照)。</p></li>
<li><p><code>&</code>、<strong>ctrl-z</strong>、<strong>ctrl-c</strong>、<code>jobs</code>、<code>fg</code>、<code>bg</code>、<code>kill</code>など、Bashのジョブ管理について詳しくなろう。</p></li>
<li><p><code>ssh</code>について知るとともに、<code>ssh-agent</code>や<code>ssh-add</code>を使ったパスワードなしの認証の基本について理解しよう。</p></li>
<li><p>ファイル管理について。<code>ls</code>や<code>ls -l</code>(特に、<code>ls -l</code>の各列が何を意味するか理解)、<code>less</code>、<code>head</code>、<code>tail</code>、<code>tail -f</code>(または<code>less +F</code>)、<code>ln</code>と<code>ln -s</code>(ハードリンクとソフトリンクの違いとそれぞれの利点の理解)、<code>chown</code>と<code>chmod</code>、<code>du</code>(ディスク使用量まとめを簡単に見るなら<code>du -sk *</code>)。ファイルシステム管理については、<code>df</code>、<code>mount</code>、<code>fdisk</code>、<code>mkfs</code>、<code>lsblk</code>。</p></li>
<li><p>基本的なネットワーク管理について。<code>ip</code>あるいは<code>ifconfig</code>、<code>dig</code>。</p></li>
<li><p>正規表現について詳しく知ろう。<code>grep</code>や<code>egrep</code>の色々なフラグも合わせて。<code>-i</code>、<code>-o</code>、<code>-A</code>、<code>-B</code>といったオプションは知っておいて損はない。</p></li>
<li><p><code>apt-get</code>、<code>yum</code>、<code>dnf</code>、<code>pacman</code>(ディストリビューションによって違う)といったコマンドでパッケージを探したりインストールする方法を学ぼう。Pythonベースのコマンドラインツールをインストールするのに、<code>pip</code>も必要だ(後に出てくるいくつかのコマンドは<code>pip</code>でインストールするのが一番簡単)。</p></li>
</ul>
<h4>日常的に使うもの</h4>
<ul>
<li><p>Bashでは、引数を補完するのに<strong>タブ</strong>を使い、コマンド履歴から検索するのに<strong>ctrl-r</strong>を使う。</p></li>
<li><p>Bashでは、最後の単語を削除するのには<strong>ctrl-w</strong>、行頭まで全て削除するには<strong>ctrl-u</strong>を使う。単語ごとに移動するには<strong>alt-b</strong>または<strong>alt-f</strong>、行末まで削除するには<strong>ctrl-k</strong>、画面のクリアは<strong>ctrl-l</strong>。Bashにおけるデフォルトのキー割り当てを全て見るには`<code>man readline</code>を参照。たくさん出てくる。例えば、<strong>alt-.</strong>は前の引数を順番に表示し、<strong>alt-</strong>*はグロブを展開する。</p></li>
<li><p>vi風のキー割り当てが好きなら、<code>set -o vi</code>を実行しよう。</p></li>
<li><p>最近実行したコマンドを確認するなら<code>history</code>。<strong>ctrl-r</strong>や<strong>alt-.</strong>で用は足りるだろうが、<code>!$</code>(直前の引数)や<code>!!</code>(直前のコマンド)といった省略形もたくさんある。</p></li>
<li><p>前のワーキングディレクトリに戻るなら<code>cd -</code></p></li>
<li><p>途中までコマンドを入力したけれど心変わりした時は、<strong>alt-#</strong>を打つと行頭に<code>#</code>が挿入され、コメントとして入力される(<strong>ctrl-a</strong>、<strong>#</strong>、<strong>enter</strong>でも同じ)。これは後でコマンド履歴から検索できる。</p></li>
<li><p><code>xargs</code>(または<code>parallel</code>)を使おう。非常に強力。行ごとにいくつのアイテムを実行するか(<code>-L</code>)や、並列度(<code>-P</code>)も制御できる。正しく実行されるか定かでないなら、まず<code>xargs echo</code>してみればよい。<code>-I{}</code>も便利。例えば以下の通り。</p></li>
</ul>
<pre><code class="bash"> find . -name '*.py' | xargs grep some_function
cat hosts | xargs -I{} ssh root@{} hostname
</code></pre>
<ul>
<li><p><code>pstree -p</code>はプロセスツリーを表示するのに便利。</p></li>
<li><p><code>pgrep</code>や<code>pkill</code>で、プロセス名で検索したりシグナルを送れる(<code>-f</code>も便利)。</p></li>
<li><p>プロセスに遅れる色々なシグナルを知っておこう。例えば、プロセスをサスペンドするには<code>kill -STOP [pid]</code>を使う。全種類見るなら、<code>man 7 signal</code>。</p></li>
<li><p>バックグラウンドプロセスをずっと実行し続けたいなら<code>nohup</code>あるいは<code>disown</code>を使おう。</p></li>
<li><p><code>netstat -lntp</code>や<code>ss -plat</code>で、どんなプロセスがリッスンしているか確認しよう(UDPなら<code>-u</code>を付ける)。</p></li>
<li><p>開かれているソケットやファイルを見るには<code>lsof</code>も参照。</p></li>
<li><p>Bashスクリプトでは、<code>set -x</code>でデバッグ出力を出せる。可能なら厳格モードを使い、エラーが起きたら強制終了するよう<code>set -e</code>する。パイプのエラーも厳格に扱うために<code>set -o pipefail</code>も使おう(これはちょっと微妙かも)。より複雑なスクリプトなら、<code>trap</code>も使おう。</p></li>
<li><p>Bashスクリプトでは、コマンドのグループを作るのにサブシェル(丸括弧で囲まれた部分)が便利。一時的にワーキングディレクトリを移動するというよくある例。</p></li>
</ul>
<pre><code class="bash"> # カレントディレクトリで何か実行
(cd /some/other/dir && other-command)
# 元のディレクトリで作業続行
</code></pre>
<ul>
<li><p>Bashでは、たくさんの変数展開の種類があることを覚えておこう。変数が存在するかチェックするなら、<code>${name:?error message}</code>。例えば、Bashスクリプトが1つの引数を取る必要があるなら、<code>input_file=${1:?usage: $0 input_file}</code>とだけ書けばよい。算術式の展開は、<code>i=$(( (i + 1) % 5 ))</code>。シーケンスは<code>{1..10}</code>。文字列のトリミングは<code>${var%suffix}</code>と<code>${var#prefix}</code>。例えば<code>var=foo.pdf</code>の時、<code>echo ${var%.pdf}.txt</code>とすると<code>foo.txt</code>が出力に。</p></li>
<li><p>コマンドの出力を<code><(some command)</code>のようにしてファイルのように扱える。例えば、ローカルとリモートのの<code>/etc/hosts</code>を比較するなら以下のようになる。</p></li>
</ul>
<pre><code class="sh"> diff /etc/hosts <(ssh somehost cat /etc/hosts)
</code></pre>
<ul>
<li><p><code>cat <<EOF ...</code>のような、Bashの「ヒアドキュメント」を理解しよう。</p></li>
<li><p>Bashでは、<code>some-command >logfile 2>&1</code>で標準出力と標準エラー出力の両方をリダイレクトできる。コマンドが標準入力に対してファイルハンドルを開きっぱなしにせず、ログインしているターミナルにひもづけておくため、<code></dev/null</code>するのもよい習慣。</p></li>
<li><p>16進と10進のASCIIテーブルを見るのに<code>man ascii</code>を使おう。一般的なエンコードに関する情報は、<code>man unicode</code>や<code>man utf-8</code>、<code>man latin1</code>が便利。</p></li>
<li><p>スクリーンの分割に<code>screen</code>や<a href="https://tmux.github.io/"><code>tmux</code></a>を使おう。特に、リモートのSSHセッションをデタッチしたりアタッチし直したりするのに有効。セッション永続化だけの簡単なものなら<code>dtach</code>。</p></li>
<li><p>SSHで<code>-L</code>あるいは<code>-D</code>(まれに<code>-R</code>)を使ったポートトンネルのやり方を覚えておくと便利。例えばリモートのサーバからウェブサイトにアクセスする時など。</p></li>
<li><p>SSHの設定を少しでも最適化しておくと便利。例えば以下の設定だと、ネットワーク環境による接続断を回避し、圧縮を使用し(帯域の細い回線を使ったscpなどで便利)、ローカルの制御ファイルを指定して同一サーバとのチャネルを多重化する。</p></li>
</ul>
<pre><code> TCPKeepAlive=yes
ServerAliveInterval=15
ServerAliveCountMax=6
Compression=yes
ControlMaster auto
ControlPath /tmp/%r@%h:%p
ControlPersist yes
</code></pre>
<ul>
<li><p>これ以外のSSHオプションはセキュリティ上の問題がある可能性があるため、有効にするには、サブネットごとやホストごとに指定したり、信頼できるネットワーク内でのみ使用するなど注意が必要。<code>StrictHostKeyChecking=no</code>、<code>ForwardAgent=yes</code>など。</p></li>
<li><p>8進数表現のファイルパーミッションは、システム設定の際に便利だが<code>ls</code>の結果にも出てこず、間違いやすい。以下のようにして取得できる。</p></li>
</ul>
<pre><code class="sh"> stat -c '%A %a %n' /etc/timezone
</code></pre>
<ul>
<li><p>何らかのコマンドの出力から、インタラクティブに値を選択したい場合は、 <a href="https://github.com/mooz/percol"><code>percol</code></a>を使おう。</p></li>
<li><p>(gitを使うなど)何らかのコマンドの出力からファイルに関するやり取りをする場合は、<code>fpp</code> (<a href="https://github.com/facebook/PathPicker">PathPicker</a>)を使おう。</p></li>
<li><p>カレントディレクトリ(とサブディレクトリ)全体を、ネットワーク内に公開されたWebサーバにするなら、<code>python -m SimpleHTTPServer 7777</code> (ポート7777で公開。Python 2の場合)あるいは<code>python -m http.server 7777</code> (Python 3の場合)。</p></li>
</ul>
<h4>ファイルとデータの処理</h4>
<ul>
<li><p>カレントディレクトリ以下のファイルをファイル名で探したいなら、<code>find . -iname '*something*'</code>。場所を指定せずにファイル名で検索したいなら、<code>locate something</code>をつかおう(ただし<code>updatedb</code>は最近作られたファイルはインデックスしていないであろうことに注意)。</p></li>
<li><p>ソースやデータファイルの(<code>grep -r</code>よりも高度な)一般的な検索には、<a href="https://github.com/ggreer/the_silver_searcher"><code>ag</code></a>を使おう。</p></li>
<li><p>HTMLをテキストに変換するなら、<code>lynx -dump -stdin</code>。</p></li>
<li><p>MarkdownやHTMLなど様々な種類のドキュメントの変換には、<a href="http://pandoc.org/"><code>pandoc</code></a>を試してみるとよい。</p></li>
<li><p>XMLを扱わなくてはならないなら、<code>xmlstartlet</code>は古いがいいツールだ。</p></li>
<li><p>JSONには<code>jq</code>を使おう。</p></li>
<li><p>ExcelやCSVファイルには、<a href="https://github.com/onyxfish/csvkit">csvkit</a>で<code>in2csv</code>、<code>csvcut</code>、<code>csvjoin</code>、<code>csvgrep</code>などが使えるようになる。</p></li>
<li><p>Amazon S3には、<a href="https://github.com/s3tools/s3cmd"><code>s3cmd</code></a>が便利で、<a href="https://github.com/bloomreach/s4cmd"><code>s4cmd</code></a>はさらに高速。AWS関連の処理にはAmazon公式の<a href="https://github.com/aws/aws-cli"><code>aws</code></a>が欠かせない。</p></li>
<li><p><code>sort</code>や<code>uniq</code>、さらにuniqの<code>-u</code>や<code>-d</code>オプションを知っておこう。後に出てくるワンライナーも参照。<code>comm</code>も確認しておこう。</p></li>
<li><p>複数のテキストファイルを操作するのには、<code>cut</code>と<code>paste</code>、<code>join</code>は知っておこう。<code>cut</code>はみんな使っているが、<code>join</code>は忘れられている。</p></li>
<li><p><code>wc</code>を理解し、改行(<code>-l</code>)、文字(<code>-m</code>)、単語(<code>-w</code>)、バイト(<code>-c</code>)それぞれの数え方も知っておこう。</p></li>
<li><p>標準入力をファイルと標準出力の両方に出す<code>tee</code>を理解しよう。<code>ls -al | tee file.txt</code>のように使う。</p></li>
<li><p>ロケールは、ソートの順序(照合順序)やパフォーマンスなど、たくさんのコマンドラインツールに微妙なところで影響することを覚えておこう。多くのLinuxディストリビューションでは、<code>LANG</code>や他のロケール変数はUS Englishのようなローカルな設定になっている。ロケールを変更するとソート順序が変わることに注意しよう。また、国際化(i18n)対応のルーチンはソートやその他の処理を<em>何倍も</em>遅く実行するようになる点も知っておこう。場合(設定の処理や一意性を見つける処理など)によっては、<code>export LC_ALL=C</code>としてしまい遅いi18n対応の処理を完全に無視してしまうことも可能だ。</p></li>
<li><p>単純なデータ加工のために<code>awk</code>と<code>sed</code>の基礎を身につけよう。例えば、テキストファイルの3カラム目の合計を出すなら、<code>awk '{ x += $3 } END { print x }'</code>。これは、Pythonで同じことをやるより3倍速くかつ3分の1の長さで書ける。</p></li>
<li><p>1つあるいは複数のファイル内の文字列を直接置き換えてしまうには、</p></li>
</ul>
<pre><code class="sh"> perl -pi.bak -e 's/old-string/new-string/g' my-files-*.txt
</code></pre>
<ul>
<li>パターンにしたがってたくさんのファイル名を書き換えるには<code>rename</code>を使おう。複雑なファイル名変更なら <a href="https://github.com/jlevy/repren"><code>repren</code></a>も便利だ。</li>
</ul>
<pre><code class="sh"> # バックアップファイルfoo.bakをfooに戻す
rename 's/\.bak$//' *.bak
# ファイル名、ディレクトリ名、ファイルの中身全てのfooをbarに置き換える
repren --full --preserve-case --from foo --to bar .
</code></pre>
<ul>
<li><p>ファイルからランダムな行を抜き出すには<code>shuf</code></p></li>
<li><p><code>sort</code>のオプションを理解しよう。キーがどのように処理されるのか(<code>-t</code>や<code>-k</code>)を知ろう。特に、最初の列だけでソートするには<code>-k1,1</code>とかく必要があり、<code>-k1</code>だと全行を見てソートされるという点に注意。</p></li>
<li><p>stableな(安定した)ソート(<code>sort -s</code>)は便利。例えば、始めに1列目でソートし、それから2列目でソートするなら、<code>sort -k1,1 | sort -s -k2,2</code>とすればよい。</p></li>
<li><p>Bashのコマンドライン上でタブを表現する必要がある場合、<strong>ctrl-v</strong> <strong>[Tab]</strong>を入力するか<code>$'\t'</code> (コピペするなら後者の方がいいかも)。</p></li>
<li><p>ソースコードにパッチを当てる基本のツールは<code>diff</code>と<code>patch</code>。diffの統計情報を見るなら<code>diffstat</code>も参照しよう。<code>diff -r</code>だと、ディレクトリ全体に対して実行される。変更点の概要を見るなら<code>diff -r tree1 tree2 | diffstat</code>。</p></li>
<li><p>バイナリファイルなら、単純な16進ダンプを見るのに<code>hd</code>、バイナリエディタには<code>bvi</code>。</p></li>
<li><p>同じくバイナリファイルに関して、テキストを抽出したいなら<code>strings</code>(と<code>grep</code>などの組み合わせ)。</p></li>
<li><p>バイナリのdiff(デルタ圧縮)なら、<code>xdelta3</code>。</p></li>
<li><p>テキストエンコーディングの変換は<code>iconv</code>を使おう。あるいはより高度なツールとして<code>uconv</code>もあり、こちらはUnicodeの高度な処理が可能。例えば以下のコマンドでは小文字に変換しアクセント記号を取り除く(展開してから削除)。</p></li>
</ul>
<pre><code class="sh"> uconv -f utf-8 -t utf-8 -x '::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] >; ::Any-NFC; ' < input.txt > output.txt
</code></pre>
<ul>
<li><p>ファイルを分割するなら<code>split</code>(サイズで分割)と<code>csplit</code>(パターンで分割)。</p></li>
<li><p>圧縮ファイルの操作は<code>zless</code>、<code>zmore</code>、<code>zcat</code>、<code>zgrep</code>。</p></li>
</ul>
<h4>システムのデバッグ</h4>
<ul>
<li><p>Webのデバッグなら<code>curl</code>や<code>curl -l</code>が便利で、<code>wget</code>も同様、よりモダンなのは<a href="https://github.com/jakubroztocil/httpie"><code>httpie</code></a>。</p></li>
<li><p>ディスクやCPU、ネットワークのステータスを知るには<code>iostat</code>、<code>netstat</code>、<code>top</code>(あるいは<code>htop</code>の方がよい)、(一番は)<code>dstat</code>。システムで何が起きているのか素早く知るにはよい。</p></li>
<li><p>更に詳しいシステムの全体像を見るには、<a href="https://github.com/nicolargo/glances"><code>glances</code></a>を使おう。ひとつのターミナル内で、いくつかのシステムレベルの統計情報を表示してくれる。複数のサブシステムを素早くチェックするのに非常に便利。</p></li>
<li><p>メモリのステータスを知るには、<code>free</code>あるいは<code>vmstat</code>を実行し、その出力の意味を理解しよう。特に、"cached"の値はLinuxカーネルにファイルキャッシュとして保持されているメモリ量であり、"free"の値を見る際に考慮すべきであることに注意しよう。</p></li>
<li><p>Javaのシステムのデバッグはまた違う困ったところがあるが、Oracleあるいは他のJVMにも共通しているシンプルなトリックは、<code>kill -3 <pid></code>でフルスタックトレースとヒープの概要が標準出力あるいはログにダンプされる(世代別GCの詳細も参考程度だが含まれている)。</p></li>
<li><p>改良版tracerouteとして<code>mtr</code>を使ってネットワークの問題を調査しよう。</p></li>
<li><p>ディスクがいっぱいになっている理由を調べるには、<code>ncdu</code>を使うと<code>du -sh *</code>より時間が節約できる。</p></li>
<li><p>帯域を使っているのがどのソケットやプロセスなのかを見つけるには、<code>iftop</code>あるいは<code>nethogs</code>を試そう。</p></li>
<li><p><code>ab</code>(Apacheに付属)は、Webサーバのパフォーマンスをざっくりチェックするのに便利。より複雑なテストには<code>siege</code>を試そう。</p></li>
<li><p>より確実なネットワークのデバッグは<code>wireshark</code>、<code>tshark</code>、<code>ngrep</code>。</p></li>
<li><p><code>strace</code>と<code>ltrace</code>について知っておこう。プログラムの実行に失敗したりハングしたりクラッシュしたりして、その理由が分からない、あるいはパフォーマンスに関する一般的情報を知りたいなら、このツールが役立つはずだ。プロファイリングのオプション(<code>-c</code>)や起動中のプロセスにアタッチする機能(<code>-p</code>)も覚えておこう。</p></li>
<li><p>共有ライブラリをチェックするなら<code>ldd</code>を覚えておこう。</p></li>
<li><p>起動中のプロセスに<code>gdb</code>で接続し、そのスタックトレースを取る方法を知ろう。</p></li>
<li><p><code>/proc</code>以下のファイルを使おう。今起こっている問題をデバッグするのには素晴らしく便利だ。例えば、<code>/proc/cpuinfo</code>、<code>/proc/xxx/cwd</code>、<code>/proc/xxx/ece</code>、<code>/proc/xxx/fd/</code>、<code>/proc/xxx/smaps</code>。</p></li>
<li><p>過去に何か問題が起きたことの原因を探るなら、<code>sar</code>がとても便利。CPUやメモリ、ネットワークなどの過去の統計情報を見られる。</p></li>
<li><p>さらに深いシステムとパフォーマンスの分析には、<code>stap</code> (<a href="https://sourceware.org/systemtap/wiki">SystemTap</a>)、<a href="http://en.wikipedia.org/wiki/Perf_(Linux)"><code>perf</code></a>、
<a href="https://github.com/draios/sysdig"><code>sysdig</code></a>。</p></li>
<li><p>どのディストリビューションを使っているか確認しよう。多くのディストリビューションでは<code>lsb_release -a</code></p></li>
<li><p>何かいつもと違うおかしなこと(大抵ハードウェアかドライバ関連の問題だ)が起きていたら、<code>dmesg</code>を実行しよう。</p></li>
</ul>
<h4>ワンライナー</h4>
<p>コマンドをまとめて使う例をいくつか。</p>
<ul>
<li><code>sort</code>や<code>uniq</code>を使ってテキストファイルの共通部分、結合、差異を求める時に特に便利なのが以下のやり方。<code>a</code>と<code>b</code>はそれぞれ内容に重複のないテキストファイルとする。この方法は高速で、数GB程度までの任意のファイルサイズで動作する(<code>/tmp</code>が小さなルートパーティションにある場合は<code>-T</code>オプションをつける必要があるが、ソートはメモリ内で行われるとは限らない)。上述の<code>LC_ALL</code>と<code>sort</code>の<code>-u</code>オプションも参照のこと。</li>
</ul>
<pre><code class="sh"> cat a b | sort | uniq > c # cはaとbの和集合
cat a b | sort | uniq -d > c # cはaとbの共通部分
cat a b b | sort | uniq -u > c # cはaとbの差異
</code></pre>
<ul>
<li><p>コンフィグが含まれている<code>/sys</code>や<code>/proc</code>や<code>/etc/</code>のようなディレクトリ内の全てのファイルの中身全部を確認するには<code>grep . *</code>を使おう。</p></li>
<li><p>テキストファイルの3列目を全て足し合わせるには以下で(Pythonで同じことをやるに比べて3倍速く3分の1の長さで書ける)。</p></li>
</ul>
<pre><code class="sh"> awk '{ x += $3 } END { print x }' myfile
</code></pre>
<ul>
<li>ファイルツリーのサイズやデータを確認したいなら、以下は再帰的な<code>ls -l</code>と同じだが<code>ls -lR</code>より見やすい。</li>
</ul>
<pre><code class="sh"> find . -type f -ls
</code></pre>
<ul>
<li>事情が許すなら<code>xargs</code>や<code>parallel</code>を使おう。行あたりいくつのアイテムを実行するか(<code>-L</code>)や並列度(<code>-P</code>)は制御できるのにも注意。正しく使えているか心配な時には、xargs echoをまずやってみよう。また、<code>-I{}</code>も便利だ。以下の例をみてみよう。</li>
</ul>
<pre><code class="sh"> find . -name '*.py' | xargs grep some_function
cat hosts | xargs -I{} ssh root@{} hostname
</code></pre>
<ul>
<li>Webサーバのログのようなテキストファイルがあり、各行には例えばURLの中に出てくる<code>acct_id</code>のような特定の値が現れるとしよう。<code>acct_id</code>が何回リクエストされているかを集計するには、</li>
</ul>
<pre><code class="sh"> cat access.log | egrep -o 'acct_id=[0-9]+' | cut -d= -f2 | sort | uniq -c | sort -rn
</code></pre>
<ul>
<li>このドキュメントからランダムに項目を抜き出すには以下の関数を実行しよう(Markdownをパースし、アイテムを抽出する)。</li>
</ul>
<pre><code class="sh"> function taocl() {
curl -s https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README.md |
pandoc -f markdown -t html |
xmlstarlet fo --html --dropdtd |
xmlstarlet sel -t -v "(html/body/ul/li[count(p)>0])[$RANDOM mod last()+1]" |
xmlstarlet unesc | fmt -80
}
</code></pre>
<h4>目立たないが便利なもの</h4>
<ul>
<li><p><code>expr</code>: 算術演算、論理演算、または正規表現の評価を実行</p></li>
<li><p><code>m4</code>: シンプルなマクロプロセッサ</p></li>
<li><p><code>yes</code>: 文字列をたくさん表示</p></li>
<li><p><code>cal</code>: いい感じのカレンダー</p></li>
<li><p><code>env</code>: コマンドを実行(スクリプト内で重宝する)</p></li>
<li><p><code>printenv</code>: 環境変数を表示する(デバッグやスクリプト内での使用に便利)</p></li>
<li><p><code>look</code>: 文字列で始まる英単語(またはファイル内の行)を見つける</p></li>
<li><p><code>cut</code>、 <code>paste</code>、 <code>join</code>: データの操作</p></li>
<li><p><code>fmt</code>: テキストの段落をフォーマットする</p></li>
<li><p><code>pr</code>: テキストをページとカラムにフォーマットする</p></li>
<li><p><code>fold</code>: テキストの行を分割</p></li>
<li><p><code>column</code>: テキストをカラムあるいはテーブルにフォーマット</p></li>
<li><p><code>expand</code> と <code>unexpand</code>: タブとスペースの相互変換</p></li>
<li><p><code>nl</code>: 行数を表示</p></li>
<li><p><code>seq</code>: 数字を表示</p></li>
<li><p><code>bc</code>: 計算機</p></li>
<li><p><code>factor</code>: 整数を因数分解</p></li>
<li><p><code>gpg</code>: 暗号化とファイルのサイニング</p></li>
<li><p><code>toe</code>: terminfoのエントリのテーブルを表示</p></li>
<li><p><code>nc</code>: ネットワークのデバッグとデータ転送</p></li>
<li><p><code>socat</code>: ソケットリレーとTCPポートのフォワーダ(<code>netcat</code>と同等)</p></li>
<li><p><code>slurm</code>: ネットワークトラフィックの可視化</p></li>
<li><p><code>dd</code>: データをファイルあるいはデバイス間で移動</p></li>
<li><p><code>file</code>: ファイルの種類を特定</p></li>
<li><p><code>tree</code>: ディレクトリとサブディレクトリをツリーで表示。<code>ls</code>に似ているが再帰的に動く</p></li>
<li><p><code>stat</code>: ファイルの情報</p></li>
<li><p><code>tac</code>: ファイルを逆から表示</p></li>
<li><p><code>shuf</code>: ファイルからランダムに選んだ行を表示</p></li>
<li><p><code>comm</code>: ソート済みファイルの行を比較</p></li>
<li><p><code>pv</code>: パイプ経由でデータの進行状況をモニタリング</p></li>
<li><p><code>hd</code> および <code>bvi</code>: バイナリファイルのダンプと編集</p></li>
<li><p><code>strings</code>: バイナリファイルからテキストを抽出</p></li>
<li><p><code>tr</code>: 文字の置き換えと操作</p></li>
<li><p><code>iconv</code> あるいは <code>uconv</code>: 文字エンコーディングの変換</p></li>
<li><p><code>split</code> と <code>csplit</code>: ファイルを分割</p></li>
<li><p><code>units</code>: 単位の変換と計算。2週間あたりのハロン(訳注 : 長さの単位)からまばたきごとのトゥウィップまで( <code>/usr/share/units/definitions.units</code>も参照のこと)</p></li>
<li><p><code>7z</code>: 圧縮率の高いファイル圧縮</p></li>
<li><p><code>ldd</code>: 動的ライブラリの情報</p></li>
<li><p><code>nm</code>: オブジェクトファイルからシンボルを表示</p></li>
<li><p><code>ab</code>: Webサーバのベンチーマーク</p></li>
<li><p><code>strace</code>: システムコールのデバッグ</p></li>
<li><p><code>mtr</code>: ネットワークデバッグのためのより高機能なtraceroute</p></li>
<li><p><code>cssh</code>: ビジュアルな並列シェル</p></li>
<li><p><code>rsync</code>: ファイルやフォルダをSSH経由で同期</p></li>
<li><p><code>wireshark</code> と <code>tshark</code>: パケットキャプチャとネットワークデバッギング</p></li>
<li><p><code>ngrep</code>: ネットワーク層のgrep</p></li>
<li><p><code>host</code> と <code>dig</code>: DNS名前解決</p></li>
<li><p><code>lsof</code>:プロセスのファイルディスクリプタとソケット情報</p></li>
<li><p><code>dstat</code>: 便利なシステム情報</p></li>
<li><p><a href="https://github.com/nicolargo/glances"><code>glances</code></a>: 高レベルに複数のサブシステムの概要を把握</p></li>
<li><p><code>iostat</code>: CPUとディスクの使用状況</p></li>
<li><p><code>htop</code>: topの改良版</p></li>
<li><p><code>last</code>: ログイン履歴</p></li>
<li><p><code>w</code>: 誰がログインしているか</p></li>
<li><p><code>id</code>: ユーザやグループの情報</p></li>
<li><p><code>sar</code>: システム統計情報の履歴</p></li>
<li><p><code>iftop</code> または <code>nethogs</code>: ソケットあるいはプロセスごとのネットワーク使用量</p></li>
<li><p><code>ss</code>: ソケットの統計情報</p></li>
<li><p><code>dmesg</code>: 起動時とシステムのエラーメッセージ</p></li>
<li><p><code>hdparm</code>: SATA/ATAディスクの操作やパフォーマンス確認</p></li>
<li><p><code>lsb_release</code>: Linuxディストリビューション情報</p></li>
<li><p><code>lsblk</code>: ブロックデバイスの一覧。ディスクとディスクパーティションのツリービュー</p></li>
<li><p><code>lshw</code> と <code>lspci</code>: RAIDやグラフィックなどを含めたハードウェア情報</p></li>
<li><p><code>fortune</code>、 <code>ddate</code>、<code>sl</code>: んー、あー、これは蒸気機関車やZippyの引用句が「便利」だと思うかどうかによる</p></li>
</ul>
<h4>さらなるリソース</h4>
<ul>
<li><a href="https://github.com/alebcay/awesome-shell">awesome-shell</a>: シェルのツールやリソースのまとめ</li>
<li>よりよいシェルスクリプトを書くには<a href="http://redsymbol.net/articles/unofficial-bash-strict-mode/">Strict mode</a></li>
</ul>
<h4>免責事項</h4>
<p>ごく一部の例外はありますが、コードは誰でも読めるように書かれています。力には責任が伴います。Bashで<em>できる</em>からといって、そうすべき必要があるという意味ではありません! ;)</p>
<h4>ライセンス</h4>
<p><a href="http://creativecommons.org/licenses/by-sa/4.0/"><img src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" alt="Creative Commons License"></a> </p>
<p>このドキュメントは<a href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International Licene</a>でライセンスされます。</p>
doublemarket
https://yakst.com/ja/posts/1829
2015-02-09T00:20:00+09:00
2017-06-18T21:02:17+09:00
bashで素晴らしく生産性を上げるための10のテクニック
<p><strong>(訳者注)</strong> 原文タイトルが「Ten tips for wonderful bash productivity」なので、10のテクニックというタイトルにしていますが、実際には9つしかありません。原文筆者に指摘したところ本人も自覚されていなかった模様?です。悪しからず。</p>
<hr>
<p>私はいつも自分の<a href="http://www.gnu.org/software/bash/">bash</a>の環境をいじったり直したりしています。同じような問題に何度も遭遇しては、その度に解決策を探さねばなりません。うんざりして座り込んでしまうまでそれは続きます。お前いつも座って仕事してるだろって? ええ、でももう皆さんお分かりでしょう。そういう場合は、カスタム関数を作って、それを<a href="https://bitbucket.org/durdn/cfg/src/master/.bashrc">.bashrc</a>に書き込んで、ログインする可能性のあるマシン全部に入れておけばいいって。</p>
<p>私がよく使っているテクニックや関数をのぞいて見ることで、コマンドラインを使う人々の助けになれば、ターミナル作業の効率を極限まで上げるための私の悪戦苦闘が役に立ったというものです。さらにこれが双方向の会話になって、読者の方々のbashショートカットを<a href="http://twitter.com/durdn">@durdn</a>や<a href="http://twitter.com/atlassiandev">@atlassiandev</a>宛、あるいは<a href="https://disqus.com/home/discussions/atlassiandevelopers/ten_tips_for_wonderful_bash_productivity/">コメント欄</a>から教えてもらえればとてもよいことです。</p>
<p>前置きはこの辺にして、今日はこのあたりをご紹介しましょう。</p>
<h3>1. ファイルの最初に1行追加する</h3>
<p>毎回このやり方を調べなくてはなりません。<a href="http://ja.wikipedia.org/wiki/Sed_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF)">sed</a>を使って、ファイルの最初に1行追加する方法です。</p>
<pre><code>sed -i '1s/^/line to insert\n/' path/to/file/you/want/to/change.txt
</code></pre>
<h3>2. 設定ファイルに文字列を追加する</h3>
<p>簡単でよく知られていますが、ファイルに複数行を追加する(>>)方法です。このやり方では、最後を示す文字列を指定して、追加したい文字列をスクリプトに埋め込めるようにするために、「<a href="http://ja.wikipedia.org/wiki/%E3%83%92%E3%82%A2%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88#Unix.E3.82.B7.E3.82.A7.E3.83.AB">ヒアドキュメント</a>」の文法を使っています。<code>EOF</code>(End Of Fileの略)を最後を示す文字列として使うことが多いでしょう。</p>
<pre><code>cat >> path/to/file/to/append-to.txt << "EOF"
export PATH=$HOME/jdk1.8.0_31/bin:$PATH
export JAVA_HOME=$HOME/jdk1.8.0_31/
EOF
</code></pre>
<p>最初の<code>EOF</code>と最後の<code>EOF</code>の間の文字列がファイルに追加されます。</p>
<h3>3. 複数ファイルに対して文字列の検索と置換を行う</h3>
<p>EclipseやIntelliJなどのIDEを使っているなら、簡単に使えて強力なリファクタリング機能があるでしょう。しかし、そういった高度な機能がないツールしか使えない場合もあります。</p>
<p>ディレクトリツリー全てに対する文字列の検索と置換は、コマンドラインではどのようにしたらよいでしょうか?お願いだから私にPerlを使わせないで。<a href="http://ja.wikipedia.org/wiki/Find">find</a>と<a href="http://ja.wikipedia.org/wiki/Sed_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF)">sed</a>ならどうでしょう?<a href="http://stackoverflow.com/questions/471183/linux-command-line-global-search-and-replace">Stack Overflow</a>、どうもありがとう。</p>
<pre><code># OSXの場合
find . -type f -name '*.txt' -exec sed -i '' s/this/that/g {} +
</code></pre>
<p>何度か実行した後、以下のような関数を<code>.bashrc</code>に追加してみました。</p>
<pre><code>function sr {
find . -type f -exec sed -i '' s/$1/$2/g {} +
}
</code></pre>
<p>そうすると、以下のようにできます。</p>
<pre><code>sr 置換前の文字列 置換後の文字列
</code></pre>
<h3>4. vimとDropboxで<em>スクラッチファイル</em>を開く</h3>
<p>私は<a href="http://www.gnu.org/software/emacs/">Emacs</a>の<a href="http://www.gnu.org/software/emacs/manual/html_node/emacs/Lisp-Interaction.html">スクラッチ機能</a>が大好きで、一時的なファイルを開いて、そこに書いたものをそのまま保存してしまうという機能をvimでも使いたくなってしまいます。そこで、<code>openssl</code>を使ってファイル名にランダムな文字列を入れるようにしてある以下のような関数を使うようになりました。</p>
<pre><code>function sc {
gvim ~/Dropbox/$(openssl rand -base64 10 | tr -dc 'a-zA-Z').txt
}
function scratch {
gvim ~/Dropbox/$(openssl rand -base64 10 | tr -dc 'a-zA-Z').txt
}
</code></pre>
<p>ターミナルから<code>sc</code>あるいは<code>scratch</code>と入力すれば、Dropboxのフォルダ内に一時ファイルが作られて、gvimあるいはmacvimのウィンドウが開き、私の<em>「鋭い洞察力にあふれた」</em>メモ(でたらめのゴミとも言う)がすぐに書けるようになるのです。</p>
<h3>5. リダイレクトをたどり、セキュリティに問題があるhttpsでもかまわずファイルをダウンロードする</h3>
<p>リダイレクト先までたどり、セキュリティ警告が出ても無視してページをダウンロードして<code>stdout</code>(標準出力)に出力するには、</p>
<pre><code>curl -Lks <some-url>
</code></pre>
<p>同じくファイルに出力するには、</p>
<pre><code>curl -OLks <some-url/to/a/file.tar.gz>
</code></pre>
<p>分かってます、分かってますよ、わざわざオプションごときをここに書かなくてもいいって!<em>とても簡単</em>で<em>短い</em><a href="http://curl.haxx.se/docs/manpage.html">curl</a>のドキュメントを読めば十分ですね!(しゃれっ気が全力稼働中です)</p>
<h3>6. Bashmarksで勝つ</h3>
<p><a href="http://www.huyng.com/projects/bashmarks/">bashmarks</a>を<code>.bashrc</code>にまだ入れていないなんて、何をしているんですか?これはもう最高です。良く使うディレクトリを保存して、すぐにそこにジャンプすることができます。私は以下のような最低限の設定をファイルに書いていますが、上のリンクには<code>.bashrc</code>にロードするもっとちゃんとした方法が載っています。</p>
<pre><code># USAGE:
# s bookmarkname - saves the curr dir as bookmarkname
# g bookmarkname - jumps to the that bookmark
# g b[TAB] - tab completion is available
# l - list all bookmarks
# save current directory to bookmarks
touch ~/.sdirs
function s {
cat ~/.sdirs | grep -v "export DIR_$1=" > ~/.sdirs1
mv ~/.sdirs1 ~/.sdirs
echo "export DIR_$1=$PWD" >> ~/.sdirs
}
# jump to bookmark
function g {
source ~/.sdirs
cd $(eval $(echo echo $(echo \$DIR_$1)))
}
# list bookmarks with dirnam
function l {
source ~/.sdirs
env | grep "^DIR_" | cut -c5- | grep "^.*="
}
# list bookmarks without dirname
function _l {
source ~/.sdirs
env | grep "^DIR_" | cut -c5- | grep "^.*=" | cut -f1 -d "="
}
# completion command for g
function _gcomp {
local curw
COMPREPLY=()
curw=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=($(compgen -W '`_l`' -- $curw))
return 0
}
# bind completion command for g to _gcomp
complete -F _gcomp g
</code></pre>
<h3>7. テーブル表示から列を抜き取る(一番よく使うawkのテクニック)</h3>
<p>これは毎日使っています。マジで。何らかの出力を受け取って、その中の2番目あるいは3番目とかだけを欲しい時、全部コマンドを入力するのは長ったらしいのです。</p>
<pre><code># git status -sコマンドの例
$ git status -s
M .bashrc
?? .vim/bundle/extempore/
# ステータスコードを削除して、ファイル名だけを得る
$ git status -s | awk '{print $2}'
.bashrc
.vim/bundle/extempore/
</code></pre>
<p>いつでも使えるようにシンプルな関数を作ったらどうでしょう?</p>
<pre><code>function col {
awk -v col=$1 '{print $col}'
}
</code></pre>
<p>これで、最初の列を削除するといった、ある列だけを取り出すのがとても簡単になります。</p>
<pre><code>$ git status -s | col 2
.bashrc
.vim/bundle/extempore/
</code></pre>
<h3>8. 行の始めのx語だけスキップする</h3>
<p>私は<a href="http://en.wikipedia.org/wiki/Xargs">xargs</a>のファンで、スライス済みの食パンぐらい素晴らしいと思っています。でも、いくつかの値をスキップするといったように、xargsの結果をいじる必要がある場合もあるでしょう。例として、古いDockerイメージを削除したいけれども、取り出した結果の最初はタイトル行だから不要、という場合を考えましょう。</p>
<pre><code>function skip {
n=$(($1 + 1))
cut -d' ' -f$n-
}
</code></pre>
<p>この関数の使い方は以下の通りです。</p>
<p><code>docker images</code>コマンドは、以下の結果を返します。</p>
<pre><code>$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
<none> <none> 65a9e3ef7171 3 weeks ago 1.592 GB
<none> <none> 7c01ca6c30f2 3 weeks ago 11.1 MB
<none> <none> 9518620e6a0e 3 weeks ago 7.426 MB
<none> <none> 430707ee7fe8 3 weeks ago 7.426 MB
boot2docker/boot2docker latest 1dbd7ebffe31 3 weeks ago 1.592 GB
spaceghost/tinycore-x86_64 5.4 f47686df00df 7 weeks ago 11.1 MB
durdn/bithub latest df1e39df8dbf 8 weeks ago 100.9 MB
<none> <none> c5e6cf38d985 8 weeks ago 100.9 MB
nginx latest e426f6ef897e 12 weeks ago 100.2 MB
zoobab/tinycore-x64 latest 8cdd417ec611 8 months ago 7.426 MB
scratch latest 511136ea3c5a 20 months ago 0 B
</code></pre>
<p>前に出てきた<code>col</code>関数を使って、以下のようにイメージのIDを取り出せます。</p>
<pre><code>$ docker images | col 3
IMAGE
65a9e3ef7171
7c01ca6c30f2
9518620e6a0e
430707ee7fe8
1dbd7ebffe31
f47686df00df
df1e39df8dbf
c5e6cf38d985
e426f6ef897e
8cdd417ec611
511136ea3c5a
</code></pre>
<p>ここでほとんど不要なものは削除できたようです。</p>
<pre><code>docker images | col 3 | xargs
IMAGE 65a9e3ef7171 7c01ca6c30f2 9518620e6a0e 430707ee7fe8 1dbd7ebffe31 f47686df00df df1e39df8dbf c5e6cf38d985 e426f6ef897e 8cdd417ec611 511136ea3c5a
</code></pre>
<p>ところが余計な「IMAGE」が始めにあるので、<code>skip</code>してしまいましょう。</p>
<pre><code>docker images | col 3 | xargs | skip 1
65a9e3ef7171 7c01ca6c30f2 9518620e6a0e 430707ee7fe8 1dbd7ebffe31 f47686df00df df1e39df8dbf c5e6cf38d985 e426f6ef897e 8cdd417ec611 511136ea3c5a
</code></pre>
<p>これで、<code>docker rmi</code>(Dockerのイメージを削除するコマンド)に渡せるよう、余計なものを削除できました。</p>
<pre><code>docker rmi $(docker images | col 3 | xargs | skip 1)
</code></pre>
<h3>9. 自分専用のコマンドパッケージを作る</h3>
<p>bashでは、自分の好きな名前空間を持った専用のコマンド集を、めちゃくちゃ簡単に作れます。私が自分で使っているものをお見せしましょう。</p>
<pre><code>function dur {
case $1 in
clone|cl)
git clone git@bitbucket.org:nicolapaolucci/$2.git
;;
move|mv)
git remote add bitbucket git@bitbucket.org:nicolapaolucci/$(basename $(pwd)).git
git push --all bitbucket
;;
trackall|tr)
#track all remote branches of a project
for remote in $(git branch -r | grep -v master ); do git checkout --track $remote ; done
;;
key|k)
#track all remote branches of a project
ssh $2 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub
;;
fun|f)
#list all custom bash functions defined
typeset -F | col 3 | grep -v _ | xargs | fold -sw 60
;;
def|d)
#show definition of function $1
typeset -f $2
;;
help|h|*)
echo "[dur]dn shell automation tools"
echo "commands available:"
echo " [cl]one, [mv|move]"
echo " [f]fun lists all bash functions defined in .bashrc"
echo " [def] <fun> lists definition of function defined in .bashrc"
echo " [k]ey <host> copies ssh key to target host"
echo " [tr]ackall], [h]elp"
;;
esac
}
</code></pre>
<p>例えばこれで、<code>dur key user@somehost</code>と入力するだけで自分のSSH鍵をどこへでもコピーできます。</p>
<h3>まとめ</h3>
<p>私の<a href="https://bitbucket.org/durdn/cfg/src/master/.bashrc">.bashrc</a>のカスタム関数を調べてみるか、自分のカスタム関数を考えてみましょう。日頃使っているターミナルのハックに役立つテクニックや短い関数ってありますか?<a href="https://disqus.com/home/discussions/atlassiandevelopers/ten_tips_for_wonderful_bash_productivity/">コメント</a>かTwitterの<a href="http://twitter.com/durdn">@durdn</a>までぜひ教えてください。いつも新しいアイディアを探しています。</p>
doublemarket
https://yakst.com/ja/posts/217
2014-02-12T00:33:00+09:00
2017-06-18T21:02:16+09:00
コマンドラインでデータを扱う方法の色々
<p><a href="http://www.r-project.org/">R</a>や<a href="http://www.python.org/">Python</a>のような成熟したコンピューティング環境は、詳しくデータを分析するのには素晴らしいものだ。しかし、素早くシンプルにデータの調査や捜査をしたいときには、UNIXのコマンドラインツールはものすごく効率的だ。この記事では、自分で見つけ日々使っているいくつかのツールに光を当ててみようと思う。新しいツールをあなたのレパートリーに加えられたらうれしい。</p>
<h3>ファイルをのぞき見る( <code>head</code> <code>tail</code> <code>less</code> )</h3>
<p><a href="http://ita.ee.lbl.gov/html/contrib/NASA-HTTP.html">NASAのサンプルWebログデータ</a>をダウンロードするところから始めよう。</p>
<pre><code>$ wget ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz
</code></pre>
<p>ダウンロードされたファイルはgzipで圧縮されており、 <code>less</code> はデフォルトではこれを展開まではしてくれない。しかしその代わり、 <code>zless</code> を使える。あるいは、 <code>gunzip</code> の結果をファイルに代わって、パイプで他のコマンドに出力を送るのに便利な <code>stdout</code> (標準出力)に送れば、 <code>head</code> や <code>tail</code> でファイルの先頭や最後をそれぞれ調査することができる。</p>
<pre><code>$ gunzip -c NASA_access_log_Jul95.gz | head -n 5
199.72.81.55 - - [01/Jul/1995:00:00:01 -0400] "GET /history/apollo/ HTTP/1.0" 200 6245
unicomp6.unicomp.net - - [01/Jul/1995:00:00:06 -0400] "GET /shuttle/countdown/ HTTP/1.0" 200 3985
199.120.110.21 - - [01/Jul/1995:00:00:09 -0400] "GET /shuttle/missions/sts-73/mission-sts-73.html HTTP/1.0" 200 4085
burger.letters.com - - [01/Jul/1995:00:00:11 -0400] "GET /shuttle/countdown/liftoff.html HTTP/1.0" 304 0
199.120.110.21 - - [01/Jul/1995:00:00:11 -0400] "GET /shuttle/missions/sts-73/sts-73-patch-small.gif HTTP/1.0" 200 4179
</code></pre>
<h3>列をフィルタリングする( <code>awk</code> <code>cut</code> )</h3>
<p>最初の列には、リクエスト元の(サブ)ドメインかIPアドレスが書いてあるようだ。こういった部分を抜き出すのによく使われるツールは <code>awk</code> と <code>cut</code> だ。</p>
<pre><code>$ gunzip -c NASA_access_log_Jul95.gz | head -n 5 | awk '{print $1}'
199.72.81.55
unicomp6.unicomp.net
199.120.110.21
burger.letters.com
199.120.110.21
</code></pre>
<h3>ユニークな行を数える</h3>
<p>ユニーク(一意)な(サブ)ドメインやIPアドレスを数えるには、行を並べ替えて( <code>sort</code> )、重複を排除し( <code>uniq</code> )、その結果の行数を数えれば( <code>wc</code> )よい。並べ替えの際には全ての行がメモリにロードされる点に注意しよう。</p>
<pre><code>$ gunzip -c NASA_access_log_Jul95.gz | awk '{print $1}' | sort | uniq | wc -l
81983
</code></pre>
<h3>行をフィルタリングする( <code>grep</code> )</h3>
<p>テキストファイルから正規表現にマッチする行をフィルタリングするのは非常によくあるオペレーションだ。これは、 <code>grep</code> とその変化形で実現できる。ソースIPがあるリクエストだけを見たいとしよう。</p>
<pre><code>$ gunzip -c NASA_access_log_Jul95.gz | egrep '^\d+\.\d+\.\d+\.\d+' | head -n 5
199.72.81.55 - - [01/Jul/1995:00:00:01 -0400] "GET /history/apollo/ HTTP/1.0" 200 6245
199.120.110.21 - - [01/Jul/1995:00:00:09 -0400] "GET /shuttle/missions/sts-73/mission-sts-73.html HTTP/1.0" 200 4085
199.120.110.21 - - [01/Jul/1995:00:00:11 -0400] "GET /shuttle/missions/sts-73/sts-73-patch-small.gif HTTP/1.0" 200 4179
205.212.115.106 - - [01/Jul/1995:00:00:12 -0400] "GET /shuttle/countdown/countdown.html HTTP/1.0" 200 3985
129.94.144.152 - - [01/Jul/1995:00:00:13 -0400] "GET / HTTP/1.0" 200 7074
$ gunzip -c NASA_access_log_Jul95.gz | egrep '^\d+\.\d+\.\d+\.\d+' | wc -l
419797
</code></pre>
<p>指定のパターンを除外したい場合は、単純に <code>-v</code> オプションを付けよう。</p>
<pre><code>$ gunzip -c NASA_access_log_Jul95.gz | egrep -v '^\d+\.\d+\.\d+\.\d+' | head -n 5
unicomp6.unicomp.net - - [01/Jul/1995:00:00:06 -0400] "GET /shuttle/countdown/ HTTP/1.0" 200 3985
burger.letters.com - - [01/Jul/1995:00:00:11 -0400] "GET /shuttle/countdown/liftoff.html HTTP/1.0" 304 0
burger.letters.com - - [01/Jul/1995:00:00:12 -0400] "GET /images/NASA-logosmall.gif HTTP/1.0" 304 0
burger.letters.com - - [01/Jul/1995:00:00:12 -0400] "GET /shuttle/countdown/video/livevideo.gif HTTP/1.0" 200 0
d104.aa.net - - [01/Jul/1995:00:00:13 -0400] "GET /shuttle/countdown/ HTTP/1.0" 200 3985
$ gunzip -c NASA_access_log_Jul95.gz | egrep -v '^\d+\.\d+\.\d+\.\d+' | wc -l
1471918
</code></pre>
<h3>データを抽出する( <code>shuf</code> <code>head</code> )</h3>
<p>もうひとつ、ファイルからランダムに行を抽出するというのもよくあるタスクだ。データがメモリに収まるなら、行をシャッフルするのに <code>shuf</code> を使い、その一部を <code>head</code> で取り出せる。OS Xでは、 <code>shuf</code> は <code>coreutils</code> をインストールすれば使える。<a href="http://brew.sh/">homebrew</a>を使っているなら、 <code>brew install coreutils</code> でよい。デフォルトでは、前にインストールされたバージョンとの重複を避けるためにツール名の最初に「g」がついた状態でインストールされることに注意しよう。つまり、 <code>shuf</code> は <code>gshuf</code> として使えるようになる。</p>
<pre><code>$ gunzip -c NASA_access_log_Jul95.gz | gshuf | head -n 5
rs710.gsfc.nasa.gov - - [24/Jul/1995:15:32:01 -0400] "GET /images/launch-logo.gif HTTP/1.0" 200 1713
annex-065.gower.net - - [21/Jul/1995:23:37:43 -0400] "GET /images/ HTTP/1.0" 200 17688
cevennes.jpl.nasa.gov - - [23/Jul/1995:16:03:57 -0400] "GET /images/vab-small.gif HTTP/1.0" 200 35709
eoi18.eda.mke.ab.com - - [05/Jul/1995:09:08:35 -0400] "GET /shuttle/countdown/countdown.html HTTP/1.0" 200 3985
131.156.47.24 - - [05/Jul/1995:10:13:50 -0400] "GET /shuttle/missions/sts-71/mission-sts-71.html HTTP/1.0" 200 8192
</code></pre>
<h3>CSVファイルを操作する( <code>csvtool</code> )</h3>
<p>CSV(comma-separated values)ファイルをコマンドラインから操作するのに一番簡単なのは <code>column -s, -t file.csv</code> だが、値が含まれなかったり、値自体にコンマが含まれているような場合にはややこしくなる。CSVファイルを扱うのにデザインされたツールはたくさんあるが、ここでは <code>csvtool</code> を紹介しよう。</p>
<p>DebianベースのLinuxディストリビューションでは、 <code>csvtool</code> は <code>sudo apt-get install csvtool</code> でインストールできる。OS Xでインストールした状態にするのは少々骨が折れるが、努力の価値はある。このツールは<a href="http://ocaml.org/">OCaml</a>で書かれているので、まずはこれをインストールする必要がある。<a href="http://brew.sh/">Homebrew</a>でなら、OCamlのパッケージマネージャ<a href="http://opam.ocaml.org/">OPAM</a>をインストールすれば、依存するOCamlがインストールされる。関連するパッケージをインストールするのにもOPAMを使えばよい。</p>
<pre><code>$ brew install opam
...
$ opam install csv
...
$ sudo ln -s ~/.opam/system/bin/csvtool /usr/local/bin/
$ csvtool --help | head -n 6
csvtool - Copyright (C) 2005-2006 Richard W.M. Jones, Merjis Ltd.
csvtool is a tool for performing manipulations on CSV files from shell scripts.
Summary:
csvtool [-options] command [command-args] input.csv [input2.csv [...]]
</code></pre>
<p>インストールはこれでやっつけたので、サンプルのCSVデータをダウンロードしよう。イギリス内閣府が配布している、<a href="http://data.gov.uk/dataset/uk-civil-service-high-earners/resource/5797c128-f9ff-4587-b17c-30b2470f8651">高級官僚の高所得者給料一覧</a>だ。</p>
<pre><code>$ wget https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/83716/high-earners-pay-2012.csv
$ wc -l high-earners-pay-2012.csv
259 high-earners-pay-2012.csv
$ head -n1 high-earners-pay-2012.csv
Post Unique Reference,Surname,Forename(s),Grade Equivalent,Job Title,Job/Team Function,Parent Department,Organisation,Total Pay Floor (£),Total Pay Ceiling (£),Change from 2011,Notes,Contact Email
</code></pre>
<p>上を見ると、このファイルには258行と、カラム名が含まれたヘッダが1行あるのがわかる。タブを区切り文字として、カラムの一部(job title, organisation, total pay floor, total pay ceiling)を取り出したいとしよう。</p>
<pre><code>$ csvtool -u TAB col 5,8-10 high-earners-pay-2012.csv | head -n 5
Job Title Organisation Total Pay Floor (£) Total Pay Ceiling (£)
Director Serious Fraud Office 165000 169999
Non-Executive Director Defence Board and Chair Audit Committee Ministry of Defence 30000 34999
Chairman Olympic Delivery Authority 250000 254999
HM Inspector of Constabulary Northern Region HM Inspectorate of Constabulary 185000 189999
</code></pre>
<p>賃金の下限(total pay floor)で降順に並べ替えて、その上位のレコードを確認しよう。まずはヘッダの行を削除して、ファイルの代わりに <code>stdin</code> (標準入力)から読み込むよう <code>-</code> を指定した <code>csvtool</code> にパイプで渡す。それから、3番目のカラムを元に降順に行を並べ替え、さらに表示を整えるために <code>csvtool</code> にパイプで渡せばよい。</p>
<pre><code>$ csvtool drop 1 high-earners-pay-2012.csv | csvtool -u TAB col 5,8-10 - | sort -t$'\t' -k3rn | head -n 4 | csvtool -t TAB readable -
Chief Executive Olympic Delivery Authority 310000 314999
Chief Executive Office of Fair Trading 275000 279999
Chief Executive Officer Nuclear Decommissioning Authority 265000 269999
NHS Chief Executive Department of Health 265000 269999
</code></pre>
<h3>JSONフォーマットのデータを操作する( <code>jq</code> )</h3>
<p>最後に一番重要なこととして、JSONフォーマットのデータを操作するのに素晴らしく便利なツールである<a href="http://stedolan.github.io/jq/">jq</a>について書きたい。バイナリは<a href="http://stedolan.github.io/jq/">ダウンロードページ</a>から入手できる。OS Xでは、<a href="http://brew.sh/">Homebrew</a>を使って <code>brew install jq</code> でもインストールできる。</p>
<p><a href="https://developer.forecast.io/">forecast.io</a>から、現在のロンドンの天気予報データをダウンロードしよう。</p>
<pre><code>$ curl "https://api.forecast.io/forecast/$API_KEY/51.51121389999999,0.11982439999997041" > forecast.json
$ wc forecast.json
0 142 25804 forecast.json
</code></pre>
<p>なんと1行で巨大なレスポンスが返ってきた。 <code>jq</code> できれいにフォーマットできる。</p>
<pre><code>$ cat forecast.json | jq . | head
{
"flags": {
"units": "us",
"darksky-stations": [
"uk_london"
],
"datapoint-stations": [
"uk-324164",
"uk-350286",
"uk-351142",
</code></pre>
<p>JSONオブジェクトに含まれるキーを全て出してみよう。</p>
<pre><code>$ cat forecast.json | jq 'keys'
[
"currently",
"daily",
"flags",
"hourly",
"latitude",
"longitude",
"minutely",
"offset",
"timezone"
]
</code></pre>
<p>現在の天気をフィルターしてみる。</p>
<pre><code>$ cat forecast.json | jq '.currently'
{
"ozone": 311.54,
"temperature": 42.65,
"precipProbability": 0,
"precipIntensity": 0,
"nearestStormBearing": 191,
"nearestStormDistance": 176,
"icon": "wind",
"summary": "Breezy",
"time": 1390689251,
"apparentTemperature": 34.98,
"dewPoint": 35.04,
"humidity": 0.74,
"windSpeed": 15.9,
"windBearing": 286,
"visibility": 10,
"cloudCover": 0,
"pressure": 1013.82
}
</code></pre>
<p><code>jq</code> には何ができるのかをほんの少し見てみた。この他にもたくさんある素晴らしい機能については、 <code>jq</code> の豊富な<a href="http://stedolan.github.io/jq/manual/">ドキュメント</a>を参照して欲しい。</p>
<p><strong>追記</strong> : <a href="https://twitter.com/jeroenhjanssens/">@jeroenhjanssen</a>のブログ<a href="http://jeroenjanssens.com/2013/09/19/seven-command-line-tools-for-data-science.html">7 command-line tools for data science</a>もチェックするのを忘れないように。ここに挙げたツールの他にも、より高度なものについても書かれている。もうすぐ発売される「Data Science at the Command Line」という彼の本にも注目だ。</p>
doublemarket
https://yakst.com/ja/posts/31
2013-08-21T00:02:00+09:00
2013-08-21T00:03:13+09:00
私が他人のシェルスクリプトから学んだこと
<p>私はシェルスクリプトの大ファンで、他人のスクリプトから面白い方法を学ぶのが大好きだ。最近、SSHサーバの2要素認証を簡単にするための<a href="https://github.com/authy/authy-ssh">authy-ssh</a>スクリプトに出会った。このスクリプト群を見まわしていて、みんなと共有したいたくさんのクールなことを見つけた。</p>
<h3>出力に色付けする</h3>
<p>出力文字列を、成功した時は緑に、失敗した時は赤に、警告は黄色に色づけしたいと思うことはたくさんあるだろう。</p>
<pre><code>NORMAL=$(tput sgr0)
GREEN=$(tput setaf 2; tput bold)
YELLOW=$(tput setaf 3)
RED=$(tput setaf 1)
function red() {
echo -e "$RED$*$NORMAL"
}
function green() {
echo -e "$GREEN$*$NORMAL"
}
function yellow() {
echo -e "$YELLOW$*$NORMAL"
}
# 成功
green "Task has been completed"
# エラー
red "The configuration file does not exist"
# 警告
yellow "You have to use higher version."
</code></pre>
<p><code>tput</code> を使うと、色の設定、文字の配置、色のリセットができる。<code>tput</code> について詳しくは<a href="http://linux.101hacks.com/ps1-examples/prompt-color-using-tput/">prompt-color-using-tput</a>を参照しよう。</p>
<h3>デバッグ情報を表示する</h3>
<p>デバッグフラグがセットされている時だけ、デバッグ情報を表示してみる。</p>
<pre><code>function debug() {
if [[ $DEBUG ]]
then
echo ">>> $*"
fi
}
# 何かデバッグメッセージ
debug "Trying to find config file"
</code></pre>
<p>イケてるギークは1行でデバッグ用関数を書いてしまうようだ。</p>
<pre><code># hacker newsで見かけたとあるイケてるギークによる
function debug() { ((DEBUG)) && echo ">>> $*"; }
function debug() { [ "$DEBUG" ] && echo ">>> $*"; }
</code></pre>
<h3>指定した実行ファイルが存在しているかどうかチェックする</h3>
<pre><code>OK=0
FAIL=1
function require_curl() {
which curl &>/dev/null
if [ $? -eq 0 ]
then
return $OK
fi
return $FAIL
}
</code></pre>
<p><code>which</code>コマンドで<code>curl</code>の実行ファイルを探している。コマンドが成功したら、実行ファイルは存在していることになるし、失敗すれば存在していなかったことになる。<code>&>/dev/null</code>は、標準出力と標準エラー出力を`<code>/dev/null</code>に送ってしまう(つまり、コンソールには何も出さない)。</p>
<p>別のイケてるギークは<code>which</code>コマンドの戻り値を直接返してしまうこともできると教えてくれた。</p>
<pre><code># hacker newsで見かけたとあるイケてるギークによる
function require_curl() { which "curl" &>/dev/null; }
function require_curl() { which -s "curl"; }
</code></pre>
<h3>スクリプトの使用方法を表示する</h3>
<p>私がシェルスクリプトを始めた頃は、スクリプトの使用方法を表示するのにechoコマンドを使っていた。使用方法が長くなると、echoコマンドがごちゃごちゃしてきてしまう。それで、<code>cat</code>コマンドを使って表示すればいいと気づいた。</p>
<pre><code>cat << EOF
Usage: myscript <command> <arguments>
VERSION: 1.0
Available Commands
install - Install package
uninstall - Uninstall package
update - Update package
list - List packages
EOF
</code></pre>
<p><code><<</code>は<a href="http://www.tldp.org/LDP/abs/html/here-docs.html">ヒアドキュメント</a>と呼ばれる仕組みだ。2つの<em>EOF</em>の間の文字列を扱う。</p>
<h3>ユーザ指定の値とデフォルト値の扱い</h3>
<p>ユーザが値を指定しなかった時、デフォルト値を使いたいことはよくあるだろう。</p>
<pre><code>URL=${URL:-http://localhost:8080}
</code></pre>
<p>この例の場合、URLという環境変数をチェックして、それが存在しなかった場合は<code>localhost</code>をURLに割り当てる。</p>
<h3>文字列の長さを判定する</h3>
<pre><code>if [ ${#authy_api_key} != 32 ]
then
red "不正なAPIキーです"
return $FAIL
fi
</code></pre>
<p><code>${#変数名}</code> で変数の長さを得られる。</p>
<h3>タイムアウト付きで入力を待ち受ける</h3>
<pre><code>READ_TIMEOUT=60
read -t "$READ_TIMEOUT" input
# 引用符が不要ならエスケープしよう
input=$(sed "s/[;\`\"\$\' ]//g" <<< $input)
# 数字を入力するなら、他の文字をエスケープすることも可能
input=$(sed 's/[^0-9]*//g' <<< $input)
</code></pre>
<h3>ディレクトリ名やファイル名を得る</h3>
<pre><code># ベースディレクトリを取得
APP_ROOT=`dirname "$0"`
# ファイル名を取得
filename=`basename "$filepath"`
# 拡張子抜きのファイル名を取得
filename=`basename "$filepath" .html`
</code></pre>
<p>ハッピースクリプティング、そしてよい一日を。</p>
doublemarket