シェルプログラミングTips

制御構文whileやforを避ける方法

ユニバーサル・シェル・プログラミング研究所では、シェルスクリプトに制御構文が増えることを極力避けるようにさまざまな々な工夫をしている。制御構文を避ける理由はコードが読みにくくなるためだ。ユニバーサル・シェル・プログラミング研究所との業務ではじめてシェルやシェルスクリプトを始めたというケースでは、最初に経験するプログラミングがユニケージ開発手法になるという方が少なくないが、やはり制御構文が最初の壁になることが多い。

制御構文はシェルスクリプトが便利だと感じるようになってから使いはじめるくらいでも良い。もしかすると、便利だと思ってからの方がすんなり頭に入るかもしれない。以降では、どのように制御構文の使用を避けるのかを紹介する。

1. seq(1)を使う

次のソースコードを見てみよう。ユニケージ開発手法においてはまず目にすることのないソースコードだ。

$ cat countup.sh
#!/bin/sh

n=1
while [ $n -le 10 ] ; do
	echo $n
	n=$((n+1))
done
$ 

このソースコードを実行すると次のような結果が得られる。

$ ./countup.sh
1
2
3
4
5
6
7
8
9
10
$ 

この処理はコマンドseq(1)を使えば次のように処理することができる。

$ seq 1 10
1
2
3
4
5
6
7
8
9
10
$ 

利用するシェルをbashに限定するなら、次のような記述方法もある。

$ echo {1..10} | tr ' ' 'n'
1
2
3
4
5
6
7
8
9
10
$ 

2. awk(1)やjuni(1)を使う

シェルの機能を使ってcat(1)のようなコマンドを実装すると次のようになる。ここでは行番号を出力するようにしており、cat(1)に-nオプションを指定した場合のような振る舞いになる。

$ cat file
山田
上田
田中
$ cat cat.sh
#!/bin/sh

n=1
while read line ; do
	echo $n $line
	n=$((n+1))
done

$ cat file | ./cat.sh
1 山田
2 上田
3 田中
$ 

ファイルから1行読み込んでシェル変数に入れるという処理はかなり遅い作業である。ここは次のように書いた方が高速になる。

$ cat cat2.sh
#!/bin/sh

awk '{print NR,$0}'
$ 

ターミナルで直接実行するなら、次のように記述できる。

$ cat file | awk '{print NR,$0}'
1 山田
2 上田
3 田中
$ 

TukubaiまたはOpen usp Tukubaiに用意されているコマンドjuni(1)を使うともっと簡単に実現できる。

$ juni file
1 山田
2 上田
3 田中
$ 

3. xargsを使う1

削除候補となるファイルのリストを作ってから、逐一削除するような作業があったとする。たとえば次のようなソースコードになる。

$ cat rmall.sh
#!/bin/sh

cat filelist	|
while read file ; do
	rm "${file}"
done
$ 

これはxargsを知っていれば次のように簡単に済む。

$ cat rmall2.sh
#!/bin/sh

cat filelist	|
xargs rm
$ 

4. xargsを使う2

xargs(1)は入力1つ1つに対して処理を実施することもできる。たとえばバックアップを取るといった次のソースコードがあったとする。

$ cat backupall.sh
#!/bin/sh

cat filelist	|
while read file ; do
	cp $file $file.old
done
$ 

xargs(1)の-Iオプションを使うと次のように記述できる。

$ cat backupall2.sh
#!/bin/sh

cat filelist	|
xargs -I % cp % %.old
$ 

5. mdate(1)で日付のリストを作る

次のようにdate(1)を使うと日付のリストを作ることができる。これはFreeBSDやMac OS XなどのBSD date(1)コマンドを使った場合の記述となる。

$ cat datelist.sh
#!/bin/sh

d=20100101
while [ $d -lt 20100401 ] ; do
	echo $d
	d=$(date -v+1d -j -f "%Y%m%d" "$d" "+%Y%m%d")
done
$ 

LinuxなどGNU Core Utilitiesのdate(1)コマンドを使っている場合には次のようになる。

$ cat datelist.sh
#!/bin/sh

d=20100101
while [ $d -lt 20100401 ] ; do
	echo $d
	d=$(date -d "$d 1 day" +%Y%m%d)
done
$ 

date(1)コマンドは-uオプションと+フォーマット指定以外はPOSIX.1に規定されていないため、ツールごとにオプションが異なっている。BSD date(1)のオプションについてはdateコマンドの便利な使い方 : BSD dateを、GNU date(1)のオプションに関してはdateコマンドの便利な使い方 : GNU Core Utilities dateを参照のこと。

date(1)コマンドにおけるフォーマット指定に関してはdateコマンドのフォーマット一覧 : IEEE Std 1003.1 "POSIX.1"に説明がまとまっている。

TukubaiまたはOpen usp Tukubaiに用意されているmdate(1)コマンドを使うともっと簡単に済ませることができる。

$ mdate -e 20100101 20100401 | tr ' ' 'n' | head
20100101
20100102
20100103
20100104
20100105
20100106
20100107
20100108
20100109
20100110
$ 

tr(1)のこうした使い方をする場合、ユニケージ開発手法ではtarr(1)を使って次のように記述する。

$ mdate -e 20100101 20100401 | tarr | head
20100101
20100102
20100103
20100104
20100105
20100106
20100107
20100108
20100109
20100110
$ 

6. loopxを使う

通常のプログラミングではよく使われる多重ループだが、シェルスクリプトではできるだけ避けたい。例えば次のような2重ループの処理があったとする。

$ cat loop.sh
#!/bin/sh

for a in A B C ; do
	for n in 1 2 3 ; do
		echo $a $n
	done
done
$ ./loop.sh
A 1
A 2
A 3
B 1
B 2
B 3
C 1
C 2
C 3
$ 

Open usp Tukubaiにはloopx(1)というコマンドがあり、こうしたリスト作成の代替処理として利用できる。

$ head a n
==> a <==
A
B
C

==> n <==
1
2
3
$ cat loop2.sh
#!/bin/sh

loopx ./a ./n	|
while read a n ;do
	echo $a $n
done
$ ./loop2.sh
A 1
A 2
A 3
B 1
B 2
B 3
C 1
C 2
C 3
$ 

これまで制御構文whileやforのはずし方について書いてきた。処理速度を高速化するためにwhileやforをコマンドに置き換えるというのは、それなりの効果が期待できる方法だ。シェルスクリプトに制御構文であるforやwhileが出てきたら、何か避ける方法がないか探してみるとより高速なスクリプトが書けるようになるかもしれない。

ユニケージユニバーサル・シェル・プログラミング研究所の登録商標。

usp Tukubaiユニバーサル・シェル・プログラミング研究所の登録商標。

※ 2012年7月から始まる教育講座に合わせて、従来の「ユニケージ」または「ユニケージ方法論」は「ユニケージ開発手法」へ名称を統一。

last modified: 2014-03-17 20:03:17