본문 바로가기

카테고리 없음

batch for문 정리

배치문법 공부하다가 for문이 제일 헷갈리고 x같아서

따로 for문을 정리해보았다.


1. for문 안에서의 루프 변수 형태

FOR %%변수 IN (집합) DO 명령어

1. 배치파일(일괄 프로그램) 내에 작성할때는 루프변수는 %%변수 형태

2. 대문자, 소문자 구분함

3. 한 글자만 가능

4. 사용시 %%변수 형태로 그대로 사용

 

예) loopvar.cmd

@echo off

for %%c in (miku daisuki) do echo %%c

pause > nul

출력은 

miku

daisuki  


2. for문 안에서의 (집합) 형태

FOR %%변수 IN (집합) DO 명령어

기본적으로 (파일명), ("문자열") 형태가 있는데

여기서 (파일명)은 파일명 아닌거 같으면 걍 문자열로 처리한다.

위의 예제 loopvar.cmd 처럼 말이다.


예) dirFor1.cmd

@echo off

for %%c in (*) do echo %%c

pause > nul

현재 폴더의 파일 목록을 출력해준다


예2) dirFor2.cmd

@echo off

for %%c in (*.cmd *.bak) do echo %%c

pause > nul

현재 폴더에서 확장자가 cmd이거나 bak인 파일 목록 출력


예3) dirFor3.cmd

@echo off

for %%c in (* miku daisuki) do echo %%c

pause > nul

현재 폴더의 파일들을 출력하고 문자열 miku와 문자열 daisuki도 출력


그리고 여러개를 처리할때는 사이에 공백이나 콤마(,) 세미클론(;)을 넣는다.

예4) multiSet.cmd

for %%a in (1;2 3,4) do echo %%a

1 2 3 4가 각 줄에 출력된다.


(파일명)과 ("문자열") 외에 

('명령어')가 있는데 이건 특수한 옵션에서만 쓰인다 

밑에서 다시 보도록 하자 


3. 증감 For문

FOR /L %%변수 IN (시작,단계,끝) DO 명령문

이건 그냥 형태만 봐도 감이 온다.


예) optionL.cmd

@echo off

for /L %%a in (1,2,100) do echo %%a

pause > nul

1부터 100까지 홀수 출력


4. recursive for문

FOR /R [경로] %%변수 IN (집합) DO 명령어

경로에서 recursive하게 탐색하여 각 디렉토리마다 for문을 실행한다.

경로를 생략하면 현재 폴더에서 탐색을 시작한다.


예) findyadong.cmd

@echo off

for /r c:\ %%a in (*.mp4 *.avi) do echo %%a

pause


5. directory For문

FOR /D %변수 IN (집합) DO 명령어

(파일명) -> (폴더명)


예) directoryFor.cmd

@echo off

for /d %%a in (*) do echo %%a

pause >  nul

현재 폴더의 폴더목록 출력


6. file For 문

FOR /F ["옵션"] %변수 IN (집합) DO 명령어

주로 파일 내용을 파싱할 때 사용한다.

(집합)이 (파일명)일 때 파일 내용을 한 줄씩 for문을 돌려 파싱하고

(집합)이 ("문자열")일 때 문자열을 파싱한다.

(집합)이 ('명령어')일 때 명령어 결과값을 파싱한다.


이 때 파싱하는 규칙["옵션"]에서 정한다.


delims(delimeters, 구분자) : 문자열을 나누는 기호 문자, 생략시 공백(스페이스,탭)으로 나눈다.

tokens : 구분자로 나뉘어진 것을 각각 토큰이라 한다. 

토큰들과 루프 변수에 1:1로 대응하도록 한다. 

이 때 루프변수의 다음 알파벳으로 자동 대응된다.

skip : 생략할 행 번호 설정

eol : 시작 문자로 생략할 행 설정, 생략시 ;(세미클론)으로 시작하는 행은 생략된다.

usebackq : 대체 인용, 파일명에 공백이 들어 갈때 문자열로 처리하는걸 막거나 명령문에 특수문자가 들어갈때 오류 방지


예1) replaceTest.cmd

@echo off

for /f "delims=: tokens=1,2" %%a in ("hello:world") do echo %%a-%%b

pause > nul

구분자(:)로 문자열 "hello:world"를 "hello" 와 "world" 토큰으로 나누게 되고

첫번째 토큰("hello")와 두번째 토큰 2("world")는 각각 %%a 와 %%b에 대응되어서

출력은 hello-world가 출력된다.


예2) hostsView.cmd

@echo off

for /f "skip=5 delims= tokens=1" %%a in (c:\Windows\System32\drivers\etc\hosts) do echo %%a

pause > nul

hosts 파일내용이 5번째 행까지는 스킵한다. 

구분자가 없으므로 hosts 파일 내부 각 행이 통채로 토큰이 되어서 %%a에 대응된다.

결국 hosts파일 내용이 6번째 행부터 그대로 출력된다.


예3) useTokens.cmd

@echo off

FOR /F "tokens=4 delims=," %%G IN ("deposit,$4500,123.4,12-AUG-09") DO echo %%G

pause > nul

"deposit,$4500,123.4,12-AUG-09" 문자열을 구분자(,)로 나누어 

"deposit" "$4500" "123.4" "12-AUG-09" 4개의 토큰을 만든다.

4번째 토큰을 루프변수 %%G에 대응시켜

출력값은 12-AUG-09


예4) useTokens2.cmd

@echo off

FOR /F "tokens=1-3 delims=," %%G IN ("deposit,$4500,123.4,12-AUG-09") DO echo %%G+%%H+%%I

pause > nul

첫번째 토큰부터 3번째 토큰까지 각각 %%G %%H %%I에 대응시켜

deposit+$4500+123.4 를 출력한다.


예6) useTokens3.cmd

@echo off

FOR /F "tokens=1,2* delims=," %%G IN ("deposit,$4500,123.4,12-AUG-09") DO echo %%G+%%H+%%I

pause > nul

첫번째 토큰과 2번째 토큰은 각각 %%G %%H에 대응되고

그 이후 문자열은 모두 %% I에 대응시킨다.

출력값은 deposit+$4500+123.4,12-AUG-09로 나온다.


예7) eolTest.cmd

@echo off

: 주석입니다

FOR /F "eol=: tokens=* delims=" %%G IN (eolTest.cmd) DO echo %%G

pause > nul

현재 파일의 각 행을 읽어서

:로 시작하는 문장을 제외한 각 행을 출력한다.


예8) 

파일명만 파싱해서 파일에 저장해보자

파일은 8번째 행부터 보이니깐 => skip=7

5번째 열부터 파일이름이 시작함 => token=5*


dirParsing.cmd

@echo off

for /f "skip=7 tokens=5*" %%a in ('dir') do echo %%a %%b

pause > nul

뽑았긴 했는데 뒤에 남음도 생겼다. 

dirParsing2.cmd

@echo off

for /f "skip=7 tokens=5*" %%a in ('dir') do (

if %%a neq 남음 echo %%a %%b

)

pause > nul

제대로 뽑았다.

dirParsingF.cmd

@echo off

for /f "skip=7 tokens=5*" %%a in ('dir') do (

if %%a neq 남음 echo %%a %%b > dirlist.txt

)

pause > nul


사실 위의 코드는 

@echo off

dir /b > dirlist.txt

pause > nul

이걸로 끝난다


그러니깐 첨부터 힘들게 파싱하지말고 기본 명령어로 먼저 해보자.


7. local 변수 

배치 for문 짜다보면 누구나 약속된 시간이 찾아오는데

바로 for문 밖 지역변수를 쓰다가 생각한거랑 다르게 작동하는 것이다.


localvarTest.cmd

@echo off

set sum=0

FOR /l %%G in (1,1,5) Do (

echo [%sum%] & set /a sum+=%%G

)

echo Total = %sum%

pause > nul

[0]

[0]

[0]

[0]

[0]

Total = 15

 

1부터 5까지 숫자를 더하고 출력하는 예제인데

출력값을 보면 이상하다. 분명 중간중간 sum값을 출력했는데 0이 나오고

마지막 sum값은 제대로 나온다.


batch파일 특성 때문인데

1. 한줄단위로 명령어를 실행된다.

2. 코드 실행전 확장(expansion)이 한번 일어난다.


문자를 파싱한다. -> 문자를 확장(expansion) 한다. -> 실행한다.


확장이란것은 실행하기 전 내부 프로세스에서 명령어를 다시 조합한다고 이해하면 될 듯하다.

이프케이프 문자열 확장하거나 괄호를 확장한다거나 변수를 확장한다거나 명령어를 확장한다거나

여러가지 확장들이 있는데 여기서 문제가 되는게 변수확장(variable expansion)이다.


위에서 작성한 코드는 다음 과정으로 한줄 씩 처리된다.


set sum=0

지역변수 sum에 0을 할당한다.


FOR %%G in (1,2,3,4,5) Do ( echo [0] & set /a sum+=%%G )

for /l문은 일반 for문으로 확장되고, %sum%은 0값으로 확장된다.

set /a는 연산처리 하도록 내부에서 확장된다.

여기서 확장은 한줄에 한번만 일어나기 때문에 

%sum%값은 계속 0인채로 출력되는 것이다. 실제 sum 값은 증가(할당)하고 있지만.


echo Total = 15

이 명령어를 읽을 때는 sum값이 15이므로 %sum%이 15로 확장된다.


이 변수확장을 이용한 재밌는 예제가 있는데

swapVar.cmd

@echo off

set var1=1

set var2=2

set var1=%var2% &set var2=%var1%

echo var1 : %var1%

echo var2 : %var2%

pause > nul

set var1=%var2% &set var2=%var1% 

이 명령어는 실행전 변수 확장으로

set var1=2 &set var2=1 

로 변한다. 임시 변수 사용없이 스왑이 가능하다는 것이다.


그럼 이제 변수확장 이걸 어떻게 풀어야 할까

2가지 방법이 있다.


1. setlocal enabledelayedexpansion 로 확장을 실행할때로 지연시키고 

기호 문자 !를 사용하여 loop 돌때마다 확장시키는 것이다.

@echo off

set sum=0

setlocal enabledelayedexpansion

FOR /l %%G in (1,1,5) Do (

echo [!sum!] & set /a sum+=%%G

)

echo Total = %sum%

pause > nul

이렇게 하면 loop 돌때마다 변수확장이 매번 일어난다.


2. 외부에서 처리 한다.(서브루틴을 사용한다.)

@echo off

set sum=0

FOR /l %%G in (1,1,5) Do (call :outsub %%G)

echo Total = %sum%

goto :end


:outsub

echo [%sum%] & set /a sum+=%1

exit/b


:end

pause > nul

이런식으로 짜면 변수확장이 매 다른 라인에서 일어나기 때문에 정상적으로 출력된다.



8. 나만을 위한 예제

다음은 이 글 전체 주제와 관계 없는 글입니다.


아래는 그냥 심심해서 돌아다니는 batch파일들 중 일부를 해석해보았다. 

http://www.snoopybox.co.kr/1403



:: MKV 파일을 MP4로 변환하는 과정입니다.


if not exist temp md temp

rem temp 폴더가 없으면 temp 폴더를 만들어라



del /q temp\*.*

rem temp 폴더 안 파일 모두 묻지고 않고 삭제


for %%a in (*.mkv) do (

rem 현재 폴더에서 mkv 확장자인 동영상들을 for문 돌린다.


Tools\mkvinfo "%%a" --output-charset UTF-8 -o temp\temp1.txt

rem 파일 정보 추출 해서 temp폴더에 텍스트 저장


Tools\mediainfo "%%a" > temp\temp2.txt

rem 파일 정보 추출 해서 temp폴더에 텍스트 저장


findstr /c:"Frame rate                       :" temp\temp2.txt > temp\temp3.txt

rem 텍스트 파일에서 해당 문자열을 포함한 행을 찾아서 다른 텍스트 파일에 저장(동영상의 fps)


findstr /c:"Track type" temp\temp1.txt > temp\temp4.txt

rem 텍스트 파일에서 해당 문자열을 포함한 행을 찾아서 다른 테스트 파일에 저장(track별 type)


for /f "tokens=5" %%d in (temp\temp4.txt) do (

rem 텍스트 파일에서 공백으로 나누어진 토큰 중 5번째로 for문으로 돌린다(순 track별 type)


if not exist temp\temp.h264 (

rem h264파일이 존재안하면


if %%d==video Tools\mkvextract tracks "%%a" 1:temp\temp.h264 2:temp\temp.audio

rem 타입이 video이면 위 방식으로 추출


if %%d==audio Tools\mkvextract tracks "%%a" 2:temp\temp.h264 1:temp\temp.audio

rem 타입이 audio이면 위 방식으로 추출

)


)


findstr /c:"Codec ID: A_AAC" temp\temp1.txt > nul

rem 텍스트 파일에서 해당 문자열을 포함한 행을 찾아서 다른 텍스트에 저장


if errorlevel 1 (

rem acc 코덱인 오디오가 존재 안하면(위에서 해당 문자열을 찾지 못했을 때)


Tools\ffmpeg -i temp\temp.audio -ac 2 temp\temp.wav

rem 먼저 wav로 인코딩


Tools\neroAacEnc -if temp\temp.wav -lc -br 128000 -of temp\temp.aac

rem 다음 aac로 인코딩


) else (

rem acc 코덱인 오디오가 존재하면


ren temp\temp.audio temp.aac

rem 확장자 acc로 변경


)


for /f "tokens=4 usebackq" %%c in (temp\temp3.txt) do (

rem 프레임 정보


Tools\mp4box -fps %%c -add temp\temp.h264:level=3.1 -add temp\temp.aac "%%~na.mp4"

rem acc파일과 h264파일을 mp4파일로 만듦


)


del /q temp\*.*

rem temp 폴더안에 파일 다 지움


)



코드 해석해보니깐 문제점을 감히 나열해보면

1. fps를 따로 뽑던데 마지막에 mp4box돌릴 때 fps 굳이 안줘도 알아서 계산해서 합쳐짐 

2. 듀얼 오디오나 자막이 존재할 때 오디오 하나 빼고 다 버려짐

(이게 좀 큰 문제임)

3. 메타 데이터 없어짐 아마도 합쳐진 mp4 영상정보 보면 메타데이터 엉망일꺼임  

4. 가변 프레임 가지고 있는건 처리 못함


시간남으면 일부 무단 수정해봐야지.feat ffmpeg