プログラマーの君！ 騙されるな！ シェルスクリプトはそう書いちゃ駄目だ！！ という話

<!-- プログラマーの君！ 騙されるな！ シェルスクリプトはそう書いちゃ駄目だ！！ という話 -->

本記事が切っ掛けとなってお声がけを頂き、本記事の増補リファイン版となる記事を[SoftwareDesign 2018年1月号](http://gihyo.jp/magazine/SD/archive/2018/201801)のシェルスクリプト特集第2章として執筆しました。リファイン版には、この記事で触れていない文法面での分かりにくさについての解説が含まれています。[その文法面での分かりにくさの解説の一部に相当する記事](https://qiita.com/piroor/items/0cb44663430bfc66c204)もありますので、ぜひそちらも併せてご覧下さい。

----

[Shell Script Advent Calendar](http://qiita.com/advent-calendar/2016/shell-script)をご覧の皆様、図々しくも[5日目][tweet.sh-解説]に続く2度目のエントリーのPiroです。

[前回は自作のBashスクリプト製Twitterクライアントをネタに実装を解説しました][tweet.sh-解説]が、今日は*他の言語で多少のプログラミング経験はあるんだけど、どうにもシェルスクリプトは苦手だ……という人のための、シェルスクリプトによるプログラミングの勘所*を解説してみようと思います。多分、プログラミング入門レベルの人や上級レベルの人よりは、*中級レベルにいる人や、初級から中級以上に成長したいという感じの人*にとって役に立つ話だと思います。

結論から先にいうと、*「引数」と「配列」の事を忘れればシェルスクリプトはグッと書きやすくなります*。というおはなしです。タイトルからして期せずして[前の日の方の話](http://qiita.com/syui/items/b859a57ed7bbda5d74b5 "Bashで便利なフレームワーク - Qiita")の全否定になっちゃいました。ごめんなさい、他意は無かったんです……

[tweet.sh]:https://github.com/piroor/tweet.sh
[tweet.sh-tweet.sh]:https://github.com/piroor/tweet.sh/blob/master/tweet.sh
[tweet.sh-README]:https://github.com/piroor/tweet.sh/blob/master/README.md
[tweet.sh-解説]:http://qiita.com/piroor/items/a999eeef9a67283b1e56
[tweetbot.sh]:https://github.com/piroor/tweetbot.sh
[tweetbot.sh-解説]:http://qiita.com/piroor/items/543da3fba81cbc3ed7e4
[インデントを保ったままの処理の解説]:http://qiita.com/piroor/items/00841eeeef74cc7bc7da#%E5%85%B1%E9%80%9A%E3%83%91%E3%83%BC%E3%83%84%E3%81%AE%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF
[シス管系女子]:http://system-admin-girl.com/
[みんとちゃんbot]:https://twitter.com/sysadgirl_mint
[日経Linux]:http://itpro.nikkeibp.co.jp/linux/



## シェルスクリプトって分かりにくい？

### 「あんなおもちゃみたいな物で……」

書かずに済むなら書きたくない物。わざわざ書くくらいなら、まともなスクリプト言語で使い捨てのコードを書くほうがマシ。システムの中にしぶとく残る前世紀の遺物。シェルスクリプトは、プログラミングを嗜む人達からはそう見られているような気がします。

というか、筆者自身がそうでした。「シェルスクリプトのみでアプリケーションを開発する」という[ユニケージ開発手法](http://www.usp-lab.com/methodology.html)の話を初めて聞いた時は、「は？　頭おかしいんじゃないの？」「随分と趣味的なこだわりで開発してるなあ……」というのが正直な感想でした。

でも[シス管系女子][]の連載の中でシェルスクリプト、具体的にはBashの理解を深め、さらに小規模のアプリケーションとして[Twitterクライアント][tweet.sh-解説]や[Twitter bot][tweetbot.sh-解説]を実際にシェルスクリプトで書いてみて、ようやく合点がいきました。自分が今までシェルスクリプトを毛嫌いしていたのは、*シェルスクリプトがプログラミング言語として駄目だからなのではなく、言語特性に反した使い方をしようとしていたから*なのでした。

### 他のパラダイムでのプログラミングを知りすぎていると、逆に分からなくなる

筆者が思うに、他の言語で以下の機能を使ったことがある人なら、恐らくシェルスクリプトの言語特性を活かしたスクリプトを容易に書けます。

 * 関数
 * イテレータ
 * 非同期処理、特にPromiseやDeferredと言われる物

逆に、以下のような知識が念頭にあるとシェルスクリプトの言語特性を理解しにくくなると思います。

 * 引数
 * 配列
 * オブジェクト指向
 * クラス



## シェルスクリプトが得意な事、不得意な事

### コマンドの自動実行だけじゃない

シェルスクリプトの得意分野として真っ先に挙げられるのは、やはり、インストールスクリプトなどの「システムのコマンドを多用する手順の自動化」でしょう。特にインストールスクリプトはポータビリティが大事ですから、環境に依存しにくいシェルスクリプトは理に適っています。

ですが、コマンドを実行するだけならNode.jsでもRubyでもPythonでも大抵の言語でできます。ここで言いたいのはそういう事ではありません。

### ステートレスか、ステートフルか

シェルスクリプトに適しているのは、*ステートレスな処理*です。ステートレスというのは内部状態を持たないということですが、具体的には、ImageMagickの画像変換処理や、`curl`によるRESTfulなWeb APIへのアクセスなど、「機能を1回実行したら、処理が行われて、結果が得られる。それで終わり。」という種類の仕事のことを言います。

またシェルスクリプトは、*入力を受け取って加工して出力するという、フィルタ的な働きが求められる種類の仕事*にも適しています。`sed`のような文字列加工はその代表でしょう。

逆に、シェルスクリプトは*ステートフルな処理にはあまり適していません*。筆者が書いた[Twitter bot][tweetbot.sh-解説]でも、ポーリング処理の部分で若干トリッキーな書き方をする必要がありました。*「その言語の基本機能で素直な書き方で実現できる処理」はその言語に適していて、「様々な機能をフルに使ったトリッキーな書き方をしないとできない事」はその言語では本来想定されていない不得意な事だ、というのはどんな言語やフレームワークについても言える原則*だと思います。


## 他の言語でのアレは、シェルスクリプトではこうやろう！

先に述べましたが、他の言語でのプログラミング経験がある人ほど、シェルスクリプトで凝ったことをやろうとして躓く気がします。筆者も、一番馴染みがある言語はJavaScript[^javascript]でその次くらいにRubyを書いていますが、勘所が分かっていなかった時は「JSだったらこう書けるのに！　キィーッ！！」とイライラする場面が多かったです。

[^javascript]: WebアプリやNode.jsではなく、FirefoxやThunderbirdのアドオンなどが主。

そこで、自分が躓いていたポイントを例にとって他の言語と比較しながら「シェルスクリプトでは、それはこうやるのがベター」という事をいくつか解説してみます。キーワードは「*パイプラインと標準入出力*」です。


### データは引数で渡さない

のっけから「は？」という感じだと思いますが、シェルスクリプトやシェル関数では*引数を活用しようとするとドツボにはまります*。

普通のプログラミングに慣れていると、データを渡すといえばまず引数を使う習慣が付いていると思います。しかしシェルスクリプトやシェル関数では、引数はあくまで「細かい動作を変えるための補助的な指定」としてのみ使うのがベストプラクティスだと筆者は思っています。

じゃあどうやってデータを渡せばいいんだ？という話ですが、そこで使うのが*標準入力*です。シェルスクリプトでは*処理対象のデータはパイプラインやリダイレクトで標準入力から受け取って、処理結果は標準出力で出力するのがベストプラクティス*です。
![pipeline.png](https://qiita-image-store.s3.amazonaws.com/0/61387/6c5fde0e-688e-7471-5552-3e931bc1a7fe.png)

#### データは標準入力で受け取る

よく考えてみれば、シェルスクリプトは*シェルのコマンド操作で使う書き方をそのまま使える*のが特長です。コマンド操作ではパイプラインを使うのに、シェルスクリプトやシェル関数になった途端にパイプラインを使わなくなるのはおかしな話ですよね。

[Twitterクライアント][tweet.sh-解説]の実装に含まれている「指定した文字列をURLエンコード（[パーセントエンコーディング](https://dev.twitter.com/oauth/overview/percent-encoding-parameters)）するシェル関数」を例に説明しましょう。以下は、説明のために簡略化したバージョンです[^single-input]。

[^single-input]: ここでは説明のためにコードを簡略化しているので、複数行の入力を与えると1行の文字列に連結されてしまうようになっています。

~~~bash:シェルスクリプトでの実装
url_encode() {
  nkf -W8MQ |
    sed 's/=$//' |
    tr '=' '%' |
    paste -s -d '\0' - |
    sed -e 's/%7E/~/g' \
        -e 's/%5F/_/g' \
        -e 's/%2D/-/g' \
        -e 's/%2E/./g'
}
~~~

~~~bash:実行例
$ echo "こんにちは" | url_encode
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
~~~

「処理対象のデータは標準入力で受け取る」という仕様にしたので、変換対象の文字列は`echo`で出力した物をパイプラインで渡しています。シェル関数の中で最初に実行されるコマンドの`nkf`には読み込み元のファイルが指定されていないため、標準入力から文字列を読み取ろうとして、結果的にこの関数自体の標準入力から文字列を受け取っています。

その後に続くコマンドも、前のコマンドの処理結果をパイプラインで受け取って処理しています。最後の`sed`の後にはもうパイプラインがないので、ここで結果が初めて関数自体の標準出力に渡ることになり、関数の標準出力が画面に表示されている[^pipeline-after-function]、というわけです。

[^pipeline-after-function]: 関数の後にパイプラインを繋げれば次のコマンドに出力が渡りますし、リダイレクトすればファイルにも保存できます。

#### JavaScriptと比較する

仮にJavaScriptの文法で、「各コマンドと同名の関数で、第1引数で処理対象のデータを受け取る」という仕様でコードを書くと、以下のようになるでしょう。

~~~javascript:引数と戻り値で愚直にデータを引き回した例
function url_encode(input) {
  return sed(
    paste(
      tr(
        sed(
          nkf(input, '-W8MQ'), // ここから評価される
          's/=$//'
        )
        '=', '%'
      ),
      '-s', '-d', '\0'
    ),
    '-e', 's/%7E/~/g',
    '-e', 's/%5F/_/g',
    '-e', 's/%2D/-/g',
    '-e', 's/%2E/./g'
  );
}
~~~

関数の戻り値を次の関数に渡すという事を愚直にやると、最初にやって欲しい処理ほど内側に書く必要があって、こんな感じでどんどんネストが深くなってしまいます。

行う処理の順番通りに読めるように書くなら、変数を使って以下のように書くことになるでしょう。

~~~javascript:行う処理の順番通りにコードを書いた例
function url_encode(input) {
  input = nkf(input, '-W8MQ');
  input = sed(input, 's/=$//');
  input = tr(input, '=', '%');
  input = paste(input, '-s', '-d', '\0');
  input = sed(input, 
              '-e', 's/%7E/~/g',
              '-e', 's/%5F/_/g',
              '-e', 's/%2D/-/g',
              '-e', 's/%2E/./g');
  return input;
}
~~~

各機能をメソッドチェーンで呼べるようになっていたら、以下のように書けるかもしれません。

~~~javascript:何か便利なライブラリを使って、メソッドチェーンで処理を書くようにした例
function url_encode(input) {
  // createStringStream() の戻り値は、
  // 各関数名のメソッドを持つオブジェクトとする。
  // 渡されたデータは内部で保持されていて、
  // メソッドを呼ぶ度にそれが加工されていく。
  return createStringStream(input).
           nkf('-W8MQ').
           sed('s/=$//').
           tr('=', '%').
           paste('-s', '-d', '\0').
           sed('-e', 's/%7E/~/g',
               '-e', 's/%5F/_/g',
               '-e', 's/%2D/-/g',
               '-e', 's/%2E/./g');
}
~~~

実際にこういう事をやるためには、ここで仮定した`createStringStream()`のような機能を提供するライブラリが必要になります。事実、「ファイルの内容に対してこのように処理を適用していく」という場面を想定している[Gulp.js](http://qiita.com/yoshinariiii/items/8340286ec6f3ed6ff888)では、その名も`pipe`という名前のモジュールを使ってこれに近い書き方をするようになっています。

#### シェルスクリプトらしくない書き方

むしろシェルスクリプトでは、変数にいちいち結果を代入する方が面倒です。

~~~bash:シェルスクリプトで、最初と同じことを引数と変数を使ってやった例
url_encode() { # 引数で文字列が渡されると仮定
  local input="$*"
  input="$(echo "$input" | nkf -W8MQ)"
  input="$(echo "$input" | sed 's/=$//')"
  input="$(echo "$input" | tr '=' '%')"
  input="$(echo "$input" | paste -s -d '\0' -)"
  input="$(echo "$input" | sed -e 's/%7E/~/g' \
                               -e 's/%5F/_/g' \
                               -e 's/%2D/-/g' \
                               -e 's/%2E/./g')"
  echo "$input"
}
~~~

~~~bash:実行例
$ url_encode "こんにちは"
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
~~~

変数への代入とコマンド置換で同じような記述が何度も出てきてうんざりですね。ここまでやるなら、最初に1回だけ`echo`で文字列を出力して、後はパイプラインにした方がずっと簡単です。

~~~bash:最初のデータの受け取りだけ引数を使った例
url_encode() { # 引数で文字列が渡されると仮定
  local input="$*"
  echo "$input" |
    nkf -W8MQ |
    sed 's/=$//' |
    tr '=' '%' |
    paste -s -d '\0' - |
    sed -e 's/%7E/~/g' \
        -e 's/%5F/_/g' \
        -e 's/%2D/-/g' \
        -e 's/%2E/./g'
}
~~~

結局、*外部から受け取った入力を順番に加工していって、その結果を出力する*という類の処理を*見た目に分かりやすく[^order]、且つ効率よく[^simple]*書こうとすると、シェルスクリプトではこういう書き方が一番*素直で楽*だという事になりますし、JSの例を見て分かる通り、他の言語でもパイプライン的な仕組みを導入したくなるというわけです。

[^order]: ここでは「行う処理の順番と同じ順番で書かれている」という事を指しています。
[^simple]: ここでは「同じ変数を何度も何度も書かなくても、暗黙的にデータが引き継がれていく」という事を指しています。

#### 重宝するぞ！ `echo`と`cat`

ところで、コマンドには`sed`や`grep`のように「*標準入力も受け取るし、ファイルパスの指定も受け付ける*」という物と、`tr`のように「*標準入力からしか入力を受け取らない物*」とがあります。でも、どのコマンドがどっちの種類なのかをいちいち覚えるのは煩雑ですよね。こういうバラバラさがシェルスクリプトの嫌な所なんだ、と思う人もいるのではないでしょうか。

でも、実は話は単純なんです。発想を逆転して、*基本的に各コマンドへの入力は標準入力から行う*ものと思っておけばいいんです。

そこで便利なのが、「引数で指定した文字列を標準出力に出力する」コマンドである`echo`と、「指定したファイルの内容を標準出力に出力する」コマンドである`cat`です。*変数に格納された文字列を処理したければ`echo`から、ファイルの内容を処理したければ`cat`からコマンド列を書き始める*、という風に覚えておけば、「あれ、このコマンドってファイル指定を受け付けたっけ……？」と悩む必要はありません[^possibly-slow]。

[^possibly-slow]: 「`cat`から始めるとメモリを余計に消費するのでは？」という議論もあるようですが、実際の所は[小刻みに読み込むのでそれほど大量にメモリを使う訳ではない](http://qiita.com/Neetless/items/f1ddad76d6ad856601cb)みたいです（このレベルのメモリ消費も問題になるような前提ならそもそもシェルスクリプトでやるのが不適切と言えるでしょう……）。ちなみに、ファイルの内容をコマンドの標準入力に渡すのは`cat`を使わなくても`コマンド名 <ファイル名`あるいは`<ファイル名 コマンド名`という書き方でもできますが、筆者は余計な事は憶えたくないズボラなので、いつも`cat`とパイプラインで済ませています。

また、`cat`とヒアドキュメントを併用すれば「シェルスクリプト内に複数行のテキストをリテラルとして埋め込む」ような事もできます。

~~~bash:ヒアドキュメントの内容を標準出力に出力する関数の例
common_params() {
  cat << FIN
oauth_consumer_key $CONSUMER_KEY
oauth_nonce $(date +%s%N)
oauth_signature_method HMAC-SHA1
oauth_timestamp $(date +%s)
oauth_token $ACCESS_TOKEN
oauth_version 1.0
FIN
}
~~~


### 繰り返し処理は配列ではなくイテレータで

シェルスクリプトでもう一つ鬼門になるのが*繰り返し処理*です。

#### 配列のこと、忘れて下さい

普通のプログラミングに慣れていると、「繰り返し処理」と「配列」はほぼワンセットで捉えるクセが付いているのではないでしょうか。

しかし、シェルスクリプト……というかBashでの配列の取り扱いは他の言語に比べてやたら面倒です。[解説の記事](http://qiita.com/b4b4r07/items/e56a8e3471fb45df2f59)を見ても各要素へのアクセスや長さの取得などで特殊な記法が連発されていて、途中で投げ出したくなることうけあいです。[前日の記事のbash-oo-framework](http://qiita.com/syui/items/b859a57ed7bbda5d74b5 "Bashで便利なフレームワーク - Qiita")でも、[その実装を見ると](https://github.com/niieani/bash-oo-framework/blob/master/lib/TypePrimitives/array.sh "bash-oo-framework/array.sh at master · niieani/bash-oo-framework")配列を簡単に扱えるようにするためにかなりの労力を割いている様子が伺えます。

はい、ここで白状しておきます。*筆者はBashの配列を使えません！*　Linuxのコマンド操作とシェルスクリプトの*基礎*を解説する記事のはずの[シス管系女子][]でも、もう連載6年目に突入しようかというのに配列は完全にスルーしています[^only-iteration]。拙作[Twitterクライアント][tweet.sh-解説]や[Twitter bot][tweetbot.sh-解説]でも配列は一切使用していません。

[^only-iteration]: これには理由があります。[シス管系女子][]の連載では、*紹介する技術は必ず何かの目的、何かの問題の解決のための手段として紹介する*という事を心がけています。いたずらにツールや文法だけをたくさん紹介しても、それらの適切な使い道が分からないと混乱が増すだけだからです。そう考えて話を選んできた結果、「どうしても配列を使わなければ解決できない」という種類の問題を思いつけずにいるため、今の今まで配列を紹介しないままで来たという次第です。同じ理由で`xargs`も今のところスルーしています。

じゃあどうやって複数の要素を持つデータを処理するのかという話なんですが、筆者は*`while`ループと`read -r`の組み合わせ*を多用しています。前述の「データは標準入力で受け取って標準出力で出力する」という話とワンセットで覚えておけば、大抵のことはこれでどうにかなってしまいます。*シェルスクリプトでは、繰り返し処理は`while`ループ（と`read -r`）でやるのがベストプラクティス*と言っていいでしょう。
![read.png](https://qiita-image-store.s3.amazonaws.com/0/61387/48ba79e4-0dea-b68b-52f3-83194e8ddb0a.png)

#### RubyやJavaScriptのイテレータ

`while`と`read -r`の組み合わせの振る舞いは、他の言語で言う所の*イテレータ*によく似ています。

繰り返し処理したい対象のデータについて、全体の個数のことは一旦忘れて「最初の要素を取得して処理する」という操作を繰り返して対象のデータすべてを処理する、という設計を「イテレータパターン」と言います。Rubyでは`each`メソッドがあるオブジェクトに対して以下のような書き方でループを回して個々のデータを処理できますが、これがイテレータです。

~~~ruby:Rubyでのイテレータを使った繰り返し処理
uris = [
  "http://www.example.com/",
  "http://www.example.net/",
  "http://www.example.jp/",
]
for uri in uris do
  p uri
end
~~~

ES2015などのモダンな仕様のJavaScriptでも似たような書き方ができます。

~~~javascript:JavaScriptでのイテレータを使った繰り返し処理
uris = [
  "http://www.example.com/",
  "http://www.example.net/",
  "http://www.example.jp/"
];
for (let uri of uris) {
  console.log(uri);
}
~~~

ところで、JavaScriptのループといえば以下のような書き方もあります。上記の記法が導入される前からJavaScriptを使ってる人なら、こっちの方が馴染みがあるのではないでしょうか。

~~~javascript:JavaScriptでのイテレータを使わない繰り返し処理
for (let i = 0; i < uris.length; i++) {
  console.log(uris[i]);
}
~~~

この書き方と上の2つの例との最大の違いは、*データの個数が出てくるかこないか*、もっと言うと*処理を始める前にデータの全体像が見えている必要があるか無いか*です。3つめの書き方はデータの個数を条件に使った「ループを何回実行するか」という観点のコードになっているので、当然ですが事前に配列の個数が決まっていないといけません。

それに対して前2者のイテレータパターンでは、「データをすべて処理するまでループを実行する」という観点なので、*データの個数は見ていないし、データの総数が不明のままでも問題なく各要素を処理できます*。例えばフィボナッチ数列は無限に続く数列なので、有限長の配列としては表現できず、3番目の例の書き方でも取り扱えません。しかし、[イテレータを使えば通常の処理の中で無理なく取り扱えます](http://qiita.com/kura07/items/d1a57ea64ef5c3de8528 "JavaScript の ジェネレータ を極める！ - Qiita")。

#### シェルスクリプトでもイテレータ

先程例として示した「指定した文字列をURLエンコードするシェル関数」は省略版ですが、実際に使用している物は以下のようになっています。

~~~bash:複数行の入力を受け取って、各行をURLエンコードして出力するシェル関数の実装
url_encode() {
  while read -r line
  do
    echo "$line" |
      nkf -W8MQ |
      sed 's/=$//' |
      tr '=' '%' |
      paste -s -d '\0' - |
      sed -e 's/%7E/~/g' \
          -e 's/%5F/_/g' \
          -e 's/%2D/-/g' \
          -e 's/%2E/./g'
  done
}
~~~

関数の標準入力にパイプライン経由で渡されてきたデータを`while read -r line`で1行ずつ[^while-read-r]取り出して、`echo "$line"`でエンコード処理を行い標準出力に出力しています。これによって、このシェル関数は入力行すべてをきちんとエンコードできるようになっています。

~~~bash:複数行の入力に対する実行例
$ cat data.txt
おはよう
こんにちは
こんばんは
おやすみ
$ cat data.txt | url_encode
%E3%81%8A%E3%81%AF%E3%82%88%E3%81%86
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
%E3%81%93%E3%82%93%E3%81%B0%E3%82%93%E3%81%AF
%E3%81%8A%E3%82%84%E3%81%99%E3%81%BF
~~~

この例のように、シェルスクリプトやシェル関数では「1行＝1単位」という構造のデータであれば何でも`while read -r`でループを回して処理できます。

[^while-read-r]: このとき、`read`に`-r`オプションを指定していることに注意して下さい。`-r`は標準入力の中のバックスラッシュをエスケープ文字ではなくバックスラッシュという文字としてそのまま受け取るオプションです。これを指定しておかないと、行の中に`\n`というテキストがあるとそれが入力の区切りの改行として解釈されてしまい、1行＝1単位という構造が崩れてしまいます。なお、*`read`は改行の前後で連続する半角スペースやタブなどの空白文字があるとそれらを取り除く仕様です*ので、行頭のインデントを含んでいるデータ（何かのソースコードなど）をインデントを保持したまま取り扱う場合は、厳密に改行文字だけで区切るように`IFS= read -r`と指定する必要があります。詳しくは[別の記事][インデントを保ったままの処理の解説]もご参照下さい。

#### イテレータと非同期処理

イテレータには非同期処理との相性が良いという特長もあります。*次のデータが無い状態で次を取り出そうとしたら一旦処理を止め、新しいデータが届くのを待つ*という風にすれば、少しずつ渡されてくるデータを随時処理するのも容易になるからです。

`read`コマンドには*入力が途絶えた状態で`read`を実行すると次の入力があるまで待つ*という性質があります。これを`while`ループと併用すれば、断続的に流入してくるデータを待ち受ける非同期処理も簡単に実現できます。以下は、Twitterの[User streams API](https://dev.twitter.com/streaming/userstreams)で新しい通知を待ち受けて少しずつ処理する例です。

~~~bash:Twitterの新しい通知を待ち受けて処理する例
watch_twitter_events() {
  curl --get ... https://userstream.twitter.com/1.1/user.json |
    while read -r event
    do
      # eventには1つ1つのツイートやイベントを表すJSON文字列が格納されている。
    done
}
watch_twitter_events & # 末尾に「&」を付けて関数を実行すると、子プロセスで非同期に実行される。
~~~

このように使われる事を想定して、*複数のデータを扱うコマンドラインツールの多くは、1行1単位の形で標準出力に結果を出力するように設計されています*。Slackのクライアントである[slackcat](https://github.com/rlister/slackcat)も、チャンネルの内容を監視する時は受信したイベント・メッセージを1行1データで出力してくれるので、やはり`while read -r`で処理することができます。

~~~bash:Slackのチャンネルの新しい発言を待ち受けて処理する例
watch_slack_channel() {
  slackcat --channel team:general --stream --plain |
    while read -r post
    do
      # postには1つ1つの発言が格納されている。
    done
}
watch_slack_channel &
~~~

シェルスクリプトやシェル関数では、`read`をイテレータ的に使うことによって、データが静的な物だろうが動的に生成（返却）されるものだろうが全く等価に扱えるということをお分かり頂けるでしょう。

#### `for ... in`ループは標準入力から受け取るデータには使わない

ところで、シェルスクリプトでループというとこんな書き方もあります。

~~~bash:シェルスクリプトでのforループ
for host in fileserver mailserver authserver
do
  scp /path/to/file uploader@$host:~/
done
~~~

これも確かにイテレータパターンなのですが、こちらは`for ...in`の後に*すべての項目をあらかじめ列挙しておかなくてはならない*書き方です。こちらだと、前述したような「全体像が未知のままでも処理できる」というイテレータならではの利点を活かせません。

一応、「コマンド置換を使うとコマンドの実行結果を文字列として参照できる」という事と「引数無しで`cat`を実行すると標準入力からデータを受け取る」という事を組み合わせると、パイプラインで受け取ったデータをシェル変数に格納してメモリ上に保持したり、コマンド置換でコマンド列の中に展開したりということはできます。なので、以下のような書き方も可能です。

~~~bash:シェルスクリプトでのforループで標準入力を処理する例
for line in $(cat)
do
  # 各行に対する処理
done
~~~

しかし、これは以下の理由からオススメできません。

 1. *標準入力から一旦データをすべて読み取り終えることができるのが前提となる*ため、前述したストリーミングAPIからの情報の受け取りのようにデータの流入がいつ終わるか分からない場面では使えません。
 2. 読み取り元のデータが巨大な場合、メモリを大量に消費する事になります。そういう場合は一時ファイルに書き出して`cat`し直した方が安全です。
 3. `for`のループでは改行だけでなく半角スペースなども区切り文字になるため、流入するデータの内容によっては期待通りに分割されません[^for-delimiter]。

[^for-delimiter]: 区切り文字を明示的に変える方法もありますが、トリッキーなので使わない方がいいと自分は思っています。トリッキーなやり方は基本的に避けた方が無難です。

コマンド置換を使ってパイプラインで受け取ったデータをシェル変数に格納する方法は、「全体像が分かっていて、メモリ上に保持しても問題にならないような小さな規模のデータ」を「複数回使い回したい」という場面に限って使うのがよいでしょう。またその時も、上記の3番目の理由から、`for`ではなく`while read`を使うのがオススメです。

~~~bash:関数に渡された標準入力を一旦蓄えて、複数回使う例
url_encode() {
  input="$(cat)"
  echo "  -------------------" 1>&2
  echo "  Input:" 1>&2
  echo "$input" | sed 's/^/  /' 1>&2
  output="$(echo "$input" |
    while read -r line
    do
      echo "$line" |
        nkf -W8MQ |
        sed 's/=$//' |
        tr '=' '%' |
        paste -s -d '\0' - |
        sed -e 's/%7E/~/g' \
            -e 's/%5F/_/g' \
            -e 's/%2D/-/g' \
            -e 's/%2E/./g'
    done)"
  echo "  -------------------" 1>&2
  echo "  Output:" 1>&2
  echo "$output" | sed 's/^/  /' 1>&2
  echo "  -------------------" 1>&2
  echo "$output"
}
~~~

~~~bash:実行例
$ cat data.txt
おはよう
こんにちは
$ cat data.txt | url_encode
  -------------------
  Input:
  おはよう
  こんにちは
  -------------------
  Output:
  %E3%81%8A%E3%81%AF%E3%82%88%E3%81%86
  %E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
  -------------------
%E3%81%8A%E3%81%AF%E3%82%88%E3%81%86
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
~~~

ここではデバッグログ出力用に一旦`cat`で標準入力をすべて読み出して`input`という変数に保持し、それを`echo`で出力してデバッグメッセージと本来の処理の両方に使っています。




### 他のコマンドやシェル関数にもパイプラインでデータを渡す

データの受け取りには標準入力を使うのがベストプラクティスなら、データの出力にも標準出力を使うのがベストプラクティスです。ここで押さえておきたいのは、*標準出力には何度でも、どこからでも、いつでもデータを出力できる*という点です。

一般的なプログラミングでは関数は1回しか値を返せません。なので、関数内部で結果を収集して最後にまとめて返す設計にする必要があります。

~~~javascript:改行区切りのテキストを受け取って、すべてをURLエンコードして返すJavaScriptの関数の実装
function url_encode(input) {
  var lines = input.split('\n');
  var encodedLines = lines.map(function(line) {
    line = nkf(line, '-W8MQ');
    line = sed(line, 's/=$//');
    line = tr(line, '=', '%');
    line = paste(line, '-s', '-d', '\0');
    line = sed(line, 
               '-e', 's/%7E/~/g',
               '-e', 's/%5F/_/g',
               '-e', 's/%2D/-/g',
               '-e', 's/%2E/./g');
    return line;
  });
  return encodedLines.join('\n'); // ←ここで初めて値が返される
}
~~~

この発想のままシェルスクリプトを書くと、こんな風になります。

~~~bash:JavaScriptと同じ発想のシェルスクリプトの場合
url_encode() {
  result=''
  while read -r line
  do
    # 結果の文字列を収集
    result="${result}\n$(echo "$line" |
      nkf -W8MQ |
      sed 's/=$//' |
      tr '=' '%' |
      paste -s -d '\0' - |
      sed -e 's/%7E/~/g' \
          -e 's/%5F/_/g' \
          -e 's/%2D/-/g' \
          -e 's/%2E/./g')"
  done
  echo -e "$result" # ←ここで初めて結果が標準出力に出力される
                    #   （echoで「\n」を改行として出力するには
                    #     「-e」オプションを付ける必要がある）
}
~~~

でも、ちょっと待ってください。先に述べたとおり、標準出力への書き込みは時と場所を選びません。*わざわざ結果を収集して最後の1回でまとめて出力しなくても、処理できた結果からちょっとずつ出力してもいい*はずです。

~~~bash:シェルスクリプトらしい書き方の場合
url_encode() {
  while read -r line # ←標準入力からデータが渡ってくる度にループが1回実行される
  do
    echo "$line" |
      nkf -W8MQ |
      sed 's/=$//' |
      tr '=' '%' |
      paste -s -d '\0' - |
      sed -e 's/%7E/~/g' \
          -e 's/%5F/_/g' \
          -e 's/%2D/-/g' \
          -e 's/%2E/./g' # ←ループが回る度に毎回ここで結果が標準出力に出力される
  done
}
~~~

実際にこの2パターンに対して複数行の入力を与えて実行してみると、前者はちょっと待たされてから結果がまとめて出力されますが、後者は少しずつ結果が出力される様子を見て取れます。

この書き方[^simple-code]なら、`while read`のイテレータ的な振る舞いを組み合わせてパイプラインで繋げると、*処理全体の完了を待たなくてもデータを準備できた物から順次処理して次に引き渡す*ということができます。

~~~bash:非同期処理との組み合わせ
list_tweet_bodies() {
  curl --get ... https://userstream.twitter.com/1.1/user.json |
    while read -r tweet # ←Web APIからデータが1件届く度にループが1回実行される
    do
      echo "$tweet" |
        jq -r .text |
        url_encode # ←ループが回る度に毎回ここで結果が標準出力に出力される
    done
}

list_tweet_bodies |
  url_encode |
  sed ... # Web APIからデータが1件届く度にパイプラインを通じてデータが流れてくる
~~~

これが、*真の意味で「データの出力に標準出力を使う」という事*です。

他の言語の一般的な関数の振る舞いに慣れていると「関数は、実行して1回値を返したらすぐ終了する」「そうして順番に関数が実行されていく」というイメージがあると思います。

しかし、シェルスクリプト内でのコマンドやシェル関数は、実はそれとは違った振る舞いをしています。
![pipeline.png](https://qiita-image-store.s3.amazonaws.com/0/61387/6c5fde0e-688e-7471-5552-3e931bc1a7fe.png)
これは先にも1回出てきた絵ですが、*コマンドやシェル関数をパイプラインで繋げて実行したときは、パイプラインをデータが流れてくる間は彼らはずっと生き続けている*のです。そうしてパイプラインの最初のコマンド（この絵では`cat`）が「もうこれ以上データは無し！終わり！」とギブアップして終了した時点でやっと、パイプラインの先にいるコマンドやシェル関数達も終了していくというわけです。

[^simple-code]: 最後にまとめて`echo`で結果を出力する例に比べて、随時`echo`で結果を出力する例では、コマンド置換や`echo -e`のように珍しいオプションを使っている部分が無くなって簡潔になっているという事に気付いたでしょうか？ これも「その言語の基本機能で素直な書き方で実現できる処理」はその言語に適している、という冒頭の話の実例です。


### シェルスクリプトでの「ライブラリ」

#### 他の人の作ったライブラリを使いたい？

シェルスクリプトには便利なモジュール機構はありません。`gem`や`npm`のようなライブラリ集も（多分メジャーな物は）ありません。

が、先の例で示したように「結果を標準出力で、1行1単位で出力するコマンド」であれば何でも処理対象のデータ提供元になります。また、「パイプライン経由で1行1単位でデータを受け取れるコマンド」であれば何でもデータを渡す先にできます。よって、シェルスクリプトでは*システムにあるコマンドラインツールすべてをライブラリとして利用できる*と言えます。先程紹介した`slackcat`も`gem`のパッケージとして提供されているコマンドツールです[^websocket]が、何の違和感も無くシェルスクリプトの中で使えていましたよね。

[^websocket]: 話はずれますが、`slackcat`が使用するSlackのストリーミングAPIは一般的なHTTPベースのAPIではなくWebSocketに基づいており、純粋にシェルスクリプトだけで実装しようとするとかなり大変です。`slackcat`は、そのようにシェルスクリプトとの親和性が低いAPIをラップして、標準入出力ベースで結果を受け取れるようにする変換器として働きます。その他のそのままだとシェルスクリプトにとても組み込めそうにない技術も、何らかの方法で標準入出力ベースのコマンドの形にすれば、他のコマンドと同じ形式となり、容易にシェルスクリプトに組み込めるようになります。これは、各言語において「バインディング」を実装するのと意味的には同じだと言えるでしょう。

「シェルスクリプトを使うには、いろんなコマンドの使い方に精通していないといけない」と言う人もいますが、よく考えれば普通のプログラミングでも、ライブラリの使い方を把握するために色々なドキュメントを読むものです。それに比べると、`--help`オプションを付けたり`man`コマンドを実行したりすれば簡単な使い方を自分から説明してくれる分、コマンドの方がむしろ開発者に優しいと言えるかもしれません。

#### 自分で作ったライブラリを使いたい？

特定のシェルスクリプト内でシェル関数として定義した物を別のスクリプトでも流用したい場合には、`source`コマンドで読み込むという方法もあります。筆者の書いた[Twitter bot][tweetbot.sh-解説]でも機能ごとにスクリプトを分けていますが、共通のユーティリティ的な処理は[1つのファイルにまとめて](https://github.com/piroor/tweetbot.sh/blob/master/common.sh)おいて、各スクリプトから`source`で読み込んで利用しています。これはRubyでいえば、`require`の後に`include`してMix-inで定義されたメソッドを取り込むようなイメージです。

しかし、この時問題になるのは変数です。シェルスクリプトには変数のスコープが「スクリプト全体のグローバル変数」と「関数内のローカル変数」の二種類しかありませんから、`source`で読み込んだ「ライブラリ」の中にある関数名や変数名が読み込み元と被った場合に衝突してしまいます。

そういう時も前述の考え方が役立ちます。*自作ライブラリとして用意したスクリプトも、`source`で読み込まずに普通のコマンドと同様に呼び出して実行すればよい*のです。

実行される側のスクリプトと実行する側のスクリプトでは変数のスコープがはっきり分かれます。逆に言えば、*変数のスコープを分けたい時はスクリプト自体を分ければ良い*ということです。実際に拙作[Twitter bot][tweetbot.sh-解説]でも、[Twitterクライアントのスクリプト][tweet.sh-解説]は実行する形でのみ使用しています。


## シェルスクリプトの活かし方

……と、ここまで「シェルスクリプトってこんなに使えるんだぜ！」という話をしてきましたが、だからといって何でもシェルスクリプトでやりましょうとは自分には言えません。

最初に述べたとおり、シェルスクリプトが適しているのは*ステートレスな処理*です。「機能を1回実行したら、処理が行われて、結果が得られる。それで終わり。」という種類の仕事であればすんなり実装できます。が、内部で複雑な状態を持つ必要があるステートフルなスクリプトを書こうとすると、途端に「シェルスクリプトって厄介だ……」という印象が強まります[^difficult-points]。そういう「厄介さ」を隠蔽しようとすると、（何度も引き合いに出して申し訳ないのですが）[bash-oo-framework](http://qiita.com/syui/items/b859a57ed7bbda5d74b5 "Bashで便利なフレームワーク - Qiita")のように「元の文法の上に全く別の文法を構築する」ようなアプローチを取らざるを得ません。そんな訳なので、GUIアプリを作ろうみたいな事は考えない方がいいです。

[^difficult-points]: 例えば、Bashスクリプトでパイプラインを使うと変数のスコープがパイプラインの先で閉じてしまうため、パイプラインの先にある`while`ループの中と外で変数の値を共有しようと思うと[プロセス置換機能](http://sechiro.hatenablog.com/entry/2013/08/15/bash%E3%81%AE%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E7%BD%AE%E6%8F%9B%E6%A9%9F%E8%83%BD%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%A6%E3%80%81%E3%82%B7%E3%82%A7%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%84%E3%82%B9)を使った書き方にする必要があります。

しかしその一方で、一見すると複雑なプログラムが必要そうな作業でも、見方を変えて問題を捉え直せばステートレスな作業で処理できることが分かり、シェルスクリプトですっきり解決できる場合があるのもまた事実です。

 * 複雑な問題を小さな問題に解きほぐして、「パイプラインと標準入力と標準出力と、後はせいぜい`if`や`case`での条件分岐程度の組み合わせだけで解決できる問題」あるいは「そういった単純な問題が集まってできた問題」として再定義する[^minimize]。
   * 実際に、[筆者の書いたTwitterクライアントのスクリプト][tweet.sh-解説]は、「Twitterクライアント」という物をTweetDeckやスマホのTwitterアプリのように「常時表示されていてユーザの操作を受け付ける」ステートフルな形態ではなく、「1つコマンドを実行したら、その結果だけを出して終了する」ステートレスな形態で実現する、という風に問題を再定義しています。
 * そのように再定義できない種類の問題は、無理してシェルスクリプトでは扱わない。

[^minimize]: 筆者が過去に執筆に関わった[1年目から身につけたい！ チーム開発 6つの心得](http://gihyo.jp/dev/feature/01/team-development6)という記事の中で、[適切な粒度に実装を分ける考え方](http://gihyo.jp/dev/feature/01/team-development6/0003 "第3章　細かな粒度で実装しよう―単純なパーツを組み合わせた見通しの良い設計：1年目から身につけたい！ チーム開発 6つの心得｜gihyo.jp … 技術評論社")や、[関数や変数の名前付けのしやすさを目安として適切な粒度を見極める考え方](http://gihyo.jp/dev/feature/01/team-development6/0004 "第4章　わかりやすい名前を付けよう―無理なく自然なメソッド・変数の命名：1年目から身につけたい！ チーム開発 6つの心得｜gihyo.jp … 技術評論社")を解説していますので、そちらも是非併せて読んでみて下さい。


これがシェルスクリプトを効果的に使うためのコツだと筆者は考えています。これらを踏まえた上で、

 * 最終的にやりたい事の重要な部分が、コマンドラインツールとして提供されている場合。例えばTwitterのAPIを呼びたいのであれば[twurl](https://github.com/twitter/twurl)、SlackのAPIを呼びたいのであれば[slackcat](https://github.com/rlister/slackcat)など。
 * 言語のバージョン依存や環境依存を減らして、ポータビリティを高くしたい場合。

といった前提条件がある場合には、シェルスクリプトが現実的に問題解決のための最適な選択肢になり得るでしょう。


## まとめ

ところで、「流入してくるデータを加工して返却する」「ステートレスな」処理が得意で、「内部で複雑な状態を持つ必要のある」「ステートフルな」処理が苦手だというのは、関数型言語にも見られる特徴です[^function-type]。

[^function-type]: 実際、関数型言語が得意な人にこの説を披露してみた所、「関数型言語というのはストリーム型のデータを処理するという所がキモ（なので、解釈としてそう外してはいない）」という感じの感想を頂きました。

そう考えると、シェルスクリプトで凝った事をしようとする人があまり多くないのも頷けます。「関数型言語は難しい」と言われる事が多いですが、*他の言語とパラダイムが違う言語だ*と思えば、これまでのプログラミングのパラダイムに囚われたままでは理解が進まないのも当然です。

ここはくどいくらいに強調しておきたいのですが、*どのパラダイムや言語が絶対的に優れているという事はありません*。解決したい問題の種類や性質、あるいは状況（外部要因）によって、最適なアプローチの仕方は変わってきます。実際に、ここまでで他の言語とシェルスクリプトのコードを比較する例をいくつか書いてきましたが、*シェルスクリプトでは非効率的な書き方が、JavaScriptでは一番素直な書き方だ*という例があった事を思い出して下さい。「その前提条件の下ではJSの方が素直に問題を解決できる」という場面ではJSを使った方が良いのです。

別のパラダイムを知ると、普段のプログラミングでできる事の幅も広がります。使える武器が増える訳ですから、遭遇した問題の種類に応じてより適切な武器を選んで立ち向かえるようになります。「関数型言語を勉強する」と言うと仰々しく感じられて腰も重くなりがちですが、シェルスクリプトなら普段から使っているコマンド操作の延長線上で気軽に始められるでしょう。プログラマーとしての成長に役立てるためにも、シェルスクリプトに苦手意識のある方はこれを機に挑戦してみてはいかがでしょうか？


## この記事に書いてある書き方のシェルスクリプト、めちゃくちゃ遅いんだけど？（2019年1月20日追記）

この記事で解説した内容は、シェルのコマンドラインやシェルスクリプトにいまいち馴染めないという人向けのファーストステップとして、「とりあえずこれを覚えておけば動く物は書ける」という汎用性の高さを強く意識しています。その反面、実行速度は遅くなりがちです。数万件といった単位の大量のデータを取り扱う場面では、このやり方だと異常に時間がかかるという事もあり得ます。

そういう事態に遭遇したときは、汎用性よりも実行速度を重視した書き方を覚えるチャンスです。シェルスクリプトは書き方によって実行速度が大きく変わります。より高速に実行できるコマンド列やシェルスクリプトの書き方を紹介した記事として、以下のようなおすすめがありますので、ぜひ読んでみて下さい。

* [シェルスクリプトを何万倍も遅くしないためには —— ループせずフィルタしよう](https://qiita.com/8x9/items/f1156503694d3683e78d#%E5%AE%9F%E8%A3%85-v4-cut--tr--awk%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E5%AF%BE%E8%B1%A1%E6%96%87%E5%AD%97%E3%81%AE%E6%8A%BD%E5%87%BA%E3%82%92-tr-%E3%81%AB%E4%BB%BB%E3%81%9B%E3%82%8B)
  * [続: シェルスクリプトを何万倍も遅くしないためには —— やはりパイプは速いし解りやすい](https://qiita.com/8x9/items/69c4968d3cd1a2eec13f)
* [xargsでサクッと書く](https://qiita.com/knknkn1162/items/806604341508d32b160e?utm_campaign=email&utm_content=link&utm_medium=email&utm_source=public_mention#comment-1084ec71c3f68508baa1)

## それでもまだ遅いんだけど！？（2021年5月6日追記）

xargsを使うようにしたり、パイプラインを活用するようにしたりしても、まだ速度が致命的に遅い（例えば[シェルスクリプト1つの実行に30分かかってしまう](https://qiita.com/yumetodo/items/3665e343c8063f56ab6a)とか）場合は、*「シェルでフツーにやれること・やるべきことの範囲を逸脱したこと」をやろうとしている*、という状況だと僕は考えます。僕の場合、そこまで速度が問題になるようだと、別の言語（JSとかRubyとかGoとか）でやる方向に切り替えると思います。

僕がそう判断するのは、シェルスクリプトの言語としての優劣とは無関係に、僕自身が*「シェルスクリプト書いてる時間より、他の言語のコードを書く時間の方が、どうしたって圧倒的に長い」という状況にあるから*です（具体的には、業務上で関わる時間・頻度・重要度の高さで言えば、[NSIS](https://qiita.com/yuki_nishina/items/176d6d77911f2b54bd75)の方がシェルスクリプトよりも上位にきてしまうので、[LogicLibというライブラリを使わなければif文すら書けない低機能言語](https://qiita.com/yuki_nishina/items/10620b20e821291cac07)であるにもかかわらず、シェルに熟達するのに時間を投じるよりも、NSISに投じる時間を増やした方が個人的に得になる、という事実があります）。

そのような前提がなく言語の選択が完全に自由な場合や、何らかの理由でどうしてもシェルでやらなければならない場合には、以下の記事で紹介されているような、高速化に繋がるテクニックが参考になるでしょう。基本的には、*外部プロセスが起動される機会を極力減らして、シェル自体の機能でできる事はそちらでやるようにする*、ということになります。

* [シェルスクリプト リファクタリング ～遅いシェルスクリプトが供養されてたので蘇生して256倍に高速化させました～](https://qiita.com/ko1nksm/items/fbefb4da7e54e8a29a25)
* [sedなんか投げ捨てて変数展開で高速に文字列を置換しよう](https://qiita.com/yumetodo/items/3665e343c8063f56ab6a)

ただ、そこで必要になる知識は特定のシェル実装に依存した知識になりがちで、他の言語には直接的にはあまり応用が利きませんし、話に付いてこれる人も減ってくる印象があります（恥ずかしながら、僕自身も脱落してしまいます）。先に述べたとおりの前提もあって「シェルは、なるべく深入りせずに、最小限のことを浅く知って、要領よく使おう」と考える立場の僕としては、「ぜひ覚えよう」とまでは言い辛い感覚がある、ということは付記しておきたいと思います。


## 「シス管系女子」について

なんか妙にアクセスが伸びてるので、本文中でもちょいちょい触れている[シス管系女子][]の事もせっかくだから改めて紹介させて頂きます。

この記事の反応で「`while read line`のどれをどういう順番で書けばいいのか覚えられない」という物を複数見かけました。例えば「シェルスクリプト 繰り返し 空白を含む文字列」などのキーワードでぐぐって出てきた書き方をそのまま丸暗記したりコピペしたりして使っているだけだと、意味が頭に入っていないため記憶に定着しないという事なのではないでしょうか？　Linuxのコマンド操作やシェルスクリプトではそういう悩みを抱えている方が多いような印象があります。というか筆者もかつてはその1人でした。

自分は「シス管系女子」というケーススタディ形式の解説マンガ記事を2011年から[日経Linux][]誌上で連載させて頂いています。この記事のようなガチプログラミングな内容はあまり含まれていませんが、主にSSH越しのコマンド操作をするという場面を想定して、コマンドのはたらきやパイプラインの仕組み、`while`ループの理屈や`read`コマンドの作用の仕方などについてもそれぞれの意味を踏まえながら丁寧に解説しています。このレベルでの理解を得ておくと、「あ、ここはコマンド名だな」「ここは引数だな」と自分で理解しながら自由にコマンド列を組み立てられるようになるのではないでしょうか。

新人や後輩のための1冊としてご紹介頂いている事例もあるようで、内容についてはわりかしご好評を頂けていますので、もし良かったらお近くの大きめの書店で[シス管系女子][]の本や[掲載誌][日経Linux]を手に取ってチラ見して頂けましたら幸いです。

あと、この記事で解説したTwitterクライアントを使って運用中の[みんとちゃんbotアカウント][みんとちゃんbot]ではイラスト[^イラスト]や本編に入りきらなかった小ネタを流したり、Webサイトの方にも[連載や本では扱わなかったもっと基礎的な話の特別編](http://system-admin-girl.com/comic/#begins)を置いていたりします。[「シス管系女子」をテーマにしたAdvent Calendar](http://www.adventar.org/calendars/1692)というのも公開＆参加者募集中です。

[^イラスト]: [これまでのイラスト一覧](http://system-admin-girl.com/comic/#illust)。

ということで、[Shell Script Advent Calendar](http://qiita.com/advent-calendar/2016/shell-script)の8日目でした。次の方の記事もお楽しみに！

----

追記。ここで語っている事を使って文字列処理するときに恐らくやりたくなるであろう事のフォローアップ記事として、[`join()`や`split()`に相当する事をシェルスクリプトでやる場合の話](http://qiita.com/piroor/items/55ff672cb9f8e375e659)を書きました。そちらも併せてご覧頂ければ幸いです。