seq を真面目に実装してみる

ref:技術メモ帳 - seq コマンド for BSD
ref:メモ帳 - my bashrc

代わりに、ものすごく高機能な jot コマンドというのが入っているので
それを使って seq を実装してみた。

function seq(){ jot $2 $1 $2 }

ええと、$1 が 1 以外のときにうまく動かないと思うなぁ。しかも、-f も -w も使えないのはちょっとつらいかなぁ。
話の元ネタがこれ。

function rseq() {
    ruby -e '((ARGV[0].to_i)..(ARGV[1].to_i)).each {|x| printf "%0#{ARGV[0].length}d\n", x }' $1 $2
}

zero padding しようとして失敗しているのが面白いところであります。試さなかったんでしょうか。 第1引数を 001 とかにすれば zero padding されますね。すみません。
というかですね、.bashrc とか .zshrc に shell function として書いたとしても shell script からは使えなくてはとても悲しいと思うのですが、どうでしょう。あとみなさん "$1" "$2" みたいに引数を double quote で囲ったりしないんでしょうか*1
つうわけで、とりあえず

  • -wオプションを受け付ける
  • 取り扱いは整数の範囲
  • step は正のみ。(count down は非対応)

なんて仕様で、Python で書いてみる。エラー処理とかない適当実装。

#!/usr/bin/env python

import sys
import getopt

try:
    (options, args) = getopt.getopt(sys.argv[1:], "w")
except getopt.GetoptError, e:
    print >>sys.stderr, e.msg
    sys.exit(1)

if len(options) > 0: equal_width = True
else: equal_width = False

first, step = 1, 1
if len(args) == 1:
    last = int(args[0])
elif len(args) == 2:
    first, last = map(int, args)
elif len(args) == 3:
    first, last, step = map(int, args)

if equal_width:
    width = len(str(last))
    format = "%0" + str(width) + "d"
else:
    format = "%d"

for i in range(first, last + 1, step):
    print format % i

調子に乗って、負の step とか -s オプション、 -f オプションにも対応したのを getopts の練習がてら bash script で書いてみる。

#!/bin/bash

fill_zero=
sep=$'\n'
format=
start=1
step=1

while getopts "ws:f:" option
do
    case $option in
        w)
            fill_zero=1
            ;;
        s)
            sep="$OPTARG"
            ;;
        f)
            format="$OPTARG"
            ;;
    esac
done

[ $OPTIND > 1 ] && shift $((OPTIND - 1))

case $# in
    0)
        echo "$0: missing operand" 1>&2
        exit 1
        ;;
    1)
        end=$1
        ;;
    2)
        start=$1 end=$2
        ;;
    3)
        start=$1 step=$2 end=$3
        ;;
esac

if [ -n "$format" -a -n "$fill_zero" ]
then
    echo "$0: format string may not be specified when printing equal width strings" 1>&2
    exit 1
fi

if [ -n "$fill_zero" ]
then
    width=$(printf '%g' $end | wc -c | tr -d ' ')
    format="%0${width}g"
fi

[ -z "$format" ] && format="%g"

if [ $step -ge 0 ]
then
    op=-le
else
    op=-ge
fi

if [ ! $start $op $end ]
then
    exit
fi

i="$start"
while true
do
    printf "$format" $i
    i=$((i + step))
    if [ $i $op $end ]
    then
        echo -n "$sep"
    else
        echo
        break
    fi
done

なげぇ。
で、さらにまじめに long options にも対応したのをまた Python で。

#!/usr/bin/env python

import operator

def seq(start, last, step):
    if step > 0:
        op = operator.le
    else:
        op = operator.ge

    i = start
    while op(i, last):
        yield i
        i += step

if __name__ == '__main__':
    import sys
    from optparse import OptionParser

    step   = 1
    first  = 1

    parser = OptionParser()
    parser.add_option("-w", "--equal-width", action="store_true", dest="equal_width", default=False)
    parser.add_option("-f", "--format", dest="format", default="%g")
    parser.add_option("-s", "--separator", dest="separator", default="\n")
    (options, args) = parser.parse_args()

    if len(args) == 1:
        last = int(args[0])
    elif len(args) == 2:
        first, last = map(int, args)
    elif len(args) == 3:
        first, step, last = map(int, args)
    elif len(args) > 4:
        parser.print_usage()
        sys.exit(1)
    else:
        parser.print_usage()
        sys.exit(1)

    sep = options.separator
    format = options.format
    if options.equal_width:
        width = max(len(str(first)), len(str(last)))
        format = "%0" + str(width) + "d"
    print sep.join(format % i for i in seq(first, last, step))

追記

wc -c の結果に空白が入る環境があるようなので tr を追加。thanks id:lurker さん。

*1:何気に zsh だと quote しなくても大丈夫だったりするが