2012年7月27日 星期五

jQuery 原始碼解讀 (III) - ready 解析


Ready . Set . Go 
jQuery 原始碼解讀 (I) / (II) 看完了 jQuery 進入點的部分之後,我們來看看使用 jQuery 的時候 99.9% 的人一定會在一開始的時候就呼叫的一個 method:
.ready(handler)  
如果你剛好是那 0.1% 的人沒用,放心 jQuery 自己也會幫你呼叫 :) jQuery 自己會在一開始就先偷偷註冊兩個 onReady handler 去做 browser feature detection。

.ready 的用法有三種:
  1. $(document).ready(handler) 
  2. $().ready(handler)
  3. $(handler) 
前兩個方法,如 jQuery 原始碼解讀 (II) 所講的會去呼叫 jQuery.fn.init 然後以傳入的第一個參數當作 selector,由於 document 本身就是一個 DOMNode 所以直接就是回傳 context 為 document 的 jQuery object instance,而 $() 則是回傳一個空的 context 的 jQuery object instance,但因為 .ready 其實跟 jQuery object 本身綁定的 context 無關,所以其實你傳什麼 selector 進去都可以直接呼叫 .ready 。但是除了 document 或是 null/""/undefined 之外的 selector 在 ready 前其實都不會 work,所以基本上就只能 $(document) 或是 $()。 

至於第三個方法,其實只是 jQuery 故意留一個 shortcut,但感覺好像也沒什麼必要

而且這個 shortcut 的 condition check branch 是在最後一段,所以其實效能是最差的,但是 jQuery 官方文件反而是建議不要使用第二個方法?有點不解。綜合 jQuery 文件跟筆者小小的看法,我們還是用第一個好了 :) 

不過,以筆者的個人看法,第二個方法效能才是上等的建議啊,不然客倌您看:
是不是在第一個 if 的時候就回來了!

接著來看看 .ready 裡面做了什麼事情:

看得出來這邊的重點在於使用了 readyList 來追蹤所有的 onReady handler,而 readyList 是一個 jQuery.Callbacks 回傳的 object instance,傳入的參數是以空格分隔的, once memory 代表的意思是當 fire 過一次就失效。 Callbacks 基本上很類似一個 array 裡面存放 function references,同時加上一些 fire event 的處理。之後當收到 DOMContentLoaded event 的時候就可以透過這個 readyList 去 fire 所有已註冊的 on ready hanlders。

之後接著檢查如果已經是在 complete 的 readyState 下,那就直接觸發 jQuery.ready。
正常情況下則是去註冊 DOMContentLoaded 去觸發 jQuery.ready,然後後面再註冊 load event 以避免在某些瀏覽器沒有 DOMContentLoaded 的 support 的情況下整個程式就不會動了。 DOMContentLoaded 與 load 的差別在於 DOMContentLoaded 會在所有的 DOM nodes 都 ready 的時候觸發,而 load 則是必須在所有的 Node 的內容都 ready 才會觸發。比如說你的網頁中有放 <img>,那麼 DOMContentLoaded 觸發的時候 <img> 的圖片內容一定是還沒觸發,但 load 被觸發的時候一定是在 <img> 圖片已經完全載入的時候。所以如果你預計再圖片載入完之後去計算出新的 layout,那你就不能用 .ready 而要改用 .load。

另外一點值得提的是,也可以對 img element 註冊 load 事件,但是 load 無法處理瀏覽器 cached images,也就是說如果你預期每次都要收到 img 的 onload 事件來做一些運算去重新 layout 的話,一定會遇到很多問題,比如 Pinterest like 的 layout 就必須取得每一張圖片的大小來計算每一個 Pin 的高度,這時候單純依賴 load 就會有問題。因此有了一個對應的 Plugin - imageloaded 來幫忙處理 image 的 load event。

至於前面提到的 jQuery 會自己註冊的 feature detection 的 function 可以參考這個 gist: https://gist.github.com/3183078,裡面會做以下 features 的偵測:


偵測的技巧是值得學習的,原理大概是建立一個 DOM element,並加入 DOM tree,再重這個 DOM node 的屬性或是 computedStyle 去偵測 feature。相關的技巧應用的很完整的是 Modernizr,有興趣的話可以找一下 EricSKModernier 源碼剖析文章。


jQuery 系列文章:

2012年7月19日 星期四

開信率和點擊率以外,你一定要知道的一個指標 - RPE (Revenue Per Email)

Calculating Revenue Per Email budget

在作電子報行銷效益分析時,很多人的注意力都放在提高開信率和提高點擊率,但開信率和點擊率的追蹤對很多公司來說並不是那麼容易,因為它是需要有些程式技巧才有辦法追蹤的到,如果沒有開信率和點擊率這些數字的話,我們該如何評斷電子報的行銷效益呢? 我今天來為大家介紹一個很容易追蹤但常被忽略又很重要的一個指標 - RPE (Revenue Per Email) (每封信的效益)

這算法很簡單

RPE (每封信的效益)  = 產生的效益 / (電子報寄出總數 - 退信數)

這意思是什麼呢?我舉個例子大家就明白了,我朋友的老闆最常說一句話【不要告訴我什麼開信率、點擊率,你只要告訴我寄這些名單,公司的收入會增加多少?】,是的,就是每寄出一封電子報,會帶來多少效益(也許是營收、流量、加入會員數....之類的)。

如何計算產生的效益呢?

如果你是經營網路相關事業,現在網路上有很多免費流量分析的工具,例如 Google Analytics、Yahoo!奇摩站長工具..等,如果你是經營實體相關事業,那你應該把電話行銷、電子報行銷或是其它人工的行銷方式清楚區隔開來。

所以你就可以計算

  1. 每月、季、年的RPE
  2. 每個行銷活動的RPE
  3. 每個產品線的RPE
  4. 每個客群的RPE
一旦你開始計算RPE後,你就多了一個評斷電子報行銷效益的數據,接下來你就能判斷在目前這個階段,你需要提高的是RPE,還是開信率、還是點擊率 ? 你就能判斷你的行銷預算應該下在那裡,下多少了。

(Photo via 401K, CC License)

2012年7月18日 星期三

App Store SEO 可以使用的參考工具

對於 Web 搜尋引擎的 SEO 其實已經有不少資料了,但是在 App Store 上面的 SEO 其實許多如我一樣的初學者都還在探索當中。最近看到了這篇 App Store SEO: The tools we used,順手翻譯出來跟大家分享一下看看別人是怎麼樣利用一些工具來研究 App Store 的 SEO。如果大家有什麼心得,歡迎可以一起討論分享 :) 以下為原文翻譯:

幾個禮拜之前,Apple 通過了我們 1.5 版的 Craigslist app,把新功能丟給我們的使用者用當然很好,不過這個版本的發佈有更重要的理由,這讓我們有機會透過一些 app store SEO 的技巧調整我們在 iTunes app Store 的排名。根據我們在 iTunes connect 所看到的結果,我們成功了。這篇文章會談到我用來評估和改善我們在 app store 排名的工具。

在我們第一版的 app 中,我其實不太確定對於 App Store SEO 的努力有沒有用。App Store 的排名和搜尋結果對於 app 來說是兩個很重要的元素,不過 Apple 幾乎沒說過背後的機制是怎麼樣的。此外,iTunes Connect 只提供了像是每天的下載數之類的數據之外,沒有提供一些其他的訊息來評估在 Apple App Store 的排名。

所以對於我們 app 的第一版,我只能透過一些假設來猜看看哪些事重要的。很明顯的名字是個很重要的部份,幾乎所有搜尋結果排在前面的 Craigslist apps 都使用了 “Craig” 或是 “Craigslist” 的變化型(包含了 “Craigslist Mobile”, “Nationwide Craigslist”, “Craigster” 還有在尾端加上了一些標點符號,像是 “Craigslist`” 跟Craigslist.)我們其實不打算像是這些 app 一樣改變我們的名字,除了這些名字跟我們的長期目標不吻合外,感覺起來這樣做也不太對。在 iTunes Connect 中,有一個欄位叫做 keywords field。這些 keywords 不會顯示出來,不過也扮演了一個很重要的角色,根據 Apple’s Developer Guide:

"關鍵字(Keywords) 可以幫助顧客有效率的在 App Store 做搜尋,你的應用程式會透過應用程式名稱、公司名稱和關鍵字被搜尋到"

其中也有一些警告:

"如果你輸入了一個別間公司的註冊商標、app 名字或是公司名字,那麼你的 app 有可能會被 App Store 移掉"

我在這件事情上面考慮了一下,不過根據之前的一些例子 (像是 “Craigslist” 尾端加上一些標點符號) 還有一堆複製其他有名的 app 的現象,註冊商標好像一向會都被忽略,這些 app 還是會被審核通過。

在 keywords 欄位沒有太多的空間(只有一百個字元,包含了空白跟標點符號)所以在 1.0 版的 app 當中我用了下面的 Keywords:

"classified, ad, yard, sale, buy, sell, inventory, craigslist, mobile, ebay, camera, used, cars, furniture, local"

我對 “ebay” 或是 “Craigslist” 這種 keywords 感覺還是毛毛的,不過我希望人們可以找到我們的 app。我也加了 “inventory” 這個 keyword 以避免使用者打錯我們產品的名字(譯註:本文作者產品名稱為 Invantory)。

之前我們對於 app 敘述有些問題(關於這個問題,可以看看這篇文章)。在網路的世界,一個頁面的 “主要內容” 對 SEO/SERP 的排名很重要,不過對 Apple App Store 也是這樣嗎?我決定我盡量的讓 app 敘述簡單扼要。我在 app 敘述這邊也提到了 “Craigslist",不過加了一個 (™) 的標籤。

1.0 版的 Invantory 在四月的時候發佈,我們在那時衝了一些下載量。這是因為新的 app 會在 “new releases” 的列表呆個一天兩天。我們也用了 MailChimp 做了一些行銷 (關於 MailChimp,本文作者有發布過這篇文章), 還用了部落格、twitter 還有家人和朋友,我們透過這些方式有了幾百個下載量。

不過接下來每天下載量變少了,狀況比較好的時候,我們一天只有大概 20 次左右的下載量,這件事情讓人感到相當挫折,我們有一個很不錯的 app - 根據我們獲得的評價還有我們的分析工具(Flurry)得到的結果,使用者喜歡看到他們所看到的東西。那麼為什麼我們的下載量沒有增加呢?我想那是因為人們沒辦法在 App Store 找到我們的 app,我們真的相信我們有個超棒的 Craigslist app UX,而且希望所有 iOS 的使用者可以試看看我們的 app,但是他們就是不知道我們的存在。

因為 Apple 不讓使用者對已經發布的 app 的 keywords 或是名稱做更改,我們不能對 app store SEO 做實驗,所以我們得等到送審的時候才能做修改,在這個時候,這些是 “editable state",代表我們可以做些像是更改名稱、keywords 等等的動作。

在做出任何變動之前,我對於 app store SEO 做了很多的研究,從線上的討論還有一些個人的建議當中,我發現了這份簡報:

App Store SEO tutorial
View more presentations from AppCod.es

App Store SEO tutorial

這份簡報很簡短,你大概花五分鐘左右就可以看完,不過它相當的有用,也清楚的告訴了我們哪些是重要的(app 名稱第一、keywords 第二),而且也有舉了一些例子告訴我們哪些事情要做、哪些事情不要做。

AppCod.es 也提供了一個簡潔的搜尋引擎,它讓開發者可以看看根據不同的搜尋關鍵字會出現怎樣的 app,也會看看其他的 app 使用了哪些關鍵字。使用了這個工具和從簡報當中學到的知識,你可以某種程度的對 App Store 搜尋引擎做到反向搜尋,了解到為什麼有些 app 會出現在結果當中,而你的 app 不會。 我也找到一些其他有用的工具,App Annie 可以追蹤在特定國家中某一個分類當中的排名,它讓我們知道我們的 app 在 Lifestyle 當中的 app 大概是排名在中間的位子。

在 1.5 版,我們決定對 Invantory 的名字增加了一個敘述的短詞,我也重新編排了 keywords:

  • 我透過注意在 AppCod.es 當中的搜尋結果,我不再只看熱門的關鍵字,不過也會關注一些比較稀奇的關鍵字組合,看看哪些會讓 Invantory 放在搜尋結果的前面,甚至看看哪些組合會讓我們的 app 會成為唯一找到的 app。我透過 Google Spreadsheets 來追蹤這些組合,並且藉此來縮小關鍵字的列表。

  • 我把 “classified” 這個關鍵字移掉了,除了這個關鍵字被開發者過度使用之外,我也懷疑這並不是大多數的使用者會拿來找 app 的關鍵字。

  • “Camera” 這個關鍵字也被移掉了,因為它對我們的 app 也不是一個太重要的關鍵字。

  • 我加入了一些人面在找 Craigslist app 的時候會使用的地理性術語,基本上我透過 city/state 的長度來決定我要選哪些字(由於 100 個字的限制,所以只有縮寫),另外 Google Adwords 的關鍵字工具也是我拿來參考的對象。儘管 App Store 搜尋跟 Google 搜尋不太一樣,不過我相信人們會把一些在 Web 搜尋當中的習慣帶進來 Apple App Store 當中。

  • 我把一個其他 app 的名字加入到關鍵字當中 - Pinterest。我們認為 Invantory 的 grid view 看起來很像 Pinterest,而且我們曾經看過有人在 twitter 上面問有沒有一個有 Pinterest-style UI 的 Craigslist app。

結果看來是很顯著的,很快的我們在下載數看到了一個顯著的成長:

app_store_seo

此外,透過 AppCod.es 的搜尋引擎以及 App Store 的實際結果,我確定了哪些術語的組合是有用的。那樣是最有趣的呢?不管我在 keywords 跟 “Pinterest” 用了怎樣的組合,對於搜尋結果都沒啥影響,Invantory 就是不會出現。所以我又把 “Pinterest” 移掉了,並且又新增了額外的幾個關鍵字到下一版的 Invantory,希望在這個禮拜可以通過審核。

2012年7月16日 星期一

幫你的網頁加上 backbone(.js) 吧(五)!

請訪問 iCoding Blog 主站 

今天要介紹的是 backbone.js 在 AJAX 的應用,也是「幫你的網頁加上 backbone(.js) 吧」 系列的第五集。文章內容跟範例皆取材自 Joe Zimmerman 的 Javascript Blog 影片教學單元看影片學寫程式是一件非常棒的事情,但若想快速複習影片中的重點,文字會是更有效率的方式,這也是這系列文章存在的初衷,翻譯如有不周也懇請不吝指正。

Backbone.JS 支援 REST 的開發風格,可以讓你用比較正確的方式使用 Web 標準來寫 code。例如當遇到一些典型的資料操作 CRUD - Create, Read, Update, Delete 邏輯時,可以很容易地對應到 POST, GET, PUT, DELETE 等 HTTP 方法來達成任務,避免無謂的再造輪子

底下的範例是基於先前介紹過的 ModelCollection 等基礎觀念,再加上讓前端 Model 在 CRUD 操作時能夠直接和後端的資源(URL)做到同步能力。

此範例的環境設定比較複雜,需要準備好一臺後端的 HTTP Server 和資料庫,如果您無法做出這樣的環境,歡迎到這裡來看看DEMO程式(記得開啟JavaScript主控台)。而後端 API 有興趣的話也可以到這裡參考原始碼,但請注意這個後端程式純屬 DEMO 用, Update 和 Create 的功能都沒有實作出來,只是剛好做出示意的環境來說明原理,實際要用還是需做修改及調整。

非常強烈建議閱讀本章的同時要打開 JavaScript 的 Console (或是 FireBug 這類的工具),並且觀察網路的狀況,仔細看送給後端的 HTTP header 資料。

緊接著,讓我們來看看原始碼吧:


一開始的 Backbone.emulateHTTP 和 Backbone.emulateJSON 是為了和舊的 HTTP Server 相容所設的(畢竟我們無法確保所有的 Server 都能完整支援 HTTP 的各種方法)。,emulateHTTP 是利用 POST 來模擬 PUT 和 DELETE 兩個 HTTP 做向下相容,模擬的方式是在 Form Data 的後面加上 _method 屬性來說明這個 POST 是 PUT 還是 DELETE。而 emulateJSON 則可以把要送給出的 JSON 在序列化(Serialize)之後,偽裝成 HTML Form 的 MIME 資料達到相容的效果。

接著,我們利用 Person 的 Model 建構子 initialize 來記錄所有被觸發的 event 用來檢驗整個範例程式流程的正確性。

在過去所學的 Model 範例中,Model 所指定的資料是無法永久保存的,每當該 JavaScript 頁面被重新執行一次,之前的資料就會被重新產生、覆蓋一次。除非,我們能指定一個能永久儲存資料的 URL 給該 Model。在 Person 裡的 url 方法,就是用來指定這個儲存位置給 Model。而 urlRoot 顧名思義是用來定義或回傳 URL 的字根(prefix),因此,從我們定義的 url 方法可以看出,Person 這個 Model 的 url 樣式大概會長這樣:

/examples/backbonejs-ajax/backbone.php?id=3

url 這個的方法會在各種 event 被觸發的時候被使用到,像是 fetch, save, destroy 等,而 url 這方法必須根據 model 本身的狀態來回傳該方法所需的 URL。舉例來說,當 save 這個方法被呼叫的時候,可以透過 isNew 方法先檢查 model 是否已經存到 server 來決定是要回傳 Create(POST)用的 URL 還是 Update(PUT)。

呼叫 fetch() 時會對 server 做 GET 的請求,因此當資料是 {id:1} 的時候,GET 的 URL 將會是

/examples/backbonejs-ajax/backbone.php?id=1

呼叫 save() 將會對 server 做 POST,當 model 的 data 是 {name:"Joe Zim", age:23} 時,由於沒有指定 id,因此將會對後端發出 POST 請求,其 URL 會是

/examples/backbonejs-ajax/backbone.php

如果 model 包含 id ,則呼叫 save() 時相當於要對某筆指定 id 的資料做 Update,此時由於 Backbone.emulateHTTP 的關係,會對後端送出假的 PUT 請求,相當於是一個 POST 並且在 HTTP Form Data 加上 _method: PUT 以來說明這個 POST 是要模擬成 PUT 的行為:


舉一反三,我們不難猜測 destroy() 正是呼叫 DELETE 方法,一樣用 POST 來模擬 DELETE,URL 會像是

/examples/backbonejs-ajax/backbone.php?id=1

也會會自動送出一個 Form Data 內容是 _method: DELETE。

小結上述,由 fetch() 、 save() 或 destroy() 來決定要對後端發送請求的方法是 GET、POST 或 DELETE,而當 model 資料包含某筆 id 時,則表示要送 PUT。

Collection 相當於在 Model 外面再多包一層殼,collection 在 create 資料的時候相當於新建一筆 model(所以兩者的 add event 都會被觸動)並呼叫 model 的 save(),接下來的運作原則跟前面 Model 也非常相似,差別主要在於多一層 People event 的觸發,而主要的 CRUD 操作都還是取決於 Model 上的定義 。

希望以上的內容能幫助您多認識一點 Backbone.JS 無縫使用 AJAX 的小伎倆。

Happy Coding!


延伸閱讀



2012年7月15日 星期日

如何利用RGBA_8888,壓縮浮點RGBA [0.0, 1.0]數值

Dear All:
這各部分是很久以前遇到的問題,問題是:“要將GPU所算出的rgba三原色,存入一張dds圖檔,當成Lightmap使用”,很直覺的問題在如何將[0.0, 1.0]的數值存到8bit中?其實這不算什麼新鮮的問題,翻開HDR的歷史,過去舊時代的GPU(如Ps2、XBox)因沒有支援浮點格式的RenderTarget,所以必須想辦法將浮點數壓縮存在一張整數格式的RenderTarget上,來達到HDR的效果,來看看我找到的方法吧!

//這裡將[0.0, 20.0]的數值壓縮到[0.0, 1.0]之間:
Vector4& color = vTempTexture[ py * pCurTexture->Width() + px ];
color *= (1.0f / 20.0f);

//找出RGB最大者,做為將來的除數:
float fA = max( max( color.r, color.g ), max( color.b, 1e-6 ) );
if( fA >= 1 ) fA = 1; else if( fA <= 0 ) fA = 0;

//將fA存在Alpha channel,並將所有RGB用fA以除之:
Vector4 rgbm;
rgbm.a = fA;
rgbm.a = ceil( rgbm.a * 255.0f ) / 255.0f;
rgbm.r = color.r / rgbm.a;
rgbm.g = color.g / rgbm.a;
rgbm.b = color.b / rgbm.a;

//還原回整數表示:
int iValueA = (int)(rgbm.a * 255.0f);
int iValueR = (int)(rgbm.r * 255.0f);
int iValueG = (int)(rgbm.g * 255.0f);
int iValueB = (int)(rgbm.b * 255.0f);

if( iValueA > 255 ) iValueA = 255;
if( iValueR > 255 ) iValueR = 255;
if( iValueG > 255 ) iValueG = 255;
if( iValueB > 255 ) iValueB = 255;

//存入貼讀中:
d3dPixel = ( iValueA << 24 ) | ( iValueR << 16 ) | ( iValueG << 8 ) | iValueB;
而在Shader Code使用這張貼時,記得還原壓縮RGB數值:

diffuseLightmap = lightmap.rgb * lightmap.a * 20;

其實說穿了,這各方法就是針對每一各Pixel找三各channel中的最大值去做壓縮,當然還有另一種方式為per-texture的,很直覺得它是找出貼圖中所有的Pixel裡channel數值最大者進行壓縮,也不失是依各好方法!!

2012年7月9日 星期一

[UnityIN] 效能...效能...效能!!!




UnityIn這次整理了一些程式人員在UNITY開發上與“效能”相關,需要注意的“關鍵點”:
  1. 暫存常用的Component:
    根據官方文件的描述,每次GetComponent的呼叫,UNITY都會花點時間尋找所要求的object上。要省下這點時間,建議利用Private變數在Object Awake時,將常的用component暫存下來。
     
  2. 不要在任何Update的function內,使用Find:
    GameObject.Find這個函式用來搜尋全域、特定名稱之物件,效能上有一定的損耗,所以請盡量在Start或Awake裡使用,而非Update函式裡。若要每個Frame搜尋請改用GameObject.FindWithTag
     
  3. 多多使用BuildIn Array:
    如果物件數量是固定的,type[]的寫法是速度最快的選擇
     
  4. 運算少用“除”,多用“加、減、乘”, 根據UNITY官方數據 :
    “除法”,需要30 - 40 cycles來完成
    “加、減、乘”,只需要一兩個cycles
    “平方根、Sin、Cos” ,需要60 - 100 cycles來完成
     
  5. 單純比較向量距離,少用Normalize:
    Normalize = vec / sqrt( vec.x^2 +  vec. y^2 +  vec. z^2),所以盡量改用sqrMagnitude
     
  6. 少用Dynamic typing (JavaScript):
    使用static typing可以讓JavaScript的程式碼跑起來跟C#一樣快。依據官方數據,JavaScript在UNITY裡的執行速度是一般C++的50%、比Mozllia JavaScript快20倍。
     
  7. 只Update在畫面上或距離較近的物件,最好在需要時才開啟enable
  8. 多使用Trigger或Event delegeate來觸發或通知狀態的改變,而非每個每個Update作檢查
     
  9. 盡量避免每個Frame作Raycasting:
    對於Mesh物件作Raycasting有一定的Cost, 所以盡量避免每個Frame作Raycasting,可用Culling (Layer) mask先濾掉不必要的物件
     
  10. 別忽視SkinMesh與DrawCall的傷害:(Mobile Platform)
    目前iPhone 3GS與Nexus One以下的機種,對於SkinMesh與DrawCall的數量還是相當敏感。以手邊的測試數據來看,800面數的角色在NEXUS ONE同一畫面上,維持30FPS可撐7~9隻角色
  11. 少用Alpha testing,多用Alpha Blending:(Mobile Platform)
    依據PVR所公佈的數據,在iPhone 3GS以前的機種GPU的設計上,Alpha Testing是比Alpha Blending昂貴的

     
  12. 減少動態光照: (Mobile Platform)
    不管是Vertex或PerPixel Lighting,都會在DrawCall上增加數量,因此對於較低階的機種會有影響。PixleLight的部分,因為是對每個Pixel做光照計算,對GPU的影響會更勝Vertex Lighting。解決方法是將光bake到貼圖上。
     
  13. Mesh compress壓縮比調整到最高以降低容量:
    除非發現壓縮之後的Mesh出現,如:破洞、閃爍...等問題,不然盡量將這個選項設定的越高越好
     
  14. 手動呼叫gabage collection:
    在固定時間間隔下手動呼叫System.GC.Collect(),確保記憶體的回收,如下:

    if( Time.frameCount % m_frameFreq == 0 )
        System.GC.Collect();

    這個用法在Profiler下,可能會發現在CPU Cost上固定間隔的突波,但實際在手機上測試時,對於FPS並沒有顯著的影響。
如果各位先進覺得還有其他可以補中的點,也歡迎告訴我們,讓這篇文章更具指標性喔!!謝謝


作者:Bric Lin, Email: ericlin09@gmail.com
曾任職台灣某遊戲公司研發Game Engine,為書籍“OGRE 入門指南”的譯者之一,專攻Rendering技術與遊戲開發

到底怎麼樣可以同時把std error, std out 記錄到檔案中呢?

錯誤記錄是很重要的

身為一個專業的工程師,當程式出現錯誤時,一定要把錯誤好好的記錄下來,而要能夠把錯誤有彈性的記錄下來,在Java上請使用log4j,在Python上請好好研究logging module, 在C++上有log4j的兄弟: log4cxx。若不好好善用這些logging module, 你所寫出來的服務是絕對沒有辨法在Production環境下生存的。

那吐出 std error 的那些壞孩子呢?


每個人都有不得已的時候,總是有許多的程式是不得不把訊息送往std out, 及 std error. 比如說一些簡短 Shell Script 或是較老舊的程式碼。

那我們只好另外把這些訊息集中處理。


這樣的工作看似不難,工程師跟Shell 一定有點交情,IO Redirection 多少都有交手過幾次,直覺可能會寫出類似下面的指令:
some_command 2>&1 >/var/log/certain_message
字面上直接翻譯: "我想把std error 導到 std out, 再把  std out 導到檔案中"。

但是事情絕對沒有那簡單,上面的指令是不work的。

你可以用下面這個指令來認清事實:

python -c "from sys import *; stderr.write('err'); stdout.write('out')" 2>&1  > /tmp/o.txt

這個指令在沒有IO Redirect前,會送 'err' 這個字串到 std error ,送 'out' 到 std out。而在設定完IO Redirection後,err這串字會竟然還是只會出現在畫面上,而out 會出現在 /tmp/o.txt 這個檔案裡。

唉,事與願違,錢不好賺。

我該怎麼做

如果你是個懶鬼,我就跟你說,換順序就好,本篇文章可以不用看下去了,但記得給個讚之類的東西。

    some_command >/var/log/certain_message 2>&1
     
上面這段 Command 就可以把所有的訊息收到集起來了,把 2>&1 往後擺即可。

但要能夠理解順序的影響,就要記得一個Process 與其所掌握IO Device 之間隔了一層抽象層: File Descriptor. Process 是經由 File Descriptor 才能對 IO Device 有所影響。

我們先以這個簡短的command 來做圖解釋: echo "cnt" 2>&1 > o.txt 。
並且請記得,預設情況下,FD1是指向 std out,  FD2是指向 std error

下圖一是我們還沒有做任何 IO Redirection 以前的樣貌。

圖一: File Descriptor 與 IO Device 的對應

Bash 的man page 有說到,會由左到右分析指令。
當以在圖二中,Shell 會首先分析  2>&1 ,這會讓FD 2 指向  FD 1 所指向的裝置。因此,FD2會指向 std out. 而 std err 就像是個 dangling pointer,不再有機會被使用到了。
圖二: 2 指向了 std out, 而此時, std error 成了孤兒了

下圖三: 下一個IO Redirection:  > o.txt, 使 FD1指向了 o.txt.
這只改變FD1, 不會對 FD 2 造成任何影響, FD2 還是指向 std out。
這樣子的結果,達不到我們想要收集訊息的目地。
圖三: IO Redirection 設定結束了,只有FD1 被收集到了檔案中


那如下圖四的正確順序指令 echo "cnt" > o.txt  2>&1 又會呈現什麼樣貌呢?

圖四: 正確順序的指令又會呈現什麼樣貌呢?
 Shell 首先面對了 > o.txt, 會把 FD1 指向 o.txt.
圖五: FD1 被指向了 o.txt

在下圖六,Shell 遇到了 2>&1, 會把FD2 指向 FD1 所指向的IO Device, 也就是 o.txt。


最後,我們的Process 對FD1及FD2 的output, 都會直接導向了 o.txt.

結論

上面這些圖,是幫助我們理解Shell 背後的運作,只要記住 file descriptor 那一層間接性,我們就可以清楚地推導出 io redirection 所造成的結果而不用去硬記指令的寫法,如果你想寫出更複雜的指令組合,也絕不會出錯。 如果喜歡這類文章,你在這個Blog可以看到加入 icoding 粉絲團的方式,請不吝惜給我們支持與鼓勵!


  
Related Posts Plugin for WordPress, Blogger...