2014年12月12日 星期五

linux, bash, find 的應用(-exec, sed -i, 檔案內取代, 與xargs比較)

find -exec

find -exec 指的是將找到的檔案,送到後面的指令去處理。從-exec\;為止,代表是接受從find送來要處理的指令,而送來的檔案將用{}代表找到的檔案。

譬如你想把所有副檔名為.log的檔案刪除掉,可以這麼做

find . -type f -name "*.log" -exec rm {} \;

(其實我加上-type f有點多餘,因為我已經指定-name "*.log"了,就不可能輸出資料夾了。)

如果刪檔案的時候,一直要你按yes,可以這樣

yes | find . -type f -name "*.log" -exec rm {} \;

yes這個指令會一直串流輸出yes,這樣刪檔案就自動一直輸入yes

例子中find找到的檔案,就放到-exec\;之間的指令去處理,譬如找到了檔案develop.log,就會變成

rm develop.log

另外,{}並不是規定只能出現一次,譬如你要將資料夾內所有檔案加上副檔名log,可以這麼做

find . -type f -exec mv {} {}.log \;

這指令會包含子資料前內的檔案都加上.log副檔名。如果只想要目前資料夾的檔案,所以可以加上-maxdepth 1,若要地回到下一層資料夾可以將1改成2,3或4以此類推

find . -maxdepth 1 -type f -exec mv {} {}.log \;

因此,若要批次改變檔案的內容,就可以搭配find -execsed -i。sed 加上 -i 參數,代表直接對檔案內容做修改。

我常常在幫別人複製或移動網站,很多人的網址都寫成包含域名的絕對路徑,所以常常要用這個指令去找出所有含舊域名的檔案,並改成新域名。

譬如舊域名為http://www.old.com,要改成http://www.new.com,我會這麼做

find . -type f -exec sed -i 's/www.old.com/www.new.com/g' {} \;

(sed這個指令在OS X上面如果照上例那樣執行,會有錯誤訊息。詳細請參考stackoverflow上面的討論)

這樣子會找遍所有的檔案,需要的時間有點久。我會再加上參數-not -name "不需要的檔名",來排除一些不需要尋找的檔案

find . -type f -not -name "*.log" -not -name "*.jpg" -exec sed -i 's/www.old.com/www.new.com/g' {} \;

像這樣跳過所有的log檔與jpg圖檔,有需要可以加上其他如-not -name "*.png",才加快速度。或者乾脆指定-name "*html"-name "*.php",只尋找相關網頁檔。

xargs

xargs使用起來就沒有像find -exec可以將檔名以{}的形式傳給指令去安排。xargs只能收管線資料當成指令參數來執行。

雖然使用上沒有靈活,但管線指令就是他的優點,所以他不只可以搭配find指令,echo資料給他也可以,譬如

echo a b c d e | xargs touch

一次創造了a b c d e五個檔案。如果find後面不用-exec,可以改用管線指令傳給xargs,譬如本篇第一個刪除檔案的例子,改用xargs變成

find -name "*.log" | xargs rm

但是後面改檔名的例子,就沒辦法這麼簡單的處理(可以處理,但改用xargs還要搭配其他指令,本篇已經太長,不再節外生枝介紹)

另外像改檔案內容的指令,因為檔名參數接在最後面,所以也可以很簡單的改用xargs來處理

find . -type f | xargs sed -i 's/www.old.com/www.new.com/g'

因此,當我用find在處理檔案時,我會用find -exec比較靈活。其他情況下我就用管線串流到xargs來處理

2014年12月9日 星期二

HTML, CSS, 相對視窗或螢幕的高度與寬度

w3school.com網站,CSS Units有各種與寬度的表示法

以我使用的頻率來排序:

  • px: 使用螢幕幾個像素(但是還要考慮Retina螢幕像素是一般螢幕像素的兩倍)
  • %: 相對父層的大小比例
  • vh, vw: 相對於瀏覽器展示網頁區域的大小(不是整個瀏覽器的大小,沒包含瀏覽器的工具列,只有展示網頁的區域)
  • vmin: vh, vw取最小值(另外還有vmax則是取最大值,但是目前IE跟safari不支援)

px%很常用,vhvwvmin是CSS3的新產物,表示相對瀏覽器展示頁面的大小


相對視窗大小

這邊先說明,vhvwvmin只包含網頁顯示區域的長寬,不包含瀏覽器的工具列

先從dom的最根本講起好了,一份HTML文件,根是<html></html>(雖然沒有嚴格規定,不寫也能顯示),然後這個根的父元件就是瀏覽器的網頁頁面顯示區

因此,如果對<html></html>宣告大小是100%就跟宣告100vh一樣,因為都是指瀏覽器的網頁頁面顯示區大小

這個範例是將html設定為100vh

<html style="height: 100vh">
    <head>
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

        <script type="text/javascript">
            console.log($('html').height());
        </script>
    </head>
</html>

這個範例是將html設定為100%

<html style="height: 100%">
    <head>
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

        <script type="text/javascript">
            console.log($('html').height());
        </script>
    </head>
</html>

這兩者在瀏覽器的console應該輸出一樣的大小

如果從html, body到div , 這些tag都填入100%,則從頭到尾都填滿整個視窗(當然還注意body預設有padding, margin佔有一些寬度,要把他們都設為0,才會真的佔滿全畫面)

不過現在有vh, vw,就不用這麼麻煩了,可以只設定長度為100vh, 寬度為100vw,就可以填滿整個畫面(依然得注意body預設佔有padding跟margin)

像下面這樣就能把兩個div排在一起,並且以瀏覽器寬度3:7的比例,分割成兩個區塊

<html>
    <head></head>
    <body style="margin: 0;padding: 0;">
        <div style="width: 30vw;float:left;">Left...</div>
        <div style="width: 70vw;float:left;">Right...</div>
    </body>
</html>

還有一個問題,以往使用桌上型電腦的螢幕,我們可以預期大部分人的電腦,都是寬度大於長度,大部份的人也都是用全螢幕瀏覽網頁。所以我們很信任的將長寬都設定成vh就可以變成正方形,並且不超過瀏覽器大小。

但是現在手機螢幕,可以拿直的,也可以拿橫的,那要怎麼辦呢?就使用vmin

譬如我們要顯示一張正方形的圖片,希望以最大張的方式呈現

<html>
    <head></head>
    <body style="margin: 0;padding: 0;">
        <img src="圖片路徑" style="width: 100vmin;height: 100vmin;">
    </body>
</html>

這樣就不怕桌機或手機,螢幕擺直得或橫的,都以最大張,且不會超出瀏覽器範圍欄顯示圖片


相對螢幕大小

那可不可以相對於使用者整個螢幕的大小?

可以,但是要使用Javascript去設定,在Javascript裡面直接使用screen.heightscreen.width,就可以取得螢幕大小(關於screen是什麼?請參考w3school的相關頁面)

之後再用dom語法,去改你要改的地方,假設你要將所有的div tag寬度都隨使用者螢幕寬度改變,這邊使用jQuery:

$('div').width(screen.width);

這時候不管使用者怎麼拉動瀏覽器的寬度,所有的div tag寬度都等是螢幕寬度

如果想單純使用javascipt的話,語法比較冗長一點,可以參考w3chool的頁面,注意他傳入的參數後面還要接單位,所以在screen.width之後記得再加上"px",之後才當引數傳進去

回到原本的jQuery版本,如果想要75%的螢幕大小

$('div').width(screen.width * 0.75);

想要30%的螢幕大小

$('div').width(screen.width * 0.3);

以此類推,我一開始會害怕對screen.width做數學運算,因為不確定內容是字串還是數字。但是多慮了,javascript可以對數字做數學運算,也可以對內容是數字的字串做數學運算

'10' * '10' // 輸出100
'10' * 10 // 也輸出100

如果是在Ruby,則是輸出"10101010101010101010",所以在javascript裡面,不用怕,他內容是數字,就直接對他做數學運算,不用先檢查型別。(若字串內容不是數字,會輸出NaN,Not a Number的意思)

2014年12月4日 星期四

Linux, bash, 壓縮, tar, bz2, xz, 7z, 壓縮率

Linux, bash, 壓縮, tar, bz2, xz, 7z, 壓縮率

如果只是想查各種副檔名的解壓縮指令,只要Google副檔名就可以找到(例如:tar.bz2)

這邊主要是在對資料夾壓縮時,為了自訂壓縮率而深入研究壓縮指令而產生。

tar 可以對檔案或資料夾進行單存的打包(或解開打包),後面接c代表打包,x代表解開打包。這邊在Bash底下產生一個簡單的文字檔來練習:

echo "Hello World" > text
tar c text
# 輸出 text000644 000765 000000 00000000014 12437746653 012241 0ustar00wemeewheel000000 000000 Hello World

# 如果要儲存,加上參數f,並指定存檔名稱
tar cf text1.tar text

# 或者使用串流來寫入檔案也可以(實際應用不建議)
tar c text > text2.tar

原始檔案12B, 打包後2K, 使用串流寫入的10K, 可以確定打包完全不會有壓縮的效果

 12B 12  4 10:57 text
2.0K 12  4 11:01 text1.tar
 10K 12  4 11:01 text2.tar

其實打包主要目的在於,我常用的bz2壓縮,只能對檔案壓縮,不能對目錄壓縮,所以才需要先把目錄打包成一個檔案,再用bz2去壓縮它

接下來,弄個目錄跟文字檔來測試

mkdir dir
echo "Hello World" > dir/text

tar cf dir1.tar dir
tar c dir > dir2.tar

結果一樣,使用串流寫入的,佔的容量比較大

102B 12  4 11:28 dir
2.5K 12  4 11:44 dir1.tar
 10K 12  4 11:44 dir2.tar

接下來,使用bz2壓縮看看

bzip2 -z dir1.tar
bzip2 -z dir2.tar

來看看所佔用的容量

102B 12  4 11:28 dir
167B 12  4 11:44 dir1.tar.bz2
169B 12  4 11:44 dir2.tar.bz2

壓縮過後都變小了,分成先打包,再用bz2壓縮,其實是因為tar指令只是打包,頂多只能加上參數j,表示打包過程使用bz2,(或參數z,表示打包過程使用gz),無法指定壓縮比

# 將目錄打包之後,再用bz2壓縮並指定壓縮比
bzip2 -9 -z dir1.tar

如果要寫成一行的話,可以打包時,輸出到串流,在使用管線指令輸入到bzip2, 因為輸入是串流,bzip2無法指定要壓縮的檔案,所以把壓縮結果使用參數c,再一次輸出到串流,並儲存成檔案

tar c dir | bzip2 -c > dir.tar.bz2

# tar 無法指定壓縮比,在bzip2指定壓縮比
tar c dir | bzip2 -9 -c > dir.tar.bz2

同理,如果使用xz壓縮時,想一併指定壓縮比,也用同樣方式

tar c dir | xz -9 -c > dir.tar.xz

因為bz2壓縮,在Windows開啟中文檔名,常常變成亂碼,可以改用7zip來壓縮,如果使用7zip,他沒有辦法吃管線進來的資料,那怎麼辦?分兩行指令,先打包在壓縮嗎?

不用啦~ 7zip可以直接對目錄壓縮,直接壓縮,只是它指定壓縮比的參數是-mx數字

7za a -mx9 dir.7z dir

這樣子就直接對dir這個目錄以最大壓縮的壓縮比去執行了

2014年12月3日 星期三

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby裡面,對於物件的複製有兩個方法,分別是對物件呼叫dupclone,這兩種有什麼不同呢?這兩種方法並非只是alias的名稱而已。

先不討論淺層複製深層複製的問題,先來看看呼叫dup做了什麼事情。假設有個class:

class Foo
    def foo
        'foo'
    end
end

我們來試試看,產生Foo物件,然後對他呼叫dup

foo = Foo.new
dup_foo = foo.dup

dup_foo.foo # => 'foo'

一切依照預期,順利的進行複製了,接下來我們對foo動態增加一些Method上去,看看能不能夠複製(至於如何對物件動態增加Method請參考這篇)

class << foo
    def run
        'run'
    end
end

foo.foo # => 'foo' 原本class就有宣告 沒問題
foo.run # => 'run' 動態新增的Method 也沒問題

dup_foo_2 = foo.dup
dup_foo_2.foo # => 'foo' 原本class就有宣告 沒問題
dup_foo_2.run # => 發生錯誤,No Method Error

由此,可以知道,dup方法,僅對完整宣告的class進行複製,動態增加的不會複製,那麼我們再多測試一點,使用另一種方法增加Method。我們把整個程式碼完整再寫過

class Foo
    def foo
        'foo'
    end
end

foo = Foo.new
dup_foo = foo.dup

# 使用另一種方法增加Method到Foo上面去
class Foo
    def go
        'go'
    end
end

foo.go # => 'go'
dup_foo.go # => 'go', 正常執行

由此可見,只要是宣告成所有class Foo可以使用的Methods都可以被dup所複製,只有動態為單一Foo物件增加的Methods不能被dup複製進去

那麼clone方法,相對dup方法,就是完整地複製出一模一樣的物件

class Bar
    def bar
        'bar'
    end
end

bar = Bar.new

class << bar
    def run
        'run'
    end
end

clone_bar = bar.clone
clone_bar.bar # => 'bar'
clone_bar.run # => 'run', 動態增加的方法也一併複製了

接下來,來探討一下淺層複製深層複製的問題,都只是淺層複製

ary = ['hello']
dup_ary = ary.dup
clone_ary = ary.clone

dup_ary[0][0] = 'X'
p ary # => ["Xello"], 原本的ary被改掉了

clone_ary[0][0] = 'Y'
p ary # => ["Yello"], 原本的ary也被改掉了

如果要進行深層複製,就得遞迴對陣列中每個物件,都進行複製。

在Ruby on Rails裡面,直接提供deep_dup(但沒有提供deep_clone)

如果是在傳統的Ruby,我們可以自己實作deep_dup方法,來進行深層複製,在stackoverflow上面,我找到一個不錯的寫法

# 寫得不錯,如果物件是Array, 就繼續遞迴
class Array
    def deep_dup
        map {|x| x.deep_dup}
    end
end

# 直到不是Array,就進行複製,但這邊沒考慮到Ruby的容器還有Hash, Set ... 等,這些也需要做深層複製,所以這邊若遇到Hash, Set之類的容器就變成淺層複製了
class Object
    def deep_dup
        dup
    end
end

# 這邊小心Numeric類別的物件,沒有dup方法,所以就直接回傳本身
class Numeric
    def deep_dup
        self
    end
end

這個例子只有對Array類別進行深層複製,如果你有用到Hash, Set,甚至是你自己寫的容器,自己實做的iterator pattern ... 等,就得自行再加上deep_dup方法上去。

2014年12月2日 星期二

RUBY,大寫數字轉小寫數字,國字數字轉阿拉伯數字

RUBY,大寫數字轉小寫數字,國字數字轉阿拉伯數字

今天,我在處理中文地址的時候,發現有人的門牌號碼填國字,譬如中正路四三號,也有人會填中正路43號,當然沒人是填中正路肆參號,反正程式邏輯是一樣的。(本方法忽略四十三->43這種格式,若有必要,請自行寫程式判定京兆億萬千百十出現的位置,是否合理,然後再進行轉換)

一般想到的就是把字串裡面每個字遍尋一遍,然後寫個switch,對出現的字元進行轉換,譬如:

// psudo code
for each character in string
    switch(character)
    case: '一'
        convert_to(1)
    case: '二'
        convert_to(2)
        .
        .
        .
    case: '九'
        convert_to(9)
    end
end

但其實你使用的是Ruby語言的話,其實Ruby語言有提供一個很簡潔的方法string.tr(from_str, to_str)

使用的方式是這樣子的,提供兩個字串,兩個字串相對應的位置會互相轉換,譬如提供兩個字串:

#輸入字串1
'abc'
#輸入字串2
'123'

則會將a變為1, b變為2, c變為3,舉個實際例子:

"xx_abc_xx".tr('abc', '123')
# 輸出 "xx_123_xx"

"xx_ccc_xx".tr('abc', '123')
# 輸出 "xx_333_xx", '1'跟'2'都沒出現?因為呼叫tr的字串裡面也沒有'a'跟'b'呀~

"cba".tr('abc', '123')
# 輸出 "321", 為什麼反過來?因為呼叫tr的字串是'cba'呀~(a變為1, b變為2, c變為3)

上面的例子,是比較快速簡易的應用,兩個輸入的字串長度一樣,對於呼叫tr方法的字串,就會將內容替換掉。那如果要傳入兩個字串不一樣長,譬如第一二個比較長,那多出來的部分則不會用到,譬如:

"abc".tr('ab', '123')
# 輸出"12c", 第二個引數中,那個'3'則不會用到

如果是第一個字串比較長,譬如:

"abc".tr('abc', '12')
# 輸出"122", 第一個引數比第二個引數的長度還要長,則第二個引數比較長沒得對應的部分,直接用第二個引數中,最後一個字元替換

#同理
"abcdefghijk".tr('abcdefg', '12')
# 輸出"1222222hijk"

所以我們將大小寫國字數字轉阿拉伯數字,可以全部用列舉的方舉出來,讓他們一一對應

chinese = "零一二三四五六七八九"
arabic = "0123456789"

"一三五七九".tr(chinese, arabic)
# 輸出 "13579"

# 反過來,也可以用阿拉伯數字轉國字
"98765".tr(arabic, chinese)
#輸出 "九八七六五"

#多個大寫數字
big_chinese = "零壹贰叁肆伍陆柒捌玖"
"壹贰叁肆伍".tr(big_chinese, chinese)
# 輸出"一二三四五"

但這邊有個簡易的寫法,參考Ruby的正規表示式(可以在這個網頁做測試)

輸入引入的部分,如果正規表示式中,使用中括號的用法,例如:

[abc]  # 表示任何abc
[^abc] # 表示任何非abc,注意'^'符號放在中括號內,代表'非',而不是代表開頭字元
[1-9] # 表示任何1到9之間的字元

所以我們數字轉換的例子也可以寫成

chinese = "零一二三四五六七八九"
"一三五七九".tr(chinese, '0-9')
# 輸出 "13579"

就可以直接用'0-9'代表所有的阿拉伯數字,那麼是否也可以用'零-九'來代表所有的國字數字呢?

不行的,因為國字的排序並不像英文字元一樣的排法,你可以這樣測試看看

('零'..'九').each{|c| puts c}
# 沒有印出任何字元

('一'..'九').each{|c| puts c}
#印出 一 丁 丂 七 丄 丅 丆 ... 九

可見國字是用筆劃排序的,所以無法使用'零-九'來代表數字,得一一列舉出來。

所以關於門牌號碼,就可以這樣子處理了,全部轉為阿拉伯數字

address = '中正路四三號'
chinese = "零一二三四五六七八九"

address.tr(chinese, '0-9')
輸出"中正路43號"

同理,也可以玩玩英文大小寫轉換

# 小寫轉大寫,一般寫法
'hello world'.upcase # => "HELLO WORLD"

# 使用tr, 多此一舉一下
'hello world'.tr('a-z', 'A-Z') # => "HELLO WORLD"