今天想利用 BAT 檔來完成 檔案的複製工作,
像是 "定期備份桌面的檔案" 之類的阿~
要是滑鼠點下去就自動完成多棒啊!(所謂懶惰是科技創新的動力XD)
要生出一個批次檔其實不難:1) 在記事本裡打一些文字,2) 另存成 .bat 檔,就這樣。
難是難在語法不曉得怎麼用啊!! XD
於是我就查了查 DOS 批次檔語法~
解釋一下:
BAT 是 Batch 的簡寫,因此 Batch Language 就是批次檔所使用的語法喔!
DOS 是 Microsoft 的命令提示字元 (cmd),也就是黑底白字加新細明體的那個啦XD
換句話說,這邊的指令只適用 Windows 喔! MacOS 的 terminal 指令是不一樣的~
以下直接以實例來講解,講解部分都很精簡,請自己試一試、揣摩看看來幫助理解喔!
範例 1:
@ECHO OFF
CD C:\Program Files (x86)\Google\Chrome
PAUSE
REM 複製到 D:\ 底下~~~
COPY *.txt D:\David\備份BA~1\電腦\Chrome
ECHO finish
第一行打 "ECHO OFF",可以讓後續的指令都不會出現在螢幕上,改成 "ECHO ON" 的話則會出現;至於 "@" 則是讓第一行本身也不顯示~
第二行 CD <路徑> 就是前往那個路徑,這是基本的 DOS 語法(不知道的請見下面連結)
第三行 PAUSE 會暫停,cmd 視窗就會停住,等你按任意鍵繼續
第四行 REM 是註解 (remark),註解是給人看的不是給機器看的,DOS 會略過此行
第五行 COPY *.txt <路徑>,是複製所有 txt 結尾的檔案,到目標資料夾那裡( * 是萬用字元,也是基本 DOS 語法喔)
第六行 ECHO finish,會在螢幕上顯示 ECHO 後面的字串,所以就會顯示 "finish"
注意第五行的:
COPY *.txt D:\David\備份BA~1\電腦\Chrome
它完整的路徑其實是 "D:\David\備份 Backups\電腦\Chrome"
但是因為 "備份 Backups" 這個檔名裡面含有空格,DOS 無法接受,所以需要轉換成 8.3 format,
轉換方法是在某個目錄底下打 "dir/x",就會以 8.3 的格式列出目錄底下的檔名了~
常用 DOS 語法:https://mrtang.tw/blog/post/9751934 (有提到萬用字元 * )
https://mrtang.tw/blog/post/9848241 (講解 CD, MD, RD, DIR)
https://mrtang.tw/blog/post/10779313 (講解 REN, TYPE, ATTRIB)
BAT 批次檔語法:http://33tsai.blogspot.tw/2008/04/bat.html
上面是第一頁,第二頁在這邊:http://33tsai.blogspot.tw/2008/04/bat_21.html,本篇主要都是參考這兩篇的喔~
檔名轉換成 DOS 8.3 格式:https://community.microfocus.com/adtd/silktest/w/wikiid-128/35565/how-can-i-get-a-file-name-in-dos-8-3-format
範例 2:
@ECHO OFF
IF "%1" == "A" (ECHO Apple)
IF "%1" == "B" (ECHO Banana)
輸入:D:\test.bat A
輸出:Apple
假設這個程式叫做 "test.bat",並且是放在 D:\ 底下,
那麼,當我們在 cmd 中輸入 "D:\test.bat A" 時,"A" 這個參數就會被存進 %1 裡面;
因此,第二行就會變成 IF "A" == "A",條件會成立,所以會執行後面的 ECHO 而產生以上的輸出。
補充一點:最多可以傳送九個參數給 BAT 喔,會對應到 %1 ~ %9。
範例 3:
@ECHO OFF
CD D:\David\
IF EXIST %1 GOTO PrintFile
GOTO FileNotExist
:PrintFile
TYPE %1
GOTO End
:FileNotExist
ECHO File Not Exist!
GOTO End
:End
輸入:D:\test.bat list.txt
輸出:(list.txt 檔案裡的內容)
假設這個程式叫做 "test.bat",並且 "D:\David\" 底下有個叫 "list.txt" 的文字檔;
當我們提供上面那一行輸入時,程式就會把 .txt 裡面的內容印出來;而若找不到有此名稱的檔案,就會印出 "File Not Exist!"
第三行,IF EXIST <完整路徑加上檔名> <動作> ;理論上要提供完整的路徑 (eg. D:\David\list.txt),但因為我們已經 CD 到同一個資料夾中了,所以提供 "list.txt" 即可。另外,其變種為 IF NOT EXIST <.....>
接著 GOTO PrintFile,效果是直接跳到第五行 ":PrintFile" 那裡執行,也就是說中間的都會略過不執行;標籤的名字可以隨便取,像是 "ABC_NAME001" 等等
第四行,如果第三行沒有跳走的話,就會 GOTO 跳到 FileNotExist
第五~七行,是 PrintFile 所要執行的內容;TYPE <檔名> 會把那個檔案印出來,eg. TYPE list.txt
第七行,執行完記得要再 GOTO 到底下的 :End,否則它會繼續往下執行第八行、第九行喔!
第八~十行,是 FileNotExist 所要執行的內容
到這邊,大家應該已掌握了基本的 CD、IF、參數以及 GOTO 了吧!
接下來我想介紹一下如何使用迴圈、讀/寫檔案以及使用函式~(難度較高請自行斟酌XD)
範例 4:
@echo off
REM ***** 1) 重複三次,共三秒 *****
for /L %%I in (1,1,3) do (
call :ShowTime
timeout /t 1 >nul
)
goto :eof
:ShowTime
REM ***** 2) 將現在時間 (HH:MM:SS) 存進 now *****
for /F "tokens=1 delims=." %%A in ("%time%") do (set now=%%A)
REM ***** 3) 切割出 HH, MM, SS *****
for /F "tokens=1-3 delims=:" %%A in ("%now%") do (
echo 現在時間是 %%A 點 %%B 分 %%C 秒
)
exit /b
輸入:D:\test.bat
輸出:現在時間是 19 點 02 分 34 秒
第一行,再熟悉不過的 @ECHO OFF;不過不太一樣的是這次我改用小寫,因為 BAT 裡面其實都 OK。
第三行,for /L %%I in (開始, 增加, 結束) do (動作),是一種產生數字的 FOR 迴圈。簡單來說,%%I 會從 1 開始,每次增加 1,一直到 3 結束。
第四行,call :<標籤>,效果是直接跳到標籤那行(也就是 :ShowTime),直到撞見 exit /b 才會跳回來
第五行,timeout /t 1 會停頓一秒鐘。後面的 ">null" 是重新導向的意思,目的是讓它閉嘴,默默的停頓就好XD
換句話說,FOR 會執行三次,每次 CALL 完會停頓一秒;接下來我們來看 :ShowTime 裡面在幹嘛:
註解 2) 底下:for /F "額外選項" %%A in ("字串") do (動作),是 FOR /F 的三種用法之一(見底下連結)
- 首先,我們的字串 "%time%" 其實是 "19:02:34.76",因為 %TIME% 能夠取得現在的時間
- 接下來,FOR 會把字串切割成 "19:02:34" 以及 "76",因為 "delims=." 相當於告訴它,使用點去做切割
- 切割完之後,"tokens=1" 相當於告訴它,我們只要第一個結果(紅字);它會存到 %%A 裡面
- 最後,括號內的動作是 "set now=%%A",意思是把結果存到 now 變數中。
註解 3) 底下:同樣是字串切割的 FOR /F。這次我們使用冒號做切割,並取得第 1~3 個結果(就是全部啦)
- 字串 "%now%" 會替換成剛剛的 "19:02:34",因為變數的前後接上 % 就會跑出裡面的值
- 切割完,第一個結果會在 %%A,第二個在 %%B,第三個在 %%C,以此類推。
最後,goto :eof 會結束目前的程式(如果在函式中就會結束函式)
Guide to Windows Batch Scripting:https://steve-jansen.github.io/guides/windows-batch-scripting/index.html
英文網站,有提到很多東西 (算式、setlocal、%~n、errorlevel、重導向、函式),看不懂就再留言吧~
命令提示字元 19:迴圈進階:https://lnpcd.blogspot.com/2012/09/19.html
介紹 FOR /F 的三種用法!
Windows CMD SS64:https://ss64.com/nt/set.html
這個是以 SET 為例,它有最詳盡的使用說明!(應該說它就是說明書,最詳盡但未必好懂就是了)你可以在搜尋框框打任何想查詢的其他指令~
範例 5:
@echo off
set now=%time:~0,8%
REM ***** 1) 從 time.txt 中讀取上次的時間 *****
if exist time.txt (
for /F "delims=" %%A in ('type time.txt') do (set last_time=%%A)
) else (
set last_time=%now%
)
call :CalculateDiff %last_time% %now%
echo 距離上次執行過了 %diff% 秒
echo %now%>time.txt
goto :eof
:CalculateDiff
REM ***** 2) 計算兩者 (HH:MM:SS) 相差幾秒,存進 diff *****
for /F "tokens=1-3 delims=:" %%A in ("%1") do (
set /A "sec1=%%A*60*60 + %%B*60 + %%C"
)
for /F "tokens=1-3 delims=:" %%A in ("%2") do (
set /A "sec2=%%A*60*60 + %%B*60 + %%C"
)
set /A "diff=sec2-sec1"
exit /b
輸入:D:\test.bat
輸出:距離上次執行過了 176 秒
第二行,%time:~0,8% 會直接從 "19:02:34.76" 當中取得 "19:02:34"(此為子字串的語法,見底下 QA)
註解 1) 底下,我們會先檢查 time.txt 檔案是否存在。
- 如果存在,for /F "額外選項" %%A in ('指令') do (動作) 會把指令(type time.txt)的結果存到 %%A 中(注意這邊是使用單引號而非雙引號喔!)
- "delims=" 會告訴它,不要使用任何字元做切割。所以整句話的效果即:將檔案的內容存進 last_time 中。
- 如果不存在,我們就把 last_time 設為現在的時間。
接下來,我們會呼叫 :CalculateDiff 函式做一些計算。注意,函式呼叫時是可以加上額外參數的,這些參數進到函式中就會變成 %1、%2 等。
註解 2) 底下,第一個 FOR /F 會將時間切割成小時、分鐘及秒鐘。
- set /A "變數=數學計算式" 會先做數學計算,算完再將結果存進變數中。
- 因此,sec1 及 sec2 會是換算成秒數的時間;兩者相減後會被存進 diff,然後程式會跳回去 call 的地方。
結束前,我們使用重導向符號 ">" 將 %now% 寫到檔案中,這樣下次就能算出正確的 "上次執行時間" 了。
本篇只教了核心的用法,還有很多東西是要靠自己查詢 (eg. 手冊) 或讀其他文章 (eg. 英文QQ) 來學習的。
以下整理了一些我個人遇到過或者網友提問過的問題~
Q1: Batch 如何用正規表示式 (RegEx) 來篩選特定的字串呢?
Ans: 使用 findstr /r (也可搭配 errorlevel)
你可以來實驗看看以下這段,之後再試著把var 改成 dbcefg 看看有何不同,最後再試著把第二行框框裡的改成 [a-z]
set var=abcefg
echo %var% | findstr /r "^[a-c]">nul
if errorlevel 1 (echo no) else (echo yes)
第一行就是簡單的設定變數,將var 設定成那串字
第二行首先,就是先把var 給印出來(記得,變數取用時需要在前後都加上%)
但是後面有個 "|",是重新導向的意思,所以原本要印到螢幕上的字,就丟給了後面的 findstr 處理。
"^[a-c]" 意思是以 a~c 字母開頭者,像是 ^[a-zA-Z0-9],就相當於尋找所有的英文及數字開頭者。(詳情請google正規表示式)
最後的 ">" 也是重新導向的意思,丟給nul 的目的是讓它不要顯示出來。你可以試試看把 ">nul" 拿掉,結果就會被印到螢幕上了。
第三行是依據 errorlevel 的值,來決定要顯示 yes or no。原理是這樣的:任何指令都會有回傳值,照理說如果正確執行了,就會回傳 0,若失敗了就不是 0。
而errorlevel 會保存上一行指令的最終回傳值,因此若 findstr 有成功找到,則回傳值為 0,反之則大於 0。
Q2: 如何正確的 echo Parentheses () 以及 Angle brackets <> ?例如 if 1 == 1 (echo (abc)) 就會跳出錯誤。
Q3: 如何檢查執行 script 的人是否有 Admin 權限?
Q4: 如何把指令的結果存到一個變數當中?
Q5: 如何取得子字串 (Substring)?
Q6: 如何在 IF ... ELSE 的條件裡面使用 AND 或是 OR?
Ans: 沒辦法,但可以用一些迂迴的方式,例如想要使用 AND 的話,可以:
if %a% == 1 if %b% == 1 (echo Both are true.)
要達成 OR 的效果,則可以這樣:
set res=
REM 上面這行很重要!因不能保證 res 之前存的是什麼
if %a% == 1 (set res=1) else if %b% == 1 (set res=1)
if defined res (echo One of them is true.)
Q7: 如何從一個 BAT 腳本呼叫其他的 BAT 腳本?
Ans: 請使用 CALL "D:\其他腳本的路徑.bat"。
Q8: 註解應該要用 REM 還是 :: 哪個比較好?
留言列表