정확한 변경 집합 만들기

ShellBeginner
지금 연습하기

소개

참가자가 늘어난 뒤 Field Notes는 워크숍 방 변경 두 건을 승인했습니다. 여러 세션 제목과 방 이름이 반복되므로 안전한 계획에는 정확한 식별자, 승인 범위, 충분한 목적지 정원이 필요합니다.

작동하는 작은 변경을 미리 본 뒤 재사용 가능한 PowerShell 계획 검증기를 만듭니다. 이 실습은 PowerShell for Beginners의 객체, JSON, 필터링, 매개변수, 오류 처리를 전제로 하며 Bash 스크립트는 필요 없습니다. 도구와 원본 합성 이벤트 데이터는 별도의 독립 작업 영역에 준비되어 있습니다. 소요 시간은 읽기와 일반적인 실수를 포함한 저자의 예상치입니다.

ID 기반의 완전한 계획 만들기

이 단계에서는 준비된 변경 하나를 미리 본 다음, 일정을 바꾸지 않고 전체 업무 계획을 저장하는 재사용 가능한 검증기를 만듭니다.

PowerShell에 들어가 요청 하나 미리 보기

Terminal을 엽니다. 처음에는 Linux 셸이 실행됩니다. 다음 실행 명령은 이 PowerShell 세션에만 UTF-8을 설정하며 전역 셸을 바꾸지 않습니다. env는 해당 환경 값을 프로그램에 전달합니다. -NoLogo는 시작 배너를 숨기고 -NoProfile은 개인 시작 스크립트를 건너뜁니다.

env LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pwsh -NoLogo -NoProfile

PS로 시작하는 프롬프트를 기다립니다. 이후 명령은 모두 PowerShell 안에서 실행합니다. Set-Location은 작업 디렉터리를 바꿉니다.

Set-Location /home/labex/project/room-changes

준비된 preview-one.ps1은 제공된 단일 연습 요청만 처리합니다. PowerShell의 네이티브 -WhatIf를 지원하므로 작업을 수행하는 대신 설명합니다. 작동하는 작은 예제이며, 앞으로 만들 여러 요청용 재사용 검증기는 아닙니다. ./는 현재 디렉터리의 스크립트를 나타냅니다.

./preview-one.ps1 -WhatIf

Move s-a: r-old -> r-equal과 대상 ./rehearsal-only.json이 포함된 네이티브 메시지가 나와야 합니다. ID는 세션과 기존 방, 제안된 방을 식별합니다. 같은 제목의 모든 세션을 옮긴 것이 아닙니다. 실제 원본 JSON을 Get-Content -Raw로 읽습니다. 이 명령은 파일 전체를 하나의 문자열로 반환합니다.

Get-Content ./event.json -Raw

s-a 레코드의 RoomId는 여전히 r-old입니다. Test-Path는 아무것도 만들지 않고 존재 여부를 확인합니다.

Test-Path ./rehearsal-only.json
False

실수로 -WhatIf를 빼면 연습용 별도 파일이 만들어질 수 있지만 입력을 덮어쓰지는 않습니다. Remove-Item -LiteralPath ./rehearsal-only.json은 그 연습 출력만 삭제합니다. 파일이 있을 때만 사용한 뒤 올바른 미리 보기를 반복하세요. -LiteralPath는 와일드카드를 확장하지 않고 경로를 문자 그대로 처리합니다. 실수를 복구하려고 원본 이벤트나 요청을 삭제하지 마세요.

도구를 만들기 전에 관계 읽기

행사장은 대상 시간에 목적지 방이 비어 있음을 이미 확인했습니다. 승인 범위와 수용 인원을 검사하는 것이지 달력 충돌을 해결하는 것은 아닙니다. 요청된 매핑을 읽고 준비된 읽기 전용 도구로 실제 원본 일정을 봅니다.

Get-Content ./requests.json -Raw
./Show-RoomSchedule.ps1 -Path ./event.json

요청은 s-a를 r-equal로, s-c를 r-large로 옮깁니다. s-a와 s-b의 제목은 모두 Shared maps이지만 이 요청에서 승인된 것은 s-a뿐입니다. Studio라는 방도 두 개 있습니다. 표시 이름이 아니라 SessionId와 RoomId로 연결하세요. 첫 요청의 세션은 참가자 24명이고 목적지 정원도 정확히 24명입니다. 같은 값은 허용됩니다.

event.json, requests.json, small-request.json, bad-request.json과 무관한 notes.txt는 바이트, 파일 모드, 소유자, 수정 시간(mtime)을 포함해 보존할 원본입니다. 준비된 도구를 편집하지 마세요. 자신의 스크립트와 이 실습에서 지정한 새 결과 파일은 만들 수 있습니다. 과정 경로와 전체 디렉터리 체인은 실제 디렉터리입니다. 출력은 입력과 다른 새 파일이어야 하고 실제 부모 디렉터리가 이미 있어야 합니다. 기존 파일, 디렉터리, 링크는 대상이 없는 링크까지 포함해 변경 없이 거부해야 합니다. 다른 동시 쓰기 작업이 없는 상태에서 작업합니다.

제공된 JSON은 올바른 UTF-8입니다. Rooms, Sessions, ApprovedSessionIds가 있는 이벤트 객체와 고유하고 정확한 세션 ID 및 목적지 방 ID를 담은 비어 있지 않은 요청 배열로 구성됩니다. ID는 대소문자를 구분하는 문자열입니다. 인원과 정원은 음이 아닌 정숫값입니다. 알 수 없는 메타데이터에는 20단계 이내의 중첩 객체, 순서 있는 배열, 문자열, 불리언, null, 범위가 제한된 유한 JSON 수가 포함될 수 있습니다. 완전히 중복된 객체 키, 잘못된 스키마, 임의의 악성 입력은 이 과정의 범위 밖입니다.

계획을 저장하기 전에 모든 요청 검증하기

이름 있는 매개변수 EventPath, PlanPath, OutputPath 세 개를 갖는 Build-RoomPlan.ps1을 만듭니다. 인접한 도우미 파일 없이 이 인수와 설치된 명령으로 다른 제공 이벤트와 ID에서도 동작해야 합니다. 결과는 요청 순서의 JSON 배열입니다. 각 행에는 정확히 SessionId, OldRoomId, NewRoomId, Attendees, Capacity가 있습니다. 현재 객체 관계를 설명하는 업무 계획이지 완료를 주장하는 표시가 아닙니다. 일정을 변경하지 않습니다. 다음 실습에서 갱신된 이벤트 전체를 씁니다.

스크립트를 저장하기 전에 추가 사항을 읽으세요.

  • [CmdletBinding()]은 매개변수가 있는 고급 스크립트로 만듭니다. [Parameter(Mandatory)]는 인수를 필수로 하고 [string]은 텍스트를 선언합니다. try, catch, throw, exit 1은 선수 오류 처리 실습과 같습니다. $ErrorActionPreference = 'Stop'은 명령 오류가 현재 작업을 끝내게 합니다. catch는 Write-Error -ErrorAction Continue로 설명을 표시한 뒤 명시적 exit 1에 도달합니다.
  • PowerShell의 $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(...)는 현재 PowerShell 디렉터리를 기준으로 출력을 해석합니다. Split-Path -LiteralPath는 부모 경로를 반환합니다. Get-Item -Force는 숨김 객체와 대상 없는 링크도 찾습니다. 여기서 -ErrorAction SilentlyContinue는 새 출력이 아직 없다는 예상 상태를 허용합니다. Test-Path -PathType Container는 부모 디렉터리를 요구합니다.
  • ConvertFrom-Json -AsHashtable은 대소문자만 다른 미지의 키도 포함해 JSON 객체를 사전으로 보존합니다. -DateKind String은 날짜처럼 보이는 문자열을 문자열로 유지하고 원래 오프셋을 보존합니다. 준비된 PowerShell은 이 옵션을 지원합니다. -NoEnumerate는 원소가 하나뿐이어도 요청 배열을 배열로 유지합니다.
  • @(...)는 일치가 없거나 하나여도 배열로 모읍니다. -ceq는 대소문자를 구분해 비교합니다. -ccontains는 정확한 포함 관계를 검사하며 -cnotcontains는 그 반대입니다. -ne는 같지 않음, -gt는 큼, -or는 어느 한 조건이 성립함을 뜻합니다. -isnot [array]는 배열이 아닌 값을 거부합니다.
  • $seen += ...와 $changes += ...는 컬렉션에 추가합니다. 알 수 없는 ID, 미승인 세션, 중복 요청, 너무 많은 참가자는 출력을 쓰기 전에 오류를 발생시킵니다. -gt이므로 정원과 참가자 수가 정확히 같아도 허용됩니다.
  • [pscustomobject]@{...}는 계획의 알려진 필드를 모읍니다. ConvertTo-Json -InputObject $changes -Depth 5는 전체 배열을 직렬화합니다. 충분한 깊이는 중첩 객체가 표시용 문자열로 변환되는 것을 막습니다. JSON 속성 순서와 들여쓰기는 중요하지 않지만 배열 순서는 중요합니다. 불리언은 숫자와 다르며 1과 1.0 같은 동일한 유한 수 표기는 같은 JSON 수를 나타냅니다.
  • PowerShell은 [Type]::Method(...)로 .NET 메서드를 호출할 수 있습니다. [System.IO.File]::WriteAllText는 선택한 텍스트를 선택한 파일 경로에 씁니다. [Environment]::NewLine은 줄바꿈을 추가하고 [System.Text.UTF8Encoding]::new($false)는 바이트 순서 표시가 없는 UTF-8 인코딩을 만듭니다. 이 메서드는 자동으로 WhatIf를 따르지 않습니다. 다음 실습에서 명시적인 권한 승인 분기 안에 넣습니다.

리터럴 here-string으로 전체 스크립트를 저장합니다. @'로 열고 단독 줄의 '@로 닫습니다. 작은따옴표는 저장 중 스크립트의 변수를 그대로 보존합니다. Set-Content -Encoding utf8는 이 스크립트 파일만 바꿉니다.

Set-Content -Path ./Build-RoomPlan.ps1 -Encoding utf8 -Value @'
[CmdletBinding()]
param(
    [Parameter(Mandatory)][string]$EventPath,
    [Parameter(Mandatory)][string]$PlanPath,
    [Parameter(Mandatory)][string]$OutputPath
)
try {
    $ErrorActionPreference = 'Stop'
    $fullOutput = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath)
    if (Get-Item -LiteralPath $fullOutput -Force -ErrorAction SilentlyContinue) {
        throw 'Output already exists; choose a new path.'
    }
    $parent = Split-Path -LiteralPath $fullOutput
    if (-not (Test-Path -LiteralPath $parent -PathType Container)) {
        throw 'Output parent must already exist.'
    }
    $event = Get-Content -LiteralPath $EventPath -Raw | ConvertFrom-Json -AsHashtable -DateKind String
    $requests = Get-Content -LiteralPath $PlanPath -Raw | ConvertFrom-Json -AsHashtable -DateKind String -NoEnumerate
    if ($requests -isnot [array] -or $requests.Count -eq 0) { throw 'Plan must be a nonempty array.' }
    $changes = @()
    $seen = @()
    foreach ($request in $requests) {
        if ($seen -ccontains $request.SessionId) { throw "Duplicate session ID: $($request.SessionId)" }
        $seen += $request.SessionId
        $sessions = @($event.Sessions | Where-Object { $_.SessionId -ceq $request.SessionId })
        $rooms = @($event.Rooms | Where-Object { $_.RoomId -ceq $request.RoomId })
        if ($sessions.Count -ne 1) { throw "Unknown or ambiguous session ID: $($request.SessionId)" }
        if ($rooms.Count -ne 1) { throw "Unknown or ambiguous room ID: $($request.RoomId)" }
        if ($event.ApprovedSessionIds -cnotcontains $request.SessionId) { throw "Unapproved session ID: $($request.SessionId)" }
        if ($sessions[0].Attendees -gt $rooms[0].Capacity) { throw "Capacity exceeded: $($request.SessionId) -> $($request.RoomId)" }
        $changes += [pscustomobject]@{
            SessionId = $request.SessionId
            OldRoomId = $sessions[0].RoomId
            NewRoomId = $request.RoomId
            Attendees = $sessions[0].Attendees
            Capacity = $rooms[0].Capacity
        }
    }
    $json = ConvertTo-Json -InputObject $changes -Depth 5
    [System.IO.File]::WriteAllText($fullOutput, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
    Write-Output "Saved plan: $fullOutput"
}
catch {
    Write-Error ("Plan failed: {0}" -f $_.Exception.Message) -ErrorAction Continue
    exit 1
}
'@

자식 PowerShell 프로세스에서 검증기를 실행합니다. -NonInteractive는 무인 프롬프트를 금지하고 -File은 스크립트를 선택합니다. 자식의 exit 1은 현재 대화형 세션을 닫지 못합니다.

pwsh -NoLogo -NoProfile -NonInteractive -File ./Build-RoomPlan.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./validated-plan.json

$LASTEXITCODE는 마지막 외부 프로그램의 네이티브 종료 상태입니다. 즉시 읽습니다.

$LASTEXITCODE
0

실제 결과물을 다시 엽니다.

Get-Content ./validated-plan.json -Raw

요청한 두 행인 s-a, r-old, r-equal, 24, 24와 s-c, r-old, r-large, 18, 40이 있습니다. 원본 일정은 그대로입니다. 기존 출력은 조용히 대체하는 대신 의도적으로 거부합니다. 자신의 계획을 다시 만들려면 Remove-Item -LiteralPath ./validated-plan.json으로 validated-plan.json만 삭제한 뒤 수정한 검증기를 다시 실행합니다.

뒤에 잘못된 요청이 있으면 전체 거부하기

bad-request.json에는 첫 번째 유효한 변경 뒤에 알 수 없는 목적지 방이 있습니다. 다음 명령은 의도적으로 실패합니다.

pwsh -NoLogo -NoProfile -NonInteractive -File ./Build-RoomPlan.ps1 -EventPath ./event.json -PlanPath ./bad-request.json -OutputPath ./bad-plan-result.json

Plan failed:와 알 수 없는 방에 대한 설명이 나와야 합니다. 네이티브 결과를 즉시 읽습니다.

$LASTEXITCODE
1
Test-Path ./bad-plan-result.json
False

앞의 유효한 행도 부분 계획으로 게시되지 않았습니다. 알 수 없는 세션 ID, 미승인 ID, 중복, 정원 초과도 쓰기 전에 호출을 거부해야 합니다. 비어 있거나 없는 요청 파일은 성공한 빈 작업이 아닙니다.

일반적인 경로 오타는 Get-Location과 Get-ChildItem으로 디렉터리와 이름을 확인한 뒤 Set-Location /home/labex/project/room-changes로 돌아와 해결합니다. 스크립트 오타는 수정한 전체 here-string을 다시 저장해 자신의 스크립트만 바꿉니다. 오류를 없애려고 원본 데이터를 편집하지 마세요. 최종 검사는 실제 업무 계획과 원래 속성을 읽고 다른 ID가 있는 격리 이벤트에서 스크립트의 정확한 바이트를 실행합니다. 초기 답의 고정 목록을 받아들이거나 실제 일정을 바꾸지 않습니다.

요약

정직한 네이티브 미리 보기를 사용하고 정확한 ID로 세션과 방을 연결했으며 승인과 정원을 확인한 뒤 완전히 검증된 업무 계획을 저장했습니다. 정원과 같은 인원은 허용하고 같은 제목의 레코드는 구분했으며 뒤의 잘못된 요청은 부분 계획을 만들지 않았습니다. 원본 이벤트와 노트는 그대로입니다. 다음 실습은 네이티브 확인을 통해 검증된 전체 계획을 적용합니다.