2013-12-03

配列(その2) - Bash Advent Calendar - Day 2

すでに時間ギリギリ。さて、昨日は初期化と代入を説明したので、今日は削除と参照をば。

削除

変数まるごと削除は unset で。
$ foo=("bar" "baz")
$ unset foo
$ declare -p foo
bash: declare: foo: 見つかりません

特定の要素を削除するには unset 変数[インデックス]で。
$ foo=("bar" "baz")
$ unset foo[0]
$ declare -p foo
declare -a foo='([1]="baz")'

削除については以上。よほど大規模な Bash スクリプトでもない限り、unset とかわざわざ使うこともないでしょう。

参照

これだけでかなりの量を書ける気がするのですが、二日目にして既に息切れ状態なので手短に。

もう日本語が面倒になってきたので、以下の例参照。
$ foo=("bar" "baz")
$ echo ${foo[0]}
bar
$ echo ${foo[1]}
baz
$ echo ${foo}
bar
$ unset foo[0]
$ echo ${foo}
    # 何もなし
というわけで、${変数[インデックス]} で参照できます。インデックスを省略した場合、それは [0] と等価(最小インデックスを参照してくれるわけではない)になります。どうがんばって見ても、インデックス指定なしの参照は配列変数にアクセスしているようには見えないので、変に省略するのはやめましょう。もとい、そんな変な仕様知ってる人なんてほとんどいないでしょう。

すべての要素を参照

ここまではウォームアップです。これから実用性アップのはず。

さて、配列にまとめてアクセスするには * もしくは @ という特殊なインデックスを使います。
$ foo=("bar" "baz")
$ echo "${foo[*]}"
bar baz
$ echo "${foo[@]}"
bar baz
この例では * と @ に違いがありませんが、その違いは "$*" と "$@" の違いと一緒です。

わからない人にとっては何言ってるのか意味不明だと思いますが、これはこれで一日分使えるのでまたの機会に。とりあえず、IFS を変更しない限り @ の方を使うと覚えておけば大丈夫です。

これを応用することで、配列のコピーができます。
$ foo=("bar" "baz")
$ qux=("${foo[@]}")
$ declare -p qux
declare -a qux='([0]="bar" [1]="baz")'
これは2行目の qux= の部分が
$ qux=("bar" "baz")
と変数展開された結果です。さて、初期化の部分で述べたように、Bash の配列はインデックスが飛び飛びでも構わないのですが、変数展開ではインデックスの値までは展開されないので、このコピーは完璧ではありません。

インデックスの参照

というわけで、格納されてるインデックスを参照したくなりますよね。
$ foo=("bar" "baz")
$ echo "${!foo[@]}"
0 1
$ unset foo[0]
$ echo "${!foo[@]}"
1
というように "${!変数名[@]}" でインデックスをまとめて取り出せます。ここでも * と @ を使えますが、その違いについては割愛します。ここでもとりあえず @ で覚えておけば間違いありません。

このテクニックを使えばコピーも完璧です。
$ foo=([1]="bar" [3]="baz")
$ qux=()
$ for i in "${!foo[@]}"; do
$   qux[i]="${foo[i]}"
$ done
$ declare -p qux
declare -a qux='([1]="bar" [3]="baz")'
 力尽きたので、さらに明日に続きます。
 



2013-12-01

とあるインフラエンジニアの独り言

「SlideShareのスライドをダウンロードできるサイトを作りました。」の件を肴に、ちょっと独り言。

端的に言えば、ある人が「SlideShare がスライドダウンロードさせてくれないからダウンローダー作ったよ」とおっしゃってますが、それどうなのよ、から派生してスクレーパーとかマジやめてというお話。

規約の内容

まずは SlideShare の規約がどうなってるかですが、Terms of Service に
You agree not to use or launch any automated system, including without limitation, "robots," "spiders," "offline readers," etc., that accesses the Site in a manner that sends more request messages to the SlideShare servers in a given period of time than a human can reasonably produce in the same period by using a convention on-line web browser. 
と書いてあります。てきとーに意訳すると、
人間が一般的なブラウザを使った場合に送るリクエストよりも早いペースでリクエストを送る「ロボット」や「スパイダー」、「オフラインリーダー」といったような自動化されたシステムを使用しない、または立ち上げないことに同意します。
ってところでしょうか。後続の文では検索エンジンのロボットに対する条件が書いてありますが、このダウンローダーは該当しないので割愛します。

ダウンローダーは規約違反?

世の中には次に読むであろうページのプリフェッチ、さらにはプリレンダリングまでするブラウザが存在するらしい、というかそんな自社製品があるような気がするので、"human can reasonably produce" すなわち普通の人間がブラウザを操作することによって要求しうる QPS は一般人が思ってるよりは大きいのですが、当該サイトがその妥当性を考慮してダウンロードリクエストのペースを調整しているか分かりません。

ということで規約違反がどうか外からは判断できませんが、ある程度の可能性はあると思っています。

実際、何が悪いの?

実際上は、SlideShare に送っている QPS はそんなに大したことないでしょうし、ちょっと行儀が悪いだけのオフラインリーダーぐらいのものでしょう。なので、多分 SlideShare が規約をたてに何かアクションを取る (e.g. アクセスブロックする) ようなことはないとおもいます。

ただ、この手の proxy とか scraper といった「一つのリクエストを受けて複数のリクエストを他サイトに投げる」サービスは、悪い人に使われると DoS の片棒を担がされたりする危険があるし、前述したような規約違反の可能性、そのほか著作権 (特に DMCA) 違反の可能性など色々面倒ごとがあるので、おおっぴらにやるのは避けたほうがいいよというだけの話です。

本当に言いたいこと

ここからは SlideShare の一件から離れて一般論。

そんでもって何故こんなこと書いてるかっていうと「ユーザーが見られるのをそのままダウンロードできるようにして何が悪い」的な意見が散見されるからです。

確かに、ある個人が wget や curl やらで一回だけプレゼンまるごとコピーするだけのレベルならそうですが、最初に参照した SlideShare の規約にあるように、そうしたものを自動化してサービスとして提供するのはちょっと待ってくださいな、とサービスを提供する側の人間は思ってるわけです。どのくらいの QPS なら妥当・常識的・礼儀正しくて、どのくらいの QPS だと悪用・非常識・お行儀悪いとみなされるかは様々ですが、少なくとも「ユーザーができることなら何やったっていい」という訳ではないということです。

それでもスクレーパーを書く人へ

せめて無駄な 404 Not Found とか起こさないようにしてください。たとえ 404 を返すだけでもコンピューターリソースを消費するんです。スクレーパーも誰かの役に立つのならいいんですが、404 を返すためにこちらがコンピューターリソースを浪費するなんてやってられません。

それでもスクレーパーを書く人へ (その2)

お行儀よい実装を心がけてください。4xx とか 5xx が返ってきたり、そもそも TCP レベルでコネクションが確立できない場合には、すぐにリトライするのはやめてください。特にこっちがちゃんと 429 (RFC6585)「お前リクエスト送りすぎだからペース落とせや」 って返してるのを無視されると、中の人が激おこぷんぷん丸になってしまうかもしれません。

またリトライするにしても、リクエスト queue コントロールをちゃんとして「元々予定していた QPS + リトライ分の QPS」を送りつけるなんてことをしないようにしてください。

それでもスクレーパーを書く人へ (その3)

リクエストに勝手なパラメーターを追加しないでください。多くの場合、リクエスト内の無関係なパラメーターは無視されるだけですが、CDN とか透過なキャッシュレイヤーがあった場合に、キャッシュの邪魔をする場合があります。

逆に、キャッシュを回避するためにわざとタイムスタンプっぽいパラメーターをつけてるのを見つけた日には、中の人が激おこスティックファイナリアリティぷんぷんドリームになっちゃうかも。

本当の本当に書きたかったこと

実は一度「激おこスティックファイナリアリティぷんぷんドリーム」ってブログに書いてみたかったんです。ただそれだけでここまで書きました。多分、数年後に読み返して恥ずかしい思いをするのは自分自身です。

配列 - Bash Advent Calendar - Day1

毎日続くかどうかわかりませんが、できる限り続けていきたいと思います。

さて、何から書いていいかわからないので、とりあえず最初は A から始まる単語はないかと探してみたところ、alias, array, associative array, argument, arithmetic evaluation と、とりあえず数日はいけそうな感じ。

というわけで今日は配列について。

まず注意点として、配列は sh にはない Bash 独自の機能です。なので #!/bin/sh で書き始めてあるスクリプト内で使うと、後々困ることがあるかもしれなので要注意。そもそも /bin/sh が Bash ってどうなのという議論は勝手にやりたい人だけがしてください。

変数の宣言、初期化

ちょっとかしこまった宣言

普段目にすることはあまりないかもしれませんが、C 言語出身者が好むスタイル(偏見100%)。
$ declare -a foo
これで変数 foo は配列変数になります。

変数には初期値を入れた明示的な初期化が必要と考える、gccマンセー、もしくはC++出身者であれば(これも偏見100%、以下同じ)
$ declare -a foo=()
で、同じ中身が空の配列変数が宣言されます。

ちなみに、想像で適当に書いてるので、実際にこんな空の配列を使ってるのを見たことは一度たりともありませんw

値の代入による初期化

普通の変数の場合、わざわざ宣言などせず、いきなり値を代入しますよね。配列変数においても同じです。ある書式にのっとって値を代入すれば勝手に配列変数になります。
$ foo=("bar" "baz")
中身が実際にどうなってるかを確認したい場合には、
$ declare -p foo
declare -a foo='([0]="bar" [1]="baz")'
というように declare -p を使いましょう。ちゃんと declare -a と配列変数になっているのが分かります。配列のインデックスもこれで確認できます。

さて、配列のインデックスが出てきたのでもう一つ。初期化時に、インデックスを指定することもできます。上の declare -p の出力から類推できるように、
$ foo=([1]="bar" [3]="baz")
$ declare -p foo
declare -a foo='([1]="bar" [3]="baz")'
とすることで、特定の位置に値を代入できます。またこの例から分かるように、インデックスは飛び飛びでも構いません。

最後にもう一つ、あまりおすすめしませんが
$ foo[1]="bar"
$ declare -p foo
declare -a foo='([1]="bar")'
という形の代入でも配列変数を作成できます。これは、本来は foo という配列変数が既に存在しているときにその値を書き換えるものです。なので、これを初期化に使ってしまうと、foo という変数がそれまでに初期化されてないのがバグなのか、それともこれが意図したものかが分かりづらくなってしまいます。

値の代入、追加

初期化時に値を代入する方法は既に述べました。ここでは既存の配列に値を追加、配列内の値を変更する方法について述べます。

まず、初期化の最後で述べた通り、
$ foo=("bar" "baz")
$ foo[1]="qux"
$ declare -p foo
declare -a foo='([0]="bar" [1]="qux")'
変数名に続けてインデックスを指定することで、特定のインデックスの値を変更、または挿入することができます。

"+=" を使って複数値をまとめて追加することもできます。
$ foo=("bar" "baz")
$ foo+=("qux" "quux")
$ declare -p foo
declare -a foo='([0]="bar" [1]="baz" [2]="qux" [3]="quux")'
"+=" は配列の末尾に与えられた配列を追加するので、その応用として
foo=("a" "b")
for ((i=0;i<10;i++)); do
  foo+=($i)
done
のように、値が1個だけの配列を使って、値を末尾に追加するのはよくあるパターンです。

一度たりとも使ったことがないし、他の人が使ってるのも見たことがないのですが、右辺の配列にインデックスが指定されていれば、その値が尊重されます。すなわち
$ foo=([0]="bar" [2]="qux")
$ foo+=([1]="baz" [3]="quux")
$ declare -p foo
declare -a foo='([0]="bar" [1]="baz" [2]="qux" [3]="quux")'
と配列のマージができるのですが、インデックスが重複した場合の挙動がお節介
$ foo=([0]="bar" [2]="qux")
$ foo+=([0]="baz" [1]="quux")
$ declare -p foo
declare -a foo='([0]="barbaz" [1]="quux" [2]="qux")'
だったり、そもそも
$ foo=([0]="bar" [2]="qux")
$ foo+=("baz" "quux")
$ declare -p foo
declare -a foo='([0]="bar" [2]="qux" [3]="baz" [4]="quux")'
と挙動が違う、すなわち ([0]="baz" [1]="quux") と ("baz" "quux") が等価ではない時点で糞仕様認定は避けられません。

予想外に長くなったので、明日に続く。

追記

この手のIT系 Advent Calendar の定番だと毎日違う人が担当したりしますが、俺が一人で勝手にやってるだけなので、他に Bash Advent Calendar を担当している人はいません。

というわけで、一日で完結しないなんて当たり前w

追記その2

拡張 POSIX シェルスクリプト Advent Calendar 2013とかいうのを見つけたので、有用なのを探している方はこんなのよりそちらへどうぞ。


2013-09-23

アメリカのオンラインバンキング

なんか進化の方向が間違ってる。

相変わらずの小切手文化のまま IT 化が進んでます。この前銀行にの窓口に用があって行ったのですが、となりの窓口のにーちゃんがしきりに「スマホでできるよ、写真をとってうんたらかんたら」と顧客に説明してました。

話からして多分小切手の換金ができるんじゃないかな、そういえば俺も換金してない小切手があったなとスマホアプリをインストール、小切手の換金ができるかどうか試してみました。

小切手をそのアプリ内の写真機能で撮ると画像認識処理が走り、やれ枠内に収まってないとか、裏書きが認識できないとか教えてくれます。そしてちゃんとした写真が撮れると前に進めて、やっとアップロード完了。多分処理時間からして、小切手の発行元の口座番号あたりは事前に判別してそうな感じです。スマホアプリも進化したね。

でも、なんか日本での感覚からするとおもいっきり何かが間違ってる感じがします。普通に振り込んでくれればいいんじゃないかと。システムとか手数料体系が小切手文化に合わせてあるっぽいので、振込の方が手数料が高そうだけど、明らかに小切手を使ったやり取りのほうが人的コストが掛かってるよね。

きっと何年も住んでるうちに慣れてしまうのだろうけど、今の時点では何かおかしなことをやってる感がぬぐえません。

Raiders完敗

MNF は地区内ライバルのデンバー戦でしたが、P. Manning はファンブルロスト以外ほぼ完璧、なすすべなしでした。T. Branch 一人欠けたからとかそんなレベルじゃなかったようです。おまけに D. McFadden のランも完全に封じられて攻守ともに完敗です。

相手は3戦全勝で早くも地区優勝は厳しい感じなので、後はワイルドカード争いに加われるかというのがポイントになってくるかと思います。

2013-09-11

NFL 2013-2014 シーズン開幕

NFLって書いたけど、Raiders以外のはノーチェックです。

さて、我らがRaidersは残念ながら黒星発進です。最後まで惜しかったですが、Red Zoneで2度もインターセプトされてたら勝てる試合も勝てません。

さて、次週は Jaguarsとの対戦なのですが、いきなりリーグ最下位決定戦とか言われています。というのもESPNのランキングで我らがRaidersがブービーの31位、そして相手のJaguarsがドベの32位だからです。

どうせ弱いなら負けに負けまくって、来年のドラフトで頑張るしかないですね。

ブログの見た目をちょっと変更

Google+への統合が進んだらしいので、設定見直しのついでにゴテゴテとガジェットを追加してみました。

とりあえず上部にある検索バーが超絶カッコ悪い気がしますが、とりあえずはこのままでいこうかと思います。

prometheusのrate()関数の罠

 久しぶりのAdventカレンダー挑戦、うまくいく気がしません。 閑話休題。実のところ、rate()関数というよりは、サーバー側のmetric初期化問題です。 さて、何らかのサーバーAがあったとして、それが更に他のサーバーBにRPCを送っているとします。サーバーBの方でホワイトボ...