2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

bcコマンドによるプログラミング

Posted at

bc コマンドって知ってますか?

皆さま、bc コマンドって知ってますか?
シェルで計算するときにお馴染みのアレですね。
だいたい、皆さんこんな感じで使ってると思います。

$ echo '1.5 * 3.75' | bc
5.62

気付く方は気付くと思うんです。

なんで echo したものをパイプしてんだ? 」って。

引数にこんな感じで書ければいいじゃないの、って。

$ bc '1.5 * 3.75'

…でも、bc コマンドはあくまで、引数ではなくて、標準入力でパイプ使って渡すんです。
なんででしょうね?

まるで、もっとたくさん計算が書けるみたいですよね。

そう。 bc はもっと複雑な計算ができるよう、ソースファイルに書いた計算を読ませることができるようになっているのです。

仕様

bc コマンドのソースファイル…つまり「 bc スクリプト」の仕様を, いろいろ試しつつ整理してみました。

開始と終了

$ bc スクリプトファイル

で開始し, quit という命令で終了します。

quit 命令がないと, 終了せずにそのまま次の命令を待っちゃいます。
むしろデバッグに便利?

$ cat スクリプトファイル | bc

などというパイプにスクリプトを流す形式で開始すると, quit を待つことなく終了するようです。

計算結果の出力

計算式を書くと, 行全体の計算をして, 結果を出力します。

"文字列" と書くと、その文字列を出力します。
ただ文字列操作は POSIX では特に記載がなく, GNU bc のマニュアルでも触れられていないので期待はしないほうが良さそうです。
あくまで数字の前にちょっと説明書くとかに留めたほうがよさそう。

コメント

コメントは /* で始まり, */ で終わります。
処理系によっては # で始まる行コメントもサポートしているようです。

変数と代入

変数が使えます。
変数名 = 式 で計算結果を変数に代入できます。

POSIX では, 変数名は 1 文字の小文字とされているようです。 (特殊変数を除く)
ただし, 処理系によってこの制限がなく、ある程度の文字数の変数名が使えます。

また、代入では計算結果が出力されません。

配列

なんと配列もあります。
やはり POSIX では配列名も 1 文字の小文字とされています。
配列の要素は 配列名[添字], 配列そのものを表すときは 配列名[] と書きます。

a[添字] = 式

で代入できます。添字は整数のみです。
事前に宣言やサイズの指定、領域の確保とかは不要で、勝手に定義されて勝手に拡張されるようです。

制御構文

あるんです。

if (比較式) {
    比較式が真の場合
}

POSIX では else はありませんが、処理系によっては else もあるようです。まあ不便だし。

POSIX では比較式は制御構文の中だけで有効です。もちろん真偽値を変数に入れられる処理系もあります。

while (比較式) {
    比較式が真のあいだ繰り返す部分
}
for (初期化式; 比較式; カウンタ更新式) {
    比較式が真のあいだ繰り返す部分
}

なんと for までありました。意外と普通のスクリプトっぽくなってきましたね。

関数

実は関数も定義できます。まじで。
何気にローカル変数も使えます。

define 関数名(引数名, 引数名, 引数名) {
    auto ローカル変数名, ローカル変数名, ローカル変数名
    関数本体
}

やはり POSIX では関数名や引数名やローカル変数名も 1 文字とされています。
ちょっと面倒ですね。

ローカル変数以外はすべてグローバル変数なようです。

処理系によっては引数名に 配列名[] とすることで、配列を渡すことができます。

なお関数定義、関数呼び出し共に () を省略することはできません。

変数は直後に何もなし, 配列は必ず [] を伴う, 関数は必ず () を伴うことで統一されているようです。

return (式) で呼び出し元に値を返すことができます。
戻り値を指定せず return とした場合は 0 を返すらしいです。

特殊変数

重要なのは scale 特殊変数ですね。
演算時の 小数点以下の桁数を指定します。

この 演算時の ってところがポイントで, 既に変数に実数が入っているときに, scale を変更してもその変数の値が切り捨てられたりはしません。
あくまで計算時に使われます。

scale = 0 とすると整数演算となります。
% で余りを計算するときにも有効桁数として使われるので、プログラミング言語に慣れている人は, 余りを計算する前にゼロをセットしておきましょう。

あとは ibaseobase があります。
16 進計算をする際に 16 にするようです。今のところ bc で 16 進計算したことない…

特殊関数というか定義済み関数

平方根を計算する sqrt() あたりは使いどころが多そうですね。
あと scale()length() で小数点以下の桁数や全体の桁数が判るようです。length() が配列のサイズとかではない点に注意。

それと, bc 起動時にコマンドオプションとして -l を与えると, 以下の関数が自動で定義されます。

  • s() … sin()
  • c() … cos()
  • a() … atan()
  • l() … log()
  • e() … exp()
  • j() … ベッセル関数、らしい。よく知らない。

チョイスが割と謎なところはありますが、あると便利ではあります。
問題は, POSIX では貴重な「関数名は 1 文字で定義」の枠をつぶしちゃうこと…

スクリプト例

さて, いろいろ書いてみましょう!

FizzBuzz

#!/usr/bin/bc

# 変数:
n = 0    # いま数えている数値.
v = 0    # ダミー変数.

# 関数:
define p() {
    if (n % 15 == 0) {
        "FizzBuzz
"
        return
    }
    if (n %  5 == 0) {
        "Buzz
"
        return
    }
    if (n %  3 == 0) {
        "Fizz
"
        return
    }
    n
}

# メイン:
for (n = 1; n <= 30; n++) { v = p() }
quit

素数を数える

#!/usr/bin/bc

# 変数:
# i        # (auto) 配列添字.
n = 0      # 現在の数値.
u = 100    # 数値上限.
v = 0      # ダミー変数.

# 配列:
s[0] = 0   # エラトステネスの篩.

# 関数:
define t() {
    auto i
    if (s[n] != 0) { return }
    n
    for (i = n; i < u; i += n) { s[i] = 1 }
}

# メイン:
for (n = 0; n < u; n++) { s[n] = 0 }
n = 2
v = t()
for (n = 3; n < u; n += 2) { v = t() }
quit

ドドスコスコスコラヴ注入♡

#!/usr/bin/bc

/* このスクリプトでは整数しか扱わないので, 小数点以下は捨てる. */
scale = 0

/* シード値. 変えると結果が変わります. ゼロにはしないこと. */
s = 8192

/* 線形合同法. */
define r () {
    s = (48271 * s) % 2147483647 /* 定番の定数. */
    return (s)
}

/* 0 か 1 を返す乱数. */
define b () {
    return (r() / 1073741824) /* 乱数範囲内の最上位ビットを返す. */
}

/* 0 ならスコ, 非ゼロならドドを出力. */
define p (d) {
    if (d != 0) {
        "ドド
"
        return (d)
    }
    "スコ
"
    return (d)
}

/* 最初に 12 要素分のドドスコをセット. */
for (i = 0; i < 12; i++) { t[i] = p(b()) }

/* メインループ. */
while (1 == 1) {
    /* ドドスコ判定. */
    if (t[0] != 0) if (t[1] == 0) if (t[ 2] == 0) if (t[ 3] == 0) {
    if (t[4] != 0) if (t[5] == 0) if (t[ 6] == 0) if (t[ 7] == 0) {
    if (t[8] != 0) if (t[9] == 0) if (t[10] == 0) if (t[11] == 0) break }}

    /* 配列内の要素を 1 つズラし, 空いた枠に新しくドドスコを入れる. */
    for (i = 1; i < 12; i++) { t[i - 1] = t[i] }
    t[11] = p(b())
}
"ラヴ注入♡ "

quit

感想

意外とちゃんとプログラミング言語してた。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?