Step 10. ★ 프로젝트: 파일 자동 분류 — 다운로드 폴더를 청소하는 로봇

Step 10. ★ 프로젝트: 파일 자동 분류 — 다운로드 폴더를 청소하는 로봇

Level 0 — 컴퓨터 조작과 구조의 이해 | 난이도 ★★★☆☆ | 예상 소요 시간 3~4시간

전제: Step 9 완료. 윈도우 파워쉘에서 진행합니다. Step 7~9의 모든 기술이 동원되는 첫 종합 프로젝트입니다.

  • 준비물: 윈도우 PC, 파워쉘, 메모장, 그리고 정리할 파일이 몇 개 들어 있는 폴더(다운로드 폴더 또는 연습용 폴더).
  • 안전: 파일을 옮기는 스크립트는 잘못 쓰면 파일이 엉뚱한 곳으로 갑니다. 그래서 -WhatIf(미리보기)라는 안전장치를 먼저 배우고, 그 다음에 진짜 실행합니다. 이 순서가 오늘의 핵심 습관입니다.
  • 주의: 이 챕터는 ★ 프로젝트 챕터입니다. 새 개념보다 "배운 것을 조립해 실물을 만드는" 데 무게가 있습니다. 저장 인코딩은 Step 9에서 배운 대로 "UTF-8(BOM 포함)"을 기억하세요.

여러분의 다운로드 폴더는 지금 어떤 모습입니까? 설치 파일, 사진, PDF, 압축 파일이 뒤섞여서, 필요한 걸 찾으려면 스크롤을 오르내리는… 대부분의 컴퓨터에서 다운로드 폴더는 "디지털 창고"가 됩니다. 오늘 만들 것은 스크립트 한 번 실행하면 폴더 안의 파일들이 확장자별 폴더로 정리되는 자동 분류기입니다. 완성하고 나면 처음으로 "배움"이 "도구"가 되는 순간을 맞습니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • 코드를 짜기 전에 알고리즘(처리 순서)을 우리말로 먼저 설계한다
  • 환경 변수($env:USERPROFILE)로 사용자 이름에 상관없이 돌아가는 코드를 쓴다
  • -WhatIf로 위험한 명령의 결과를 미리 확인한 뒤 실행한다
  • 확장자 없는 파일·이름 충돌 같은 예외 상황을 코드로 처리한다
  • Step 7~9의 기술을 조립해 동작하는 자동 분류 스크립트를 완성한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 파워쉘 5.1 스크립트(.ps1), 대상은 윈도우 파일 시스템
오늘의 명령어 New-Item -ItemType Directory(폴더 생성), Move-Item(파일 이동), -WhatIf(미리보기), Test-Path(존재 검사)
복습 명령어 Get-ChildItem -File(Step 2), foreach·if(Step 8), 변수·환경 변수(Step 7)
필요한 개념 알고리즘(처리 순서 설계), 예외 상황(edge case), 멱등성

이 프로젝트에 들어가는 기술 대부분은 이미 배운 것들입니다 — 변수(Step 7) + 파일 목록 얻기(Step 2) + 반복문과 조건 판단(Step 8) + 폴더 만들기·파일 옮기기(Step 2) + 스크립트 실행(Step 9). 새로 배우는 명령은 위 표의 네 개뿐입니다. 나머지는 복습이자 조립입니다.

2-1. 코드 전에 생각부터 — 알고리즘

프로젝트 챕터에서는 코드 전에 설계를 먼저 합니다. "어떤 순서로 무엇을 하면 되는가"를 우리말로 적는 것 — 이걸 알고리즘(algorithm)이라 합니다. 거창한 용어 같지만, "요리 순서"나 "조립 설명서"를 적는 것과 같은 작업입니다.

우리 분류기의 알고리즘:

1. 대상 폴더의 파일 목록을 얻는다
2. 파일마다 반복한다:
   2-1. 이 파일의 확장자를 확인한다 (.pdf, .jpg...)
   2-2. 그 이름의 폴더가 없으면 만든다
   2-3. 파일을 그 폴더로 옮긴다
3. 결과를 확인한다

이 순서가 머릿속에 선명해야 코드가 술술 나옵니다. 초보와 숙련자의 차이는 타이핑 속도가 아니라 이 그림을 먼저 그리는가입니다.

2-2. -WhatIf — 오늘의 주인공

-WhatIfMove-Item 같은 명령 뒤에 붙이는 스위치로, 실제로는 아무것도 옮기지 않고 "실행했다면 일어났을 일"만 보여 줍니다. 위험한 명령(이동, 삭제, 변경) 전에 리허설하는 습관 — 이게 초보와 프로를 가르는 안전 감각입니다.

2-3. 왜 하필 실제 폴더인가

이 프로젝트를 연습용으로 예쁘게 만들어 둔 파일이 아니라 실제 폴더에 적용하는 이유가 있습니다. 진짜 데이터를 상대해야 진짜 예외 상황을 만나기 때문입니다. 한글 파일명, 공백이 들어간 이름, 확장자 없는 이상한 파일은 반드시 등장합니다. 그 낯선 입력을 처리하는 경험이 이번 프로젝트의 숨은 목표입니다. 예상 못 한 입력을 만났을 때 당황하지 않고 "아, 예외 상황이로구나" 하고 코드에 if를 하나 더하는 태도 — 그게 이 프로젝트가 주는 진짜 훈련입니다.

(다만 진짜 다운로드 폴더가 처음부터 부담스럽다면, 연습용 폴더를 하나 만들고 아무 파일이나 여러 종류를 넣어 두고 시작해도 됩니다. 흐름은 같습니다.)

2-4. 예외 상황 — 완벽한 계획에 구멍 뚫기

설계 단계에서 "어떤 파일이든 잘 옮겨질까?"를 한번 의심해 봅시다:

  • 확장자가 없는 파일은? (README 같은 것) → 어떤 폴더로 가야 할까요?
  • 옮기려는 자리에 같은 이름의 파일이 이미 있으면? → 덮어쓰기? 실패?
  • 폴더 그 자체가 목록에 섞이면? → 폴더를 폴더로 옮기나요?

이런 걸 예외 상황(edge case)이라 합니다. 오늘은 이 셋을 전부 처리하는 코드를 씁니다. "평소엔 안 일어나는 일을 먼저 걱정하는 것" — 이것 역시 보안 사고방식입니다. 공격자는 항상 예외 상황을 노리거든요.


3. 따라 하기

3-1. 대상 폴더 지정하기 — 환경 변수

먼저 다운로드 폴더의 주소를 변수에 담습니다:

$src = "$env:USERPROFILE\Downloads"
$src
C:\Users\dlqht\Downloads

(여러분의 사용자 이름이 나옵니다.)

새로운 것 — $env:USERPROFILE: 윈도우가 미리 만들어 둔 변수(이런 걸 환경 변수라 합니다)로, "현재 사용자의 홈 폴더" 주소가 들어 있습니다. 이걸 쓰면 사용자 이름이 누구든 코드를 고칠 필요가 없습니다. 이식성 — 다른 컴퓨터에서도 그대로 돌아가는 코드 — 를 만드는 첫 기술입니다.

3-2. 파일 목록 얻기 — 파일만, 폴더는 빼고

$files = Get-ChildItem $src -File
$files.Count
6

(개수는 여러분 폴더 사정에 따라 다릅니다.)

-File 옵션이 포인트: 파일만 가져오고 폴더는 제외합니다. 이 한 옵션이 2-4절의 예외 상황 ③("폴더를 옮기는 사고")을 원천 차단합니다.

목록이 0개면? 폴더가 비어 있다면 연습용 파일을 몇 개 만들어 넣으세요. 아무 파일이나(메모장으로 a.txt, 그림판으로 b.png 등) 저장하면 됩니다.

3-3. 확장자 확인하기 — 파일의 꼬리표

$files[0].Extension
.pdf

출력 읽는 법: 첫 번째 파일의 확장자가 나옵니다. 파일 객체는 이름 말고도 여러 정보를 품고 있는데, .Extension이 그중 "확장자"입니다. .Name(전체 이름), .Length(크기)도 찍어 보세요.

왜 하는가: 우리는 이 확장자를 폴더 이름으로 쓸 겁니다. .pdf 파일은 PDF 폴더로. 분류 기준이 이 꼬리표 하나인 셈입니다.

3-4. 안전장치 먼저 — -WhatIf 리허설

본격 조립 전에, 오늘의 핵심 습관부터 몸에 넣습니다. 아무 파일이나 하나 골라 가짜로 옮겨 봅시다:

Move-Item $files[0].FullName "$src\TEST_FOLDER\" -WhatIf
WhatIf: 대상 "항목: C:\Users\...\Downloads\report.pdf 대상: C:\Users\...\Downloads\TEST_FOLDER\"에서 "파일 이동" 작업을 수행합니다.

(2026-09-09 실측. 메시지 안에 원본 경로와 목적지 경로가 둘 다 표시됩니다.)

출력 읽는 법: "수행합니다"라고 쓰여 있지만 실제로는 아무 일도 안 일어났습니다. 폴더를 확인해 보세요 — 파일이 그대로 있습니다. 이게 -WhatIf입니다. 진짜 실행인 척하며 결과만 보여 주는 리허설입니다.

왜 하는가: 파일 이동·삭제 스크립트의 실수는 되돌리기 어렵습니다. 특히 반복문 속에서 잘못되면 수십 개 파일이 한꺼번에 엉망이 됩니다. "반복문 + 파일 조작" 조합을 쓸 때는 무조건 -WhatIf 먼저 — 이걸 오늘 반드시 몸에 붙이세요.

3-5. 전체 조립 — 분류기 완성

이제 전체를 한 스크립트로 조립합니다. 메모장에 아래를 쓰고 organize.ps1로 저장하세요 (인코딩: UTF-8 BOM 포함!):

# 파일 자동 분류기 - 대상 폴더를 확장자별로 정리
$src = "$env:USERPROFILE\Downloads"
$files = Get-ChildItem $src -File

foreach ($f in $files) {
    $ext = $f.Extension
    if ($ext -eq "") { $ext = "NOEXT" }        # 예외 1: 확장자 없는 파일
    $folderName = $ext.TrimStart(".").ToUpper() # ".pdf" -> "PDF"
    $dest = "$src\$folderName"

    if (-not (Test-Path $dest)) {
        New-Item -ItemType Directory -Path $dest | Out-Null
    }
    Move-Item $f.FullName $dest -WhatIf          # 첫 실행은 미리보기!
}

새 표현 세 개 해설:

  • $ext.TrimStart(".").ToUpper().pdf에서 앞의 점을 떼고(TrimStart) 대문자로(ToUpper) → PDF. 폴더 이름을 깔끔하게 만드는 가공입니다
  • -not (Test-Path $dest) — "그 폴더가 존재하지 않으면" — Step 8의 if에 Test-Path(존재 검사)가 들어간 형태입니다
  • | Out-Null — New-Item의 출력을 버립니다. 조용히 만들기 위한 장치(파이프의 새 쓰임!)

3-6. 리허설 → 실전

먼저 -WhatIf 상태로 실행합니다:

.\organize.ps1
WhatIf: 대상 "항목: ...\photo.jpg 대상: ...\Downloads\JPG\photo.jpg"에서 "파일 이동" 작업을 수행합니다.
WhatIf: 대상 "항목: ...\README 대상: ...\Downloads\NOEXT\README"에서 "파일 이동" 작업을 수행합니다.
WhatIf: 대상 "항목: ...\report.pdf 대상: ...\Downloads\PDF\report.pdf"에서 "파일 이동" 작업을 수행합니다.
...

(2026-09-09 실측: 확장자 없는 README가 NOEXT로, 한글 파일명 "내 문서.txt"도 TXT로 정상 분류됐습니다.)

확인할 것: ① 각 파일이 올바른 폴더 이름으로 향하는가, ② 이상한 목적지(빈 이름, 이상한 경로)는 없는가. 문제가 보이면 코드를 고치고 다시 리허설합니다.

알아둘 것: 리허설인데 폴더는 실제로 만들어집니다. -WhatIfMove-Item에만 붙어 있고, 폴더를 만드는 New-Item은 그대로 실행되기 때문입니다. 빈 폴더가 몇 개 생기는 것은 정상이며, 파일은 옮겨지지 않았습니다.

납득이 가면, 스크립트에서 -WhatIf지우고 저장한 뒤 다시 실행:

.\organize.ps1

이번엔 조용히 끝납니다(출력 없음 = 정상). 폴더를 열어 보세요 — 확장자별 폴더가 생기고 파일들이 정리되어 있을 겁니다.

예측해 보기: 스크립트를 한 번 더 실행하면 어떻게 될까요? (정답: 최상층에 남은 파일이 없으므로 $files가 0개, 아무 일도 안 일어납니다. 2026-09-09 실측 확인. 이미 정리된 상태에서 다시 실행해도 안전한가 — 이걸 "멱등성"이라 부르는데, 자동화 스크립트의 중요한 품질입니다. 직접 확인해 보세요.)


4. 미션과 연습문제

미션 — 분류기 업그레이드

완성된 분류기에 다음 기능을 하나씩 추가해 보세요:

  1. 보고서 출력: 정리가 끝나면 "총 N개 파일을 M개 폴더로 정리했습니다"를 출력 (힌트: $files.Count와 누적 변수)
  2. 이름 충돌 처리: 목적지에 같은 이름의 파일이 있으면 옮기지 않고 "건너뛰기: 파일명"이라고 출력 (힌트: Test-Path "$dest\$($f.Name)"와 if/else)
  3. 대상 바꾸기: 바탕 화면($env:USERPROFILE\Desktop)을 정리하도록 $src를 바꿔 실행

연습문제

문제 1. -WhatIf는 정확히 무엇을 하나요?

문제 2. $env:USERPROFILE를 쓰면 코드에 어떤 이점이 생기나요?

문제 3. 확장자 없는 파일(README 같은 것)을 그냥 두면 이 스크립트에서 어떤 일이 생기나요? 어떻게 처리했나요?

문제 4. 목적지 폴더에 같은 이름의 파일이 이미 있을 때 Move-Item은 어떻게 동작하나요?


5. 모범 답안과 완료 기준

미션 해설

기능 1 — 보고서 출력: 스크립트 맨 위에 $moved = 0을 두고, Move-Item 다음 줄에 $moved++(1 증가)를 추가합니다. 반복문이 끝난 뒤 이렇게 출력합니다:

"총 $($files.Count)개 파일을 정리 대상으로 처리했습니다 (이동: $moved 개)"

폴더 수는 $folderName을 모아 중복을 제거해 세면 됩니다 (힌트: @() 빈 배열에 담고 Select-Object -Unique).

기능 2 — 이름 충돌 처리: Move-Item 직전에 목적지 안에 같은 이름이 있는지 검사합니다:

if (Test-Path "$dest\$($f.Name)") {
    "건너뛰기: $($f.Name)"
} else {
    Move-Item $f.FullName $dest
}

검증하는 법: 기능 2를 시험하려면 일부러 같은 이름의 파일을 목적지 폴더와 대상 폴더 양쪽에 만들어 두고 실행해 보세요. "건너뛰기" 메시지가 뜨고 원본이 덮이지 않았으면 성공입니다.

기능 3 — 대상 바꾸기: $src = "$env:USERPROFILE\Desktop"으로 한 줄만 바꾸면 됩니다. 환경 변수 덕분에 코드의 나머지는 손댈 필요가 없습니다 — 2-3절에서 말한 이식성의 힘입니다.

심화: 이 스크립트를 작업 스케줄러(Step 30)에 등록하면 "매일 밤 자동 정리"가 됩니다. 그때 -WhatIf는 빼야 합니다 — 자동 실행은 리허설할 사람이 없으니까요. 대신 로그 파일에 결과를 기록하게 만드는 게 답입니다 (Step 4, 5의 기술).

연습문제 해답

문제 1 해답. 명령을 실제로 실행하지 않고, 실행했다면 일어났을 일만 메시지로 보여 주는 미리보기 스위치입니다. 파일 이동·삭제·변경처럼 되돌리기 어려운 명령 앞에서 결과를 먼저 확인하는 안전장치입니다.

문제 2 해답. 사용자 이름을 코드에 직접 박아 넣지 않아도 되므로, 누구의 컴퓨터에서도 고침 없이 그대로 돌아갑니다(이식성). 윈도우가 현재 로그인한 사용자의 홈 폴더 경로를 자동으로 채워 줍니다.

문제 3 해답. 확장자가 없으면 $ext가 빈 문자열 ""이 되어, 이름이 비어 있는 이상한 폴더가 만들어지려 하거나 오류가 납니다. 그래서 if ($ext -eq "") { $ext = "NOEXT" } 한 줄로 확장자 없는 파일을 NOEXT 폴더에 넣도록 처리했습니다. "특이한 입력을 먼저 골라내 별도 취급"하는 것이 예외 처리의 표준 모습입니다.

문제 4 해답. 덮어쓰지 않고 Cannot create a file when that file already exists. 오류를 내며 멈춥니다 (2026-09-09 실측 — 이 메시지는 한글 윈도우에서도 영문으로 나옵니다). "덮어쓰지 않는 게 기본"인 것은 소중한 원본을 지키기 위한 설계입니다. 미션 2번처럼 Test-Path로 충돌을 먼저 검사하고 건너뛰는 로직을 넣으면 깔끔하게 처리할 수 있습니다.

완료 기준 체크리스트

  • [ ] 코드를 짜기 전에 알고리즘을 우리말로 적을 수 있다
  • [ ] $env:USERPROFILE 같은 환경 변수의 용도를 설명할 수 있다
  • [ ] -WhatIf의 동작을 설명하고 실제로 사용할 수 있다
  • [ ] 확장자 없는 파일 같은 예외를 코드로 처리할 수 있다
  • [ ] 분류기 스크립트를 실행해 실제 폴더를 정리했다
  • [ ] 재실행해도 안전한 이유(멱등성)를 설명할 수 있다
  • [ ] 미션 업그레이드 중 1개 이상을 완성했다

6. 흔한 실수와 해결

벽 1. 스크립트를 실행했는데 아무 일도 안 일어난다

증상: 오류도 없고 결과도 없음.
원인 후보: ① -WhatIf를 지우는 걸 잊음(리허설 메시지도 안 뜨면 이 경우는 아님), ② 대상 폴더에 파일이 없음(전부 이미 정리됨 — 3-6절의 멱등성 확인과 같은 상황), ③ $src 경로가 엉뚱함.
해결: $src$files.Count를 스크립트 맨 위에서 출력해 보세요. "어디를 보고 있나, 몇 개를 봤나"가 보이면 원인이 바로 드러납니다.

벽 2. 폴더 이름이 이상하게 만들어진다

증상: .PDF처럼 점이 붙은 폴더가 생기거나, 소문자 폴더가 생김.
원인: TrimStart(".")ToUpper() 가공을 빼먹거나 순서를 바꿔 씀.
해결: "$ext -> $folderName"을 리허설 때 출력해서 가공 결과를 눈으로 확인하세요. 데이터 가공은 "만들고 반드시 확인"입니다.

벽 3. 같은 이름 파일 때문에 오류가 난다

증상: Cannot create a file when that file already exists. 오류 (한글 윈도우에서도 이 메시지는 영문으로 나옵니다 — 2026-09-09 실측).
원인: 목적지 폴더에 동일 이름 파일이 이미 있습니다. 기본적으로 Move-Item은 덮어쓰지 않고 멈춥니다.
해결: 미션 2번의 "충돌 시 건너뛰기" 로직을 추가하세요. (급하면 -Force로 덮어쓰기도 가능하지만, 되돌릴 수 없으니 권하지 않습니다. "덮어쓰지 않는 게 기본"인 이유를 생각해 보세요.)

벽 4. 확장자 없는 파일 때문에 폴더 이름이 비어 버린다

증상: 이름이 없는 폴더가 만들어지려 하거나 오류.
원인: 예외 상황 ① — 확장자가 없는 파일의 $ext는 빈 문자열 ""입니다.
해결: 조립 코드의 if ($ext -eq "") { $ext = "NOEXT" } 줄이 있는지 확인하세요. 이 한 줄이 예외 처리의 표준 모습입니다 — "특이한 입력을 먼저 골라내 별도 취급".

벽 5. 파일을 잘못 옮겨서 되돌리고 싶다

증상: 엉뚱한 폴더로 파일들이 들어감.
원인: 리허설 없이 바로 실행했거나, 폴더 이름 가공 로직에 버그가 있었음.
해결: 당황하지 마세요 — 이동은 복사와 달리 원본이 남지 않지만, 폴더째로 다시 Move-Item 하면 되돌릴 수 있습니다. 그리고 교훈: 다음엔 반드시 -WhatIf 먼저. 이 벽을 한 번 겪으면 리허설 습관이 평생 갑니다.


7. 정리

오늘의 기술 조립도

재료 출처 오늘의 역할
변수·환경 변수 Step 7 경로 저장 ($env:USERPROFILE)
Get-ChildItem Step 2 파일 목록 (-File로 파일만)
foreach + if Step 8 파일마다 분류 판단
New-Item / Move-Item Step 2 확장 폴더 생성, 파일 이동
스크립트 실행 Step 9 .\organize.ps1로 재사용

오늘의 새 도구

도구 기억할 것
-WhatIf 위험한 명령 전 리허설 — 실제 변경 없음
Test-Path 존재 여부 검사 (참/거짓)
$f.Extension 파일 객체의 속성들
TrimStart / ToUpper 문자열 가공 — 가공 후 반드시 눈으로 확인

명령어보다 중요한 감각

첫째, -WhatIf 습관: 침해 대응 현장에서 증거를 다룰 때 "되돌릴 수 없는 조작"은 금기입니다. 미리보기부터 하는 습관이 그 훈련입니다. 둘째, 예외 상황 사고: 공격자는 항상 "설계자가 생각 못 한 입력"을 찾습니다. 확장자 없는 파일을 처리한 오늘의 if 한 줄이 방어적 프로그래밍의 첫 경험입니다.

그리고 자동화의 양면성을 기억하세요. 오늘 여러분은 파일들을 1초 만에 정리했지만, 악성코드도 똑같은 속도로 수천 개 파일을 암호화(랜섬웨어)하거나 훔쳐 갑니다. 자동화는 도구이고, 도구의 방향은 쓰는 사람이 정합니다. 여러분은 오늘 보안 운영 센터(SOC)의 로그·증거 자동 분류 스크립트의 축소판을 만든 겁니다.


전부 체크되면 Step 10 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.