Создайте точный набор изменений

ShellBeginner
Практиковаться сейчас

Введение

После увеличения числа участников Field Notes одобрила две смены комнат для мастер-классов. Несколько названий сессий и комнат повторяются, поэтому безопасному плану нужны точные идентификаторы, разрешённый объём изменений и достаточная вместимость цели.

Вы просмотрите небольшое работающее изменение, а затем создадите многократно используемый валидатор плана на PowerShell. Предполагается знание объектов, JSON, фильтрации, параметров и обработки ошибок из PowerShell for Beginners; сценарии Bash не требуются. Инструменты и исходные синтетические данные мероприятия подготовлены в отдельной независимой рабочей области. Длительность оценена автором с учётом чтения и обычных ошибок.

Создайте полный план на основе ID

На этом шаге вы просмотрите одну подготовленную замену комнаты, а затем создадите многократно используемый валидатор, сохраняющий полный рабочий план без изменения расписания.

Войдите в PowerShell и просмотрите один запрос

Откройте Terminal. Изначально в нём работает оболочка Linux. Следующая команда задаёт UTF-8 только для этого сеанса PowerShell и не меняет глобальную оболочку. 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 — строки с учётом регистра. Числа участников и вместимости — неотрицательные целые значения. Неизвестные метаданные могут содержать вложенные объекты, упорядоченные массивы, строки, логические значения, null и ограниченные конечные числа JSON с вложенностью до 20 уровней. Полностью повторяющиеся ключи объекта, некорректные схемы и произвольные вредоносные входные данные не входят в курс.

Проверьте каждый запрос до сохранения плана

Вы создадите Build-RoomPlan.ps1 с тремя именованными параметрами: EventPath, PlanPath и OutputPath. Он должен работать с другими предоставленными мероприятиями и 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.
  • $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(...) в PowerShell разрешает выходной путь относительно текущего каталога 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 может вызывать методы .NET через [Type]::Method(...). [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. Исходное расписание не изменилось. Существующий результат намеренно отклоняется, а не заменяется без уведомления. Чтобы пересоздать свой план, удалите только validated-plan.json командой Remove-Item -LiteralPath ./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, проверили одобрение и вместимость и сохранили полный проверенный рабочий план. Равенство вместимости было принято, одноимённые записи остались разными, а поздний неверный запрос не создал частичного плана. Исходное мероприятие и заметки не изменились; в следующей работе вы примените полный проверенный план через встроенное подтверждение.