Blog 已遷移 http://steventtud.com,logdown 版本不再更新,麻煩大家前往新網站觀看^^

在 OSX 環境中測試 etc/hosts 是否成功修改

前言

透過修改 etc/hosts 讓我們不需要透過 DNS 也能模擬網域名稱連線到伺服器。
可是設定是否成功?這時候我們可以用一些測試工具來檢測之。

OSX環境下

OSX 內建 dscacheutil 工具,可以用來檢測網址名稱對應 ip 的結果。
使用方法

dscacheutil -q host -a name 檢測的網域名稱

比如說我想要檢測 google.com 對應的 ip

$ dscacheutil -q host -a name google.com
name: google.com
ipv6_address: 2404:6800:4008:c03::66

lsname: google.com
ip_address: 74.125.203.101
ip_address: 74.125.203.100
ip_address: 74.125.203.138
ip_address: 74.125.203.139
ip_address: 74.125.203.113
ip_address: 74.125.203.102

如果我在 etc/hostsgoogle.com 設定至自定義的 ip。

111.111.111.111 google.com

檢測的結果即變成

$ dscacheutil -q host -a name google.com
name: google.com
ipv6_address: 2404:6800:4008:c03::64

name: google.com
ip_address: 111.111.111.111

參考資料

mac - How can I install getent on Snow Leopard? - Ask Different

其他相關工具

macos - OS X 10.10.1 /etc/hosts & /private/etc/hosts file is being ignored and not resolving - Ask Different

Linux 環境

dns - How to test /etc/hosts - Unix & Linux Stack Exchange

Ruby - 讓 irb 更好用

前言

在使用pry的時候我們可以回到上一個輸入的指令,擁有記錄指令歷史的功能。其實irb也可以!

開始修改

修改~/.irbrc

require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 200
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-history"

修改完之後按下方向鍵上和下即可使用上一個用過的指令。而實際上,歷史紀錄是儲存在~/.irb_history

另一個常用的功能 - autocomplete 我們也順便把它開起來。
~/.irbrcirb/completion即可,現在你的 irb 按下 tab 即可以自動補完。

require 'irb/ext/save-history'
require 'irb/completion'
IRB.conf[:SAVE_HISTORY] = 200
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-history"
IRB.conf[:AUTO_INDENT] = true
參考資料

Module: IRB (Ruby 2.0.0)

Have ruby irb console save history | kitt hodsden's nags of a similar ilk

Ruby metaprogramming - Open Class and Refine

簡介

Open Class 是 Ruby 常用的技巧,指的是我們可以覆寫已經存在的方法,來修改物件或類別的行為。
在這邊我沒有要講詳細的作法,要介紹的是在 Ruby 2.0 中新增的 refine

Refine

Refine 是 ruby 2.0 之後加入的元素。
會使用 Refine 的原因是使用一般 Open Class 的技巧時並不會有明顯的提示。
所以我們如果沒有用比較清楚的方法標示 Open Class 作用的地方,造成的後果是非常難以維護且可怕的。
透過 refine 我們可以更安全的覆寫原本的 Class,尤其是系統中本來存在的一些方法。
用法是先定義一個 Module ,Module 裡面寫下你要覆寫的 Class 與要覆寫的方法。
並且用 Using 決定在何時使用。

class Cat
  def meow
    puts "Meow"
  end
end

module IAmRefineModule
  refine Cat do
    def meow
      puts "meow in Refine Module"
    end
  end
end

c = Cat.new
c.meow

using IAmRefineModule

c = Cat.new
c.meow

執行結果:

$ ruby refine_ex.rb
-> Meow
-> meow in Refine Module

要呼叫using IAmRefineModule後,我們定義的 refine Module,那麼 Class 方法才會被覆寫到原有的 class。

Method Lookup

接下來我們來跟 Module 的 Prepend 來比較看看,看看 refine 定義的方法,是否會優先於 prepend module 所引入的方法。

module IAmModuleInclude
  def meow
    puts "Module include Meow"
  end
end

module IAmModulePrepend
  def meow
    puts "Module prepend Meow"
  end
end

class Cat
  prepend IAmModulePrepend
  # include IAmModuleInclude

  def meow
    puts "Meow"
  end
end

module IAmRefineModule
  refine Cat do
    def meow
      puts "meow in Refine Module"
    end
  end
end

c = Cat.new
c.meow

using IAmRefineModule

c = Cat.new
c.meow

來執行看看

$ ruby refine_prepend_compare.rb
-> Module prepend Meow
-> meow in Refine Module

我們發現,即使 prepend Module 之後,只要我們使用 refine,就會優先使用 refine 所定義的方法。
實際上使用 refine 的時候,因為語意上非常明顯,作為 open class 的選擇我覺得是很棒的。

Elasticsearch 實戰筆記

前言

最近工作上使用的資料庫主要以 Elasticsearch 為主。而 Elasticsearch 跟傳統的關聯式資料庫有諸多的不同之處。初期在開發的時候並不是那麼快的上手。所以記錄下該如何使用 Elasticsearch 與如何在官方文件中找到自己需要的功能。

1. 準備工作

1.1 你需要知道的名詞

一開始我對名詞的對應並不是特別的重視,隨著實戰上的需求,我開始需要查找 API 的時候,發現文件有點不知道從何看起。後來隨著使用的功能越來越多。必須對 Elasticsearch 有更深一層的了解,於是花了大約兩天左右的時間把文件重要的部份大略的看過一次。這樣的過程讓我理解了哪些功能可以在哪些地方找到,也是我寫下這篇筆記的動機,

index 對應關聯式資料庫中的 database
type 對應關聯式資料庫中的 table
docuement 對應的是關聯式資料庫中的一筆資料。
mapping 對應關聯式資料庫中的 schema

1.2 文件導覽

常用的文件分為兩部分 definitive guidereferenece 。為了直接對應官方文件這邊就直接用英文名詞。

基本的資料操作和一些基本的用法可以在 The Definitive Guide 中找到,Definitive Guide 整體比較偏向 如何達成你要做的事情。包括以下幾種:

  • 文件的新增刪除
  • 如何做全文搜索
  • 如何搜尋

而 API 的詳細分類和用法的範例則可以在 Elasticsearch Reference 中找到。比較常用的有:

  • Document API - 單筆資料的操作在這邊查找
  • Search API - Elasticsearch 支援的搜尋方法非常多種,Search API 是非常重要的一環。了解 search api 後可以搭配 update_by_querydelete_by_query 等等的 api 來做更新或刪除的動作
  • Cat API - 可以獲得系統目前的資訊
  • Indice API - 用來管理 index 的 api。這邊我一開始看到疑惑了一下。過了一會兒才聯想到 elasticsearch 的 index 等於關聯式資料庫的 db。所以其實每個 api 開的名稱都是很有條理和整齊度的
  • Aggregations - aggregation 對應關聯式資料庫的 GROUP,做分組後取得結果。
  • Query DSL - 如果需要自定義搜尋,elasticsearch 有提供query DSL 讓你可以高度彈性的組出想要的搜尋。

另外 Elasticsearch Ruby 的 API 位置在這邊。因為文件的連結沒有在官方 github 中明顯的寫出來,一開始讓我疑惑了一下。

1.3 可以用哪些方法存取 Elasticsearch

跟 Elasticsearch 的溝通只要透過 HTTP Request 就可以存取。這也是雖然 Elasticsearch 是以 Java 作為底層,但是卻可以跟大多數其他程式語言製作系統輕鬆串接重要原因。所以操作上只要你可以發出 HTTP Request 就可以跟 Elasticsearch 溝通。而因為安全性的關係,Elasticsearch 常常架設在內網。需要在遠端存取的時候透過VPN 會是比較安全的作法。

我比較常用的方法有以下幾種:

  1. 直接在終端機下使用 curl 取得結果,指令由官網複製下來修改成自己的格式。好處是這樣做可以排除很多不確定因素,例如gem是否有bug等等的問題。壞處是取得的結果並不好看。如果要好看的話需要自行處理。
  2. 使用 Restclient 將 curl 指令包起來發 HTTP Reqeust 給 Elasticsearch。跟 Elasticsearch-ruby 比較起來,這是單純的 HTTP Request,比較不容易有 Gem 中的 Bug。如果在終端機中確定指令可用,可以直接用 Restclient打造。直接使用system call也是這邊的替代方案之一。
  3. 使用外掛(plugin)存取,好處是方便存取,看到的結果也會是整理過的。缺點是要架設需要花一段時間先把環境整理好。
  4. 使用 Elasticsearch-ruby存取 Elasticsearch 。好處是搜尋出來的結果已經經過包裝。缺點是需要大量新增或更新資料的時候因為過度包裝速度會比較慢。一開始使用的時候會擔心有些API不是官方的最新版本。實際測試的心得是大部分都是可行的。如果得不到預期的結果再使用 curl 來下原始的指令。到這邊可以發現,你需要對 elasticsearch的行為有一定的認識,不然你是無法好好的使用他。

2. mapping

2.1 Schema Free 更要嚴謹的定義資料庫

mapping 即關聯式資料庫中的 schema。但是 Elasticsearch 有著 schema free 的特性。即如果你想存入的欄位的型別與 mapping的型別不同時你仍然可以存入。還未定義過的欄位也可以直接存入資料庫。

有著 schema free 的特性更需要注重資料的格式與資料欄位的設計,如果不當的使用很可能讓資料庫中資料變成一堆難以整理的垃圾。

2.2 特別需要注意的 String 型態

Elasticsearch 的 String 比較特別。如果不做特別設定的話,Elasticsearch 會預設處理方式為 full text,也就是會幫你的 string 做切字的動作。如果要把整個 string 欄位視為一個 keyword ,則需要在 mapping 的時候加上 index: "not_analyzed"

PUT my_index
{
  "mappings": {
    "my_type": {
      "properties": {
        "full_name": { 
          "type":  "string"
        },
        "status": {
          "type":  "string", 
          "index": "not_analyzed"
        }
      }
    }
  }
}

相關連結:

String datatype | Elasticsearch Reference [2.4] | Elastic
Field datatypes | Elasticsearch Reference [2.4] | Elastic

2.3 keyword 、 full text 與 analyzer

keyword的行為跟關聯式資料庫的欄位行為比較像。除了用字串比對的方式以外沒什麼其他的搜尋方法。而full text 則會通過 analyzer 進行分析,你可以使用系統提供的 analyzer。系統提供的 analyzer 只能切英文字。如果要切中文可以用一套叫做 elasticsearch-analysis-ik的開源解決方案來處理。或是自行撰寫也是一個選項。

2.4 查詢 mapping 的語法

在終端機中使用 curl

curl -XGET 'http://localhost:9200/spider/_mapping/tkecw'

使用RestClient

res = RestClient.get "http://localhost:9200/spider/_mapping/tkecw"
JSON.parse res.body

相關連結:

Get Field Mapping | Elasticsearch Reference [2.4] | Elastic

參考資料

Getting Started | Elasticsearch: The Definitive Guide [2.x] | Elastic
Module: Elasticsearch::API::Actions — Documentation for elasticsearch-api (2.0.0)
Field datatypes | Elasticsearch Reference [2.4] | Elastic

未完待續...

Ruby metaprogramming - Method Lookup

介紹

ruby metaprogramming 這本書除了教如何用 ruby 來生成其他的程式語言外,對語言特性的描述是比較深入的。因此在研讀這本書的同時,記錄下一些我覺得重要的部份。這些筆記不會依照章節的順序性。而是隨機記錄我需要的部分。

Method Lookup 即 Ruby 物件中查找方法的順序。從 Module 得到的方法與從 Class 繼承的方法其實是有順序性的。
知道其順序性後我們在編寫的程式碼的時候才可以比較清楚的預想程式行為的發生的情況。在追蹤原始碼的時候對語言特性多一份的理解追起來就會順利一點快速一點。

prepend 與 include

這邊是 Method Lookup 的順序圖,左邊是 instance method 右邊是 class method。使用 include 的時候可以發現,如果 class 中原本就有方法,那 include 進來優先權還是無法比 class 原有的方法高。但是如果使用 prepend ,則可以取代。我們用個小例子來證實這點。

ex1 - 使用 include 引入方法

module IAmModule
  def meow
    puts "Module Meow"
  end
end

class Cat

  include IAmModule

  def meow
    puts "Meow"
  end

end

c = Cat.new
c.meow

執行結果:

$ ruby ex1.rb
-> Meow

ex2 - 使用 prepend 引入方法

module IAmModule
  def meow
    puts "Module Meow"
  end
end

class Cat

  prepend IAmModule

  def meow
    puts "Meow"
  end

end

c = Cat.new
c.meow
$ ruby ex2.rb
-> Module Meow

我們可以使用 ancestor 方法來看看優先順序:

ex1

[ Cat, IAmModule, Object, Kernel ,BasicObject ]

ex2

[IAmModule, Cat, Object, Kernel, BasicObject]

ex1 使用 include 所以 Module 在 Class 之後,會優先呼叫 Class 的方法。
ex2 使用 prepend , Module 引入的順序在 Class 之前,因此會優先執行 Module 的方法。

相關的還有與 refine 的比較,不過我想要把 refine 整理在 open class 獨立成一篇。敬請期待~

參考資料 & 圖片來源

Get the Lowdown on Ruby Modules

Ruby - 爬網頁時遇到的編碼錯亂問題

問題描述

編碼問題是寫爬蟲常會遇到的問題。當你沒有處理好編碼問題,爬回來的網頁無法進行字串的切割,也無法使用 nokogiri 抽離需要的部份。

解決方法

  1. 找到原始網頁的編碼chartset='big'
  2. 把網頁 force_encoding 至原始格式
  3. 將網頁轉換成 utf-8,這是 ruby 預設的編碼,也是 nokogiri 接受的編碼。

force_encoding 的意思是強制使用某種編碼格式,但是其實不會進行編碼的轉換,因為ruby預設是utf-8,
所以如果網頁是 big5 我們就得先幫網頁加上網頁原有的編碼格式。

設定好網頁原始的編碼之後,我們才可以將載下來的網頁轉成我們要的格式,所以最後得使用encode指令來轉換成我們要的utf-8格式。

完成以上步驟之後,你就可以順利的進行字串的處理了:)

Ruby - 利用 ARGV 特性執行指定的方法

情境

我想手動執行 class 的某個方法。這邊的例子是當我想移動的時候,我可以選擇走路、跑步或是游泳。

方法一: 在 irb 中引入 Ruby 檔

class Move

  def self.walking
    puts "walking"
  end
    
  def self.running
    puts "running"
  end

  def self.swimming
    puts "swimming"
  end
end

打開 irb ,輸入 require_relative argv_ex1.rb,argv_ex1.rb 是上面程式碼的檔名。

[1] pry(main)> require_relative 'argv_ex1.rb'
=> true
[2] pry(main)> Move.swimming
swimming
=> nil

用這樣的方式我們可以執行 Class 中的某個方法,不過還是麻煩了點。現在我們來試試透過 ARGV 來執行 Move 中的方法。

方法二:使用 ARGV 執行指定的 method

2.1 簡單介紹 ARGV

當你在 Command line 模式中輸入除了原本檔名以外的參數,會自動被儲存成一個 ARGV 陣列。

argv = ARGV

puts "ARGV Type is: " + argv.class.to_s
puts "array elements are: "
puts argv

打開 Terminal 輸入ruby argv_ex1.rb cat dog rabbit snake,會得到:

ARGV Type is:Array
array elements are:
cat
dog
rabbit
snake

簡單的說參數的第一個會對應 ARGV[0],第二個會對應 ARGV[1],運用這個特性我們可以依照需求來決定要執行的方法或內容。

2.2 應用到例子
class TryARGV
  def self.execute( command )
    if command.to_sym == :walk
      self.walking
    elsif command.to_sym == :swim
      self.swimming
    else
      puts "swim or walk?"
    end
  end

  def self.walking
    puts "walking"
  end

  def self.running
    puts "running"
  end

  def self.swimming
    puts "swimming"
  end
end

TryARGV.execute( ARGV[0] )

現在我們只要在 terminal 中輸入 swim 或是 walk 即會呼叫對應的方法。

$ argv_practice ruby argv.rb swim
swimming
$ argv_practice ruby argv.rb walk
walking

結論

可以用來呼叫類別方法的還有 rake 也可以達到相同效果。不過如果要使用的話還需要另外設定 .rake 檔。ARGV 的方式在輕量使用的時候是個不錯的選擇。

Ruby 爬蟲小技巧 - 處理 Html Entity

這是一個在工作上遇到的小問題。
把網頁爬回來的時候有 HTML Entity 的編碼,看起來很不美觀。
舉例來說,爬回來的標題如果含有 HTML Entity 會是這個樣子:

PURUS空氣清淨器(鴻海集團創星出品)

如果我想要使用資料建立自己資料庫的時候勢必要對 html entity 做一些處理
這時候 Gemhtmlentities就派上用場了。使用方法:

require 'htmlentities'

str = "PURUS空氣清淨器(鴻海集團創星出品)"
puts HTMLEntities.new.decode(str)
=> PURUS空氣清淨器(鴻海集團創星出品)

現在你可以把品名存進資料庫了 :D
其實這篇只是想記錄一下,HTML Entity這個名詞。歸類到編碼的類別方便以後查找。

參考資料:

htmlentities.rubyforge.org

ActiveRecord - 更新大量資料

前言

本文使用的兩種方法,實際上都是用一個 sql 插入或更新所有的資料。
原因是使用其他的方法都沒有使用一個sql插入快。
如果插入的筆數過多,需要調整 sql buffer 的大小。
本例子的情景是一次更新100筆資料,資料量不大,所以不會遇到這個問題。

方法一:純 SQL

  1. 因為欄位很多,我要傳送的欄位又是完整的一個不少,所以我用 Model.attribute_names 來組合要傳入的欄位名稱。
  2. 然後將要更新的 Hash 組成 VALUES (x1, y1, z1, ...), (x2, y2, z2, ...), ...字串
  3. 最後將要更新的欄位組成 flag_string=VALUES(flag_string) 這種格式

想要組出的 sql

ActiveRecord::Base.connection.execute("
  INSERT INTO linkouts (#{linkouts_attr_names_string})
    VALUES #{query_string}
    ON DUPLICATE KEY UPDATE #{equal_val_string}
")

組出來的結果

query string 這邊比較難處理,因為是 raw sql,如果在 integer 型態的欄位塞入 '',或是在字串的欄位沒有用引號括起來都會噴錯。這也是使用 raw sql 撰寫的缺點。

[1] pry(#<MastersController>)> linkouts_attr_names_string
=> "id,kind,report_id,keyword_id,ezpd_id,url,name,price,full_match,created_at,updated_at,merchant_price,price_check,status,merchant_id,remark,out_of_stock,flag_at,flag_string,remark_status"

[2] pry(#<MastersController>)> query_string
=> "(22442,'0',8,126,'https://www.etungo.com.tw/inside/413/419/608/20711.html','【CLENSURE可蘭秀】美容離子導入儀 (SNOWY)',1580,1/1,'2016-08-01 17:31:20','2016-08-01 18:01:52',2)"

[3] pry(#<MastersController>)> equal_val_string
=> "kind=VALUES(kind),report_id=VALUES(report_id),keyword_id=VALUES(keyword_id),ezpd_id=VALUES(ezpd_id),url=VALUES(url),name=VALUES(name),price=VALUES(price),full_match=VALUES(full_match),created_at=VALUES(created_at),updated_at=VALUES(updated_at),merchant_price=VALUES(merchant_price),price_check=VALUES(price_check),status=VALUES(status),merchant_id=VALUES(merchant_id),remark=VALUES(remark),out_of_stock=VALUES(out_of_stock),flag_at=VALUES(flag_at),flag_string=VALUES(flag_string),remark_status=VALUES(remark_status)"

完整程式碼:

linkouts_attr_names = Linkout.attribute_names
    linkouts_attr_names_string = linkouts_attr_names.join(",")
    string_arr = []

    bm[:linkout_list].each_with_index do |l, i|

      l["status"] = 2
      l["flag_string"] = nil
      l["flag_at"] = nil

      if i == 0 # 這邊只拿出一組來組合sql,以免sql太亂難除錯

        current_arr = []
        l.each_pair do |k, v|
          if k == "created_at" || k == "updated_at"
            current_arr << + "'" + v.strftime('%Y-%m-%d %H:%M:%S') + "'"
          elsif v.class == 'boolean'
            "#{v}"
          elsif v.to_i > 0
            current_arr << "#{v}"
          elsif v != nil
            current_arr << "'#{v}'"
          end
        end
        string_arr << "(" + current_arr.join(",") + ")"

      end
    end

    query_string = string_arr.join(",")
    linkouts_attr_names.delete("id")
    equal_val_string = linkouts_attr_names.inject([]) do |result, attr|
      result << "#{attr}=VALUES(#{attr})"
    end
    equal_val_string = equal_val_string.join(",")
    ActiveRecord::Base.connection.execute("
      INSERT INTO linkouts (#{linkouts_attr_names_string})
        VALUES #{query_string}
        ON DUPLICATE KEY UPDATE #{equal_val_string}
    ")

這樣組出的 SQL 結果如下

INSERT INTO linkouts (id,kind,report_id,keyword_id,ezpd_id,url,name,price,full_match,created_at,updated_at,merchant_price,price_check,status,merchant_id,remark,out_of_stock,flag_at,flag_string,remark_status)
        VALUES (22442,'0',8,126,'https://www.etungo.com.tw/inside/413/419/608/20711.html','【CLENSURE可蘭秀】美容離子導入儀 (SNOWY)',1580,1/1,'2016-08-01 17:31:20','2016-08-01 18:01:52',2)
        ON DUPLICATE KEY UPDATE kind=VALUES(kind),report_id=VALUES(report_id),keyword_id=VALUES(keyword_id),ezpd_id=VALUES(ezpd_id),url=VALUES(url),name=VALUES(name),price=VALUES(price),full_match=VALUES(full_match),created_at=VALUES(created_at),updated_at=VALUES(updated_at),merchant_price=VALUES(merchant_price),price_check=VALUES(price_check),status=VALUES(status),merchant_id=VALUES(merchant_id),remark=VALUES(remark),out_of_stock=VALUES(out_of_stock),flag_at=VALUES(flag_at),flag_string=VALUES(flag_string),remark_status=VALUES(remark_status)
    )

方法二:使用 AcitveRecord-import

AcitveRecord Import 是一個專門用來批次新增或是修改資料的 gem。
用法很簡單,在原本的 Model 後面加上要新的陣列,並指定要更新的欄位即可。
要更新的欄位也支援使用 sql 語句。

Model.import array_to_be_update, on_duplicate_key_update: [:title]
Model.import [book1, book2], on_duplicate_key_update: "author = values(author)"

結論

組 Sql 不需要先把資料包成物件,效能會比較好。
不過相對來說需要多花一些時間處理資料欄位型態的問題,
而本例中的 mysql 因為是一個指令加上要更新的欄位可能很多,
所以 debug 的難度比較高。
這時候就看取捨了,如果初期趕著功能上線可以先用 activeRecord-import 來寫,
當遇到效能需要最佳化的時候再改成純 sql 會是比較好的處理方式。

Linux - 如何查詢 OS 版本

要接手 Server 第一件事情就是要了解 server 的環境啦。
這邊要記錄的是如何判別 Linux 系統類別的方式

1. 確認 Kernel 版本

使用 uname -or 可以取得 kernel 的版本

$ uname -or
=> 3.10.0-327.el7.x86_64 GNU/Linux

如果要知道詳細的資訊,輸入 uname -a,但即使這樣也無法清楚的看出 OS 的種類。

$ uname -a
=> Linux username 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

2. 針對不同 OS 查找對應的說明系統名稱的文件

如果是 Ubuntu,輸入 cat /etc/lsb-release。有找到的情況會出現以下訊息

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"

如果你的系統不是 Ubuntu,則會出現以下訊息

$ cat /etc/lsb-release
cat: /etc/lsb-release: No such file or directory

Debian

雖然跟 Ubuntu 同體系但是存放版本號文件的位置不太一樣,
輸入cat /etc/debian_version

$  cat /etc/debian_version
8.0

Fedora, Red Hat and CentOS have:

Fedora: $ cat /etc/fedora-release
Fedora release 10 (Cambridge)

Red Hat/older CentOS: $ cat /etc/redhat-release
CentOS release 5.3 (Final)

newer CentOS: $ cat /etc/centos-release
CentOS Linux release 7.1.1503 (Core)

最後用表格來整理以上資訊

系統 位置
ubuntu /etc/lsb-release
debian /etc/debian_version
Fadoara /etc/fedora-release
Red Hat/older CentOS /etc/redhat-release
newer CentOS /etc/centos-release

如果你的 OS 上面列表都找不到的話,可以找 etc 資料夾內的有 release 這個詞的

cat /etc/*{release,version}

Reference

command line - How do I find out what version of Linux I'm running? - Super User
How to Check CentOS Version Number