Construir el conjunto exacto de cambios

ShellBeginner
Practicar Ahora

Introducción

Field Notes ha aprobado dos cambios de sala de talleres tras aumentar la asistencia. Varios títulos de sesión y nombres de sala se repiten, por lo que un plan seguro necesita identidades exactas, un ámbito aprobado y capacidad suficiente en el destino.

Previsualizarás un pequeño cambio funcional y crearás después un validador de planes reutilizable con PowerShell. Este laboratorio presupone los objetos, JSON, filtros, parámetros y gestión de errores de PowerShell for Beginners; no requiere scripts Bash. Las herramientas y los datos sintéticos originales del evento están preparados en un espacio de trabajo independiente. La duración es una estimación del autor que incluye lectura y errores corrientes.

Construir un plan completo basado en ID

En este paso, previsualizarás un cambio preparado y crearás un validador reutilizable que guarda el plan de trabajo completo sin modificar la programación.

Entrar en PowerShell y previsualizar una solicitud

Abre Terminal. Al principio ejecuta un shell de Linux. El siguiente comando establece UTF-8 solo para esta sesión de PowerShell; no cambia el shell global. env proporciona esos valores de entorno al programa. -NoLogo oculta el mensaje inicial y -NoProfile omite los scripts personales de inicio:

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

Espera al indicador que comienza con PS. Todos los comandos restantes se ejecutan dentro de PowerShell. Set-Location cambia su directorio de trabajo:

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

El script preparado preview-one.ps1 solo procesa la única solicitud de ensayo suministrada. Admite el parámetro nativo -WhatIf de PowerShell: describe una acción en lugar de realizarla. Es un pequeño ejemplo funcional, todavía no tu validador reutilizable de varias solicitudes. ./ identifica un script del directorio actual:

./preview-one.ps1 -WhatIf

Espera un mensaje nativo que contenga Move s-a: r-old -> r-equal y el destino ./rehearsal-only.json. Los ID identifican la sesión, su sala actual y la propuesta. No ha trasladado todas las sesiones con ese título. Lee el JSON de origen real con Get-Content -Raw, que devuelve todo el archivo como una cadena:

Get-Content ./event.json -Raw

El registro s-a sigue teniendo RoomId igual a r-old. Test-Path comprueba la existencia sin crear nada:

Test-Path ./rehearsal-only.json
False

Si omitiste -WhatIf por accidente, el ensayo puede crear ese archivo separado, pero no sobrescribe la entrada. Remove-Item -LiteralPath ./rehearsal-only.json elimina únicamente esa salida de ensayo; úsalo solo si existe y repite después la previsualización correctamente. -LiteralPath interpreta la ruta literalmente, sin expandir comodines. No borres el evento ni las solicitudes originales para recuperarte de un error.

Leer la relación antes de crear la herramienta

El recinto ya comprobó que las salas de destino están libres a esas horas. Vas a comprobar el ámbito aprobado y la capacidad, no a resolver conflictos de calendario. Lee las asignaciones solicitadas y utiliza el lector preparado de solo lectura para ver la programación original real:

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

Las solicitudes trasladan s-a a r-equal y s-c a r-large. s-a y s-b comparten el título Shared maps; solo s-a está aprobada para esta solicitud. También hay dos salas llamadas Studio. Relaciona mediante SessionId y RoomId, nunca mediante los nombres mostrados. La primera sesión solicitada tiene 24 asistentes y su destino admite exactamente 24: la igualdad está permitida.

Debes conservar los originales event.json, requests.json, small-request.json, bad-request.json y el archivo ajeno notes.txt, incluidos sus bytes, modo de archivo, propietario y hora de modificación (mtime). No edites las herramientas preparadas. Puedes crear tus scripts y los nuevos resultados nombrados en este laboratorio. Las rutas del curso y toda su cadena de directorios son directorios reales. Una salida debe ser nueva, distinta de las entradas y tener un directorio padre real ya existente; hay que rechazar sin tocar cualquier archivo, directorio o enlace existente, incluso un enlace roto. Trabaja sin otros escritores concurrentes.

El JSON proporcionado es UTF-8 bien formado: un objeto de evento con Rooms, Sessions y ApprovedSessionIds, y un array de solicitudes no vacío con ID exactos únicos de sesiones y los ID de sus salas de destino. Los ID son cadenas sensibles a mayúsculas y minúsculas. Los recuentos y las capacidades son valores enteros no negativos. Los metadatos desconocidos pueden contener objetos anidados, arrays ordenados, cadenas, booleanos, null y números JSON finitos acotados, con hasta 20 niveles de anidamiento. Las claves de objeto exactamente duplicadas, los esquemas malformados y las entradas hostiles arbitrarias quedan fuera del curso.

Validar cada solicitud antes de guardar un plan

Crearás Build-RoomPlan.ps1 con tres parámetros con nombre: EventPath, PlanPath y OutputPath. Debe funcionar con otros eventos e ID suministrados, utilizando esos argumentos y los comandos instalados, sin archivos auxiliares vecinos. Su resultado es un array JSON en el orden de las solicitudes. Cada fila contiene exactamente SessionId, OldRoomId, NewRoomId, Attendees y Capacity. Es un plan de trabajo que describe relaciones actuales entre objetos, no una marca que afirma que la tarea terminó. No cambia la programación. El siguiente laboratorio escribirá un evento actualizado completo.

Lee estas incorporaciones antes de guardar el script:

  • [CmdletBinding()] lo convierte en un script avanzado parametrizado; [Parameter(Mandatory)] exige un argumento y [string] declara texto. try, catch, throw y exit 1 siguen el laboratorio previo de gestión de errores. $ErrorActionPreference = 'Stop' hace que los errores de comandos terminen la operación actual. El bloque catch utiliza Write-Error -ErrorAction Continue para mostrar la explicación y alcanzar después el exit 1 explícito.
  • $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(...) de PowerShell resuelve la salida respecto al directorio actual de PowerShell. Split-Path -LiteralPath devuelve su padre. Get-Item -Force también descubre objetos ocultos y enlaces rotos. Aquí -ErrorAction SilentlyContinue permite la ausencia esperada de una salida nueva. Test-Path -PathType Container exige su directorio padre.
  • ConvertFrom-Json -AsHashtable conserva los objetos JSON como diccionarios, incluidas las claves desconocidas que solo difieren en mayúsculas. -DateKind String mantiene como cadenas los textos con aspecto de fecha y conserva sus desplazamientos originales; la versión preparada de PowerShell admite esa opción. -NoEnumerate mantiene unido el array de solicitudes aunque solo tenga un miembro.
  • @(...) recoge las coincidencias en un array, incluso si hay cero o una. -ceq compara distinguiendo mayúsculas. -ccontains comprueba la pertenencia exacta; -cnotcontains es su inverso. -ne significa distinto, -gt mayor que y -or que se cumple cualquiera de las condiciones. -isnot [array] rechaza un valor que no sea un array.
  • $seen += ... y $changes += ... añaden elementos a colecciones. Un ID desconocido, una sesión no aprobada, una solicitud duplicada o demasiados asistentes provocan un error antes de escribir salida alguna. -gt permite que la capacidad sea exactamente igual a la asistencia.
  • [pscustomobject]@{...} reúne los campos conocidos del plan. ConvertTo-Json -InputObject $changes -Depth 5 serializa todo el array; una profundidad suficiente evita convertir los objetos anidados en cadenas de presentación. El orden de las propiedades JSON y la sangría no importan; el orden del array sí. Los booleanos son distintos de los números, mientras que representaciones numéricas finitas iguales, como 1 y 1.0, representan el mismo número JSON.
  • PowerShell puede llamar a métodos .NET con [Type]::Method(...). [System.IO.File]::WriteAllText escribe el texto seleccionado en la ruta de archivo seleccionada. [Environment]::NewLine añade un salto de línea y [System.Text.UTF8Encoding]::new($false) crea una codificación UTF-8 sin marca de orden de bytes. Este método no respeta WhatIf automáticamente; en el siguiente laboratorio lo colocarás tras una rama de autorización explícita.

Un here-string literal guarda el script completo: @' lo abre y '@ lo cierra en una línea propia. Las comillas simples conservan las variables del script al guardarlo. Set-Content -Encoding utf8 reemplaza únicamente este archivo de script:

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
}
'@

Ejecuta el validador en un proceso hijo de PowerShell. -NonInteractive prohíbe las preguntas sin supervisión y -File elige el script. El exit 1 del hijo no puede cerrar tu sesión interactiva:

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

$LASTEXITCODE es el estado de salida nativo del último programa externo. Léelo inmediatamente:

$LASTEXITCODE
0

Vuelve a abrir el artefacto real:

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

Contiene las dos filas solicitadas: s-a, r-old, r-equal, 24, 24; y s-c, r-old, r-large, 18, 40. La programación original no cambia. Una salida existente se rechaza deliberadamente en vez de sustituirse en silencio. Si necesitas reconstruir tu plan, elimina únicamente validated-plan.json con Remove-Item -LiteralPath ./validated-plan.json y vuelve a ejecutar el validador corregido.

Rechazar por completo una solicitud posterior no válida

bad-request.json contiene un primer cambio válido seguido de una sala de destino desconocida. El siguiente comando falla intencionadamente:

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

Espera Plan failed: y una explicación sobre la sala desconocida. Lee el resultado nativo inmediatamente:

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

La fila válida anterior no se publicó como plan parcial. Los ID de sesión desconocidos, los ID no aprobados, los duplicados y la capacidad excedida también deben rechazar la invocación antes de escribir. Un archivo de solicitudes vacío o ausente no es un trabajo vacío satisfactorio.

Ante un error corriente de escritura en una ruta, utiliza Get-Location y Get-ChildItem para comprobar el directorio y los nombres; vuelve con Set-Location /home/labex/project/room-changes. Ante un error en el script, guarda de nuevo el here-string completo corregido, sustituyendo solo tu script. No edites los datos de origen para hacer desaparecer un error. La comprobación final lee el plan de trabajo real y las propiedades originales, y ejecuta los bytes exactos de tu script sobre eventos aislados con otros ID. No acepta una lista fija de respuestas iniciales ni cambia tu programación real.

Resumen

Utilizaste una previsualización nativa honesta, relacionaste sesiones con salas mediante ID exactos, comprobaste la aprobación y la capacidad y guardaste un plan de trabajo validado completo. Se aceptó la igualdad de capacidad, los registros con el mismo título se mantuvieron diferenciados y una solicitud posterior no válida no produjo un plan parcial. El evento original y las notas permanecieron intactos; el siguiente laboratorio aplica todo un plan validado mediante confirmación nativa.