2010年11月4日 星期四

BAT大補帖 - for(04)

接下來要介紹的是For的這一個指令。

FOR的這一個指令功能之多,有一些非迴圈的功能都隱藏在FOR這一個指令裡面。

列印1,3,5,7,9

@ECHO OFF
FOR /L %%P IN (1,2,10) DO (
echo %%P
)
PAUSE

這裡要注意,如果要在批次程式中使用FOR命令,請指定%%p,而不要指定%%P。 變數名稱有大小寫的區分,所以%%p不同於%%P。

若有一個檔案,myfile.txt,內容如下
; This is sample code start
a1,a2,a3,a4,a5,a6
b1,b2,b3,b4,b5,b6
c1,c2,c3,c4,c5,c6
d1,d2,d3,d4,d5,d6
; This is sample code end

file.bat
@ECHO OFF
FOR /F "eol=; tokens=2,3* delims=, " %%i in (myfile.txt) do (
echo first: %%i
echo second: %%j
echo other: %%k
)

eol表示當我是由;開頭的行,我就不去分析它。
因為,FOR這一個指令在讀取檔案,並且去分析的時候,是以一行為單位。
由第一行開始讀取時,發現是由;開頭的,程式就會直接忽略第一行
; This is sample code start
繼續分析下一行。

在FOR的檔案分析,其實就是把每一行的內容分成許多欄位,而delims就是設定由哪一些符號當作分格符號。例如這一個例子中,我們是以,與空白字元當作分格符號。因此,第二行就會被分割成以下欄位。
第一欄: a1
第二欄: a2
第三欄: a3
第四欄: a4
第五欄: a5
第六欄: a6

而我們把這一行分成這麼多欄位,我們不一定會每一個欄位都拿來使用。而tokens就是表示說,我只要拿哪一些欄位。
若我只設定tokens=2-4
那這一個意思是說,我只會拿出欄位2到欄位4來做處理。

那拿出來的數值,會被存放在哪一個檔數中呢?
這就取決於我們設定於後面的%%i
所以,我們選用的第一的欄位會被放在%%i,而選用的第二個欄位會被放在%%j
所以,在我們file.bat的這一個例子中,在分析第二行(非註解的第一行)時,%%i裡面的內容就會是a2,而%%j的內容就會是a3

相信你應該有注意到(指)
那tokens=2,3後面接的*是什麼意思呢?
而我只有選用兩個欄位而已(2與3),怎麼%%k裡面會有內容哩?

沒有錯,若你在tokens最後面有加*的話,那麼在這一行沒有被選用的剩於的部分,會被存放在你還沒有使用的下一個變數中,這一個例子中,%%i存放a2而%%j存放a3,所以,%%k就會存放a4,a5,a6

我們可以對檔案內容分析之外,我們也可以對變數做分析
例如:我們對PATH這一個變數做分析~
我們把PATH裡面第一個欄位與第三個欄位印出來
variable.bat
@ECHO OFF
ECHO Original: %PATH%
FOR /F "tokens=1,3 delims=;" %%I IN ("%PATH%") DO (
ECHO first: %%I
ECHO third: %%J
)
PAUSE


另外,也可以分析執行完指令後的結果~
result.bat
@ECHO OFF
type myfile.txt
ECHO.
ECHO.
FOR /F "eol=; tokens=2,3* delims=," %%i IN ('type myfile.txt') DO (
echo first: %%i
echo second: %%j
echo other: %%k
)
PAUSE

這裡用到一個新的指令,type,這一個指令會把後面接的檔案的內容顯示在終端機上~

另外,要善用說明檔~
help印出目前系統可以使用的指令
FOR /?
TYPE /?
通常裡面會有很多解釋和例子,值的參考!!

[總結]
在這裡,你應該學會了以下指令或關鍵字的用法
FOR
TYPE

2010年11月3日 星期三

BAT大補帖 - IF (03)

在Script中,我們並不會希望每一行指令被執行,而是當目前的情況符合某一些情況時,就去執行某一部分的程式碼。

這個時候就要用到IF這一個關鍵字。

判斷使用者是否為CCD9527,若是,就跟CCD9527打招呼~若不是就跟大家打招呼~
if01.bat

@ECHO OFF
IF "%USERNAME%" EQU "CCD9527" (
ECHO HELLO CCD9527
) ELSE (
ECHO HELLO EVERYBODY
)
PAUSE

這裡要注意的是,在變數與要比較的數值最好都用雙引號括號起來,避免變數的內容值有空白字元,會造成判斷錯誤。

判斷file.txt是否存在
@ECHO OFF
IF EXIST "file.txt" (
ECHO THE file.txt EXIST
) ELSE (
ECHO THE file.txt DOES NOT EXIST
)
PAUSE


若要忽略大小寫字元的比較,要加個/I,如下的範例
@ECHO OFF
IF "A" EQU "a" (
ECHO A EQUAL a
)
IF /I "B" EQU "b" (
ECHO B EQUAL b
)
PAUSE


如何判斷%VALUE%這一個變數是否有被設定過?
@ECHO OFF
IF "%VALUE% EQU "" (
ECHO THE VALUE DOES NOT SET.
) ELSE (
ECHO THE VALUE IS %VALUE%
)
PAUSE


另外,要善用說明檔~
help印出目前系統可以使用的指令
IF /?
通常裡面會有很多解釋和例子,值的參考!!

[總結]
在這裡,你應該學會了以下指令或關鍵字的用法
IF

2010年11月2日 星期二

BAT大補帖 - SET (02)

這裡要介紹的是SET指令。若ECHO是輸出指令,那麼SET就是設定與輸入指令。

以下的例子就是設定YourName變數的內容為ccd9527,那麼要如何確認YourName變數內容是正確的哩?當然就是使用ECHO來確認囉~
yourname.bat

@ECHO OFF
SET YourName=ccd9527
ECHO Your Name is %YourName%
PAUSE


在與使用者的互動中,除了可以印出字串給使用者看的ECHO指令,用來接收使用者的輸入指令SET也是另一個重要的角色。
在SET後面加一個/P的參數,後面可以提示使用者輸入的提示,當使用者輸入,則會被存到前面的變數!
yourname.bat
@ECHO OFF
SET /P YourName="Please input your name: "
ECHO Your Name is %YourName%
PAUSE


在BAT中,預設是把所有的變數內容當作字串來使用,若要把內容當作數字作加減運算時,必需要加上/A的參數。
add.bat
@ECHO OFF
SET VALUE=1
ECHO %VALUE%
SET /A VALUE=%VALUE%+1
ECHO %VALUE%
PAUSE


若單單只有打SET指令,後面沒有接任何參數,則會列出目前所有環境變數的資料。
若能善用這一些環境變數,將會大大簡化程式碼的大小,而且也會更彈性喔~
env.bat
@ECHO OFF
SET
PAUSE

有幾個動態環境變數會被展開但是不會出現在SET所顯示的環境變數列表中
%CD% - 展開為含目前目錄的字串。
%DATE% - 展開為與 DATE 命令格式相同的目前日期字串。
%TIME% - 展開為與 TIME 命令格式相同的目前時間字串。
%RANDOM% - 展開為一個在 0 和 32767 之間的整數亂數值。
%ERRORLEVEL% - 展開為目前的 ERRORLEVEL 值
%CMDEXTVERSION% - 展開為目前的命令處理擴充功能的版本號碼。
%CMDCMDLINE% - 展開為執行命令處理器前的原始命令列。

只有在bat檔案有用的環境變數
%~dp0 - 表示被執行的bat檔的所在位置

另外,要善用說明檔~
help印出目前系統可以使用的指令
SET /?
通常裡面會有很多解釋和例子,值的參考!!

進階用法
Q: 若有一個變數VALUE的內容值為12 34,如何把空白填成0
A:
switch.bat
@ECHO OFF
SET VALUE=12 34
ECHO %VALUE%
ECHO %VALUE: =0%
PAUSE

[2010.11.06 補充]
這裡的意思是說,把變數裡面的空白字元由0這一個字元來取代~
我所知道在處理%time%這一個變數會用到!
請在晚上十二點過後到早上十點之前執行ShowTime.bat這一個檔案,很恐怖,請做好心理準備後在服用~
ShowTime.bat

因為,在bat檔中,空白字元是當作分格參數的特殊字元,有時候很讓人討厭(會造成程式流程錯誤),所有,有時候,我們必需有以上的處理!

@ECHO OFF
Set Now=%TIME%
ECHO %Now%
ECHO %Now: =0%
PAUSE


Q: 如何印出字串,但是,確不換行,再輸入第一筆資料?
A:
NotTurnLine.bat
@ECHO OFF
SET /P=First<NUL
SET /P=Second<NUL
PAUSE


[總結]
在這裡,你應該學會了以下指令或關鍵字的用法
SET

2010年10月31日 星期日

BAT大補帖 - First BAT (01)

在Linux下與Windows下,都有一個script的東西。
script顧名思義就是一個腳本。
就是原本要執行的二十個指令,寫在一個script中,系統由檔案中,逐行執行~
為什麼要這麼做呢?
如果常常使用指令模式在操作電腦,常常會輸入不同的指令與不同的參數,而其中可能會有許多重覆的部分,正所謂,「做重複的事情是罪惡的」,所以,我們才要把寫在一個script檔案中,讓之後可以重複利用,結省所謂的時間。

而makefile也是另一種script,但是,他算是比較進階且較我們要討論的script嚴謹。在某一些情況,要達到一樣的結果,在makefile要寫好幾行,而在script只需要寫一行。所以,我認為makefile與script都需要去學習,因為,它們都是節省重複動作的工具,值的學習!

而這一個script在windows下,就稱為bat file。

寫第一個bat程式
hello.bat

REM show message
ECHO "Hello Bat!!"
PAUSE

1. 以REM開頭的就是註解,就像C語言中的//
註解後面的東西是不會被執行的。
2. ECHO這一個指令是非常重要的,就像printf一樣,我們可以在程式中途插入一個echo,印出我們想了解某一個變數目前的數值,看是不是我們想要的數值。
3. 當script執行到PAUSE時,會停在PAUSE的地方,等待使用者輸入任意鍵再繼續。這一個指令在BAT檔中,debug是非常需要的。在bat的debug是非常弱的,他不會跟你說你第幾行出現錯誤,必需要使用PAUSE來下斷點,了解在執行哪一行後,才出現錯誤。

當執行完後,你會發現,怎麼程式碼每一行指令都被印出來啊?
我們可以在檔案的最上面透過ECHO OFF關掉這一個功能。
嗯,確實有效,但是還是會出現ECHO OFF這一個字串還是會被顯示出現。
這個時候在ECHO OFF前面加入@,表示不顯示目前這一行程式。
再執行,結果看起來清爽多了~
另外,ECHO後面也可以印出變數的內容,目前就是把USERNAME的內容印出來,要提取變數內容,必需要透過%,把變數名稱夾起來~
hello.bat
@ECHO OFF
REM show message
ECHO "Hello %USERNAME%!!"
PAUSE


另外,要善用說明檔~
help印出目前系統可以使用的指令
ECHO /?
PAUSE /?
CMD /?
通常裡面會有很多解釋和例子,值的參考!!

進階用法
Q: 為了排版,如何印出一個空白行?
A: ECHO.

[總結]
在這裡,你應該學會了以下指令或關鍵字的用法
ECHO
PAUSE
HELP
REM
@

2010年10月29日 星期五

依檔名設定filetype

在vim中,會自動判斷副檔名或是檔案內容決定這是何種檔案~

但是,有時候會有一些新的副檔名沒有辦法被vim判斷出來!

這個時候,就要在filetype.vim修改,當什麼副檔名的時候,自動設定filetype特定的filetype。

例如要把*.dsc設定與*.c的配色相同,則在filetype.vim最下面加入以下的內容

augroup filetypedetect
au! BufRead,BufNewFile *.dsc setfiletype c
augroup END


如此一來,所有的dsc檔的配色都會與c的相同

[2014.05.08 補充]
若想知道目前vim的環境變數為何,
直接輸入
:set

而若想知道filetype這一個環境變數值為何,
直接輸入
: set filetype

參考資料:
Vim Tips

2010年10月10日 星期日

加入註解

好的編輯器送你上天堂,不好的編輯器送你住套房。

但是,有好的編輯器還是要有好的設定,才能送你上天堂。

同時註解多行
用virtual mode的方式選取多行
:'<,'>s/^/\/\//g

把暫存器a的內的東西貼出來
"ap

一般來說 :g 的運作方式是根據一行一行來做的,當你用下面指令的時候,
:g/pattern/y a
這代表的意思其實只是把每次比對到的那行都 yank 複製到"a這裡面去。所以這就會造成我們最後只會拿到最後一行的結果,而不是每個比對到的都會進入到"a去。所以就必須告訴 vim 我們指定的內容是要做附加而不是覆蓋:
:g/pattern/y A

刪除選取的行數,並且存到暫存器a
"aD
或
:'<,'>d a

取得目前檔案的目錄
:echo fnamemodify("%", ":p:h")
%表示current file name

取得目前檔案的名稱
:echo expand("%")

若執行的指令很可能會出現錯誤訊息,但仍不想要顯示錯誤訊息的話,可以使用silent,例:
:silent! exe "normal /path\<CR>"
或
:silent! /xxxfdafd

在目前位置加入時間
::map <F2> a<C-R>=strftime("%c")<CR><ESC>


流程
1. 把目前的時間存到暫存器a
//[-start-strftime("%y%m%d")-CCD9527-remove]//
SET @a=time()

2. 註解掉目前選取的所有行數
:'<,'>s/^/\/\//g

3. 把選取的部分附加到暫存器a
"AD

4. 把目前的時間存到暫存器a
//[-end-strftime("%y%m%d")-CCD9527-remove]//
SET @A=time()

5. 把暫存器a貼到目前的位置
"ap


line
append
cursor
getline
mode
confirm
repeat

一行一行讀出來(getline)
加工(substitute)
加到list(add)
刪除原本的資料

_vimrc_utils

" Decide comment symbol
function CommentSymbol ()
"
" Set the default comment
"
let markid_start='//'
let markid_end='//'

"
" Set the comment by the different file type
"
if exists("&filetype") != 0
if (&filetype == "asm") || (&filetype == "pov")
let markid_start=';'
let markid_end=';'
elseif (&filetype == "make") || (&filetype == "inform") || (&filetype == "conf")
let markid_start='#'
let markid_end='#'
elseif &filetype == "dosbatch"
let markid_start='rem '
let markid_end='//'
endif
endif

return [escape(markid_start, ' /'), markid_start, markid_end]
endfunction

"Mark from N to M
function MarkN2MbyComment (N, M, Comment)
let ln = a:N
while ln <= a:M
call MarkByLine (ln, a:Comment)
let ln = ln + 1
endwhile
let @/=''
endfunction

" Remove Mark from N to M
function RemoveMarkN2MbyComment (N, M, Comment)
let ln = a:N
while ln <= a:M
call RemoveMarkByLine (ln, a:Comment)
let ln = ln + 1
endwhile
let @/=''
endfunction

" Set UserID from user input
function SetUserID ()
if exists("g:UserID") == 0

let g:UserID = inputdialog("Set Comment Symbol : (EX:IB06920001)")

if g:UserID == ""
let g:UserID = "TempUser"
endif

endif
endfunction

" Clear UserID
function ClearUserID ()

if exists("g:UserID") != 0
unlet g:UserID
endif

endfunction

" Add ceiling and floor comment
function CeilFloorComment (lnL, lnF, markid_start, markid_end, type)

exe append(a:lnL, a:markid_start . "[-end-" . strftime("%y%m%d"). "-" . g:UserID . "-" . a:type . "]" . a:markid_end)
exe append(a:lnF - 1, a:markid_start . "[-start-" . strftime("%y%m%d"). "-" . g:UserID . "-" . a:type . "]" . a:markid_end)
exe "normal " . (a:lnL + 2) . "G"

endfunction

" AddCommentBlock (F9)
function AddCommentBlock (lnF, lnL)
if line(".") == a:lnL

call SetUserID ()

let [comment, markid_start, markid_end] = CommentSymbol ()

call CeilFloorComment (a:lnL, a:lnF, markid_start, markid_end, "add")

endif
endfunction

" ModifyCommentBlock (F10)
function ModifyCommentBlock (lnF, lnL)
if line(".") == a:lnL

call SetUserID ()

let [comment, markid_start, markid_end] = CommentSymbol ()

call CeilFloorComment (a:lnL, a:lnF, markid_start, markid_end, "modify")

endif
endfunction

" RemoveCommentBlock (F11)
function RemoveCommentBlock (lnF, lnL)
if line(".") == a:lnL

call SetUserID ()

let [comment, markid_start, markid_end] = CommentSymbol ()

call MarkN2MbyComment (a:lnF, a:lnL, comment)

call CeilFloorComment (a:lnL, a:lnF, markid_start, markid_end, "remove")

endif
endfunction

function VisualAddCommentBlock ()
let lnF = line("'<")
let lnL = line("'>")
call AddCommentBlock (lnF, lnL)
endfunction

function NormalAddCommentBlock ()
let lnF = line(".")
let lnL = line(".")
call AddCommentBlock (lnF, lnL)
endfunction

function VisualModifyCommentBlock ()
let lnF = line("'<")
let lnL = line("'>")
call ModifyCommentBlock (lnF, lnL)
endfunction

function NormalModifyCommentBlock ()
let lnF = line(".")
let lnL = line(".")
call ModifyCommentBlock (lnF, lnL)
endfunction

function VisualRemoveCommentBlock ()
let lnF = line("'<")
let lnL = line("'>")
call RemoveCommentBlock (lnF, lnL)
endfunction

function NormalRemoveCommentBlock ()
let lnF = line(".")
let lnL = line(".")
call RemoveCommentBlock (lnF, lnL)
endfunction

function VisualAddSelectionComment ()
let lnF = line("'<")
let lnL = line("'>")

call AddSelectionComment (lnF, lnL)

endfunction

function NormalAddSelectionComment ()

let lnF = line(".")
let lnL = line(".")

call AddSelectionComment (lnF, lnL)

endfunction

function AddSelectionComment (lnF, lnL)
if line(".") == a:lnL

let [comment, markid_start, markid_end] = CommentSymbol ()

call MarkN2MbyComment (a:lnF, a:lnL, comment)

endif
endfunction

function RemoveSelectionComment (lnF, lnL)
if line(".") == a:lnL

let [comment, markid_start, markid_end] = CommentSymbol ()

call RemoveMarkN2MbyComment (a:lnF, a:lnL, comment)

endif
endfunction

function NormalRemoveSelectionComment ()

let lnF = line(".")
let lnL = line(".")

call RemoveSelectionComment (lnF, lnL)

endfunction

function VisualRemoveSelectionComment ()

let lnF = line ("'<")
let lnL = line ("'>")

call RemoveSelectionComment (lnF, lnL)

endfunction

function MarkByLine (CurrentLine, comment)
let TempContent = getline(a:CurrentLine)
let TempContent = substitute (TempContent, '^', a:comment,'g')
exe a:CurrentLine . "d"
exe append (a:CurrentLine - 1, TempContent)
exe "normal " . a:CurrentLine . "G"
endfunction

function RemoveMarkByLine (CurrentLine, comment)
let TempContent = getline(a:CurrentLine)
let TempContent = substitute (TempContent, '^\(\s*\)' . a:comment, '\1', 'g')
exe a:CurrentLine . "d"
exe append (a:CurrentLine - 1, TempContent)
exe "normal " . a:CurrentLine . "G"
endfunction

"AddSelectionComment
nmap <F3> :call NormalAddSelectionComment ()<CR>
vmap <F3> :call VisualAddSelectionComment ()<CR>
"RemoveSelectionComment
nmap <F4> :call NormalRemoveSelectionComment ()<CR>
vmap <F4> :call VisualRemoveSelectionComment ()<CR>

" 移除每一行最後多餘的空白
nmap <F6> :silent! %s/ \+$//gc<CR>

" 把tab轉換成空白
nmap <F7> :silent! %s/\t/ /gc<CR>

nmap <F8> :call ClearUserID ()<CR>
vmap <F8> :call ClearUserID ()<CR>
vmap <F9> :call VisualAddCommentBlock ()<CR>
nmap <F9> :call NormalAddCommentBlock ()<CR>
vmap <F10> :call VisualModifyCommentBlock ()<CR>
nmap <F10> :call NormalModifyCommentBlock ()<CR>
vmap <F11> :call VisualRemoveCommentBlock ()<CR>
nmap <F11> :call NormalRemoveCommentBlock ()<CR>


參考資料:
vim 的選擇模式 (visual mode)
有多少種方式可以在vim中插入行號
我的 VIM 整理
vim 提供的函數
vim 選擇模式下的熱鍵( Key mapping )

2010年9月2日 星期四

建立空白檔案

在Linux下,要透過指令建立一個空白檔案,很簡單,透過touch指令就可以了~

%> touch FileName.txt

而在Windows下要建立空白檔案就比較難一點了~
最後想到一個辦法~
%> createnewfile 1>FileName.txt 2>NUL
1:是表示Standard Output
2:是表示Error Output
上面的意思是說,把Standard Output放到FileName.txt這一個檔案中,而把Error Output放到NUL設備中,NUL就像一個黑洞~東西都會被吸進去~
換句話說,我們本來就沒有createnewfile這一個指令,所以,它不會有Standard Output,所以產生的FileName.txt裡面就不會有內容!而本來應該顯示的錯誤訊息就被黑洞吸走,所以也不會顯示在螢幕上!