日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

眠れない方へのシェル芸

シェル芸勉強会入門編をやってみたいと思う今日この頃だが、どんな感じにすればいいのか?なかなか難しいなあと思っているところである。

シェル芸の発想はやはりストリーム的な処理だ。データを変数に持たせて処理するというより、データをパイプでストリーム処理というのが本質ではないだろうか?

ということで、題材として考えたのが眠れない方へ羊が100匹まで数えるシェル芸。下記のような出力を、1行毎に1秒間をおいて出力することを考える。

羊が1匹
羊が2匹
羊が3匹
.,..
羊が98匹
羊が99匹
羊が100匹

変数とループ構造を使っての手続き型な書き方から、ストリーム的な書き方へ。下記のような流れで考えてみよう。ちなみにbashを使ったワンライナーである。csh系のシェルを使っていらっしゃる方はbashを起動。

カウントアップで100まで

手続き型で素直に書くとしたら下記の通りだろうか?

$ n=1;while [ $n -le 100 ]; do echo '羊が'$n'匹'; sleep 1; n=`expr $n + 1`; done

ワンライナーで見づらいかもしれないので、シェルスクリプトにしたのがこちら。bash独自の機能は使わず、Bourne Shellの機能のみを利用。シェルの文法やexprコマンドについては割愛する。

#!/bin/sh
n=1
while [ $n -le 100 ]
do
  echo '羊が'$n''
  sleep 1
  n=`expr $n + 1`
done

bashの数値計算機能を使ったのがこちら。exprコマンドが無くなったこと以外は変わらず。

$ n=1;while [ $n -le 100 ]; do echo '羊が'$n'匹'; sleep 1; n=$((n+1)); done

1から100までの数列を使う

forループに1から100までの数列を与えて処理する。seqコマンドで数列を作成。

$ for n in `seq 100`; do echo '羊が'$n'匹'; sleep 1; done

bashのブレース展開で数列を作成してもいいだろう。

$ for n in {1..100}; do echo '羊が'$n'匹'; sleep 1; done

seqコマンドで数列を作成して、パイプでwhileループへ渡すパターン。(これは罠が有るので使う際は要注意)

$ seq 100 | while read n; do echo '羊が'$n'匹'; sleep 1; done

xargsで繰り返し処理

xargsを使うと制御構文を使うことなく繰り返し処理が出来る。ストリーム的な処理として記述しやすくなる。今回は1行ずつ処理するため、-Iオプションを使うのがポイントだ。

$ seq 100 | xargs -I@ sh -c 'echo 羊が@匹; sleep 1'

seqコマンドは-fオプションでフォーマット指定が出来る。

$ seq -f '羊が%g匹' 100 | xargs -I@ sh -c 'echo @; sleep 1;'

シェルもコマンドの一つ

UNIXのシステムから見ると、シェルも一つのコマンドに過ぎないのだ。よって100までの数列から実行するコマンド列を作成してやって、パイプでシェルに渡してしまえばよい。

$ seq -f 'echo 羊が%g匹; sleep 1' 100 | sh
$ seq 100 | sed 's/^/echo 羊が/' | sed 's/$/匹; sleep 1/' | sh

こんな感じで、手続き型的な発想からストリーム処理的な発想に慣れていけると、シェル芸人へのステップアップが出来るのではないかと思う。