投稿

12月, 2013の投稿を表示しています

ターミナルウィンドウに雪を降らせよう! - Bash Advent Calendar - Day 13

いまは、ぼくのこころの中では 12 月 13 日の 210時ぐらいです。もはや Advent Calendar の体をなしてない気がしますが、細かいことは気にしないで行きましょう。
terminfo 端末エミューレーター(以下「ターミナル」)は色々と標準、デファクトスタンダードが存在します。これから使う ANSI エスケープシーケンスについては、それと同等の機能のあるターミナルであれば、まずもって ANSI エスケープシーケンスに対応しています。
が、ターミナルの機能は ANSI エスケープシーケンスでサポートされているものだけではなく色々あるので、そうした色々な機能を抽象化して定義したのが terminfo です。昔は termcap が主流でしたが、最近は terminfo の方が Linux 界隈では優勢だと思います。 tputコマンド tputコマンドは、terminfoで定義されている機能名を引数にとり、使用しているターミナルに対応するコマンドを出力します。
たとえば、画面クリア "clear" ではclear="$(tput clear)"
printf "%q\n" "$clear"
$'\E[H\E[2J' となります。ちなみに、printf %q とか $'...' 記法に「Bashらしさ」があるので、それで記事が書けそうな気がしてきました。 乱数 $RANDOM を参照すると Bash は擬似乱数を返してくれます。が、値は[0, 32767]の範囲とかなりしょぼい仕様です。以下では、頑張って[0, ターミナルカラム数)で一様に分布するっぽい乱数を生成していますが、実用上、Linux であればrand() {
  local -i r="0x$(
    dd if=/dev/urandom bs=4 count=1 2>/dev/null |
    od -A n -t x4 |
    tr -d ' ')"
  echo $((r % $1))
} でいいでしょう。64bit 環境であれば dd と od の引数に出てくる 4 は 8 に変えてください。
さて、[0, 32767] の範囲でしか値を返さ…

タブ補完(その1) - Bash Advent Calendar - Day 12

いまは、ぼくのこころの中では 12 月 12 日の 234 時ぐらいです。当然「昨日」とは 12 月 11 日のことです(すべて US 太平洋時間)。
compgen関数昨日は補完の仕様をさらっと説明しましたが、実際に補完をどう生成すればいいのかというのを説明していませんでした。COMPREPLYは配列変数なので、この Bash Advent Calendar を初日から読めば、自力でかなりのことができるはずです。

とは言え、Bashさんも鬼ではありません。ありがちなパターンに簡単に対応する関数 compgen 関数が予め用意されてます。、一般的には
COMPREPLY=($(compgen オプション... -- 補完したい単語)) という形で使います。

たとえば "-foo", "-bar", "-buzz" というオプション名を補完させたいとしましょう。そしてユーザーが "-" まで入力している場合には
COMPREPLY=($(compgen -W "-foo -bar -buzz" -- "-")) となります。compgen だけを動作させると以下のようになります
$ compgen -W "-foo -bar -buzz" -- "-"
-foo
-bar
-baz
$ compgen -W "-foo -bar -buzz" -- "-b"
-bar
-baz
$ compgen -W "-foo -bar -buzz" -- "f"
$ echo $?
1 候補が何もなかった場合に、終了ステータスが1になります。

ではよく使うオプションについて説明します。

-W文字列
引数で与えられた文字列を「単語分割」($IFS のいずれかの文字で区切る)して、それを候補とみなします。 補完候補の中に空白が含まれる場合は、IFSを適宜変更しておく必要があります。何を言ってるかわからない人は諦めてくださいw-P文字列
引数で与えられた文字列を、補完候補それぞれの先頭に挿入します。

例えばディレクトリ名を ":" 区切りで入力するとします。…

タブ補完(その0) - Bash Advent Calendar - Day 11

ネタがない。
準備 基本的にありものを使ってね、というのは嫌なのですが、まずは bash-completion というパッケージを導入するのが近道です。そこで共通で使われてる関数達が超絶便利なんです。とりあえず、それをインストールしたら今日はおしまいです。続きは明日。
仕様 ユーザーが入力している情報 とりあえず、自作関数で補完をしようとした場合、以下の変数のお世話になります。

COMP_WORDS
現在入力中の行が、単語分割されて、配列として格納されています。ただし、単語分割は Bash の文法とは関係なく、readline というライブラリが好き勝手に分割してくれます。COMP_WORDBREAKS
上記 readline ライブラリが単語と単語の区切りとみなす文字のリスト。 参照オンリーです。この値を変更することは可能ですが、この値を変更しても readline ライブラリは何もしてくれません。昔は変更すると Bash が死んで楽しかったのですが、最近はそんなことはさすがにないみたいです。COMP_CWORD
現在カーソルがある場所の、その単語が COMP_WORDS で格納されている位置。COMP_LINE
現在入力中の行全体。COMP_POINT
現在のカーソルの場所、COMP_LINEでの文字位置に相当(0スタートなので、行末にカーソルがある場合はCOMP_POINTの長さに同じ) 他にも少し、名前が COMP_ で始まる変数があるのですが、使い道がよくわかりません。
とりあえず ${COMP_WORDS[COMP_CWORD]} で「何となく」のカーソル位置の単語が取れるのですが、COMP_WORDBREAKS に中の文字がカーソル位置の単語に含まれている場合にうまくいきません。 補完候補をユーザーに返す方法 COMPREPLYという配列変数に文字列をセットすると、それらが補完候補とみなされます。これはグローバル変数なので、なんの気にもせず上書きしてオッケーです。 TABでの補完時に自作関数が使われるようにする complete -F 関数名 [-o オプション [-o オプション]... ] コマンド名 で登録が可能です。オプションは以下から複数指定が可能です、というか機能的にどうみても複数指定可能じゃないとおかしいだけど、man ページ読んだだけじゃ分からな…

拡張case文 - Bash Advent Calendar - Day 10

今日も本当に1行もスクリプトも書いてないし、なんの検証もしてない状態でこのブログを書き始めました。途中で破綻しないといいんですけどね。
そもそも、今日のお題はどちらかというと extglob なので、"c" の順番じゃないんじゃないかと思うんですが、所詮は個人でやってる Advent Calendar、細かいことは気にしないでください。

もし気にする方は、代わりにお題をください。いや、気にしない方もこれ読んだらお題をください。
globってなんぞや 今日の本当のお題である extglob という単語は拡張 extended glob の略です、多分。じゃ glob って何って言ったら、そりゃ Wikipedia でも参照してください。Wikipedia の当該エントリ曰く、"Global command" の略だそうです。

で、世間一般では、よく言われるのはワイルドカードってやつですね。POSIX だと、単に「パターンマッチング」とか呼ばれてるアレです。man 7 glob でもいけるかもしれません。あの、
*任意の0文字以上マッチ?任意の1文字にマッチ[c...]ブラケット内のいずれか1文字([a-z]のような範囲指定も可)にマッチ という、単純なやつです。で、こんなの拡張するぐらいだったら正規表現使えばいいじゃんと、世界中から総ツッコミが入りそうなところですが、シェルスクリプトでは "echo ファイル*" みたいなパス名マッチングだけでなく
${parameter%pattern}
case ... in pattern) ... と、それ以外にも Bash では
[[ str == pattern ]] とか pattern が使われるところがあるので、これを拡張すると他のLLは嫌いだけどシェルスクリプトの最新機能は好きとかいう偏食家に大いに受けるわけです。ちなみに最後のは、複雑なことをしたければ [[ str =~ regex ]] 使ってください、と言いたいところだけど例のごとく Bash は遅いので、sed とか awk, grep を使ったほうが無難です。
case文 簡単に。書式としては case WORD in
  pattern) expression;;
  pattern | p…

Base64 decoder - Bash Advent Calendar - Day 9

むか~し、このネタをどこかに書いた気がするので、既に見たことがあったらゴメンナサイ。何度も書くけどネタがないんです。ネタください。
Base64の仕様RFC3548読んでください。以上。だって、ここ読むような人は大体知ってるか、知らなくてもこの後読めば分かるでしょ。

では何なので、エンコーダーの場合

3 octet (24 bit, ASCII char 3 文字分) を 6bit のかたまり x 4 に展開各 6 bit を以下の文字に変換0 〜 25: "A" 〜 "Z"26 〜 51: "a" 〜 "z"52 〜 61: "0" 〜 "9"62: "+", 63: "/" 処理の最後、入力が 1 octet もしくは 2octet しかない場合には "=" で padding という感じで変換します。これでも意味不明なら、Wikipedia でもなんでも、適当に検索して自分で調べてね。 Bashの64進数 前回、基数が37以上の場合を説明しませんでしたが、下記64進数のマッピングから類推してください。で、64進数のマッピングは
0 〜 9: "0" 〜 "9"10 〜 35: "a" 〜 "z"36 〜 61: "A" 〜 "Z"62: "@", 63: "_"  となっております。そんなわけで、上のマッピングのの違いを吸収してあげるだけで、Base64デコーダーが算術式評価で簡単に作れる、はず。paddingとか面倒だけど。 検証用入力値 "Bash" AND "base64" で検索すると出てくる数々の、「コマンドライン上でBase64」を処理する方法。全然、Bash スクリプトじゃないじゃないか。そこですぐスクリプトが検索結果に出てきたらネタにならないんですけどね。
とにかく、検索したところ base64 コマンドを使えと。まんまですね。というわけで $ echo "Hel…

Misc - Bash Advent Calendar - Day 8

今までちょっと説明が足りなかったところのランダムな補足。正直、既に息切れなので小休止。

配列変数の展開 特定の要素に対する文字列展開は、普通の変数の文字列展開と一緒なので省略。

${var[@]:-str}
var が存在し1個以上あれば ${var[@]} に同じ、そうでなければ str になる。var の中身を見て、それが空白文字だったら str に置きかわる、という動作をするわけではない。

${var[@]-str}
var が存在し1個以上あれば ${var[@]} に同じ、そうでなければ str になる。中身がない空の配列でも str に置き代わってしまうという残念な動作。仕様としては未定義っぽいけど。

${var[@]:?str}, ${var[@]:+word}
上と同様。

${var[@]:=str}
エラーになる。正直、エラーにならない場合、どんな動作をすべきか思いつかないので、妥当な仕様だと思う。

${var[@]:offset}, ${var[@]:offset:length}
offset 番目から length 個の要素を取り出す。実は同様に ${@:offset} とか ${@:offset:length} で、位置パラメータを部分的に取得できるんだけど、普通は shift 使えということで目にすることはまずないでしょう。残念ながら、各要素の部分文字列展開をしたい場合には、for ループで回すしかない。

${#var[@]}
配列の要素数になる。「各要素の文字列の長さ」が返ってくるわけではない。残念だけど、じゃぁ、配列の要素数を返すのに他に適当な記法があるかといえば、たしかにこれがベストと言わざるを得ないでしょう。

${var[@]#str}, ${var[@]##str}, ${var[@]%str}, ${var[@]%%str}
それぞれの要素に対して、先頭または末尾にマッチする文字列が削除、その結果を返します。

${var[@]/pattern/str}, ${var[@]//pattern/str}
それぞれの要素に対して、パターンにマッチした文字列を置換して、その結果を返します。

${var[@]^pattern}, ${var[@]^^pattern}, ${var[@],pattern}, ${var[@],,patte…

算術式(その2) - Bash Advent Calendar - Day 7

昨日の続き、算術式内の変数について。

数値インデックスの配列変数(連想配列じゃない方の配列)にも対応しています。なので
$ declare foo=(1 2 3)
$ declare -i bar="foo[0] + foo[1] + foo[2]"
$ declare -p bar
declare -i bar="6" これまでと同様、再帰的に算術式として評価されるので
$ declare foo=(0 1)
$ declare bar="foo[0] + foo[1]"
$ declare -i buzz
$ buzz="bar[0] + bar[1]"
$ declare -p buzz
declare -i buzz="3" これで何が嬉しいのか書いてる本人もよく分かりませんが、とりあえずこんなこともできますよと。
基数#文字列 これで昨日のリストの1〜4を説明しました。6は単純な数値なので、これで最後、今日のメインディッシュ "基数#文字列" です。

これまでに説明した8進数、16進数、そして普段使いの10進数以外に使いそうなのは2進数ぐらいですかね。
$ declare -i foo
$ foo="2#10100101"  # 0xA5
$ declare -p foo
declare -i foo="165" これまで同様、何が嬉しいのかよくわからないですね。

11進数から36進数までは数字とアルファベット(大文字、小文字の区別なし)で数値を表現します。すなわち
$ declare -i foo
$ foo="36#sekine"
$ declare -p foo
declare -i foo="1717524842" 36進数とか言われても、これで正しいかどうなのかよく分からんですね。せっかくなのでこれまでの算術式やら、配列など色々駆使して、この値を検証してみましょう。
$ char_code_hex=($(echo -n 'asekine' | od -A n -t x1))
$ declare -i -a char_code_hex…

算術式 - Bash Advent Calendar - Day 6

Array, Associative Array と予定どおり A から順にネタを消化中で、今日は Arithmetic Expression。辞書順的に後戻りですが、細かいことは気にしないでいきましょう。
扱える範囲 とりあえず小数は扱えません。小数を使うような計算が必要なときは素直に他のLLを使いましょう。

扱える整数の範囲は実装依存で、いわゆる long の範囲です。最近の 64bit 環境であれば -2^63 〜 2^63 - 1 なので、十分実用に堪えると思います。
算術式が使われる場所 ちゃんとチェックしてないので抜けがあるかもしれませんが

数値変数への代入 (local, readonly, declare, typeset コマンドを含む)let "算術式"((算術式))for ((算術式; 算術式; 算術式)) do ... done$((算術式))  ($[算術式] もいまだに使えると思いますが、そのうち使えなくなるはず)配列変数[算術式] や ${配列変数[算術式]} などの配列変数のインデックス。部分文字列展開: ${変数:算術式} および ${変数:算術式:算術式}数値比較 [[ 算術式 OP 算術式 ]] (OP は -gt, -ge, -lt, -le のいずれか)
ぐらいですかね。最後のは undocumented なので、期待したとおりに動かなくても当方は関知いたしません。
数値変数 説明の都合上数値変数の定義だけ。

declare -i で変数を数値変数として定義できます。
$ declare -i foo=1
$ declare -p foo
declare -i foo="1" declare -p の結果の右辺が "1" とクォートされているので文字列っぽく見えますが、ここはそういうものだと(今日のところは)思っててください。declare の後にちゃんと -i と付いているので、これは確かに数値変数です。

ちなみに、配列 / 連想配列とは直行する概念なので
$ declare -ia foo
$ declare -iA bar
$ declare -p foo bar
declare -ai foo='()'
declare -Ai bar=…

連想配列 - Bash Advent Calendar - Day 5

今日は連想配列についてです。なんか予想以上に読者が少なくてモチベーションが上がりません。
初期化 基本的な書式は数値インデックスの配列(以下「普通の配列」)と変わりません。declare で指定する引数が大文字の A になるぐらい。
$ declare -A foo
$ declare -A foo=() ただし、普通の配列の場合にはインデックスを指定しない場合には 0 から順に勝手にインデックス値を決めてくれましたが、(ある意味当然ですが)連想配列の場合は必ずインデックス文字列が必要です。
$ declare -A foo=("bar" "baz")
bash: foo: bar: 連想配列を設定するときには添字をつけなければいけません
bash: foo: baz: 連想配列を設定するときには添字をつけなければいけません というわけで、 $ declare -A foo=(["bar"]="BAR" ["baz"]="BAZ")
$ declare -p foo
declare -A foo='([bar]="BAR" [baz]="BAZ" )' でメデタシメデタシとなります。
一つ普通の配列と違うことは、declare 文(または typeset)が省略できないことです。 $ unset foo bar baz
$ foo=(["bar"]="BAR" ["baz"]="BAZ")
$ declare -p foo
declare -a foo='([0]="BAZ")' 一見すると意味不明かもしれませんが、これについては後述します。Bash の仕様としては、宣言されてない変数に対して配列を代入しようとした場合、それは常に普通の配列として初期化されるということになります。 値の代入、追加 細かいところはDay 1での説明を参照してください。常にインデックスが必要な以外は普通の配列と一緒です。+= の糞仕様も健在です。 $ declare -A foo=(["bar"…

配列操作の性能 - Bash Advent Calendar - Day 4

昨日まで配列について説明してきましたが、残念なお知らせです。

遅い、とにかく遅い。私が初めてこの機能を知った時、これで多くの場面で sed, awk が要らなくなり、fork() しなくてすむことによりスクリプトが高速になるのではないかと思いました。で、試しにとあるスクリプトを書き換えてみたのですが、むしろかなり遅くなってしまいました。

bash, ksh, zsh の速度比較 - 拡張 POSIX シェルスクリプト Advent Calendar 2013でも述べられている通り、bash さん遅いんです(配列操作に限りません)。ここで bash と他のシェルを比べた時に、他のシェルに負けると悔しいので、AWK と対決。
#!/bin/bash
DICT=/usr/share/dict/american-english
declare -a english_words
declare dict_content
function load_dict() {
  local IFS=$'\n'
  dict_content="$(< ${DICT})"
  english_words=(${dict_content})
  readonly dict_content english_words
}
function replace_st_to_ts() {
  local unused=("${english_words[@]//st/ts}")
}
function now() {
  date +%s.%N
}
load_dict
start=$(now)
for ((i=0;i<10;i++)); do
  replace_st_to_ts
done
end=$(now)
echo -n "Bash: "
bc -l <<< "${end} - ${start}"
start=$(now)
for ((i=0;i<10;i++)); do
  awk '{gsub(/st/, "ts"); print}' <<< "${dict_content}" > /d…

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

すでに3日めにして当初の勢いは全くなし。このままずっと続く気がしません。

閑話休題、変数展開との組み合わせで面白いことできるんじゃないの的なお話。
変数展開 普通の変数展開については既に知ってる前提で。で、配列変数においても変数展開が可能です。

まずは特定要素の変数展開をば。
$ foo=("bar" "baz")
$ echo "${foo[0]:-qux}"
bar
$ echo "${foo[2]:-qux}"
qux と期待通りの動作をしてくれます。デフォルト値の設定も同様に
$ foo=("bar" "baz")
$ : "${foo[2]:=qux}"
$ declaare -p foo
declare -a foo='([0]="bar" [1]="baz" [2]="qux")'
$ : "${foo[1]:=quux}"
$ declaare -p foo
declare -a foo='([0]="bar" [1]="baz" [2]="qux")' と期待通りの動作ですね。

その他にも ${var:?word} ${var:+word} も同様に想像通りの動作をしてくれます。
応用編 特定要素ではなくインデックスに @ を指定して変数展開をすると、文字列処理が一気に出来ます。Python ユーザーあたりにはウケがよさそうな $ foo=("bar" "baz")
$ qux=("${foo[@]//a/A}")
$ declare -p qux
declare -a qux='([0]="bAr" [1]="bAz")' みたいなことができてしまいます。これは配列の各々の要素において "a" を "A" に置換しています。このように for ループ無しで一気に文字列を処理できます。
これって Python で…

配列(その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 この例では * と @ に違いがありませんが、その違いは "$*" と "$…

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

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 は一般人が思ってるよりは大きいのですが、当該サイトがその妥当性を考慮してダウンロードリクエストの…

配列 - 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=(…