時代はシェルスクリプト

  • 155
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

らしい。

さあ、始まりました Shell Script Advent Calendar 2015 1 日目。思ったよりも AC 全体として盛り上がりなさを感じているのは自分だけでしょうか。

タイトルからして恐れ多いですが、1 日目だしいいでしょ感で書いています。

なんで今更

「今更そんな枯れツール使わんしょw」勢が一定数いることも知っていてのこのタイトルです。

意外にもシェルスクリプトってのは根強く使われ続けていて、以下に面白いグラフがあります。

via The RedMonk Programming Language Rankings: June 2015

これは RedMonk 社が、2015年1月版のプログラミング言語ランキングを発表したもので、GitHub での各言語の使用率と、Stack Overflow での発言量を元に作られた相関グラフです。

Shell が意外にも人気ですね(ランキング11位)。

じゃあ何がいいの

さて、シェルスクリプトが

  • 扱いやすいから
  • すでに広く普及しているから
  • 必要だから
  • 便利だから

なのかそれともそのすべてなのか、なぜ人気なのかはわかりませんが、個人的に思ういいところを挙げていこうと思います。

ファイル操作をやらせたら随一

当たり前ですがシェルスクリプトはファイル操作コマンド(cprmfileln...)などを組み合わせて作っていくグルー言語の一種です。他の言語が必要とする記述量が一行、一語で書けてしまいます。シェルスクリプトにおいて、パスの通った各コマンドが各種ライブラリのようなものです。

マルチコア/メニーコアが追い風

今となっては CPU のコア数がたくさんなマシンが増えてきました。
よく、パイプは処理が遅いといった意見を目にします。パイプはそのパイプ先が別プロセスで実行されますが、シェルにおいてプロセスの起動は意外とコストが高いという認識故にそう思われているのだと思います。しかし、パイプにおいてはそうとも言えず、むしろ高速化が期待できるポイントになっています。というのも、パイプ先で生成されるプロセスはデータが流れたときに作られるのではなく、一斉に作られ常にデータ待ちをしていて、流れてきた瞬間から逐次処理をしているためです。

ちょっと具体例を。1から10000までの合計を出すスクリプトです。以下のスクリプトでは、10000回のループの中でプロセスを作って変数に結果を代入しています。

#!/bin/bash
n=0
for i in $(seq 10000)
do
    n=$(echo $n + $i | bc)
done
echo $n

結果がこちら。32秒です。

$ time bash calc.sh
50005000
bash calc.sh  13.55s user 23.51s system 112% cpu 32.830 total

次にこちら。これはループの中でプロセスを作っていません。

#!/bin/bash
for i in $(seq 10000)
do
    printf "$i + "
    [ $i -eq 10000 ] && printf "0\n"
done | bc

結果がこちら。0.2 秒です。およそ157倍です。

$ time bash calc.sh
50005000
bash calc.sh  0.20s user 0.05s system 119% cpu 0.209 total

パイプは繋げばつなぐほどスケールし、高速化が期待できます(UNIX の先人はこれを見越して...)。

蛇足: シェル芸はパイプを繋いでコマンドラインで処理していく技法ですが、シェルスクリプト内においてもパイプをつなぐことは有用で UNIX ライクなやり方です。

非同期処理が楽に書ける

最近は Go 言語の勢いが凄いですね。人気の理由の一つに「非同期処理が書きやすい」ってのがあるそうです。

\シェルでも楽に非同期処理できますよ/

簡単な例です。非同期処理がラクチンな Go 言語のパッケージを非同期でかたしましょう。

#!/bin/bash
cnt=0
# リポジトリ名の書かれたファイルを期待する
# なれけばパイプ先を読みに行く
if [ -z "$1" ]; then
    cat <&0
else
    cat "$1"
fi | while read line
do
    echo "Installing $line"
    # バックグラウンドで処理(サブプロセス化)
    go get -u "$line" &
    # プロセスの立ち上げ過剰を防ぐ
    (( (cnt += 1) % 16 == 0 )) && wait
done
# 同期
wait

少し丁寧に書いたので長くなっていますが、実質ループ処理と & 行と wait 行の 2 行+αで片付きます。簡単ですね。どちらもシェルの組み込み機能なので何にも依存していないのも :thumbsup: です。

この非同期処理をバリバリに使った zsh のプラグインマネージャを紹介しておきます。

更に便利な例として:

実質 2 行でマルチプロセス・非同期ストリームが実装できます。

レガシーな技術

シェルスクリプトは POSIX で定められている唯一のスクリプト言語です。逆に言えば POSIX なシェルスクリプトを書くことで、UNIX なファイルシステムでは同じように動くでしょう。

POSIX でなくとも Linux のデフォルトシェルは bash にスイッチしました(一部 dash の模様)。bash スクリプトを書くことで一定の文法水準を保ちつつ、高い汎用性のあるスクリプトを記述できます。

じゃあ何がダメなの

これも個人的な見解です。

可読性?なにそれ

ひどいもんですよね。文法体系はとりあえず置いておくとして、人によってまるで書き方が違うのも見づらさに貢献しています。

以下は"まだ"見やすい例(だと思っている):

スクリーンショット 2015-12-01 14.56.51.png

データ構造が弱い

配列しかありません(基本的に)。連想配列(ハッシュ・マップ・辞書)も配列の一種として扱われます。

zsh ともなれば連想配列のキー・バリューに対するアプローチが大きく増えますが、データ構造としては配列どまりです。

つまりどういうことだってばよ

シェルスクリプトが好きです。

参考文献

最近、「時代はシェルスクリプトだなあ」と思った記事。

明日は @jmatsu さんです。

追記

Qiita Advent Calendar ランキング出ました。

この投稿は Shell Script Advent Calendar 20151日目の記事です。