DOS指令 -- FOR迴圈

DOS指令  --  FOR迴圈


因為最近有在寫Dos的指令,有感而發寫一些自己有在用的東西

先解釋基本的For指令
再說明遞增/遞減的 For迴圈和搜索檔案用的迴圈
最後就是筆者最常用的For迴圈


For指令常常用在重複執行類似的動作


如 :
在字串最後面加上數字
像是 :
朋友1
朋友2
朋友3

執行指令並依序處理傳回的文字
執行 dir 並比對回傳的檔案
開啟文字文件依序讀取每一行的文字,並依不同狀況執行


後兩種是我最常使用的功能,先介紹基本的 For 指令的使用方式


FOR %%a IN (香蕉,你的,巴拉) DO (
  echo %%a
)
輸出會像:

香蕉

你的

巴拉

(  ) 內的東西都用光以後,迴圈就會自動結束。
而每次取得的值,這個值會存在變數 %%a 裡面,並可以在 DO 後面的 ( ) 內執行指令

說明一下每個指令的意思
FOR -- 要開始一個For迴圈最前面一定需要的文字 For

%%a -- 用來儲存For迴圈產出的值,這個變數可以由使用者自行決定,可以是 %%b 或 %%g。
             前面一定要有兩個%%後面加上一個英文字。注意 : 這個英文字有大小寫之分
             在這個例子中用來儲存 香蕉.....

IN -- 指令文字,後面一定要接  (香蕉...) <- (裡面可以放很多東西)

(香蕉...) -- 文字串,在For裡面依序使用的東西 (後面會說到裡面還可以放檔案、指令)

DO -- 指令文字, 後面一定要接 (echo  ....)

(echo  ....) -- 要執行的指令,在這裡可以使用For的變數 %%a。
                     如果指令有兩個以上可以像例子中一樣用 ( ) 包起來。




以上就是最基本的For,接著說明數字的遞增和遞減


數字的遞增和遞減

For /L %%a in (start,step,stop) DO ( Do something)


例子
For /L %%a in (1,2,7) DO (
  echo %%a
)

輸出 :
1
3
5
7




/L -- For後面可以下不同的參數,這會改變For對 IN 後面的 ( ) 所做的動作。
        /L 會讓For 認定 ( ) 內的是數字,並且是遞增或遞減
start -- 起始數字,For迴圈從這個數字開始
step -- 每次迴圈變化的量,1 就是一次加一,-1就是一次減一。
           例子中是每次加二
stop -- 當數字超過stop所設定的數字的時候迴圈就會終止。
           (超過可能是比它大或比它小,端看start的數字與stop的數字的比較)
           當數字等於stop的時候,並不會停止迴圈。



搜索檔案用的迴圈

For /R [[drive:]path] %%a in (檔名,檔名) do (Do something)


例子
For /R c:\users\myaccount\Desktop %%a in (生日*,會議*) do (
echo %%a
)

輸出:


c:\users\myaccount\Desktop\生日壽星.txt
c:\users\myaccount\Desktop\生日蛋糕.doc
c:\users\myaccount\Desktop\會議.doc



/R -- 設定為搜索檔案的For迴圈
drive: -- 搜索的磁碟機位置,如C:\或D:\等
              可以省略不輸入,當沒有輸入時會認定為執行目錄的磁碟機,一般是 C:\
path -- 開始搜索的資料夾,重這個資料夾開始往子資料夾搜索
            可以省略不輸入,當drive和path都沒有輸入時,會使用執行目錄為預設路徑
(檔名,檔名...) -- 輸入要尋找的檔名,其中檔名要加上 * 號
                           * 號代表萬用字元,例子中,生日*,"生日"後面不管接什麼名字都算符合
                           也可以放在前面,*生日.doc,也就變成檔名最後是生日的都算符合



最後是筆者最常使用的For迴圈

指令或讀取檔案的For迴圈

For /F "option" %%a in (檔案,檔案...) do (Do something)
For /F "option" %%a in ("字串,字串") do (Do something)
For /F "option" %%a in ('指令') do (Do something)

稍微說明一下,in後面 ( )中 :
不加任何符號 (除了 , )代表這是一個檔案,可以被開啟讀取
前後加上 " " ,表示裡面是字串
前後加上 ' ' ,表示裡面是個指令,For會執行這個指令並收集回傳值

如果檔案的檔名有空白會造成For迴圈的判讀錯誤,因為Dos的For 會把空白當作分行,空白前後是不同檔案,這個時候option要加上 "usebackq",此時For迴圈的判讀為

For /F "usebackq" %%a in ("檔案,檔案...") do (Do something)
For /F "usebackq" %%a in ('字串,字串') do (Do something)
For /F "usebackq" %%a in (`指令`) do (Do something)

 ( )內用 " ",代表檔案,這樣可以把有空白檔名的檔案包在裡面
' ', ' = " + shift, "符號再按shift就是這個符號,這個符號會被判定為字串
` `, 最左上角的按鈕 ( 1 的左邊),這個符號會被判定為指令


例子:
For /F  "tokens=4,5" %%A in ('dir') do (
echo 檔名:%%B 檔案大小:%%A
)


輸出:

檔名:.. 檔案大小:<DIR>
檔名:.. 檔案大小:<DIR>
檔名:project.doc 檔案大小:4414
檔名:readme.txt 檔案大小:12
檔名:runforlife.mov 檔案大小:65434567





其中option有很多選項,整個option要用 " " 包起來,如 "tokens=1,2 eol=+ skip=2"

eol -- 當碰到設定的符號時,就不會再儲存後面的文字 (只能指定一個字元)

skip -- 略過開頭的 n 行,設定幾行就會跳過幾行

delims -- 設定分隔符號,預設的分隔符號為 空白 ,可以指定多個符號
               如"delims=+=:a ",指定的符號有 +號 =號 :號 a英文字和一個空白

tokens -- 當回傳的文字被分隔符號 (delims)切隔成多個時,就要用tokens來取得想要的文字
               預設是 1 ,可以用"tokens=1,3,5-9,*",可以一次指定一個或用 - 符號指定多個
               也可以用 * 符號把從這個以後的文字都設定在同一個變數中。
               (tokens的使用會在下面介紹)

usebackq 改變 /F 讀取的格式            



這邊說明一下tokens
tokens的使用會引用額外的變數,如 :
For /F "tokens=1,2,3 %%d in (aaa bbb ccc ddd) do (
echo %%d
echo %%e
echo %%f
echo %%g
)

會顯示
aaa
bbb
ccc
%g

一旦指定tokens以後,For迴圈會自動增加變數,並依照英文符號第增
如果指定的變數是大寫,如%%C,自動增加的變數也會是大寫%%D、%%E...
最後一個會顯示 %g 是因為沒有指定token,所以第四個變數沒有文字可以儲存

如果tokens使用 m-n的方式,如:
For /F "tokens=1,2-3,4 %%D in (aaa bbb ccc ddd) do (
echo %%D
echo %%E
echo %%F
echo %%G
)

會顯示
aaa
bbb ccc
ddd
%G

最後一種tokens的用法是tokens=1,*,星號代表從這個 (在這邊是1) 以後的文字都存在同一個變數,如 :

For /F "tokens=1,2,* %%D in (aaa bbb ccc ddd) do (
echo %%D
echo %%E
echo %%F
echo %%G
)

會顯示
aaa
bbb
ccc ddd
%G



這裡做簡單的小結
For 後面代不同的參數會使For有不同的運作和判斷
For ------ 最基本的For迴圈,筆者還沒用過
For /L -- 讓數字遞增或遞減的For迴圈,像是讓一個指令重複執行10次
For /R -- 搜索檔案,可以搜索特定檔案或全部搜索,並回傳各種檔案的屬性
For /F -- 讀取檔案或執行指令,並儲存回傳的值 (常用),並且有很多個option可用
For /D -- 使用延伸指令,重來沒用過




底下介紹筆者在用的使用方法



讀取電腦的IP
各位應該有很多種抓取電腦IP的方法,這邊用For迴圈加ipconfig指令的方式
@setlocal
@echo off
for /F "tokens=16" %%a in ('ipconfig ^| findstr /R /C:"10.1.1" ^| findstr /R /C:"IPv4"') do (
 set ip=%%a
)
echo %ip%
endlocal

輸出:
10.1.1.20


更進一步的,只要10.1.1.20裡面的最後一個數字 20 ,可以在迴圈裡面再新增一個迴圈
@setlocal
@echo off
for /F "tokens=16" %%a in ('ipconfig ^| findstr /R /C:"10.1.1" ^| findstr /R /C:"IPv4"') do (
 for /F "tokens=4 delims=." %%c in ("%%a") do (set ip=%%c)
)
echo %ip%
endlocal
輸出:
20


說明一下這邊用到的參數和指令
tokens=16 -- 可能有人會問,為什麼知道是第16個位置,那是因為我用試的試出來的
                      方法有點像 : tokens=1,2,3,4,5,6,7,8,9,10,11,12,然後用echo %%a-1, %%b-2,....
                      這樣的方法找出位置
delims=. ----  因為IP之間是用 . 隔開,因此設定間隔符號為 . 。
ipconfig -- 顯示電腦IP和其它相關資訊
findstr ---- 在文件中尋找文字,在這邊用來尋找 ipconfig 執行的結果
^| --------- 這裡有兩個符號 ^ 和 | 。
                 ^ 在批次檔內代表逃脫符號,會讓下一個符號的特殊能力失效,變成普通的文字。
                 | 這個是管線符號 (pipe) 會把左邊執行的結果傳到右邊當作輸入
                 會用到 ^| ,是因為For裡面會認定 | 為特殊符號,會中斷For的執行

這邊用到兩個迴圈,For裡面還有另一個For,這可以降低撰寫的行數
兩個For所使用的變數建議用不一樣的,比較好區分



For裡面設定(set)變數
這邊用漸進的方式解釋變數在For迴圈理的現象
這邊使用For /R 去累計總檔案的大小,例子中的檔案大小是編出來的

@setlocal
echo off
set size=0
For /R  %%A in (Joo*) do (
echo %%~zA
)
pause
endlocal
輸出:
10
20
30
>40



現在加上一個變數來累加總共的檔案大小
@setlocal
echo off
set size=0
For /R  %%A in (Joo*) do (
set /a size=%size% + %%~zA
echo 這個檔案大小 : %%a
echo 目前累計 : %size%
)
echo 總共累計 : %size%
pause
endlocal
輸出:
這個檔案大小 : 10
目前累計 : 0
這個檔案大小 : 20
目前累計 : 0
這個檔案大小 : 30
目前累計 : 0
這個檔案大小 : 40
目前累計 : 0
總共累計 : 40

這個例子多了set /a size=%size% + %%~zA用來累加每個檔案的大小
並在下一行使用echo %size%告知現在size的累計量

從結果看,在迴圈內的 size 變數沒有被加總的感覺
最後一個檔案大小是 40 ,跟"總共累計"大小一樣,也就是 size變數只有加到最後一次
因為在 For 裡面的 % % 變數,是取用For之前的變數做的位置
因此每次執行set /a size=%size% + %%~zA這個裡面的%size%,是取用最前面set size=0 的這個位置
也就是在For裡面呼叫%size%時,每次呼叫的值都是 0。
底下說明如何在For迴圈裡面使用 set 變數。



新增參數 enableextensions enabledelayedexpansion

@setlocal enableextensions enabledelayedexpansion
echo off
set size=0
For /R  %%A in (Joo*) do (
set /a size=!size! + %%~zA
echo 這個檔案大小 : %%a
echo 目前累計 : !size!
)
echo 總共累計 : %size%
pause
endlocal
輸出:
這個檔案大小 : 10
目前累計 : 10
這個檔案大小 : 20
目前累計 : 30
這個檔案大小 : 30
目前累計 : 60
這個檔案大小 : 40
目前累計 : 100
總共累計 : 100

在 setlocal 後面再加上參數enableextensions enabledelayedexpansion,可以使用 ! ! 呼叫變數
在For裡面呼叫 %size%會一直是零,原因上面有提到,為了要反應迴圈內的變數要使用 !size! 去呼叫變數。
這樣的呼叫方式第一次會取set size=0的值存在另一個位置
而後在迴圈內每次呼叫!size!都會使用這個新的位置進行儲存和讀取

結論就是:
要在回圈中使用變數要增加參數enableextensions enabledelayedexpansion
迴圈中呼叫變數的方法為 !變數!
迴圈外呼叫變數的方法還是 %變數%



最後展示某個資料夾即子資料夾的檔案,超過修改時間7天後會被刪除

@setlocal enableextensions enabledelayedexpansion
echo off
set size=0

For /R  T:\abc\def\ %%A in (*) do (
rem 把檔案的日期存到變數tmp
set tmp=%%~tA

For /F "tokens=1,2,3 delims=/ " %%c in ("%%~tA") do (
  rem 檢查檔案日期的年份,如果比現在的年份小就刪除 
  if %date:~0,4% GTR %%c (
    del /f /q "%%A"
 echo %%A
 set /a size=!size! + %%~zA
  ) else (
    rem 檢查檔案日期的月份,如果比現在的月份小就刪除
    if %date:~5,2% GTR %%d (
   del /f /q "%%A"
   echo %%A
   set /a size=!size! + %%~zA
 ) else (
 set tmp_2=%%e
 rem 因為DOS變數的關係,要檢查日期是不是兩位數並做一些處理
 if "!tmp_2:~0,1!" == "0" (
   set /a tmp_2=!tmp_2:~1,1! + 6
   if  "!tmp_2:~0,1!" == "0" (set tmp_2=0!tmp_2! ) 
 ) else (
   set /a tmp_2=!tmp_2:~0,2! + 6 
 )
   rem 檢查檔案月份的天數,如果比現在的天數小7天就刪除
   if %date:~8,2% GTR tmp_2 (
     del /f /q "%%A"
        echo %%A
     set /a size=!size! + %%~zA
   )))
)

)
echo 總刪除的檔案大小 : %size%
pause
endlocal
怕有人使用後誤刪檔案,這個例子中的路徑是亂設定的。
例子內並沒有真的準確7天,在年和月的部分有可能差不到7天
可以用類似( set /a xx=30-%date:~8,2%+6) & if xx GTR 7 )的方式比較月的部分有沒有超過7天

留言

  1. 希望可以多謝一點實用的Batch語法!
    謝謝筆者提供!

    回覆刪除

發佈留言

此網誌的熱門文章

WPA_supplicant的設定方式

DOS指令 -- SET和變數

Nginx server 和 location 優先順序