Category Archives: PROGRAM

Go言語で画像ダウンローダを書いてみた

先ほどのGo言語で皆大好き htpdate を書いてみたに続いて画像をダウンロードするスクリプトを書いてみました。
PerlやRuby、Pythonほどスクレイピングをするライブラリは充実していないものの”goquery”というライブラリがGo言語でソコソコ使えそうだったので使用してみました。

ライブラリ(goquery)のインストール
# go get github.com/PuerkitoBio/goquery

ソースコード(事情は下に書きますが殴り書きです)
# vi downloader.go

package main

import (
  "os"
  "fmt"
  "path"
  "net/url"
  "net/http"
  "io/ioutil"
  "github.com/PuerkitoBio/goquery"
)

func GetImgUrl(base string) []*url.URL {
  var src_url_list []*url.URL

  doc, _ := goquery.NewDocument(base)
  doc.Find("img").Each(func(_ int, s *goquery.Selection) {
    src, exists := s.Attr("src")
    if exists {
      base, _ := url.Parse(base)
      srcs, _ := url.Parse(src)
      src_url_list = append(src_url_list, base.ResolveReference(srcs))
    }
  })

  return src_url_list
}

func DownloadFiles(url_list []*url.URL) {
  for id, url := range url_list {
    raw_url := url.String()

    _, filename := path.Split(raw_url)
    filepath := path.Join("download", filename)

    response, err := http.Get(raw_url)
    body, err := ioutil.ReadAll(response.Body)

    if err != nil {
      fmt.Println(err)
    }

    file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0666)

    if err != nil {
      fmt.Println(err)
    }

    file.Write(body)
    file.Close()

    fmt.Printf("[%d]%s %s\n", id, raw_url, filename)
  }
}

func main() {
  url := "http://akiba-pc.watch.impress.co.jp/"
  url_list := GetImgUrl(url)

  DownloadFiles(url_list)

}

ダウンロード先のディレクトリの作成
# mkdir download

スクリプトの実行
# go run downloader.go

[0]http://akiba-pc.watch.impress.co.jp/include/common/p01/images/logo/ah.l.png ah.l.png
[1]http://akiba-pc.watch.impress.co.jp/include/common/p01/images/global-nav/gn_headline.png gn_headline.png
[2]http://akiba-pc.watch.impress.co.jp/include/common/p01/images/global-nav/gn_clw.png gn_clw.png
[3]http://akiba-pc.watch.impress.co.jp/include/common/p01/images/global-nav/gn_pcw.png gn_pcw.png
[4]http://akiba-pc.watch.impress.co.jp/include/common/p01/images/global-nav/gn_dcw.png gn_dcw.png
[5]http://akiba-pc.watch.impress.co.jp/include/common/p01/images/global-nav/gn_ah.png gn_ah.png
~~~以下省略~~~

実は、Go言語の”channel”や”goroutine”など並列処理に関する実装方法について勉強するつもりで殴り書きしてたのですが、今日は時間がなさそうなので書いたところまでメモがてらに記事を書いていたりします(汗

Go言語で皆大好き htpdate を書いてみた

PCやサーバの時間合わせにntpdateを通常用いますが、80番と443番しか外部接続を許されないネットワークだとNTPが使えない事があります。そんな時にNTPの代替としてWEBサーバの時間を元にマシンの時間を合わせるコマンドが”htpdate”です。

仕組はHTTPのレスポンスヘッダに含まれる”Date”を元にソコソコ正確な時間を取得してOSの時間を設定します。
とても単純明快で清々しいくらいです。

例えばこのサーバだと、このようなレスポンスヘッダが帰ってきます。
この中の「Date: Wed, 08 Apr 2015 11:35:10 GMT」がサーバの時間となります。

HTTP/1.1 200 OK
Date: Wed, 08 Apr 2015 11:35:10 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Fri, 29 Jun 2012 10:40:46 GMT
ETag: "16e07e-5-4c39a14ab6780"
Accept-Ranges: bytes
Content-Length: 5
Connection: close
Content-Type: text/html

# vi htpdate.go

package main

import (
  "os"
  "fmt"
  "time"
  "syscall"
  "net/http"
)

// HTTPサーバへアクセスしてヘッダーからアクセス日時を取得する関数
func get_http_date(url string) (string, error) {
  response, err := http.Get(url)
  if err != nil { return "", err }

  access_time := response.Header.Get("Date")
  return access_time, nil
}

// 引数として受け取ったUNIXタイムをOSの時間に設定する関数
func settime(sec int64, usec int64) error {
  tv := syscall.Timeval{ Sec: sec, Usec: usec }

  return os.NewSyscallError("settimeofday", syscall.Settimeofday(&tv))
}

func main() {
  if len(os.Args) > 1 {
    // とりあえずHTTPサーバを1個だけ引数に受け取る
    host_name  := os.Args[1]
    target_url := "http://" + host_name

    // HTTPサーバからアクセス日時を取得
    access_time, err := get_http_date(target_url)
    if err != nil {
      fmt.Fprintln(os.Stderr, "Failed to access the HTTP server.")
      os.Exit(1)
    }

    // 取得した時間をパーズする
    server_time, err := time.Parse(time.RFC1123, access_time)
    if err != nil {
      fmt.Fprintln(os.Stderr, "Time of format is not a RFC1123.")
      os.Exit(1)
    }

    // 取得した時間をOSの時間として設定する
    if err := settime(server_time.Unix(), 0); err != nil {
      fmt.Fprintln(os.Stderr, "Failed to set the date and time.")
      os.Exit(1)
    }

    fmt.Println(time.Now())
    os.Exit(0)
  } else {
    fmt.Fprintln(os.Stderr, "Please set the HTTP server to argument.")
    os.Exit(1)
  }
}

実行してみると時間が設定される事が分かるはずです。
# go run htpdate.go orsx.net

2015-04-08 21:05:18.000046645 +0900 JST

Proxyなどは対応していませんがGo言語を勉強するうえで良い教材になりそうな気がしたので簡単に実装してみました。

[HTTP Time Protocol / htpdate] Webプロキシを経由して時刻を同期するの巻 – TrippyBoyの愉快な日々
Introduction | HTTP Time Protocol

RC-S620/SをFeliCaカードに成りきらせて好きなIDmで応答するようにする

IMG_3999
※ ArduinoとRC-S620/Sの接続については(ArduinoでRELET(FeliCa電子マネー残高照会機)モドキを作ろう)で図を描いているのでそちらを参考にしてください。

ライブラリの拡張

公式で提供されているArduino用ライブラリを拡張する必要があります。

RCS620S.h 29行目付近に追加

int tginit(const uint8_t* idm);

RCS620S.cpp 80行目付近に追加

int RCS620S::tginit(const uint8_t* idm)
{
    uint8_t  response[RCS620S_MAX_RW_RESPONSE_LEN];
    uint16_t responseLen;

    /* TgInitTarget command parts */
    uint8_t command[RCS620S_MAX_RW_RESPONSE_LEN] = {0x00};
    uint8_t param_1[ 9] = {0xd4, 0x8c, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x40};
    uint8_t param_2[10] = {0xff};

    /* Make TgInitTarget command */
    memcpy(&command[ 0], param_1,  9);
    memcpy(&command[ 9], idm,      8);
    memcpy(&command[17], param_2, 10);
    memcpy(&command[27], idm,      8);

    /* Execute TgInitTarget */
    rwCommand(command, 41, response, &responseLen);

    return 1;
}

スケッチの作成

Arduino側のスケッチ

#include <RCS620S.h>
#include <string.h>

RCS620S rcs620s;

void setup()
{
  int ret;

  Serial.begin(115200);
  ret = rcs620s.initDevice();
  while (!ret) {}
}

void loop()
{
  int ret;
  
  // SET IDm
  ret = rcs620s.tginit((const uint8_t*)"\x08\x07\x06\x05\x04\x03\x02\x01");
  rcs620s.rfOff();
}

Python (mechanize + BeautifulSoup) でPixivにログインしてタイトルタグの中身を取得する夢を見た

PythonでPixivにログインしてタイトルタグの中身を取得する夢を見たのでメモしておきます

# -*- coding:utf-8 -*-
import mechanize
import BeautifulSoup

login_page = 'https://www.secure.pixiv.net/login.php'

username = "**************"
password = "**************"

br = mechanize.Browser()
br.set_handle_robots(False)

# ログインページを開く
br.open(login_page)

# ユーザ名とパスワードを指定してログイン
br.select_form(nr=1)
br["pixiv_id"] = username
br[  "pass"  ] = password
br.submit()

# マイページを取得
html = br.response().read()

# スクレイピング
soup = BeautifulSoup.BeautifulSoup(html)
recipe_title = soup.find("title")
print(recipe_title.string)

br.close()

Twitter フォロワー同期(フォロー専用)Bot Perl 新API対応(改弐)

TwitterのAPIがすべてSSLになり403 forbiddenと表示されるようになったので対策してみました。

#!/usr/bin/perl
use utf8;
use strict;
use warnings;

# モジュール使用宣言
use Encode;
use FindBin;
use YAML::Tiny;
use Array::Diff;
use Net::Twitter;

# 現在のパスから見て設定ファイルを読み込み
my $config = (YAML::Tiny->read($FindBin::Bin . '/config.yml'))->[0];

# OAuth認証
my $twitter = Net::Twitter->new(
        traits           => ['API::RESTv1_1', 'OAuth'],
        consumer_key     => $config->{'consumer_key'},
        consumer_secret  => $config->{'consumer_secret'},
        apiurl           => 'https://api.twitter.com/1.1',
        searchapiurl     => 'https://api.twitter.com/1.1',
        legacy_lists_api => 0
);

$twitter->access_token($config->{'access_token'});
$twitter->access_token_secret($config->{'access_token_secret'});

# 認証失敗時の処理
die('Auth failed:'.$config->{'username'}) unless ( $twitter->authorized ) ;

# ユーザー名を含むユーザー情報を取得
my $own_id = $twitter->verify_credentials->{id};

my $nextc = -1; # paging default.
my @following_id_list; # outgo

# APIの仕様?から一度に100人までしか取得できないから0が返ってくるまでdoブロックをループ
do{
        # パラメータcursorは前回取得したフォローイングまでの番号が入っている
        my $following_list = $twitter->friends_ids({ id=>$own_id, cursor => $nextc });
        $nextc = $following_list->{next_cursor};
        # 配列からフォローイングのidを取得
        foreach my $id (@{ $following_list->{ids} }){
                push(@following_id_list, $id); # 後で比較するためにフォローイングを配列に保管
        }
}while($nextc!=0);
# 文字昇順でソート
@following_id_list = sort @following_id_list;

$nextc = -1;
my @followers_id_list; # income

# APIの仕様?から一度に100人までしか取得できないから0が返ってくるまでdoブロックをループ
do{
        # パラメータcursorは前回取得したフォロワーまでの番号が入っている
        my $followers_list = $twitter->followers_ids({ id=>$own_id, cursor => $nextc });
        $nextc = $followers_list->{next_cursor};
        # 配列からフォロワーのidを取得
        foreach my $id (@{ $followers_list->{ids} }){
                push(@followers_id_list, $id); # 後で比較するためにフォロワーを配列に保管
        }
}while($nextc!=0);
# 文字昇順でソート
@followers_id_list = sort @followers_id_list;

# 差分を取得(フォローイング)
my $diff_following = Array::Diff->diff(\@following_id_list, \@followers_id_list);

# 差分を取得(フォロワー)
my $diff_followers = Array::Diff->diff(\@followers_id_list, \@following_id_list);

# リムった人をリム返し
foreach my $delid_following (@{ $diff_following->{deleted} }){
        eval{$twitter->destroy_friend({user_id => $delid_following})};
        # プライベートだとエラーになる人がいるので確認用
        print $delid_following . "\n" if($@);
}

# フォローした人をフォロー返し
foreach my $delid_followers (@{ $diff_followers->{deleted} }){
        eval{$twitter->create_friend({user_id => $delid_followers})};
        # プライベートだとエラーになる人がいるので確認用
        print $delid_followers . "\n" if($@);
}

Perl SSH接続 Net::SSH2 使い方 CentOS5

PerlでSSHへ接続したくなったのでNet::SSH2を使ってみた
Net::SSH2を使うにはlibssh2-develを使用するらしいのでインストール
# yum -y install libssh2-devel

cpanを使ってNet::SSH2をインストール
# cpan -i Net::SSH2
# cpan -i Net::OpenSSH::Compat::SSH2

実装例
SSHでサーバにログインしてpublic_htmlに移動し、lsコマンドを実行する

#!/usr/bin/perl
use strict;
use warnings;
# SSHのバナーを表示したい場合
#use Net::OpenSSH::Compat::SSH2 qw(:supplant);
use Net::SSH2;

# バッファーサイズ
use constant BUFLEN => 512;

my $host = ""; # 接続先ホスト
my $user = ""; # ユーザ名
my $pass = ""; # パスワード

my ($len, $buf);

my $ssh2 = Net::SSH2->new();

$ssh2->connect($host) or die "$!";

if( $ssh2->auth_password($user,$pass)) {
    my $chan = $ssh2->channel();
    $chan->blocking(0);
    $chan->shell();

    $chan->write("cd public_html\n");
    select(undef,undef,undef,0.2);
    print $buf while ($len = $chan->read($buf, BUFLEN));

    $chan->write("ls -al ./\n");
    select(undef,undef,undef,0.2);
    print $buf while ($len = $chan->read($buf, BUFLEN));

    $chan->close();
}

これは色々使えそうですね

参考サイト
Perl Tips | Perl で、ssh でリモートのサーバにアクセスしてファイルをアップロード(SCP)する方法 (Net::SSH2)
自分イノベーション – 気まぐれ勉強メモ Net::SSH2を使ってみた
Net::SSH2と公開鍵
A little demo for Net::SSH2

Twitter フォロワー同期(フォロー専用)Bot Perl 新API対応(改)

アカウントがプライベート設定のフォロワーがいると403 forbiddenになって処理が止まるようなので一応対策しました
手動で実行することで対象のID(数字)を確認出来ます

#!/usr/bin/perl
use utf8;
use strict;
use warnings;

# モジュール使用宣言
use Array::Diff;
use Net::Twitter::Lite::WithAPIv1_1;
use YAML::Tiny;
use Encode;
use FindBin;

# 現在のパスから見て設定ファイルを読み込み
my $config = (YAML::Tiny->read($FindBin::Bin . '/config.yml'))->[0];
# OAuth認証
my $twitter = Net::Twitter::Lite::WithAPIv1_1->new(
        traits => ['API::REST', 'OAuth'],
        consumer_key => $config->{'consumer_key'},
        consumer_secret => $config->{'consumer_secret'}
);
$twitter->access_token($config->{'access_token'});
$twitter->access_token_secret($config->{'access_token_secret'});

# 認証失敗時の処理
die('Auth failed:'.$config->{'username'}) unless ( $twitter->authorized ) ;

# ユーザー名を含むユーザー情報を取得
my $own_id = $twitter->verify_credentials->{id};

my $nextc = -1; # paging default.
my @following_id_list; # outgo

# APIの仕様?から一度に100人までしか取得できないから0が返ってくるまでdoブロックをループ
do{
        # パラメータcursorは前回取得したフォローイングまでの番号が入っている
        my $following_list = $twitter->friends_ids({ id=>$own_id, cursor => $nextc });
        $nextc = $following_list->{next_cursor};
        # 配列からフォローイングのidを取得
        foreach my $id (@{ $following_list->{ids} }){
                push(@following_id_list, $id); # 後で比較するためにフォローイングを配列に保管
        }
}while($nextc!=0);
# 文字昇順でソート
@following_id_list = sort @following_id_list;

$nextc = -1;
my @followers_id_list; # income

# APIの仕様?から一度に100人までしか取得できないから0が返ってくるまでdoブロックをループ
do{
        # パラメータcursorは前回取得したフォロワーまでの番号が入っている
        my $followers_list = $twitter->followers_ids({ id=>$own_id, cursor => $nextc });
        $nextc = $followers_list->{next_cursor};
        # 配列からフォロワーのidを取得
        foreach my $id (@{ $followers_list->{ids} }){
                push(@followers_id_list, $id); # 後で比較するためにフォロワーを配列に保管
        }
}while($nextc!=0);
# 文字昇順でソート
@followers_id_list = sort @followers_id_list;

# 差分を取得(フォローイング)
my $diff_following = Array::Diff->diff(\@following_id_list, \@followers_id_list);

# 差分を取得(フォロワー)
my $diff_followers = Array::Diff->diff(\@followers_id_list, \@following_id_list);

# リムった人をリム返し
foreach my $delid_following (@{ $diff_following->{deleted} }){
        eval{$twitter->destroy_friend({user_id => $delid_following})};
        # プライベートだとエラーになる人がいるので確認用
        print $delid_following . "\n" if($@);
}

# フォローした人をフォロー返し
foreach my $delid_followers (@{ $diff_followers->{deleted} }){
        eval{$twitter->create_friend({user_id => $delid_followers})};
        # プライベートだとエラーになる人がいるので確認用
        print $delid_followers . "\n" if($@);
}

シーザー暗号 ROT13 Perl 復号

ありきたりですがPerlで書いてみました

#!/usr/bin/perl
print sub{$_[0]=~tr/A-Za-z/ZA-Yza-y/
for(1..$_[1]);$_[0]}->($s=<>,$k=<>);

文字列 + 改行 + ずらす回数 + 改行 で実行できます

結構短く書けるものですね

Perl LWP::UserAgentを使ったHTTPS接続

PerlでLWP::UserAgentを使い、HTTPSサイトへ接続を行いコンテンツを取得してくるプログラムを書く必要が出てきて、色々調べて書いたのですが情報が錯綜していたりで結構ハマったのでソースコードを載せておきます。
今回わけあったりでコンストラクタ側でProxyの情報を保持していなかったりしますが、そこはお好きに改変してお使いください。

#########################################################
#
# LWP::UserAgentを使ったHTTPS接続
#
#########################################################
use utf8;
use strict;
use warnings;

{
    package ProxyWebGet;

    use LWP::UserAgent;

    # コンストラクタ
    sub new {
        my ($class, @args) = @_;
        my %args = ref $args[0] eq 'HASH' ? %{$args[0]} : @args;
        my $self = {%args};

        # オプション項目
        $self->{ac_timeout} ||= 10;    # プロキシ接続時のタイムアウト(秒)

        return bless $self , $class;
    }

    # ホストとポート番号に対してProxy接続を行う
    # 引数にホストとポート番号と接続先URLを指定する
    sub proxy_connect {
        my $self = shift;
        my $proxy_host = shift; # ホストを受け取る
        my $proxy_port = shift; # ポート番号を受け取る
        my $target_url = shift; # 接続先URL

        # ホストとポート番号をLWP::UserAgentで利用できる形式に整形する
        my $http_proxy = 'http://' . $proxy_host . ':' . $proxy_port;

        $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = 'Net::SSL';
        $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}    = 0;
        $ENV{HTTPS_PROXY}   = $http_proxy;
        $ENV{HTTPS_VERSION} = 3;

        # LWP::UserAgentのインスタンスの生成
        my $ua = LWP::UserAgent->new(
            timeout => $self->{ac_timeout} # オプションにタイムアウト時間を指定
        );

        # ヘッダーにUTF-8を含む場合エラーが出るため解析しない
        $ua->parse_head(0);

        # 整形したProxyを指定
        $ua->proxy(['http'], $http_proxy);

        # 指定されたURLへ接続する
        # FTP等のプロトコルに接続を行った場合
        # コネクションが維持されるため強制的に破棄する
        my $res = "";
        eval{
            local $SIG{ALRM} = sub{die "timeout"};
            alarm($self->{ac_timeout});   # タイマー設定
            $res = $ua->get($target_url); # URLへ接続しコンテンツを取得
            alarm(0);                     # タイマー解除
        };

        # ステータスコードが正常である場合、コンテンツを返す
        my $result = $res->is_success() ? $res->content() : "";

        return $result;
    }
}

{
    # sub main
    my $pwg = ProxyWebGet->new();

    print $pwg->proxy_connect(
              '000.000.000.000',
              '0000',
              'https://ja.wikipedia.org/wiki/'
          );
}

Bashでインタプリタ(Brainf*ck)を書いてみる

ワケありましてBrainf*ckを書くことになりBashでこんなものを書く人は少ないかもなと思いながら実装してみました。
とりあえずWikipediaで公開されている”Hello, world!”は実行出来ます。
色んなところで公開されている”FizzBuzz”も試した限りでは動作しているようです。

#!/bin/bash
############################################################################
#
# メモ
#
# このソースコードは私のサーバにて一般公開しております
# 公開先: https://www.orsx.net/archives/4469
#
# Bashは明確にローカル変数であることを宣言しなければ
# 全てグローバル変数となるが・・・・
# とりあえず初期化する関数を定義すれば追いやすくなるかもしれないとかと
# べ、別にこんなもの無くても問題ない。。。
#
# 言語によっては最後に標準出力した値を返り値として返す物があったりするため
# 癖で関数の最後には必ず明的にreturnしてあるが・・・
# べ、別にこんなもの無くても問題ない。。。
#
# ちなみに、変数は下記のようになっている
# ・グローバル変数は大文字
# ・ローカル変数は小文字
#
############################################################################

# 使用するグローバル変数を明的に初期化する
function initialize() {
  # 変数
  TEXT_SRC=""
  CODE_LEN=0
  CODE_PTR=0
  BUFF_PTR=0
  BUFF_LEN=0

  # 配列
  CODE=()
  BUFF=()

  return
}

# EOF(CTRL+D)が来るまで読み続け改行は無視する
# グローバル変数SRCにソースコードを格納する
function readstring() {
  local line
  while read line
  do
    # 文字列連結 連結演算子は存在しない
    TEXT_SRC=$TEXT_SRC$line
  done

  # returnは数字しか返せない
  return
}

# 標準入力で1文字だけ受け取る
function readchar() {
  local line
  read line
  # 文字を数字に変換
  BUFF[${BUFF_PTR}]=$(printf "%d" "'${line:0:1}")

  return
}

# ASCIIコードからアルファベットを表示する
function echochar() {
  echo ${BUFF[${BUFF_PTR}]} | awk '{ printf("%c", $0); }'

  return
}

# 初期化されていない配列の要素を使おうとする前に初期化して拡張する
# これによりバッファーの長さを制限しなくて良くなる
function expansion() {
  if [ ${BUFF_PTR} -gt ${BUFF_LEN} ]
  then
    BUFF_LEN=$((BUFF_LEN+1))
    BUFF[${BUFF_LEN}]=0
  fi

  return
}

# ソースコードをグローバル配列CODEに一文字ずつ格納する
function tokenizer() {
  # トークンを格納する配列の添字を数える
  CODE_LEN=$((${#TEXT_SRC}-1))

  # 文字列から一文字ずつトークンを取り出し配列に追加していく
  local i
  for i in $(seq 0 ${CODE_LEN})
  do
    CODE[${i}]=${TEXT_SRC:${i}:1}
  done

  return
}

# BUFF_PTRがな0ら対応する']'まで直後までジャンプする
function left_jumper() {
  if [ ${BUFF[${BUFF_PTR}]} -eq 0 ]
  then
    local loop_ptr=1
    while [ ${loop_ptr} -ne 0 ]
    do
      CODE_PTR=$((CODE_PTR+1))
      case ${CODE[${CODE_PTR}]} in
      '[')
        loop_ptr=$((loop_ptr+1));;
      ']')
        loop_ptr=$((loop_ptr-1));;
      esac
    done
  fi

  return
}

# BUFF_PTRがな0ら対応する'['の直後までジャンプする
function rite_jumper() {
  if [ ${BUFF[${BUFF_PTR}]} -ne 0 ]
  then
    local loop_ptr=1
    while [ ${loop_ptr} -ne 0 ]
    do
      CODE_PTR=$((CODE_PTR-1))
      case ${CODE[${CODE_PTR}]} in
      ']')
        loop_ptr=$((loop_ptr+1));;
      '[')
        loop_ptr=$((loop_ptr-1));;
      esac
    done
  fi

  return
}

# 意味解析を行い実行する
function execute() {
  while [ ${CODE_PTR} -le ${CODE_LEN} ]
  do
    case ${CODE[${CODE_PTR}]} in
    '>')
      BUFF_PTR=$((BUFF_PTR+1));;
    '<')
      BUFF_PTR=$((BUFF_PTR-1));;
    '+')
      BUFF[${BUFF_PTR}]=$((BUFF[${BUFF_PTR}]+1));;
    '-')
      BUFF[${BUFF_PTR}]=$((BUFF[${BUFF_PTR}]-1));;
    '.')
      echochar;;
    ',')
      readchar;;
    '[')
      left_jumper;;
    ']')
      rite_jumper;;
     * )
      local message="Unknown Token! : ";
      echo $message${CODE[${CODE_PTR}]};
      break;;
    esac

    expansion
    CODE_PTR=$((CODE_PTR+1))
  done

  echo "";

  return
}

initialize   # 初期化
readstring   # 入力受付・プリプロセッサ
tokenizer    # 構文解析
execute      # 意味解析・実行

実行方法
直接標準入力で渡すかもしくは
適当なファイル例えば”script.bf”のようなファイルにソースコードを書き込み下記のように実行することができます。
# cat script.bf | brainfuck.sh

2013/05/07 追記
Bashでは”文字”に対して足し算を行えないようなので
文字入力時に整数型に変換するように”,”の処理を修正しました。