You are on page 1of 20

目錄

1. 簡介說明 1.1
2. 批次檔介紹 1.2
1. 變數 1.2.1
2. 呼叫外部程式與副程式 1.2.2
3. for 迴圈 1.2.3
3. 命令列特性 1.3
4. 練習 1.4

1
簡介說明

簡介說明
Windows的自動化工作
在Windows下不安裝其他工具,就能進行自動化工作1的程式語言有3種選擇

批次檔(Batch file)
WSH(Windows Scripting Host)
PowerShell

批次檔在歷經好多年的發展之後,微軟在Windows 98的時代提出了依靠Jscript與VBscript來提
供Windows作業系統腳本語言的執行環境 WSH ,後來在提供等同於UNIX shell的強大功能與結
合.Net Framework的要求下,提出了 PowerShell。

當然除了這些原生的shell之外,還可以安裝第三方(Third party)的直譯式語言來達成自動化的
目的,例如

ActivePerl
PHP
Python
Ruby

學習批次檔等同於學習 Windows 作業系統在命令列環境(shell)的用法,Windows環境目前有很


多Linux的影子,例如導向、管線、指令與檔案名稱補齊...等,理解之後未來接觸Linux也有很
大的幫助。

本著登高必自卑、行遠必自邇的學習次第,先學習批次檔基本功,用來幫助與處理日常的自
動化需求, 未來再多樣化的學習各種 Script 語言,充分體會直譯式語言的魅力。

最後修訂日期:2015/10/28 by 朱孝國

1
在此指的是搭配自動化所需撰寫程式語言,這些語言都必須搭配排定的工作(scaeduled task)來
指定執行的時間,才可能達成自動化的目的。

2
批次檔介紹

批次檔介紹
1.批次檔介紹
將命令提示字元(Command Prompt)中輸入的指令集結起來,輸入在文字檔中,用以批次執
行,稱之為批次(Batch file)檔。

批次檔指令每行的長度預設為127個字元,執行後若要中斷可按下Ctrl+C。

命令提示字元預設的字碼頁為ANSI/BIG5編碼,因此檢視UTF-8編碼檔案時會出現亂碼,所以
批次檔的編寫應盡量使用 ANSI 的編碼方式,建議使用 NotePad++ 之類的有顏色與語法提示的
純文字編輯器編寫。 NotePad++目前編輯文字檔採用的編碼方式會在右下角提示,

建議透過功能表:編碼 / 轉換至ANSI 編碼格式,將編碼

轉換為 ANSI 格式。

學習批次檔等同於學習一個作業系統在命令列環境(shell)的用法,Windows環境目前有很多
Linux的影子,例如導向、管線、指令與檔案名稱補齊...等,理解之後未來接觸Linux也有很大
的幫助。

學習批次檔一般而言相當的簡單又直覺,怎樣算是學會呢,以本人的經驗,至少要將變數延
遲展開特性與 for 迴圈指令給透徹了解才算是掌握了批次檔喔。

副檔名
預設有下面這2種,在DOS與Windows 9x 時代副檔名為.bat,在Windows NT 之後則改用.cmd,
表示在視窗模式下的命令提示字元(cmd.exe)執行

bat
cmd

註解方式
標準是使用 rem,但通常會使用2個以上的冒號來當註解符號,畢竟字數比較少嘛
3
批次檔介紹

rem
::

顯示訊息
批次檔中用來顯示訊息的指令是 echo , 其後可加上字串或變數(可混搭) ,在正常的情況下,批
次檔中的每道指令執行前都會先出現螢幕上,使用 echo off 指令,就可以關閉顯示指令,通常
在不需要互動的批次檔中都一定會出現。

@ech off
set /P myname=Please input your name:
echo Hello %myname%
echo.
echo Today is %date% %time%
pause

停止下達的指令顯示在螢幕上 => echo off


空一行 => echo.
顯示當前目錄 => echo %cd%
顯示日期 => echo %date%
顯示時間 => echo %time%

判斷(if)
if /? 說明寫的不錯,就請直接參考囉

暫停
pause

程式暫停,提示按任意鍵繼續

echo This Program is running...


pause

timeout 秒數

程式暫停指定的秒數

echo Please wait for a while...


timeout 6

回傳值
無論是在 Linux 下撰寫 shell script 或是在 Windows 下撰寫批次檔,最近一次程式執行的回傳
值判斷,在撰寫工作自動化的 Script 檔時,是非常重要的技巧。

在Windows環境中的慣例是,若指令成功時傳回0,若錯誤時,依據錯誤的狀況會傳回 1 或以
上的值,代表不同的錯誤狀況。

但並非所有指令都會根據正確或錯誤而有不同的回傳值,要用來判斷前請先測試一下。

ping 168.95.192.1
4
批次檔介紹

echo %errorlevel%

ping 123.123.123.123
echo %errorlevel%

判斷回傳值錯誤的方式有2種

1. 判斷錯誤等級變數 if %errorlevel%==1

ping 123.123.123.123 > nul


if %errorlevel%==1 echo %date%-%time% ping command fail >> pingTarget.log

2. 錯誤判斷子句 if errorlevel 1

if errorlevel 1 表示若回傳值大於等於1(>=1), 當使用多個錯誤判斷子句時,一定要根據


errorlevel遞減的順序來排列, 因為錯誤判斷子句有上述2個隱含意義,因此建議使用第1種判
斷錯誤等級變數的方式為佳

ping 123.123.123.123 > nul


if errorlevel 1 goto ONE
if errorlevel 0 goto ZERO
goto END
:ZERO
echo %date%-%time% ping command succeful >> pingTarget.log
goto END
:ONE
echo %date%-%time% ping command fail >> pingTarget.log
:END

命令列參數
假設在命令列鍵入了下列指令

test.cmd c:\windows\notepad.exe c:\windows\write.exe

此時批次檔內部自動將命令列上的參數視為特別的變數如下表

%0 (命令) $1 (參數1) %2 (參數2)


test.cmd c:\windows\notepad.exe c:\windows\write.exe

請以Notepad++編輯 test.cmd 並鍵入下面的指令,存檔後在命令列上輸入

test.cmd c:\windows\notepad.exe c:\windows\write.exe

@ech off
echo command = %0
echo argument1 = %1
echo argument2 = %2

擴充字元參數
承上例,若在批次檔內的命令列參數加上擴充字元之後,可額外得知參數的許多資訊,請參
考下表

5
批次檔介紹

%~d1 (取得磁碟機代 %~p1 (取得路 %~n1 (取得檔 %~x1 (取得副檔


%1
號) 徑) 名) 名)
c:\windows\notepad.exe c: \Windows\ notepad .exe

echo %~d1
echo %~p1
echo %~n1
echo %~x1

更多的擴充字元請輸入 call/? 或參考 http://inpega.blogspot.tw/2012/07/cd-dp0.html 來獲得。

跳行與結束程式
goto 標籤 (須定義標籤,標籤須單獨一行,並以冒號開頭)

if not exist c:\temp\nul goto newdir


cd /d c:\temp
del * /y
:newdir
mkdir c:\temp

goto :eof (無須定義標籤,直接結束程式之意)

set /p end=按 0 結束程式:


if %end%==0 goto :eof

exit /b [回傳值]

使用 exit /b 可停止批次檔或副程式的執行,若結束後需要提供回傳值讓 if 指令 檢查 errorlevel


變數,可於其後加上想要的數值

rem myping.cmd 168.95.192.1


@echo off
ping %1 > nul
if %errorlevel%==1 goto error
ping %1 ok
exit /b 0
:error
echo ping fail
exit /b 1

設定副程式結束後回傳值為5

@echo Off
call :setError
echo %errorlevel%
goto :eof

:setError
Exit /B 5

6
批次檔介紹

7
變數

變數
變數
設定變數
批次檔使用的變數就是作業系統的環境變數,一般來說都視為字串變數,而且是是全域變
數,我們可以透過下面的指令觀察有哪些環境變數。

set

設定變數時,一樣使用 set 指令,注意等號左右不能有空白。

set myname=Peter

若設定的變數代表路徑時,因為路徑中可能包含空白字元,建議以雙引號含括起來較好,單
純顯示沒問題,但在命令列解析時,因為會以空白當作參數分隔,若沒有用雙引號時,會被
分開當成2個參數處理,造成錯誤。

set ProgramPath="c:\Program Files (x86)"

取用變數
取用變數時,則需在變數前後加上%

echo %myname%

取消變數
取消變數時,只需依照設定變數的方式,但值是空白即可

set var=

變數的運算
set 使用 /a 參數,可使後面的敘述成為運算式

set var=6
set /a var+=3

需注意的是,若變數的值為08或09的時候,會被視為一個錯誤的8進位而其值為0,影響後續
的計算

set var=08
set /a var+=3
set var=09
set /a var+=3

輸入提示
8
變數

set 使用 /p 參數,等號(=)開始到冒號(:)結束的一段文字將視為輸入的提示

@echo off
:menu
echo 1.dir
echo 2.dir /w
echo 0.離開
set /p id=請輸入功能代碼:
if %id%==1 goto one
if %id%==2 goto two
if %id%==0 goto zero
:one
dir
goto menu
:two
dir /w
goto menu
:zero

字串的擷取
批次檔也可以像一般程式語言一樣,做到從字串的第n個位置開始擷取m個字元這件事

步驟 %date% %date:~0,4%
說明 當下日期變數 變數從第0位開始取4碼 1
值 2015/10/17 週六 2015

上述說明的程式碼如下

set today=%date:~0,4%/%date:~5,2%/%date:~8,2%
echo %today%
1字串擷取符號說明

1. 冒號(:)
2. 波浪符號(~)
3. 開始位置(從0開始)
4. 逗號(,)
5. 擷取幾個字元

字串的取代
批次檔也可以進行字串的取代,方式跟字串的擷取有點類似,下面的範例說明如何將
(140.128.71.1)替換為[140.128.71.1]。

步驟1 %var% %var:(=[%


說明 var變數 將 ( 替換為 [ 2
值 (140.128.71.1) [140.128.71.1)
步驟2 %result% %result:)=]%
說明 result變數(存放前一步驟的結果) 將 ) 替換為 ] 2

9
變數

值 [140.128.71.1) [140.128.71.1]

上述說明的程式碼如下

@echo off
set var=(140.128.71.1)
set result=%var:(=[%
set result=%result:)=]%
echo %result%
Pause
2字串取代符號說明

1. 冒號(:)
2. 字串中想要被替換的子字串
3. 等號(=)
4. 替換後的子字串

其他的進階用法,請輸入 set /? 來獲得。

變數延遲展開
因為批次檔的命令解譯器,會針對每一行敘述中的變數進行預處理(預先賦值)的關係,因此造
成取值的結果仍是前一個變數的狀態,必須開啟變數延遲展開(setlocal
enabledelayedexpansion),才能如同一般程式語言循序處理變數,這個特性造成很多人無法正
確駕馭批次檔的變數行為,甚為可惜。

以下會舉出幾個範例,先說明敘述中變數優先賦值的情形

請先看for 迴圈第一段的說明範例,其餘待續...

10
呼叫外部程式與副程式

呼叫外部程式與副程式
呼叫外部程式與副程式
常用的呼叫外部程式有下列幾種方式

1. call 外部程式
從批次檔中呼叫外部程式並可傳遞參數,在同一個 shell 環境下,可共同存取相同的環境
變數。

rem test.cmd
@echo off
call test1.cmd hello
pause

rem test1.cmd
@echo off
echo %1
timeout 6

2. cmd /c 外部指令
呼叫一個新的shell程式(cmd)並於指令執行完成後結束這個 shell ,返回原來的shell環
境,外部程式執行在新的shell之中,因此存取的環境變數與原程式不同,有區域變數的
遮蔽效果

@echo off
cmd /c notepad.exe
exit

留意上述範例執行情形,在notepad.exe關閉以前,不會執行 exit 指令,因為指令尚未結


束,還留在新的shell當中。

3. 開始一個新視窗執行程式 => start [program] [parameters]


因為 cmd /c 具有同步特性(會等外部程式執行完畢),因此不太適合呼叫需與使用者互動
的視窗程式,因為命令提示視窗會因等待而保持開啟,所以適合改用 start 來呼叫執行。

@echo off
start notepad.exe
exit

使用 /wait 也可以改讓 start 具有同步特性,詳情參考 start /?

副程式呼叫
批次檔的副程式呼叫也是利用 call 指令,不同的是 call 的對象不是外部程式 ,而是相同檔案
中的標籤,也可以傳遞參數。

因為批次檔的副程式僅利用標籤代表區塊的開始,因此副程式都放在程式的尾部,之後就不
要寫任何命令敘述了,代表區塊的結束。

因為批次檔循序讀取的特性,就算副程式沒有被呼叫,也會被當作標籤一般順序執行下來,
11
呼叫外部程式與副程式

因此在副程式之前通常要加上 goto :EOF 強制結束批次檔。

call :標籤 參數1 參數2...

@echo off
rem for迴圈使用方式請參考本手冊相關章節
for %%i in (*.dll *.exe) DO CALL :SubRoutin "%%i"
pause
goto :EOF

:SubRoutin
echo %1, %~n1, %~x1

12
for 迴圈

for 迴圈
for 迴圈
批次檔的 for 迴圈很重要,但有些特性比較隱晦,不容易駕馭,下面稍加整理需注意使用之處

1. for 迴圈初始化變數,在撰寫為批次檔時,請使用 %%variable,而在命令列執行時要改


用 %variable。
2. for 迴圈初始化變數有大小寫的區分,所以 %%i 不同於 %%I。
3. for 迴圈內的變數會有取值異常的情形。

以下針對第3點取值異常的情形作一說明,迴圈的敘述通常是以下列格式撰寫,左括弧與 do
同一行,右括弧放在最後面獨立成行

for %%i in (*) do (


echo %%i
timeout 1
)

但其實批次檔會把迴圈內的敘述集結成一行變成

for %%i in (*) DO echo %%i & timeout 1

但就因為集結成一行的關係,批次檔在命令解譯器進行直譯時,會對每一行敘述中的變數進
行預先賦值的動作,因此會造成同一行敘述中,對變數改變其值卻無效的情形,請猜一下底
下這個範例會顯示的值是甚麼?

@echo off
set var=Peter
set var=John & echo %var%
timeout 6

結果仍是 Peter ,因為針對第3行,命令解譯器會預先賦值給%var%,然後才進行 set var =


John,若要正確取出此變數異動後的值 John ,必須 啟用變數延遲展開(SETLOCAL
ENABLEDELAYEDEXPANSION)的功能,在開啟用變數延遲展開功能之後,取用變數的方式
要由 %var% 更改為 !var!,將範例修改如下

@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set var=Peter
set var=John & echo !var!
timeout 6

批次檔中只要是利用括弧()分成多行撰寫的指令,實際上都看成一行,在括弧()裡面取用
變數時都會遇到需要開啟用延遲環境變數擴充功能的問題,一定要特別注意。

以下直接說明範例不特別解釋語法,細節請透過 for /? 學習

找出符合條件之檔案的 for 迴圈

1. 顯示批次檔存在的目錄中所有符合.mp4 .avi *.mpg的檔案名稱

13
for 迴圈

for %%i in (*.mp4 *.avi *.mpg) DO @echo %%i

2. 顯示使用者目錄中的所有檔案名稱 此例必須直接在命令列輸入(注意變數名稱的差
別)、環境變數 userprofile 代表使用者目錄

for %i in (%userprofile%\*) DO @echo %i

找出符合條件之目錄的 for /D 迴圈

顯示使用者目錄中的所有目錄名稱 此例必須直接在命令列輸入(注意變數名稱的差
別)、環境變數 userprofile 代表使用者目錄

for /D %i in (%userprofile%\*) DO @echo %i

遞迴搜尋指定的路徑下所有符合檔案的 for /R 迴圈

將 c:\temp\ 目錄與所有子目錄下的 *.bak 刪除

for /R c:\temp\ %%G in (*.bak) do del "%%G"

可以設定開始數值、增/減數值、停止數值的 for /L 迴圈

1. 顯示 0-100 的數字 (從0開始、遞增1、終止值100)

for /L %%i in (0 1 100) do echo %%i

2. 計算從1累加至100的和

set sum=0
for /L %%i IN (100, -1, 1) DO set /a sum+=%%i
echo %sum%

3. 啟用延遲環境變數擴充功能範例

SETLOCAL ENABLEDELAYEDEXPANSION
for /L %%i in (1 1 5) do (
set var=%%i
echo !var!
)
timeout 6

逐行讀取文字檔的 for /F 迴圈

for /F 是迴圈中最重要的應用,因為它可以讀取檔案、讀取字串與讀取命令,分別說明如下:

1. 讀取檔案:逐行讀取指定的檔案,然後依照分隔符號將內容賦值給指定變數
範例:逐行讀取 test.ini 文字檔內容,以等號(=)為分隔,左邊給%%i,右邊給%%j

FOR /F "tokens=1,2 delims==" %%i IN (test.ini) DO set %%i=%%j

test.ini 內容如下

account=john
passwd=a1234567890

14
for 迴圈

上述指令執行完畢之後會有下列效果, 將原來必須放在批次檔內部的變數設定,放在外
部的 ini 檔之後進行讀取,將程式碼與設定檔分離,可減少原始檔被亂改的機會

set account=john
set passwd=a1234567890

2. 讀取字串:讀取字串或變數,然後依照分隔符號將內容賦值給指定變數
範例:讀取變數%date%, 以斜線(/)為分隔,依序給%%a、%%b、%%c三變數,再依照年/
月/日的格式,儲存到mydate變數

For /f "tokens=1-3 delims=/ " %%a in ("%date%") do set mydate=%%a/%%b/%%c

3. 讀取命令:依照分隔符號將內容賦值給指定變數

命令執行的結果,成為 for /F 迴圈讀取資料的來源,須注意此命令須包含在單引號


之間, 若命令中包含了管線 | ,則須在管線前方加上逸脫字元 ^

範例1:將命令 date /t 的執行結果, 以斜線(/)為分隔,依序給%%a、%%b、%%c三變數,


輸出年-月-日的格式

for /F "tokens=1-3 delims=/ " %%a in ('date /t') do echo %%a-%%b-%%c

範例2:將命令 sc query 的執行結果透過管線輸出給 find 指令,尋找包含有


SERVICE_NAME字串的列,然後將第2欄的內容存到 %i 變數並顯示出來

rem 此例必須直接在命令列輸入(注意變數名稱的差別)
for /f "tokens=2" %i in ('sc query ^| find /i "SERVICE_NAME"') do @echo %i

sc query 命令會顯示目前系統所有的服務

範例3:利用 findstr 過濾文字檔(.csv)包含有井字號(#)的列,然後 以逗號(,)為分隔,依序


給%%I、%%J、%%K、%%L 4變數,然後再分別設定到有意義的變數中儲存

rem 設定參數檔名與批次檔相同(.csv)
set cfg=%~n0.csv
FOR /F "tokens=1,2,3,4 delims=," %%I IN ('findstr /V [#] %cfg%') DO (
set remoteDIR=%%I
set localBackupFolder=%%J
set RetentionDay=%%K
set isCallCheckspace=%%L
)

與批次檔同名的csv檔,其內容如下

#遠端備份名稱,本地端備份路徑,備份保留天數,是否檢查硬碟剩餘空間
myweb,C:\backup,20,Y

15
命令列特性

命令列特性
2.命令列特性
多行指令合併
要將分開多行的指令寫成一行,可利用 &

dir & pause

切換目錄:CD
change directory的縮寫,注意絕對路徑與相對路徑的差別,鍵入cd /?可得到更多的說明

使用 /D 參數可以同時變更工作磁碟機及其工作目錄

D:
cd /d c:\windows\system32

路徑中含有空白字元時,請使用雙引號括起來

cd "\winnt\profiles\username\programs\start menu"

切換工作目錄至批次檔所在目錄

cd /d "%~dp0"

顯示目前工作目錄

echo %CD%

顯示目前磁碟機

echo %CD:~0,3%

導向(Redirect)
以命令的處理為主,輸入導向是檔案、輸出導向也是到檔案

1. 將命令的輸出導向到檔案或控制碼,對於檔案來說有新建立(>)與附加(>>)二種

2. 命令 > 檔案名稱

3. 命令 >> 檔案名稱
4. 命令 2>>檔案名稱
5. 命令 2>&1
1. 命令的輸入來自某個檔案或控制碼,只有 < 一種
6. 命令 < 檔案名稱

dir > list.txt


sort < list.txt
16
命令列特性

sort < list.txt > alphlist.txt

控制碼英文 控制碼數值 控制碼意義


stdin 0 鍵盤輸入
stdout 1 正常輸出至命令提示字元
stderr 2 錯誤輸出至命令提示字元

特殊裝置
Windows 效法了 Linux 作業系統將周邊裝置視為檔案的作法,至少有下列2種特殊裝置可視為
檔案來運作

主控台(console)

透過命令列方式直接keyin內容,以建立檔案

copy con newfile.txt

黑洞(nul)

一般用來將指令的正常訊息導向至黑洞,使正常訊息不要顯示在螢幕上,然後配合判斷
errorlevel 或 %errorlevel%變數,寫入log

ping 168.95.192.1 > nul


if errorlevel 1 echo ping target fail >> pinglog.txt

管線(Pipe)
將命令的輸出透過管線當作另一個命令的輸入

命令1 | 命令2

dir | sort

指令的 or 、and
表面上根據 or 、and 來判斷,實際上也是透過判斷回傳值來決定,從判斷前1個命令的成功或
失敗來決定是否執行第2個命令

命令1失敗才執行命令2 => 命令1 || 命令2

ping 123.123.123.123 > nul || echo ping command fail

命令1成功才執行命令2 => 命令1 && 命令2

ping 168.95.192.1 > nul && echo %date%-%time% ping succeful >> pingTarget.

更改命令提示字元:prompt
請在命令提示字元視窗下練習輸入下面的指令,除了觀察提示字元的變化,並留意每呼叫一
次cmd,可呼叫exit返回的特性。

17
命令列特性

prompt Level1$g
cmd
prompt Level2$g
cmd
prompt Level3$g
exit
exit

可輸入prompt /? 來獲得更多的提示字元類型。

參考資源
MSDN Library 使用批次檔
Guide to Windows Batch Scripting
Top 10 DOS Batch tips
An A-Z Index of the Windows CMD command line
Getting started with batch files
Batch Utilities

18
練習

練習
3.練習
1. 請解釋此指令 sort < list.txt > alphlist.txt
2. 請解釋此指令 if errorlevel 1 echo ping target fail >> pinglog.txt
3. 請定義並顯示一個today的變數,其格式為月日年,例如10/12/2015
4. 請撰寫一個指令檔,提示使用者需輸入姓名與出生的西元年,輸入之後請顯示XXX您
好,您的年齡為:n歲
5. 請以批次檔完成九九乘法表,顯示格式如下

2x1=2
2x2=4
.....
2x9=18
3x1=3
.....
9x9=81

6. 請解釋下列指令執行的結果

@echo off
set val=1
echo %val%
(
set val=2
echo %val%
set val=3
echo %val%
)
echo %val%
pause

7. 請利用 route print、find 等指令,撰寫一個可顯示自己電腦IP的批次檔

參考解答
1. 將 list.txt 導向輸入給 sort 命令,sort命令排序處理之後,導向輸出到 alphlist.txt
2. 若前一個指令的回傳值大於等於1,則用附加的方式導向輸出 ping taret fail 字串到
pinglog.txt 檔案
3. quiz3.cmd

@echo off
rem 答案1
set today=%date:~5,2%/%date:~8,2%/%date:~0,4%
echo %today%
rem 答案2
for /F "tokens=1,2,3 delims=/ " %%a in ('date /t') do set today=%%b-%%c-%%
echo %today%

19
練習

4. quiz4.cmd

@echo off
set /p myname=請輸入您的姓名:
set /p mybiryear=請輸入您的出生西元年:
set /a myage=2015 - mybiryear
echo.
echo %myname%您好,您的年齡為:%myage%歲
pause

5. quiz5.cmd

@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
for /L %%i in (2 1 9) do for /L %%j in (1 1 9) do (
set /a sum=%%i*%%j
echo %%ix%%j=!sum!
)
pause

6. 因為沒有啟用延遲環境變數擴充功能,因此在括弧內的取用(echo %val%)會先作用,因
此中間的echo 回應都是1,最後才執行變數的賦值(set val=3),所以最終echo的結果是3。
7. showip.cmd

@echo off
rem route print | for /f "tokens=4" %i in ('find "0.0.0.0"') do @echo %i &
FOR /F "tokens=4 delims= " %%i in ('route print ^| find "0.0.0.0"') do ech
:NEXT
pause

20

You might also like