替換 windows ^M,並練習組合一些簡單 unix 指令

有時候拿到一些在 Microsoft Windows 處理的文字檔案,會有一些斷行後有 ^M (Ctrl+M) 的字元,在 unix 上處理這類的資料很惱人,會造成不正確斷行判斷,所以可以用 find, xargs, awk, sort 和 vim 一次取代完(^M 要按 Ctrl 加上 M)。分解做法如下:

  1. 找某目錄底下以 .txt 為名的所有檔案 實作:
    $ find . -type f -name "*.txt"
    
    解釋:
    .(dot) 代表現在目錄底下
    -name 後面接的參數代表檔名,可以用 wildcards 或 regular expression
    -type 後面接的是檔案類型, f 代表一般的檔案,d 代表目錄等
  2. 找完後用 xargs (eXecute ARGuments)

    xargs Manual 這樣寫:

    The xargs utility reads space, tab, newline and end-of-file delimited strings
    from the standard input and executes utility with the
    strings as arguments.

    簡單說就是把上個程序輸出的結果當成參數,餵給後面的程式使用,有點難懂對吧?那他跟 pipe (|) 有什麼差別呢?
    pipe 字面上是水管,只負責「傳導」這件工作,像是:

    $ ls | grep keywords
    

    這個指令就代表把 ls 的輸出導給 grep 執行,而 grep 把輸出中有 keywords 的行抓出來。

    若是下列指令則會用 grepls 所列出所有的檔案抓取有 keywords 的行

    $ ls | xargs grep
    

    有了 xargs 的基本概念後,用例子來解釋,下面用 ls 會列出目錄底下有三個 .txt 檔案,分別為 1.txt, 2.txt, 3.txt。「列出這三個檔案」就是標準的輸出

    $ ls
    1.txt 2.txt 3.txt
    

    接下來若導向給 xargs,則會把標準的輸出當成是參數清單(argument list),
    ls | xargs grep keywords 執行所得到的結果則和下列指令相同:

    grep keywords 1.txt
    grep keywords 2.txt
    grep keywords 3.txt
    
  3. awk 取出檔案名稱
    因為 xargs grep keywords 的輸出類似:

    1.txt: ipsum lorem keywords consectetur adipiscing elit, sed do eiusmod
    1.txt: lorem ipsum quasi voluptate velit esse keywords dolore eu fugiat
    2.txt: voluptate velit esse cillum dolore eu fugiat keywords, lorem

    因此我們希望只列出檔案名稱,觀察上述的 pattern,就是取出 : 前的文字,使用指令為:

    awk -F':' '{ print $1}'
    

    -F 後面用 '(single quote 單引號) 包起來的是分隔符號(separate Fields),後面加數字代表分隔符號切分後第幾個欄位,例如上面的 3. 的例子中, '{print $1}' 則會將 1.txt, 2.txt 等顯示出來

  4. sortunique 整理

    前面 awk 處理完後把含有 ^M 的檔案名稱丟給 sort -u (排序整理,並刪除重複)

  5. 把整個程序寫成迴圈,用 vim 取代 ^M
    shell script 迴圈:

    for _var_ in _condition_ 
    do
        _statements_
    done
    

    shell script 中變數是加上 符號,將步驟一到四所找到有 ^M 的檔案名稱當成輸入的清單,跑個迴圈再加上 vim 取代即可。在 shell 中 vim 後面加上 + 則會執行 vim 內部的指令,我使用 regular expression 取代 ^M,指令為:

    %s/被取代的文字/取代的文字/g
    

    記得最後還要加上寫入離開指令 +wq

最後把程式整理一下,完整程序如下:

#!/usr/bin/env bash

for i in `find . -type f -name "*.txt" | xargs grep "^M"| awk -F':' '{print $1}' | sort -u`
do 
    echo "Substitute ^M in ${i}"
    vim +%s/^M//g +wq ${i}
done

參考資料

http://en.wikipedia.org/wiki/Xargs

Comments

comments powered by Disqus