Todo lo que le interesa sobre la sustitución de variables en cadenas

Hay muchas maneras de usar variables en cadenas. Estoy llamando a esta sustitución de variables, pero hago referencia a cada una de las veces que desea dar formato a una cadena para incluir valores de variables. A menudo me encuentro explicando esto a nuevos generadores de scripts.

Nota

La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin que comparta este contenido con nosotros. Visite su blog en PowerShellExplained.com.

Concatenación

Se puede hacer referencia a la primera clase de métodos como concatenación. Básicamente toma varias cadenas y las une. Existe un largo historial de uso de la concatenación para crear cadenas con formato.

$name = 'Kevin Marquette'
$message = 'Hello, ' + $name

La concatenación funciona correctamente cuando solo hay algunos valores que agregar. Sin embargo, esto puede complicarse rápidamente.

$first = 'Kevin'
$last = 'Marquette'
$message = 'Hello, ' + $first + ' ' + $last + '.'

Este sencillo ejemplo ya está resultando más difícil de leer.

Sustitución de variables

PowerShell tiene otra opción que es más sencilla. Puede especificar las variables directamente en las cadenas.

$message = "Hello, $first $last."

El tipo de comillas que usa en torno a la cadena es fundamental. Una cadena entre comillas dobles permite la sustitución, pero una cadena entre comillas simples no la permite. Hay ocasiones en las que desea una u otra, por lo que tiene una opción.

Sustitución de comandos

Todo se complica un poco cuando empieza a intentar obtener los valores de las propiedades en una cadena. Es en este punto donde muchos usuarios nuevos se confunden. Antes permítame que le muestre lo que piensan que debería funcionar (y al pie de la letra casi parece que debería).

$directory = Get-Item 'c:\windows'
$message = "Time: $directory.CreationTime"

Pese a esperar obtener CreationTime fuera de $directory, lo que obtiene es Time: c:\windows.CreationTime como su valor. La razón es que este tipo de sustitución solo ve la variable base. Considera que el punto forma parte de la cadena, de modo que deja de resolver el valor con mayor profundidad.

Simplemente ocurre que este objeto proporciona una cadena como valor predeterminado al colocarse en una cadena. En su lugar, algunos objetos proporcionan el nombre de tipo como System.Collections.Hashtable. Algo de lo que es necesario estar pendiente.

PowerShell le permite ejecutar comandos dentro de la cadena con una sintaxis especial. Esto nos permite obtener las propiedades de estos objetos y ejecutar cualquier otro comando para obtener un valor.

$message = "Time: $($directory.CreationTime)"

Esto funciona bien en algunas situaciones, pero puede volverse tan disparatado como la concatenación si solo tiene algunas variables.

Ejecución del comando

Puede ejecutar comandos dentro de una cadena. Aunque tengo esta opción, no me gusta. Se desordena rápidamente y es difícil de depurar. Ejecuto el comando y lo guardo en una variable o uso una cadena de formato.

$message = "Date: $(Get-Date)"

Cadena de formato

.NET tiene un modo de dar formato a las cadenas con el que me resulta bastante fácil trabajar. Permítame mostrarle en primer lugar su método estático antes de mostrarle el acceso directo de PowerShell para hacer lo mismo.

# .NET string format string
[string]::Format('Hello, {0} {1}.',$first,$last)

# PowerShell format string
'Hello, {0} {1}.' -f $first, $last

Lo que ocurre aquí es que se analiza la cadena para los tokens {0} y {1} y, a continuación, usa ese número para elegir entre los valores proporcionados. Si desea repetir un valor en algún lugar de la cadena, puede reutilizar el número de ese valor.

Cuanto más complicada se vuelva la cadena, más valor obtendrá de este enfoque.

Dar formato a los valores como matrices

Si la línea de formato es demasiado larga, puede colocar primero los valores en una matriz.

$values = @(
    "Kevin"
    "Marquette"
)
'Hello, {0} {1}.' -f $values

No se expande porque le pase toda la matriz, pero la idea es similar.

Formato avanzado

He indicado intencionadamente que su procedencia es .NET porque hay muchas opciones de formato que ya están bien documentadas aquí. Hay formas integradas de dar formato a diversos tipos de datos.

"{0:yyyyMMdd}" -f (Get-Date)
"Population {0:N0}" -f  8175133
20211110
Population 8,175,133

No voy a entrar en ellas, solo quería informarle de que, en caso de necesitarlo, se trata de un motor de formato muy eficaz.

Unión de cadenas

En ocasiones, realmente desea concatenar una lista de valores de forma conjunta. Hay un operador -join que puede hacerlo automáticamente. Incluso permite especificar un carácter que se va a combinar entre las cadenas.

$servers = @(
    'server1'
    'server2'
    'server3'
)

$servers  -join ','

Si desea concatenar (-join) algunas cadenas sin un separador, debe especificar una cadena vacía ''. No obstante, si eso es todo lo que necesita, existe una opción más rápida.

[string]::Concat('server1','server2','server3')
[string]::Concat($servers)

Asimismo, merece la pena señalar que también puede dividir (-split) cadenas.

Join-Path

Aunque suele pasarse por alto, es un cmdlet excelente para crear una ruta de acceso del archivo.

$folder = 'Temp'
Join-Path -Path 'c:\windows' -ChildPath $folder

Lo mejor que tiene es que resuelve correctamente las barras diagonales inversas al poner los valores juntos. Esto es especialmente importante si toma valores de usuarios o archivos de configuración.

Esto también funciona bien con Split-Path y Test-Path. También se tratan en mi publicación sobre cómo leer y guardar en archivos.

Las cadenas son matrices

Antes de continuar, necesito mencionar la adición de cadenas aquí. Recuerde que una cadena es simplemente una matriz de caracteres. Al agregar varias cadenas juntas, se crea una nueva matriz cada vez.

Fíjese en este ejemplo:

$message = "Numbers: "
foreach($number in 1..10000)
{
    $message += " $number"
}

Parece muy básico, pero lo que no ve es que cada vez que se agrega una cadena a $message, se crea una cadena nueva completa. Se asigna la memoria, se copian los datos y se descartan los anteriores. No es nada del otro mundo llevándose a cabo solo algunas veces, pero un bucle como este expondría realmente la incidencia.

StringBuilder

StringBuilder también es muy popular para crear cadenas de gran tamaño a partir de muchas cadenas más pequeñas. La razón es que solo recopila todas las cadenas que le agrega y solo concatena todas al final cuando recupera el valor.

$stringBuilder = New-Object -TypeName "System.Text.StringBuilder"

[void]$stringBuilder.Append("Numbers: ")
foreach($number in 1..10000)
{
    [void]$stringBuilder.Append(" $number")
}
$message = $stringBuilder.ToString()

Una vez más, se trata de algo para lo cual me dirijo a .NET. Ya no suelo utilizarlo, pero no está de más saber que está ahí.

Delineación con llaves

Se usa para la concatenación de sufijos dentro de la cadena. En ocasiones, su variable no tiene un límite de palabra correcto.

$test = "Bet"
$tester = "Better"
Write-Host "$test $tester ${test}ter"

Gracias /u/real_parbold por ello.

Esta es una alternativa a este enfoque:

Write-Host "$test $tester $($test)ter"
Write-Host "{0} {1} {0}ter" -f $test, $tester

Personalmente uso la cadena de formato para esto, pero es bueno saberlo en caso de que lo vea en su entorno natural.

Buscar y reemplazar tokens

Aunque la mayoría de estas características limitan su necesidad de implementar su propia solución, hay ocasiones en las que puede tener archivos de plantilla de gran tamaño donde desee reemplazar las cadenas de su interior.

Supongamos que ha extraído una plantilla de un archivo con mucho texto.

$letter = Get-Content -Path TemplateLetter.txt -RAW
$letter = $letter -replace '#FULL_NAME#', 'Kevin Marquette'

Es posible que tenga muchos tokens para su reemplazo. El truco consiste en usar un token muy distinto que sea fácil de encontrar y reemplazar. Tiendo a usar un carácter especial en ambos extremos para ayudar a distinguirlo.

Recientemente, he descubierto una nueva forma de abordar esto. Decidí dejar esta sección aquí porque se trata de un patrón que se suele usar.

Reemplazar varios tokens

Cuando dispongo de una lista de tokens que es necesario reemplazar, adopto un enfoque más genérico. Los colocaría en una tabla hash y procesaría una iteración en ellos para realizar el reemplazo.

$tokenList = @{
    Full_Name = 'Kevin Marquette'
    Location = 'Orange County'
    State = 'CA'
}

$letter = Get-Content -Path TemplateLetter.txt -RAW
foreach( $token in $tokenList.GetEnumerator() )
{
    $pattern = '#{0}#' -f $token.key
    $letter = $letter -replace $pattern, $token.Value
}

Esos tokens podrían cargarse desde JSON o CSV si es necesario.

ExecutionContext ExpandString

Existe una forma inteligente de definir una cadena de sustitución con comillas simples y expandir las variables más adelante. Fíjese en este ejemplo:

$message = 'Hello, $Name!'
$name = 'Kevin Marquette'
$string = $ExecutionContext.InvokeCommand.ExpandString($message)

La llamada a .InvokeCommand.ExpandString en el contexto de ejecución actual utiliza las variables en el ámbito actual para la sustitución. Lo clave aquí es que $message se puede definir muy pronto, antes incluso de que las variables existan.

Si ampliamos esto un poco, podemos realizar esta sustitución una y otra vez con diferentes valores.

$message = 'Hello, $Name!'
$nameList = 'Mark Kraus','Kevin Marquette','Lee Dailey'
foreach($name in $nameList){
    $ExecutionContext.InvokeCommand.ExpandString($message)
}

Para seguir trabajando en esta idea, podría importar una plantilla de correo electrónico de gran tamaño desde un archivo de texto para hacerlo. Tengo que agradecer a Mark Kraus esta sugerencia.

Lo que más le convenga

Me encanta el enfoque de cadena de formato. Definitivamente hago esto con las cadenas más complicadas o si hay varias variables. Puedo usar cualquiera en todo lo que sea muy corto.

¿Algo más?

He abarcado mucho con esto. Espero que al irse se lleve consigo este aprendizaje.

Si deseas obtener más información sobre los métodos y características que hacen posible la interpolación de cadenas, consulta la siguiente lista para leer la documentación de referencia.