2015年11月30日

使用Erlang產生大量的process

假設我們寫了一個檢查登入帳號、密碼的function,想要測試如果該function 被大量同時呼叫的話,系統會不會發生什麼狀況?這一方面也可以稍微測試一下資料庫同時能撐的連線量,因此我們可以撰寫下列的程式碼來做測試。

下列為簡易的檢查密碼的function(註:為求簡潔,忽略了帳號不存在的狀況)

%至mnesia db取帳號後檢查密碼
check(Id, Pwd)->
    [Account] = mnesia:dirty_read(account, Id),
    case Account#account.pwd of
        Pwd->
            true;
        _ ->
            false
    end

下列為產生大量concurrent process來呼叫check(Id,Pwd)

stress_test(NProcs)->
    CtrF = fun() ->
        check(UserId, Pwd) %呼叫檢查帳號、密碼的function
    end,
    [spawn(CtrF) || _X <- lists:duplicate(NProcs, 0)].

上述的程式碼會產生數量為NProcs的Process來執行CtrF 的function,重點會是在最後一行 [spawn(CtrF) || _ <- lists:duplicate(NProcs, 0)],這是使用erlang的list comprehensions來控制程式的執行(有點類似java的for迴圈),我們先了解一下最簡單的list comprehensions,我們先看下列的code,下列的code會在console上印出1~6,

[io:format("~p~n",[X]) || X <- [1,2,3,4,5,6]]

上述執行後畫面上會出現

1
2
3
4
5
6
[ok,ok,ok,ok,ok,ok]

說明: 有1個List,裡面有6個成員分別是1,2,3,4,5,6,接著我們一次取一個成員出來並將之bind到X,每次取出成員後都會執行io:format將X的值給印出來

接下來我們來看要如何產生list裡的內容?假設我們要產生1個包含10個0的list,可以使用lists:duplicate/2,如下

lists:duplicate(10, 0)
執行結果為
[0,0,0,0,0,0,0,0,0,0]

最後我們再回頭來看 [spawn(CtrF) || _X <- lists:duplicate(NProcs, 0)].分解步驟如下

  1. 假設NProcs是1萬(我們要產生1萬個process來同時呼叫check(Id, Pwd))
  2. 首先用lists:duplicate/2來產生含1萬個0的list ex[0,0,0,0.....0],要用1來取代0也可以,反正重點在於list的長度而非list的內容
  3. 每次從list取出1個,並將bind到_X。為何X前要加底線?因為我們並不在意從list取出來的值是多少,所以變數X並不會用到,所以要在X前加底線,否則編譯器會出現緊告
  4. 最後每次都會執行spawn(CtrF),spawn在erlang裡是指會建立1個concurrent process,並執行CtrF function

短短幾行就可達到想要的處理邏輯,這就是Erlang語言的特色,不過還是得先適應function programming的思考方式才行

如何使用 MongoDB

以下記錄如何以 cli 方式啟動 MongoDB,以及基本的 CRUD operations。

安裝 MongoDB

要在 centos linux 環境安裝 MongoDB,除了用 yum 安裝之外,還可以直接在 網頁 下載 tgz 檔案,以 CentOS 6 為例,選擇作業系統版本 RHEL 6 Linux 64-bit,點擊 DOWNLOAD 後,就可以下載檔案 mongodb-linux-x86_64-rhel62-3.0.7.tgz。

該壓縮檔裡面,只有一個 bin 目錄,裡面是 mongodb 的所有執行檔,我們將裡面的資料解壓縮到 /usr/share/mongodb 裡面。

tar zxvf mongodb-linux-x86_64-rhel62-3.0.7.tgz /usr/share/
cd /usr/share
mv mongodb-linux-x86_64-rhel62-3.0.7 mongodb
cd mongodb

mkdir -p data/db
mkdir -p logs

執行 mongod ,同時指定 db 以及 logfile 路徑,就可以啟動 mongodb。

/usr/share/mongodb/bin/mongod --dbpath=/usr/share/mongodb/data/db -logpath=/usr/share/mongodb/logs/mongodb.log

這是直接將啟動的參數放在 mongod 命令列中,也可以製作一個設定檔 mongodb.conf,並以 -f 指定設定檔就可以了。

/usr/share/mongodb/bin/mongod -f /etc/mongod.conf

如果希望在背景啟動 mongodb,必須加上 --fork 的參數。

[root@server mongodb]# /usr/share/mongodb/bin/mongod -dbpath=/usr/share/mongodb/data/db -logpath=/usr/share/mongodb/logs/mongodb.log --fork
about to fork child process, waiting until server is ready for connections.
forked process: 9544
child process started successfully, parent exiting
[root@server mongodb]# ps aux|grep mongo
root      9544  1.0  1.3 499800 50308 ?        Sl   15:36   0:00 /usr/share/mongodb/bin/mongod -dbpath=/usr/share/mongodb/data/db -logpath=/usr/share/mongodb/logs/mongodb.log --fork
root      9556  0.0  0.0 105388   912 pts/0    S+   15:36   0:00 grep --color mongo

mongod 的參數分為 一般參數、 windows參數、replication 參數、replica set 參數以及隱藏參數。常用的參數如下:

  1. dbpath
  2. logpath
  3. logappend: logfile 以 append 方式附加到檔案後面
  4. bind_ip
  5. port
  6. fork: 以 daemon 方式啟動 mongod
  7. journal: 開啟記錄檔功能
  8. syncdelay: 系統同步更新 disk 的時間,預設為 60s
  9. directoryperdb: 每個 db 儲存在單獨的目錄中,這樣比較容易區分不同的資料庫
  10. maxConns: 最大的 client 連線數
  11. repairpath: 執行 repair 的臨時目錄,如果沒有啟用 journal 功能,異常當機重新啟動後,必須執行 repair

如果要停止 mongod,可使用 db.shutdownServer() 指令

# mongo
> use admin;
switched to db admin
> db.shutdownServer();
2015-11-09T17:23:30.415+0800 I NETWORK  DBClientCursor::init call() failed
server should be down...
2015-11-09T17:23:30.417+0800 I NETWORK  trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2015-11-09T17:23:30.417+0800 W NETWORK  Failed to connect to 127.0.0.1:27017, reason: errno:111 Connection refused
2015-11-09T17:23:30.417+0800 I NETWORK  reconnect 127.0.0.1:27017 (127.0.0.1) failed failed couldn't connect to server 127.0.0.1:27017 (127.0.0.1), connection attempt failed

不能用 kill -9 PID 直接將 mongodb process 刪除,這樣可能會導致 mongodb 的資料損壞。只能透過 kill -2 PID 或 kill -15 PID 停止 mongod process。

CRUD

mongo: MongoDB Shell 是 MongoDB 操作及管理的界面,同時也是一個 JavaScript shell 可以執行 js code。

以下幾點是 mongodb 的一些基本使用概念

  1. mongo 不需要預先建立 collection,在第一次儲存資料時,就會自動建立
  2. 文件資料可以儲存任何結構的資料,也不需要修改 schema
  3. 每一次儲存資料,都會產生一個欄位 _id,該欄位可儲存任何資料類型,未指定時會自動產生,資料類型為 ObjectId,ObjectId 裡面就隱藏了產生這筆資料的時間。

    ObjectId: 是 12 bytes BSON 格式,包含以下資訊:

    1. 4-byte: 表示 Unix epoch 到現在的秒數,也就是 timestamp
    2. 3-byte: machine identifier
    3. 2-byte: process id
    4. 3-byte: counter,啟始值為一個亂數值
  • 儲存

    > user1={name:"sun", age: 12};
    { "name" : "sun", "age" : 12 }
    > user2={name:"moon", age: 14};
    { "name" : "moon", "age" : 14 }
    > 
    > db.solar.save(user1);
    WriteResult({ "nInserted" : 1 })
    > db.solar.save(user2);
    WriteResult({ "nInserted" : 1 })
    > 
    > db.solar.find();
    { "_id" : ObjectId("56416af4997bc79c93b12e23"), "name" : "sun", "age" : 12 }
    { "_id" : ObjectId("56416af4997bc79c93b12e24"), "name" : "moon", "age" : 14 }
    
  • 修改

    > db.solar.update({name:"moon"},{$set:{age:"16"}});
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > db.solar.find();
    { "_id" : ObjectId("56416af4997bc79c93b12e23"), "name" : "sun", "age" : 12 }
    { "_id" : ObjectId("56416af4997bc79c93b12e24"), "name" : "moon", "age" : "16" }
    
  • 刪除

    > db.solar.remove({name:"moon"});
    WriteResult({ "nRemoved" : 1 })
    > db.solar.find();
    { "_id" : ObjectId("56416af4997bc79c93b12e23"), "name" : "sun", "age" : 12 }
    > db.solar.remove({name:"moon"});
    WriteResult({ "nRemoved" : 0 })
    > db.solar.find();
    { "_id" : ObjectId("56416af4997bc79c93b12e23"), "name" : "sun", "age" : 12 }
    
  • _id 欄位可以自訂,但不能重複

    > user3={_id:"id_star", name:"star", age: 18};
    { "_id" : "id_star", "name" : "star", "age" : 18 }
    > db.solar.save(user3);
    WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "id_star" })
    >
    > user4={_id:"id_star", name:"star2", age: 20};
    { "_id" : "id_star", "name" : "star2", "age" : 20 }
    > db.solar.save(user4);
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > 
    > db.solar.find();
    { "_id" : ObjectId("56416af4997bc79c93b12e23"), "name" : "sun", "age" : 12 }
    { "_id" : "id_star", "name" : "star2", "age" : 20 }
    > db.solar.insert(user4);
    WriteResult({
      "nInserted" : 0,
      "writeError" : {
          "code" : 11000,
          "errmsg" : "E11000 duplicate key error index: test.solar.$_id_ dup key: { : \"id_star\" }"
      }
    })
    
  • 查詢後回傳 cursor,it可取得下一頁資料

    > for( var i=1; i<=40; i++ ) db.users.save( {userid:"user_"+i} );
    WriteResult({ "nInserted" : 1 })
    > db.users.find( );
    { "_id" : ObjectId("5641882a997bc79c93b12e39"), "userid" : "user_1" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3a"), "userid" : "user_2" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3b"), "userid" : "user_3" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3c"), "userid" : "user_4" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3d"), "userid" : "user_5" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3e"), "userid" : "user_6" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3f"), "userid" : "user_7" }
    { "_id" : ObjectId("5641882a997bc79c93b12e40"), "userid" : "user_8" }
    { "_id" : ObjectId("5641882a997bc79c93b12e41"), "userid" : "user_9" }
    { "_id" : ObjectId("5641882a997bc79c93b12e42"), "userid" : "user_10" }
    { "_id" : ObjectId("5641882a997bc79c93b12e43"), "userid" : "user_11" }
    { "_id" : ObjectId("5641882a997bc79c93b12e44"), "userid" : "user_12" }
    { "_id" : ObjectId("5641882a997bc79c93b12e45"), "userid" : "user_13" }
    { "_id" : ObjectId("5641882a997bc79c93b12e46"), "userid" : "user_14" }
    { "_id" : ObjectId("5641882a997bc79c93b12e47"), "userid" : "user_15" }
    { "_id" : ObjectId("5641882a997bc79c93b12e48"), "userid" : "user_16" }
    { "_id" : ObjectId("5641882a997bc79c93b12e49"), "userid" : "user_17" }
    { "_id" : ObjectId("5641882a997bc79c93b12e4a"), "userid" : "user_18" }
    { "_id" : ObjectId("5641882a997bc79c93b12e4b"), "userid" : "user_19" }
    { "_id" : ObjectId("5641882a997bc79c93b12e4c"), "userid" : "user_20" }
    Type "it" for more
    > it
    { "_id" : ObjectId("5641882a997bc79c93b12e4d"), "userid" : "user_21" }
    { "_id" : ObjectId("5641882a997bc79c93b12e4e"), "userid" : "user_22" }
    { "_id" : ObjectId("5641882a997bc79c93b12e4f"), "userid" : "user_23" }
    { "_id" : ObjectId("5641882a997bc79c93b12e50"), "userid" : "user_24" }
    { "_id" : ObjectId("5641882a997bc79c93b12e51"), "userid" : "user_25" }
    { "_id" : ObjectId("5641882a997bc79c93b12e52"), "userid" : "user_26" }
    { "_id" : ObjectId("5641882a997bc79c93b12e53"), "userid" : "user_27" }
    { "_id" : ObjectId("5641882a997bc79c93b12e54"), "userid" : "user_28" }
    { "_id" : ObjectId("5641882a997bc79c93b12e55"), "userid" : "user_29" }
    { "_id" : ObjectId("5641882a997bc79c93b12e56"), "userid" : "user_30" }
    { "_id" : ObjectId("5641882a997bc79c93b12e57"), "userid" : "user_31" }
    { "_id" : ObjectId("5641882a997bc79c93b12e58"), "userid" : "user_32" }
    { "_id" : ObjectId("5641882a997bc79c93b12e59"), "userid" : "user_33" }
    { "_id" : ObjectId("5641882a997bc79c93b12e5a"), "userid" : "user_34" }
    { "_id" : ObjectId("5641882a997bc79c93b12e5b"), "userid" : "user_35" }
    { "_id" : ObjectId("5641882a997bc79c93b12e5c"), "userid" : "user_36" }
    { "_id" : ObjectId("5641882a997bc79c93b12e5d"), "userid" : "user_37" }
    { "_id" : ObjectId("5641882a997bc79c93b12e5e"), "userid" : "user_38" }
    { "_id" : ObjectId("5641882a997bc79c93b12e5f"), "userid" : "user_39" }
    { "_id" : ObjectId("5641882a997bc79c93b12e60"), "userid" : "user_40" }
    

    可用 while 搭配 cursor.hasNext 進行迴圈處理

    > var cursor = db.users.find();
    > while ( cursor.hasNext() ) printjson(cursor.next());
    { "_id" : ObjectId("5641882a997bc79c93b12e39"), "userid" : "user_1" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3a"), "userid" : "user_2" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3b"), "userid" : "user_3" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3c"), "userid" : "user_4" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3d"), "userid" : "user_5" }
    //..... 省略
    

    也可以用 forEach

    > db.users.find().forEach(printjson);
    { "_id" : ObjectId("5641882a997bc79c93b12e39"), "userid" : "user_1" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3a"), "userid" : "user_2" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3b"), "userid" : "user_3" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3c"), "userid" : "user_4" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3d"), "userid" : "user_5" }
    

    也可以把 cursor 當成陣列

    > var cursor = db.users.find();
    > printjson(cursor[5]);
    { "_id" : ObjectId("5641882a997bc79c93b12e3e"), "userid" : "user_6" }
    > while ( cursor.hasNext() ) printjson(cursor.next());
    > printjson(cursor[6]);
    { "_id" : ObjectId("5641882a997bc79c93b12e3f"), "userid" : "user_7" }
    

    _id 裡面就隱藏了資料建立時間

    > db.users.find().forEach(function (doc){ d = doc._id.getTimestamp(); print(d.getFullYear()+"-"+(d.getMonth()+1)+"-"+d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds()) })
    2015-11-10 14:1:14
    2015-11-10 14:1:14
    2015-11-10 14:1:14
    2015-11-10 14:1:14
    //.... 省略
    

    條件查詢

    > db.users.find({userid:"user_10"}).forEach(printjson);
    { "_id" : ObjectId("5641882a997bc79c93b12e42"), "userid" : "user_10" }
    

    自訂回傳欄位的條件查詢

    > db.users.find({userid:"user_10"},{_id:true}).forEach(printjson);
    { "_id" : ObjectId("5641882a997bc79c93b12e42") }
    

    findOne: 只取得第一筆資料

    > printjson(db.users.findOne({userid:"user_10"}));
    { "_id" : ObjectId("5641882a997bc79c93b12e42"), "userid" : "user_10" }
    

    limit: 限制回傳資料的筆數

    > db.users.find().limit(3);
    { "_id" : ObjectId("5641882a997bc79c93b12e39"), "userid" : "user_1" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3a"), "userid" : "user_2" }
    { "_id" : ObjectId("5641882a997bc79c93b12e3b"), "userid" : "user_3" }
    

進階查詢

首先準備測試資料

> db.mate.save({userid:"john", username:"John Lin", city:"Taipei", age: 20, room:"301"});
WriteResult({ "nInserted" : 1 })
> db.mate.save({userid:"muder", username:"Muder Yen", city:"Taichung", age: 19, room:"301"});
WriteResult({ "nInserted" : 1 })
> db.mate.save({userid:"mary", username:"Mary Wang", city:"Tainan", age: 23, room:"601"});
WriteResult({ "nInserted" : 1 })
> db.mate.save({userid:"celina", username:"Celina Lin", city:"Taichung", age: 20, room:"601"});
WriteResult({ "nInserted" : 1 })
> db.mate.save({userid:"lunar", username:"Lunar Wang", city:"I Lan", age: 22, room:"302"});
WriteResult({ "nInserted" : 1 })
> db.mate.save({userid:"danny", username:"Danny Huang", city:"Taipei"});
WriteResult({ "nInserted" : 1 })
> db.mate.save({userid:"johnny", username:"Johnny Lo", city:"Taipei", age: null});
WriteResult({ "nInserted" : 1 })
> db.mate.find();
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e76"), "userid" : "muder", "username" : "Muder Yen", "city" : "Taichung", "age" : 19, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e79"), "userid" : "lunar", "username" : "Lunar Wang", "city" : "I Lan", "age" : 22, "room" : "302" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

$gt 超過 22 歲的人

> db.mate.find( {age:{ $gt: 22}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }

$gte 大於等於 22 歲

> db.mate.find( {age:{ $gte: 22}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e79"), "userid" : "lunar", "username" : "Lunar Wang", "city" : "I Lan", "age" : 22, "room" : "302" }

$ne: 不等於 22

> db.mate.find( {age:{ $ne: 22}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e76"), "userid" : "muder", "username" : "Muder Yen", "city" : "Taichung", "age" : 19, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

$mod: 除以10餘0

> db.mate.find( {age:{ $mod: [10,0]}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }

$all: 完全等於 20

> db.mate.find( {age:{ $all:[20]}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }

$in: 22 或 23 歲

> db.mate.find( {age:{ $in:[22, 23]}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e79"), "userid" : "lunar", "username" : "Lunar Wang", "city" : "I Lan", "age" : 22, "room" : "302" }

$nin: 不等於 22 或 23

> db.mate.find( {age:{ $nin:[22, 23]}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e76"), "userid" : "muder", "username" : "Muder Yen", "city" : "Taichung", "age" : 19, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

$exists: 不存在 age 欄位

> db.mate.find( {age:{$exists:false}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }

null: age 欄位為 null,不存在 age 也包含在內

> db.mate.find( {age:null} );
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

有 age 欄位且 age 為 null

> db.mate.find( {age:{$in:[null], $exists:true}} );
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

$regex: username 以 M 為開頭

> db.mate.find( {username:{ $regex:/^M.*/ }} );
{ "_id" : ObjectId("5641980c997bc79c93b12e76"), "userid" : "muder", "username" : "Muder Yen", "city" : "Taichung", "age" : 19, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }

$not: username 不以 M 為開頭

> db.mate.find( {username:{ $not:/^M.*/ }} );
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e79"), "userid" : "lunar", "username" : "Lunar Wang", "city" : "I Lan", "age" : 22, "room" : "302" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

以 javascript function 進行搜尋

> f=function() {return this.age>22;}
function () {return this.age>22;}
> db.mate.find(f);
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }

count(): 計算資料筆數

> db.mate.find().count();
7

skip(): 跳過幾筆資料
limit(): 限制回傳筆數

> db.mate.find().skip(2).limit(2);
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
> db.mate.find().skip(3).limit(1);
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }

sort: 針對 age 排序

> db.mate.find().sort({age:1});
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }
{ "_id" : ObjectId("5641980c997bc79c93b12e76"), "userid" : "muder", "username" : "Muder Yen", "city" : "Taichung", "age" : 19, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e79"), "userid" : "lunar", "username" : "Lunar Wang", "city" : "I Lan", "age" : 22, "room" : "302" }
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }

> db.mate.find().sort({age:-1});
{ "_id" : ObjectId("5641980c997bc79c93b12e77"), "userid" : "mary", "username" : "Mary Wang", "city" : "Tainan", "age" : 23, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e79"), "userid" : "lunar", "username" : "Lunar Wang", "city" : "I Lan", "age" : 22, "room" : "302" }
{ "_id" : ObjectId("5641980c997bc79c93b12e75"), "userid" : "john", "username" : "John Lin", "city" : "Taipei", "age" : 20, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e78"), "userid" : "celina", "username" : "Celina Lin", "city" : "Taichung", "age" : 20, "room" : "601" }
{ "_id" : ObjectId("5641980c997bc79c93b12e76"), "userid" : "muder", "username" : "Muder Yen", "city" : "Taichung", "age" : 19, "room" : "301" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7a"), "userid" : "danny", "username" : "Danny Huang", "city" : "Taipei" }
{ "_id" : ObjectId("5641980c997bc79c93b12e7b"), "userid" : "johnny", "username" : "Johnny Lo", "city" : "Taipei", "age" : null }

以下為所有測試的指令

db.mate.save({userid:"john", username:"John Lin", city:"Taipei", age: 20, room:"301"});
db.mate.save({userid:"muder", username:"Muder Yen", city:"Taichung", age: 19, room:"301"});
db.mate.save({userid:"mary", username:"Mary Wang", city:"Tainan", age: 23, room:"601"});
db.mate.save({userid:"celina", username:"Celina Lin", city:"Taichung", age: 20, room:"601"});
db.mate.save({userid:"lunar", username:"Lunar Wang", city:"I Lan", age: 22, room:"302"});
db.mate.save({userid:"danny", username:"Danny Huang", city:"Taipei"});
db.mate.save({userid:"johnny", username:"Johnny Lo", city:"Taipei", age: null});

db.mate.find( {age:{ $gt: 22}} );

db.mate.find( {age:{ $gte: 22}} );

db.mate.find( {age:{ $ne: 22}} );

db.mate.find( {age:{ $mod: [10,0]}} );

db.mate.find( {age:{ $all:[20]}} );

db.mate.find( {age:{ $in:[22, 23]}} );

db.mate.find( {age:{ $nin:[22, 23]}} );

db.mate.find( {age:{$exists:false}} );

db.mate.find( {age:null} );

db.mate.find( {age:{$in:[null], $exists:true}} );

db.mate.find( {username:{ $regex:/^M.*/ }} );

db.mate.find( {username:{ $not:/^M.*/ }} );


f=function() {return this.age>22;}
db.mate.find(f);


db.mate.find().count();

db.mate.find().skip(2).limit(2);

db.mate.find().skip(3).limit(1);


db.mate.find().sort({age:1});

db.mate.find().sort({age:-1});

MapReduce

MongoDB 的 Map, Reduce 函數都可以用 javascript 實作,Map 函數中必須呼叫 emit(key, value),對 collection 中所有紀錄運算過一次之後,再將 {key, [value1, value2, ....]} 傳遞給 reduce 進行處理,最後可再利用 finalize(),針對 reduce 的結果做更進一步的處理。

在 mongo 中,可以用 mapReduce 或是 runCommand 進行 MapReduce,help mr 看到線上的簡略說明

> help mr

See also http://dochub.mongodb.org/core/mapreduce

function mapf() {
  // 'this' holds current document to inspect
  emit(key, value);
}

function reducef(key,value_array) {
  return reduced_value;
}

db.mycollection.mapReduce(mapf, reducef[, options])

options
{[query : <query filter object>]
 [, sort : <sort the query.  useful for optimization>]
 [, limit : <number of objects to return from collection>]
 [, out : <output-collection name>]
 [, keeptemp: <true|false>]
 [, finalize : <finalizefunction>]
 [, scope : <object where fields go into javascript global scope >]
 [, verbose : true]}

query: 在進行 Map 運算前,先針對 collection 進行一次資料篩檢
sort: 針對 collection 資料排序,可減少 reduce 的處理時間
limit: 限制由 collection 回傳的資料筆數
out: 儲存統計結果的 collection,如果不指定,會放在暫存的 collection 中,連線中斷後,會自動刪除
keeptemp: 是否要保留暫存的 collection
finalize: 對 reduce 的結果再進行一次處理
scope: 指定可在 map, reduce, finalize 中使用的 global 變數
verbose: 顯示詳細的統計時間資訊

首先準備 map 與 reduce function

> mfunction = function() { emit(this.room, 1 )};
function () { emit(this.room, 1 )}
> 
> rfunction = function(key, values) {
... var x = 0;
... values.forEach( function(v) { x += v; } );
... return x;
... }

function (key, values) {
var x = 0;
values.forEach( function(v) { x += v; } );
return x;
}

runCommand,並指定輸出到 mate_res

> 
> res = db.runCommand( {
... mapreduce:"mate",
... map: mfunction,
... reduce: rfunction,
... out: "mate_res"
... });
{
    "result" : "mate_res",
    "timeMillis" : 1,
    "counts" : {
        "input" : 7,
        "emit" : 7,
        "reduce" : 3,
        "output" : 4
    },
    "ok" : 1
}
> 
> db.mate_res.find();
{ "_id" : null, "value" : 2 }
{ "_id" : "301", "value" : 2 }
{ "_id" : "302", "value" : 1 }
{ "_id" : "601", "value" : 2 }

加上一個 finalize 函數,將結果變成 room 與 count

> ffunction = function(key,value) {return {room:key, count:value};}
function (key,value) {return {room:key, count:value};}
> res = db.runCommand( {
... mapreduce:"mate",
... map: mfunction,
... reduce: rfunction,
... out: "mate_res",
... finalize: ffunction
... });
{
    "result" : "mate_res",
    "timeMillis" : 1,
    "counts" : {
        "input" : 7,
        "emit" : 7,
        "reduce" : 3,
        "output" : 4
    },
    "ok" : 1
}
> 
> db.mate_res.find();
{ "_id" : null, "value" : { "room" : null, "count" : 2 } }
{ "_id" : "301", "value" : { "room" : "301", "count" : 2 } }
{ "_id" : "302", "value" : { "room" : "302", "count" : 1 } }
{ "_id" : "601", "value" : { "room" : "601", "count" : 2 } }

加上 query,過濾 mate collection,只需要大於等於 22 歲的人

> res = db.runCommand( {
... mapreduce:"mate",
... map: mfunction,
... reduce: rfunction,
... out: "mate_res",
... finalize: ffunction,
... query: {age:{$gte: 22}}
... });
{
    "result" : "mate_res",
    "timeMillis" : 1,
    "counts" : {
        "input" : 2,
        "emit" : 2,
        "reduce" : 0,
        "output" : 2
    },
    "ok" : 1
}
> 
> db.mate_res.find();
{ "_id" : "302", "value" : { "room" : "302", "count" : 1 } }
{ "_id" : "601", "value" : { "room" : "601", "count" : 1 } }

以下為測試指令的集合

mfunction = function() { emit(this.room, 1 )};

rfunction = function(key, values) {
    var x = 0;
    values.forEach( function(v) { x += v; } );
    return x;
}

res = db.runCommand( {
    mapreduce:"mate",
    map: mfunction,
    reduce: rfunction,
    out: "mate_res"
});

db.mate_res.find();


ffunction = function(key,value) {return {room:key, count:value};}
res = db.runCommand( {
    mapreduce:"mate",
    map: mfunction,
    reduce: rfunction,
    out: "mate_res",
    finalize: ffunction
});

db.mate_res.find();


res = db.runCommand( {
    mapreduce:"mate",
    map: mfunction,
    reduce: rfunction,
    out: "mate_res",
    finalize: ffunction,
    query: {age:{$gte: 22}}
});

db.mate_res.find();

2015年11月25日

使用CocoaPods管理XCode第三方開放原始碼套件

前言


以往在XCode中使用開放原始碼的第三方套件時, 總是要手動將第三方套件的原始碼放入XCode專案中。 然而,一旦第三方套件一多,逐一更新遂成了麻煩的瑣碎事。
因此,便有了專門管理第三方套件的CocoaPods。

在Mac下安裝CocoaPods


安裝CocoaPods需要透過RubyGems 。 不過Mac OS已經預先安裝好了,因此就不再贅述。
請執行以下指令,安裝CocoaPods:
sudo gem install cocoapods

建立Podfile


接著是使用CocoaPods安裝第三方套件的步驟:
首先,在專案目錄下 (與xcodeproj檔案同一個目錄) 建立一個純文字檔案,名為Podfile



這個檔案將描述所有你所使用的第三方套件。
接著,請看第三方套件的網頁,說明其Podfile的寫法。 我們以AlamofireStarscream這兩個套件為例:

Alamofire:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Alamofire', '~> 3.0'
Starscream:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Starscream', '~> 1.0.0'
注意到了嗎? 前幾行宣告支援的環境,因此大同小異。
而最重要的在於這行:
pod '套件名稱' 
因此,我們的podfile檔案可以撰寫如下: (此Podfile描述使用了Starscream, Alamofire, 以及FMDB)
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'Starscream', '~> 1.0.0' pod 'Alamofire', '~> 3.0' pod 'FMDB'

特別注意: 若你的專案使用swift語言,請務必加上use_frameworks!
最後,執行
pod install
CocoaPods將會開始下載並設定這些第三方套件至你目前的專案。
你可以看到終端機上顯示類似以下訊息:



使用xcworkspace檔案開啟專案

第三方套件安裝完成後, 很重要的,往後開發此專案時,必須改為開啟xcworkspace檔案



開啟xcworkspace檔案後,你可以在Pods專案中看到所有第三方套件。



執行編譯時,這些第三方套件的原始碼也會一同編譯。


移除第三方套件


只要把需要移除的套件那行,拿掉或是用註解隱藏即可。
舉例來說,如果我想移除FMDB, 則可以將podfile檔案中的FMDB那行前面加上井字號:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Starscream', '~> 1.0.0'
pod 'Alamofire', '~> 3.0'
#pod 'FMDB'
之後,執行
pod update

則可以看到在終端機中看到FMDB被移除的訊息

2015年11月24日

iBeacon 偵測範例 - android client


去年七月在一個講座上第一次聽到 ibeacon 這個名詞,那時候還不是這麼熱門 google 到的第一個就是 estimote 這間公司的產品,不過因為訂購過海關還會被 NCC 攔截...所以後面就沒再繼續追蹤。不過今年跟朋友借到 estimote 的 Beacon,終於可以來測試一下

iBeacon

iBeacon 是 Apple 公司提出的一套可用於室內定位系統的協議,可以使一個智慧型手機或其他裝置在一個 iBeacon 基站的感應範圍內執行相應的命令。
使用 BLE ( Bluetooth Low Energy ) 技術,具有低功耗、低延遲、高射頻等特性
iBeacon ID 長度是 20 bytes,包含以下三個內容:
  • UUID (16 bytes)
  • major number (2 bytes)
  • minor number (2 bytes)
依距離可分為:
  • 最近:幾公分
  • 中距:幾公尺
  • 遠距:大於 10 公尺
兼容設備:
  • 藍牙 4.0 的 IOS 設備( iPhone4s 及以上,iPad 第三代及以上,iPad mini 第一代及以上,iPod Touch 第五代 )
  • Android 4.3 及以上( 如:三星 Galaxy S3 / S4 / S4 Mini, 三星 Galaxy Note 2 / 3, HTC One, Google / LG Nexus 7 2013 version / Nexus 4 / Nexus 5, HTC Butterfly, OnePlus One )

estimote Beacon 規格

  • 32-bit ARM® Cortex M0 CPU 包含加速規、溫度感測器
  • 藍芽 4.0
  • 最遠射程 70 公尺 ( 無障礙、干擾情況下,使用建議抓 40~50 公尺 )

測試範例一

使用官方釋出 estimote-sdk-preview.jar 進行偵測,官方範例可以點此下載
建立一個 BeaconManager 物件,並註冊 Listener

public static void Create(NotificationManager notificationMngr,
  Context context, final Intent i) {
 try {
  currentContext = context;

  // Create a beacon manager
  beaconManager = new BeaconManager(currentContext);

  // 設定搜尋間格時間
  beaconManager.setBackgroundScanPeriod(TimeUnit.SECONDS.toMillis(1),
    0);

  // 偵測 beacon 進入
  beaconManager.setMonitoringListener(new MonitoringListener() {
   // ... close to us.
   @Override
   public void onEnteredRegion(Region region, List beacons) {
    Log.e(ConfigUtil.TAG, "onEnteredRegion");
    Log.e(ConfigUtil.TAG, "beacons: " + beacons);
    
    if(beacons!=null && beacons.size()>0){
        if(beacons.get(0).getProximityUUID().equals(ESTIMOTE_PROXIMITY_UUID)){
         Log.i(ConfigUtil.TAG, "same beacon");
         return;
        }
     ESTIMOTE_PROXIMITY_UUID = beacons.get(0).getProximityUUID();
     Log.i(ConfigUtil.TAG, "ESTIMOTE_PROXIMITY_UUID:"+ESTIMOTE_PROXIMITY_UUID);
     sendUpdateProximityUUID(ESTIMOTE_PROXIMITY_UUID);
       }
   }

   // ... far away from us.
   @Override
   public void onExitedRegion(Region region) {
   }
  });
  
  // 在 beacon 範圍中
  beaconManager.setRangingListener(new BeaconManager.RangingListener() {
       @Override public void onBeaconsDiscovered(Region region, final List beacons) {
        Log.e(ConfigUtil.TAG, "setRangingListener");
        Log.e(ConfigUtil.TAG, "Ranged beacons: " + beacons);
        
       if(beacons!=null && beacons.size()>0){
        if(beacons.get(0).getProximityUUID().equals(ESTIMOTE_PROXIMITY_UUID)){
         Log.i(ConfigUtil.TAG, "same beacon");
         return;
        }
     ESTIMOTE_PROXIMITY_UUID = beacons.get(0).getProximityUUID();
     Log.i(ConfigUtil.TAG, "ESTIMOTE_PROXIMITY_UUID:"+ESTIMOTE_PROXIMITY_UUID);
     sendUpdateProximityUUID(ESTIMOTE_PROXIMITY_UUID);
       }
       }
     });
  
  // 建立連線
  beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
   @Override
   public void onServiceReady() {
    try {
     // 開使偵測
     beaconManager.startRanging(ALL_ESTIMOTE_BEACONS);
     beaconManager.startMonitoring(ALL_ESTIMOTE_BEACONS);
    } catch (Exception e) {
     Log.e(ConfigUtil.TAG, "Exception:"+e);
    }
   }
  });
 } catch (Exception e) {
  Log.e(ConfigUtil.TAG, "Exception:"+e);
 }
}
結束時,記得停止偵測與關閉連線

public static void stop() {
 try {
  beaconManager.stopRanging(ALL_ESTIMOTE_BEACONS);
  beaconManager.stopMonitoring(ALL_ESTIMOTE_BEACONS);
  beaconManager.disconnect();
 } catch (Exception e) {
  Log.e(ConfigUtil.TAG, "Exception:"+e);
 }
}
藍芽關閉時,也要記得停止偵測與關閉連線,不然你會看到 XXX 已停止...

public class EstimoteReceiver extends BroadcastReceiver {
 private Intent estimoteServiceIntent;

 // Method called when bluetooth is turned on or off.
 @Override
 public void onReceive(Context context, Intent intent) {
  final String action = intent.getAction();
  if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
   final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
     BluetoothAdapter.ERROR);
   switch (state) {
   case BluetoothAdapter.STATE_TURNING_OFF:
    // When bluetooth is turning off, lets stop estimotes ranging
    if (estimoteServiceIntent != null) {
     //這裡會呼叫上面的 stop()
     context.stopService(estimoteServiceIntent);
     estimoteServiceIntent = null;
    }
    break;
   case BluetoothAdapter.STATE_ON:
    // When bluethooth is turned on, let's start estimotes monitoring
    if (estimoteServiceIntent == null) {
     estimoteServiceIntent = new Intent(context,
       EstimoteService.class);
     context.startService(estimoteServiceIntent);
    }
    break;
   }
  }
 }
}
結論:使用官方釋出 estimote-sdk-preview.jar,只能用 Listener 等待接收事件,測試會出現偵測速度忽快忽慢的問題...

測試範例二

因為官方的 JAR 偵測速度忽快忽慢,所以後面又自己上網找了個 lib 取代官方的 JAR。
使用 Bluetooth LE Library,它已提供 beacon 與一般藍芽設備的辨別方式,可以省掉一些麻煩
一開始先檢查一下藍芽是否開啟

private void checkBluetooth() {
 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 if (mBluetoothAdapter == null) {
     // 如果裝置不支援藍芽
     Toast.makeText(this, "Device doesn't support bluetooth", Toast.LENGTH_SHORT).show();
     return;
 }
          
 // 如果藍芽沒有開啟
 if (!mBluetoothAdapter.isEnabled()) {
     // 發出一個intent去開啟藍芽,
        Intent mIntentOpenBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(mIntentOpenBT, REQUEST_ENABLE_BT);
 }
}
init

private void initServerData() {
 //管理所有藍芽裝置
 mDeviceStore = new BluetoothLeDeviceStore();
 //管理 BluetoothManager/BluetoothManager
 mBluetoothUtils = new BluetoothUtils(this);
 mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
}
偵測到裝置後的 callback

private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
 @Override
 public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
  synchronized(lockObj){
   final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
   mDeviceStore.addDevice(deviceLe);
   final EasyObjectCursor c = mDeviceStore.getDeviceCursor();
   for(int i=0; i<=1){
      //是否偵測到同一個beacon
      if(currentBeaconUUID.equals(iBeacon.getUUID())){
       Log.d(ConfigUtil.TAG, "same beacon");
      }else{
       if(tv_main_msg!=null){
        tv_main_msg.setText(iBeacon.getUUID());
       }
       break;
      }
     }else{
      //不是距離最近的beacon
     }
    }else{
     //not beacon device
    }
   }
  }
 }
};
開始搜尋裝置

private void startScan(){
 final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
 final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
 mDeviceStore.clear();
 mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
 if(mIsBluetoothOn && mIsBluetoothLePresent){
  mScanner.scanLeDevice(-1, true);
 }
}
關閉 app 或偵測到藍芽關閉時,記得停止掃描

private void stopScan(){
 mScanner.scanLeDevice(-1, false);
}
結論:使用 Bluetooth LE Library 真的比官方釋出的 JAR 快很多,但離開 beacon 範圍再返回後 update time 跟 distance 更新有時會很慢,或要靠近 beacon 一點...

Android 5.0 使用 estimote 官方釋出 estimote-sdk-preview.jar 藍芽問題

之前測試 android 4.X 的時候沒問題,但到了 Android 5.0 不知道為什麼就出現以下問題... 不過還好換個 thread 就解決了

05-16 09:43:32.181: E/AndroidRuntime(32313): FATAL EXCEPTION: main
05-16 09:43:32.181: E/AndroidRuntime(32313): Process: com.estimote.notificationstest, PID: 32313
05-16 09:43:32.181: E/AndroidRuntime(32313): java.lang.IllegalArgumentException: This cannot be run on UI thread, starting BLE scan can be expensive
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.internal.Preconditions.checkArgument(Preconditions.java:65)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.service.BeaconService.checkNotOnUiThread(BeaconService.java:502)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.service.BeaconService.access$600(BeaconService.java:61)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.service.BeaconService$InternalLeScanCallback.onLeScan(BeaconService.java:490)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.bluetooth.BluetoothAdapter$2.onScanResult(BluetoothAdapter.java:1892)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.bluetooth.le.BluetoothLeScanner$BleScanCallbackWrapper$1.run(BluetoothLeScanner.java:330)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.os.Handler.handleCallback(Handler.java:739)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.os.Handler.dispatchMessage(Handler.java:95)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.os.Looper.loop(Looper.java:211)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.app.ActivityThread.main(ActivityThread.java:5321)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at java.lang.reflect.Method.invoke(Native Method)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at java.lang.reflect.Method.invoke(Method.java:372)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1016)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)

References

=======================================================================

2016/08/23 補充

※ 目前已知 "Bluetooth-LE-Library---Android" 可能會出現 Beacon UUID 缺少位數的問題,這部分 "Bluetooth-LE-Library---Android" 已經在 2015/7/15 v1.0.0 版 fix 了,舊版的可以到  Bluetooth-LE-Library---Android 下載更新,或是下載附件 IBeaconManufacturerData.java 並覆蓋 "Bluetooth LE Library\src\uk\co\alt236\bluetoothlelib\device\mfdata" 下 IBeaconManufacturerData.java

PS. 範例二中 IBeaconManufacturerData.java 已更新,可直接使用

2015年11月23日

如何在 CentOS 安裝 MongoDB

MongoDB 屬於 NoSQL Database 中的文件資料庫這種類型,可以儲存 XML, JSON 或是其他格式的文件資料,這些資料都是 self-describing 的,也就是說文件之間並不需要完全一樣,換句話說,在更新 schema 時,並不需要修改既有的資料。

依照這個網頁的說明 Install MongoDB on Red Hat Enterprise or CentOS Linux,就可以把 mongodb 裝好。

Packages

  1. mongodb-org
    這是自動安裝其他四個 packages 的 metapackage
  2. mongodb-org-server
    包含 mongod daemon、configurations以及 init scripts
  3. mongodb-org-mongos
    包含 mongos daemon
  4. mongodb-org-shell
    包含 mongo shell
  5. mongodb-org-tools
    包含以下的 MongoDB 工具: mongoimport bsondump, mongodump, mongoexport, mongofiles, mongooplog, mongoperf, mongorestore, mongostat, and mongotop

mongod 的 service script 在 /etc/init.d/mongod

設定檔在 /etc/mongod.conf

資料存放在 /var/lib/mongo

log 存放在 /var/log/mongodb

vi /etc/yum.repos.d/mongodb-org-3.0.repo

[mongodb-org-3.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.0/x86_64/
gpgcheck=0
enabled=1

用以下指令安裝並啟動 mongodb

yum install -y mongodb-org
service mongod start

在 /var/log/mongodb/mongod.log 裡面看到這一行,基本上就是把 mongodb 環境安裝好了。

[initandlisten] waiting for connections on port 27017

簡單的 DB 測試

執行 mongo 就會直接連接到 test 這個 database

show dbs 是顯示所有資料庫, show collections 是顯示資料庫中的 collections,相當於關聯式資料庫的 table

> show dbs
local  0.078GB
> show dbs;
local  0.078GB
> show collections;

切換到 mydatabase 資料庫

> use mydatabase;
switched to db mydatabase
> db
mydatabase
  1. insert 新增
    建立一個 post 文件,將 post 放進

    > post = { "title": "今日天氣", "content": "晴朗", "date": new Date()}
    {
     "title" : "今日天氣",
     "content" : "晴朗",
     "date" : ISODate("2015-11-05T08:33:54.761Z")
    }
    > db.blog.insert(post)
    WriteResult({ "nInserted" : 1 })
    
  2. find 查詢
    以 find 取得所有資料

    > db.blog.find()
    { "_id" : ObjectId("563b147db835798ae60f4bae"), "title" : "今日天氣", "content" : "晴朗", "date" : ISODate("2015-11-05T08:33:54.761Z") }
    
  3. update 修改
    先修改剛剛的 post 資料,然後用 update 更新到資料庫中。

    > post.comment = ["Great!"]
    [ "Great!" ]
    > post
    {
     "title" : "今日天氣",
     "content" : "晴朗",
     "date" : ISODate("2015-11-05T08:33:54.761Z"),
     "comment" : [
         "Great!"
     ]
    }
    > db.blog.update({title: "今日天氣"}, post)
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > db.blog.find()
    { "_id" : ObjectId("563b147db835798ae60f4bae"), "title" : "今日天氣", "content" : "晴朗", "date" : ISODate("2015-11-05T08:33:54.761Z"), "comment" : [ "Great!" ] }
    
  4. remove 刪除
    以 remove 刪除資料

    > db.blog.remove({title: "今日天氣"});
    WriteResult({ "nRemoved" : 1 })
    > db.blog.find();
    

如何用 R 語言連接 MongoDB

先在 mongodb 重新新增兩筆資料

# mongo
> post = { "title": "今日天氣", "content": "晴朗", "date": new Date()}
> db.blog.insert(post)

> post2 = { "title": "明日天氣", "content": "陰天", "date": new Date()}
> db.blog.insert(post2)

在 R 語言的界面中,依照以下的過程進行測試

  1. 安裝 rmongodb 套件

    > install.packages("rmongodb")
    
  2. 使用 rmongodb library

    > library(rmongodb)
    
  3. 建立 mongodb 連線

    > mongo <- mongo.create(host="192.168.1.11" , db="test", username="", password="")
    > dbns <- mongo.get.database.collections(mongo, db="test")
    
  4. 建立查詢條件,並以 find 查詢資料

    > query <- '{ "title": { "$exists": true }, "content": { "$exists": true } }'
    > 
    > cur <- mongo.find(mongo, dbns, query=query)
    
  5. 將 cursor 結果轉換成 data.frame

     > title <- content <- date <- NULL
     > while (mongo.cursor.next(cur)) {
     +     value <- mongo.cursor.value(cur)
     +     title <- rbind(title, mongo.bson.value(value, 'title'))
     +     content <- rbind(content, mongo.bson.value(value, 'content'))
     +  
     +     date <- rbind(date, mongo.bson.value(value, 'date'))
     + }
     > 
     > posts <- data.frame(title=title, content=content, date=date)
    
     > posts
            title           content       date
     1 隞憭拇除 \xe6\xe6\x9c\x97 1446714342
     2 \xe6\x98憭拇除       \xe9憭\xa9 1446715746
    
  6. 中文顯示有問題
    在產生 data.frame 之前,必須先設定 UTF-8 Encoding

    > Encoding(title)="UTF-8"
    > 
    > Encoding(content)="UTF-8"
    > 
    > posts <- data.frame(title=title, content=content, date=date)
    > 
    > posts
      title content       date
    1 今日天氣    晴朗 1446714342
    2 明日天氣    陰天 1446715746
    

濃縮所有測試過程的指令如下

install.packages("rmongodb")
library(rmongodb)
mongo <- mongo.create(host="192.168.1.11" , db="test", username="", password="")
dbns <- mongo.get.database.collections(mongo, db="test")
query <- '{ "title": { "$exists": true }, "content": { "$exists": true } }'

cur <- mongo.find(mongo, dbns, query=query)

title <- content <- date <- NULL

while (mongo.cursor.next(cur)) {
value <- mongo.cursor.value(cur)
title <- rbind(title, mongo.bson.value(value, 'title'))
content <- rbind(content, mongo.bson.value(value, 'content'))
date <- rbind(date, mongo.bson.value(value, 'date'))
}
Encoding(title)="UTF-8"
Encoding(content)="UTF-8"
posts <- data.frame(title=title, content=content, date=date)
posts

在 rmongodb 寫入中文也會遇到編碼的問題,參考 R實踐之中文編碼轉換,必須先用 iconv 先將中文字串轉碼。

2015年11月16日

如何在 CentOS 安裝 Cassandra

Cassandra 屬於 column-family 類型的 NoSQL database,特性是 ring cluster network topology,支援類似 SQL 語法的 CQL,適合儲存依照時間排序且scheme固定的大量資料。以下嘗試在 centos 安裝 cassandra。

單一節點

tar zxvf apache-cassandra-2.2.3-bin.tar.gz -C /usr/share

#啟動
/usr/share/apache-cassandra-2.2.3/bin/cassandra

資料會放在
/usr/share/apache-cassandra-2.2.3/data 這個目錄中

系統紀錄放在
/usr/share/apache-cassandra-2.2.3/logs 這個目錄中

然後參考 cassandra Getting Started 的範例,使用 CQL 測試這個 DB。

// 建立資料庫 mykeyspace
CREATE KEYSPACE mykeyspace
WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };

// 使用資料庫
USE mykeyspace;

// 建立 table: users
CREATE TABLE users (
  user_id int PRIMARY KEY,
  fname text,
  lname text
);


// insert 三筆測試資料
INSERT INTO users (user_id,  fname, lname)
  VALUES (1745, 'john', 'smith');
INSERT INTO users (user_id,  fname, lname)
  VALUES (1744, 'john', 'doe');
INSERT INTO users (user_id,  fname, lname)
  VALUES (1746, 'john', 'smith');

// 取得 users 所有資料
SELECT * FROM users;

 user_id | fname | lname
---------+-------+-------
    1745 |  john | smith
    1744 |  john |   doe
    1746 |  john | smith

(3 rows)

// 針對 lname 建立 index
CREATE INDEX ON users (lname);

// 條件搜尋
SELECT * FROM users WHERE lname = 'smith';

 user_id | fname | lname
---------+-------+-------
    1745 |  john | smith
    1746 |  john | smith

直接用 kill -9 停止此單一節點的 cassandra 的 process。

ps aux | grep cassandra
kill -9 20109

兩個節點

在兩台機器上,都分別先將單一節點的設定方式做好。記得一定要先以單機方式先啟動一次,否則會出現 Token 錯誤的問題。

使用 /usr/share/apache-cassandra-2.2.3/tools/bin/token-generator,計算這兩個節點在 Murmur3Partitioner 時,token 分別要設定為多少?

[root@server bin]# /usr/share/apache-cassandra-2.2.3/tools/bin/token-generator
Token Generator Interactive Mode
--------------------------------

 How many datacenters will participate in this Cassandra cluster? 2
 How many nodes are in datacenter #1? ^C[root@kokola bin]# ./token-generator
Token Generator Interactive Mode
--------------------------------

 How many datacenters will participate in this Cassandra cluster? 1
 How many nodes are in datacenter #1? 2

DC #1:
  Node #1:  -9223372036854775808
  Node #2:                     0

然後分別編輯兩台機器上的 /usr/share/apache-cassandra-2.2.3/conf/cassandra.yaml

- seeds: "127.0.0.1"

改為

- seeds: "192.168.1.11,192.168.1.12"

listen_address: localhost

分別改為各自的 IP: 192.168.1.11,192.168.1.12

listen_address: 192.168.1.11

在 192.168.1.11 增加一行

initial_token: -9223372036854775808

在 192.168.1.12 則改為

initial_token: 0

然後分別啟動兩台機器的 cassandra。

就可以在第二台機器,用 CQL 取得剛剛在單一節點時建立的 mykeyspace

[root@server bin]# ./cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 2.2.3 | CQL spec 3.3.1 | Native protocol v4]
Use HELP for help.
cqlsh> use mykeyspace ;
/usr/share/apache-cassandra-2.2.3/bin/../lib/cassandra-driver-internal-only-2.7.2.zip/cassandra-driver-2.7.2/cassandra/cluster.py:3331: DeprecationWarning: ResponseFuture.result timeout argument is deprecated. Specify the request timeout via Session.execute[_async].
cqlsh:mykeyspace> 
cqlsh:mykeyspace> select * from users;

 user_id | fname | lname
---------+-------+-------
    1745 |  john | smith
    1744 |  john |   doe
    1746 |  john | smith

(3 rows)

可以用 nodetool 查看 cluster 的狀態

[root@server bin]# ./nodetool -h localhost status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address       Load       Tokens       Owns    Host ID                               Rack
UN  192.168.1.11  68.09 KB   256          ?       53b41601-c145-4c1a-81d1-9b5f8b09ac73  rack1
UN  192.168.1.12  207.75 KB  256          ?       66642998-9179-4b06-b341-e654dc8e8ca6  rack1

service script

以下為 service script,儲存這個檔案到 /etc/init.d/cassandra,chmod 755 後就可以用 service cassandra start 啟動 cassandra。

#!/bin/bash
# chkconfig: 2345 99 01
# description: Cassandra

. /etc/rc.d/init.d/functions

CASSANDRA_HOME=/usr/share/apache-cassandra-2.2.3/
CASSANDRA_BIN=$CASSANDRA_HOME/bin/cassandra
CASSANDRA_NODETOOL=$CASSANDRA_HOME/bin/nodetool
CASSANDRA_LOG=$CASSANDRA_HOME/logs/cassandra.log
CASSANDRA_PID=/var/run/cassandra.pid
CASSANDRA_LOCK=/var/lock/subsys/cassandra
PROGRAM="cassandra"

if [ ! -f $CASSANDRA_BIN ]; then
  echo "File not found: $CASSANDRA_BIN"
  exit 1
fi

RETVAL=0

start() {
  if [ -f $CASSANDRA_PID ] && checkpid `cat $CASSANDRA_PID`; then
    echo "Cassandra is already running."
    exit 0
  fi
  echo -n $"Starting $PROGRAM: "
  daemon $CASSANDRA_BIN -p $CASSANDRA_PID >> $CASSANDRA_LOG 2>&1
  usleep 500000
  RETVAL=$?
  if [ $RETVAL -eq 0 ]; then
    touch $CASSANDRA_LOCK
    echo_success
  else
    echo_failure
  fi
  echo
  return $RETVAL
}

stop() {
  if [ ! -f $CASSANDRA_PID ]; then
    echo "Cassandra is already stopped."
    exit 0
  fi
  echo -n $"Stopping $PROGRAM: "
  $CASSANDRA_NODETOOL -h 127.0.0.1 decommission
  if kill `cat $CASSANDRA_PID`; then
    RETVAL=0
    rm -f $CASSANDRA_LOCK
    echo_success
  else
    RETVAL=1
    echo_failure
  fi
  echo
  [ $RETVAL = 0 ]
}

status_fn() {
  if [ -f $CASSANDRA_PID ] && checkpid `cat $CASSANDRA_PID`; then
    echo "Cassandra is running."
    exit 0
  else
    echo "Cassandra is stopped."
    exit 1
  fi
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status_fn
    ;;
  restart)
    stop
    start
    ;;
  *)
    echo $"Usage: $PROGRAM {start|stop|restart|status}"
    RETVAL=3
esac

exit $RETVAL

如何用 R 語言連接 cassandra 資料庫

以下簡述如何使用 R 語言連接 cassandra 資料庫。

  1. 調整 cassandra 設定,將 rpc server 打開,並重新啟動 cassandra

    修改 /usr/share/apache-cassandra-2.2.3/conf/cassandra.yaml,將 rpc server 打開

     start_rpc: true
    
  2. 下載 cassandra-jdbc library
    cassandra-jdbc 網頁,下載 cassandra-jdbc-2.1.1.jar 以及 cassandra-jdbc dependencies,將所有 jar 檔都放在要執行 R 語言的電腦的一個資料夾中。

  3. 在 R 語言,安裝 RJDBC 套件

     install.packages("RJDBC")
    
  4. R 語言的範例程式

    • 載入 RJDBC library

      > library(RJDBC)
      Loading required package: DBI
      Loading required package: rJava
      
    • 載入 Cassandra JDBC driver,以及相關 jars,我是放在 D:/git/system/pkg/cassandra/cassandra-jdbc/libs/ 這個目錄中,要注意目錄最後面的 libs 後面一定要加上 /

      > cassdrv <- JDBC("org.apache.cassandra.cql.jdbc.CassandraDriver", list.files("D:/git/system/pkg/cassandra/cassandra-jdbc/libs/",pattern="jar$",full.names=T))
      
    • 連接到 cassandra

      > casscon <- dbConnect(cassdrv, "jdbc:cassandra://192.168.1.11:9160/mykeyspace")
      
    • 對此 cassandra connection 執行 CQL

      > res <- dbGetQuery(casscon, "select * from users limit 10")
      
    • 取得 CQL 結果

      > res
      user_id fname lname
      1    1745  john smith
      2    1744  john   doe
      3    1746  john smith
      
  5. 錯誤訊息
    如果在連接 cassandra 時,發生 SQLNonTransientConnectionException,就表示 cassandra server 並沒有打開 rpc server 連接的設定。

     > casscon <- dbConnect(cassdrv, "jdbc:cassandra://192.168.1.11:9160/mykeyspace")
     Error in .jcall(drv@jdrv, "Ljava/sql/Connection;", "connect", as.character(url)[1],  : 
       java.sql.SQLNonTransientConnectionException: org.apache.thrift.transport.TTransportException: Cannot write to null outputStream
    

References

[研究] Apache Cassandra 2.0.3 安裝 (CentOS 6.5 x64)

設定 Cassandra cluster 的 token

cassandra token calculator

2015年11月9日

Text Based User Interface (TUI)

如果有用過 Linux CLI interface 的使用者,一定會看過有些 script 指令執行之後,不是直接以文字的方式輸出結果,而是跳出一個圖形界面,這種圖形界面,並不像 X Windows 那麼華麗,對使用者來說,確實獲得了不少幫助,尤其是一些比較少用到的指令,或是忘了放在哪裡的設定檔要修改的時候。

舉例來說,在 centos 如果要修改網路卡、Gateway、DNS,可以直接去修改網路卡跟 DNS 的設定檔,不常修改這些設定的使用者有另一個選擇,就是直接在 console 執行 system-config-network ,就可以看到 GUI 使用者界面,省去了不少功夫。

這種圖形界面稱為 Text-based user interface,也稱為 terminal user interface,比較複雜的 TUI 還可以做出一個完整的檔案管理員。

但有時候,我們只需要一個簡單的圖形化的使用者互動界面,這時候可以借助 dialog 的幫助,讓我們能夠快速地建造一個 TUI 界面的程式。

Dialog

Dialog 是可以直接在 console 中,產生 TUI 的 library,我們可以直接找到有關 dialog 的範例。

以 centos 來說,如果沒有安裝 dialog,可以直接用 yum install dialog 將 dialog 安裝起來。然後到 /usr/share/doc/dialog-1.1/samples/ 目錄中,可以看到很多範例程式。

[root@server ~]# cd /usr/share/doc/dialog-1.1/samples/
[root@server samples]# ls
calendar          dselect          gauge2          inputmenu3        msgbox3             slackware.rc
calendar2         editbox          gauge3          inputmenu4        msgbox4-8bit        sourcemage.rc
calendar2-stdout  editbox2         infobox         inputmenu-stdout  msgbox4-eucjp       suse.rc
calendar3         editbox3         infobox1        killall           msgbox4-utf8        tailbox
calendar3-stdout  editbox-utf8     infobox2        listing           msgbox5             tailboxbg
calendar-stdout   form1            infobox3        menubox           msgbox6             tailboxbg1
checklist         form1-both       infobox4        menubox1          msgbox6a            tailboxbg2
checklist1        form1-extra      infobox5        menubox10         msgbox-help         testdata-8bit
checklist10       form1-help       infobox6        menubox2          password            textbox
checklist2        form1-utf8       inputbox        menubox3          password1           textbox2
checklist3        form2            inputbox1       menubox4          password2           textbox3
checklist4        form3            inputbox2       menubox5          passwordform1       textbox.txt
checklist5        form4            inputbox3       menubox6          passwordform1-utf8  timebox
checklist6        form5            inputbox4       menubox7          pause               timebox2
checklist7        form6            inputbox5       menubox8          pause-help          timebox2-stdout
checklist8        fselect          inputbox6-8bit  menubox-8bit      progress            timebox-stdout
checklist-8bit    fselect1         inputbox6-utf8  menubox9          progress2           wheel
checklist9        fselect1-stdout  inputbox7       menubox-utf8      radiolist           whiptail.rc
checklist9.txt    fselect2         inputbox-both   mixedform         radiolist10         yesno
checklist-utf8    fselect2-stdout  inputbox-extra  mixedform2        radiolist2          yesno2
copifuncs         fselect-stdout   inputbox-help   mixedgauge        radiolist3          yesno3
copismall         gauge            inputmenu       msgbox            radiolist4          yesno-both
debian.rc         gauge0           inputmenu1      msgbox1           README              yesno-extra
dialog.py         gauge0-input-fd  inputmenu2      msgbox2           rotated-data        yesno-help

由 dialog 手冊,我們可看到 dialog 支援的 dialog box 有以下這些:
calendar, checklist, dselect, editbox, form, fselect, gauge, infobox, inputbox, inputmenu, menu, mixedform, mixedgauge, msgbox (message), passwordbox, passwordform, pause, progressbox, radiolist, tailbox, tailboxbg, textbox, timebox, and yesno (yes/no).

Sample 1

連續產生三個 yesno dialog,後面的 yesno 會直接覆蓋在前面的 yesno 上面。

dialog                         --begin 2 2 --yesno "" 0 0 \
    --and-widget               --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

產生三個 yesno dialog,但新的 dialog 出現前,會先把前面的清空,所以最後只會顯示第三個 yesno。

dialog           --clear       --begin 2 2 --yesno "" 0 0 \
    --and-widget --clear       --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

跟第一個範例的結果很像,後面的 yesno 會蓋掉前面的 yesno,差別是跳出這三個 yesno 之後,畫面上顯示的是第一個 yesno。

dialog           --keep-window --begin 2 2 --yesno "" 0 0 \
    --and-widget --keep-window --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

顯示第一跟第三個 yesno。

dialog           --keep-window --begin 2 2 --yesno "" 0 0 \
    --and-widget --clear       --begin 4 4 --yesno "" 0 0 \
    --and-widget               --begin 6 6 --yesno "" 0 0

Sample 2

  • hello world

    dialog --title 'Message' --msgbox 'Hello, world!' 5 20
    
  • yesno

    dialog --title "Message"  --yesno "Are you having fun?" 6 25
    
  • wait 4 seconds and exit

    dialog --infobox "Please wait" 10 30 ; sleep 4
    
  • inputbox - get result in file named "answer"

    dialog --inputbox "Enter your name:" 8 40 2>answer
    
  • textbox: file content display with scroll bar

    dialog --textbox /etc/profile 22 70
    
  • option 三選一

    dialog --menu "Choose one:" 10 30 3 1 red 2 green 3 blue
    
  • options 多選

    dialog --checklist "Choose toppings:" 10 40 3 \
          1 Cheese on \
          2 "Tomato Sauce" on \
          3 Anchovies off
    
  • radiolist

    dialog --backtitle "CPU Selection" \
    --radiolist "Select CPU type:" 10 40 4 \
          1 386SX off \
          2 386DX on \
          3 486SX off \
          4 486DX off
    
  • guage: progress bar
    將以下的 script 放到一個 test.sh 中,直接執行這個 script

    #!/bin/bash
    { for I in 10 20 30 40 50 60 70 80 90 \
        80 70 60 50 40 30 20 10 0; do
     echo $I
     sleep 1
    done
    echo; } | dialog --guage "A guage demo" 6 70 0
    

References

Linux程序設計入門 - Dialog
在 shell script 中使用圖形式互動元件 - dialog 與 zenity 使用範例
Dialog Screenshots

dialog man page

Dialog: An Introductory Tutorial

Linux Apprentice: Improve Bash Shell Scripts Using Dialog

Zenity -- 在 Shell Script 中顯示圖形使用者介面 Using GUI in shell script

ncurses