Todo lo que le interesa sobre la sustitución de variables en cadenasEverything you wanted to know about variable substitution in strings

Hay muchas maneras de usar variables en cadenas.There are many ways to use variables in strings. 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.I'm calling this variable substitution but I'm referring to any time you want to format a string to include values from variables. A menudo me encuentro explicando esto a nuevos generadores de scripts.This is something that I often find myself explaining to new scripters.

Nota

La versión original de este artículo apareció en el blog escrito por @KevinMarquette.The original version of this article appeared on the blog written by @KevinMarquette. El equipo de PowerShell agradece a Kevin que comparta este contenido con nosotros.The PowerShell team thanks Kevin for sharing this content with us. Visite su blog en PowerShellExplained.com.Please check out his blog at PowerShellExplained.com.

ConcatenaciónConcatenation

Se puede hacer referencia a la primera clase de métodos como concatenación.The first class of methods can be referred to as concatenation. Básicamente toma varias cadenas y las une.It's basically taking several strings and joining them together. Existe un largo historial de uso de la concatenación para crear cadenas con formato.There's a long history of using concatenation to build formatted strings.

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

La concatenación funciona correctamente cuando solo hay algunos valores que agregar.Concatenation works out OK when there are only a few values to add. Sin embargo, esto puede complicarse rápidamente.But this can get complicated quickly.

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

Este sencillo ejemplo ya está resultando más difícil de leer.This simple example is already getting harder to read.

Sustitución de variablesVariable substitution

PowerShell tiene otra opción que es más sencilla.PowerShell has another option that is easier. Puede especificar las variables directamente en las cadenas.You can specify your variables directly in the strings.

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

El tipo de comillas que usa en torno a la cadena es fundamental.The type of quotes you use around the string makes a difference. Una cadena entre comillas dobles permite la sustitución, pero una cadena entre comillas simples no la permite.A double quoted string allows the substitution but a single quoted string doesn't. Hay ocasiones en las que desea una u otra, por lo que tiene una opción.There are times you want one or the other so you have an option.

Sustitución de comandosCommand substitution

Todo se complica un poco cuando empieza a intentar obtener los valores de las propiedades en una cadena.Things get a little tricky when you start trying to get the values of properties into a string. Es en este punto donde muchos usuarios nuevos se confunden.This is where many new people get tripped up. Antes permítame que le muestre lo que piensan que debería funcionar (y al pie de la letra casi parece que debería).First let me show you what they think should work (and at face value almost looks like it should).

$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.You would be expecting to get the CreationTime off of the $directory, but instead you get this Time: c:\windows.CreationTime as your value. La razón es que este tipo de sustitución solo ve la variable base.The reason is that this type of substitution only sees the base variable. Considera que el punto forma parte de la cadena, de modo que deja de resolver el valor con mayor profundidad.It considers the period as part of the string so it stops resolving the value any deeper.

Simplemente ocurre que este objeto proporciona una cadena como valor predeterminado al colocarse en una cadena.It just so happens that this object gives a string as a default value when placed into a string. En su lugar, algunos objetos proporcionan el nombre de tipo como System.Collections.Hashtable.Some objects give you the type name instead like System.Collections.Hashtable. Algo de lo que es necesario estar pendiente.Just something to watch for.

PowerShell le permite ejecutar comandos dentro de la cadena con una sintaxis especial.PowerShell allows you to do command execution inside the string with a special syntax. Esto nos permite obtener las propiedades de estos objetos y ejecutar cualquier otro comando para obtener un valor.This allows us to get the properties of these objects and run any other command to get a value.

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

Esto funciona bien en algunas situaciones, pero puede volverse tan disparatado como la concatenación si solo tiene algunas variables.This works great for some situations but it can get just as crazy as concatenation if you have just a few variables.

Ejecución del comandoCommand execution

Puede ejecutar comandos dentro de una cadena.You can run commands inside a string. Aunque tengo esta opción, no me gusta.Even though I have this option, I don't like it. Se desordena rápidamente y es difícil de depurar.It gets cluttered quickly and hard to debug. Ejecuto el comando y lo guardo en una variable o uso una cadena de formato.I either run the command and save to a variable or use a format string.

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

Cadena de formatoFormat string

.NET tiene un modo de dar formato a las cadenas con el que me resulta bastante fácil trabajar..NET has a way to format strings that I find fairly easy to work with. Permítame mostrarle en primer lugar su método estático antes de mostrarle el acceso directo de PowerShell para hacer lo mismo.First let me show you the static method for it before I show you the PowerShell shortcut to do the same thing.

# .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.What is happening here is that the string is parsed for the tokens {0} and {1}, then it uses that number to pick from the values provided. Si desea repetir un valor en algún lugar de la cadena, puede reutilizar el número de ese valor.If you want to repeat one value some place in the string, you can reuse that values number.

Cuanto más complicada se vuelva la cadena, más valor obtendrá de este enfoque.The more complicated the string gets, the more value you get out of this approach.

Dar formato a los valores como matricesFormat values as arrays

Si la línea de formato es demasiado larga, puede colocar primero los valores en una matriz.If your format line gets too long, you can place your values into an array first.

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

No se expande porque le pase toda la matriz, pero la idea es similar.This is not splatting because I'm passing the whole array in, but the idea is similar.

Formato avanzadoAdvanced formatting

He indicado intencionadamente que su procedencia es .NET porque hay muchas opciones de formato que ya están bien documentadas aquí.I intentionally called these out as coming from .NET because there are lots of formatting options already well documented on it. Existen formas integradas de dar formato a diversos tipos de datos.There are built in ways to format various data types.

"{0:yyyyMMdd}" -f (Get-Date)
"Population {0:N0}" -f  8175133

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.I'm not going to go into them but I just wanted to let you know that this is a very powerful formatting engine if you need it.

Unión de cadenasJoining strings

En ocasiones, realmente desea concatenar una lista de valores de forma conjunta.Sometimes you actually do want to concatenate a list of values together. Hay un operador -join que puede hacerlo automáticamente.There's a -join operator that can do that for you. Incluso permite especificar un carácter que se va a combinar entre las cadenas.It even lets you specify a character to join between the strings.

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

$servers  -join ','

Si desea concatenar (-join) algunas cadenas sin un separador, debe especificar una cadena vacía ''.If you want to -join some strings without a separator, you need to specify an empty string ''. No obstante, si eso es todo lo que necesita, existe una opción más rápida.But if that is all you need, there's a faster option.

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

Asimismo, merece la pena señalar que también puede dividir (-split) cadenas.It's also worth pointing out that you can also -split strings too.

Join-PathJoin-Path

Aunque suele pasarse por alto, es un cmdlet excelente para crear una ruta de acceso del archivo.This is often overlooked but a great cmdlet for building a file path.

$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.The great thing about this is it works out the backslashes correctly when it puts the values together. Esto es especialmente importante si toma valores de usuarios o archivos de configuración.This is especially important if you are taking values from users or config files.

Esto también funciona bien con Split-Path y Test-Path.This also goes well with Split-Path and Test-Path. También se tratan en mi publicación sobre cómo leer y guardar en archivos.I also cover these in my post about reading and saving to files.

Las cadenas son matricesStrings are arrays

Antes de continuar, necesito mencionar la adición de cadenas aquí.I do need to mention adding strings here before I go on. Recuerde que una cadena es simplemente una matriz de caracteres.Remember that a string is just an array of characters. Al agregar varias cadenas juntas, se crea una nueva matriz cada vez.When you add multiple strings together, a new array is created each time.

Fíjese en este ejemplo:Look at this example:

$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.It looks very basic but what you don't see is that each time a string is added to $message that a whole new string is created. Se asigna la memoria, se copian los datos y se descartan los anteriores.Memory gets allocated, data gets copied and the old one is discarded. No es nada del otro mundo llevándose a cabo solo algunas veces, pero un bucle como este expondría realmente la incidencia.Not a big deal when it's only done a few times, but a loop like this would really expose the issue.

StringBuilderStringBuilder

StringBuilder también es muy popular para crear cadenas de gran tamaño a partir de muchas cadenas más pequeñas.StringBuilder is also very popular for building large strings from lots of smaller strings. La razón es que solo recopila todas las cadenas que le agrega y solo concatena todas al final cuando recupera el valor.The reason why is because it just collects all the strings you add to it and only concatenates all of them at the end when you retrieve the value.

$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.Again, this is something that I'm reaching out to .NET for. Ya no suelo utilizarlo, pero no está de más saber que está ahí.I don't use it often anymore but it's good to know it's there.

Delineación con llavesDelineation with braces

Se usa para la concatenación de sufijos dentro de la cadena.This is used for suffix concatenation within the string. En ocasiones, su variable no tiene un límite de palabra correcto.Sometimes your variable doesn't have a clean word boundary.

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

Gracias /u/real_parbold por ello.Thank you /u/real_parbold for that one.

Esta es una alternativa a este enfoque:Here is an alternate to this approach:

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.I personally use format string for this, but this is good to know incase you see it in the wild.

Buscar y reemplazar tokensFind and replace 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.While most of these features limit your need to roll your own solution, there are times where you may have large template files where you want to replace strings inside.

Supongamos que ha extraído una plantilla de un archivo con mucho texto.Let us assume you pulled in a template from a file that has a lot of text.

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

Es posible que tenga muchos tokens para su reemplazo.You may have lots of tokens to replace. El truco consiste en usar un token muy distinto que sea fácil de encontrar y reemplazar.The trick is to use a very distinct token that is easy to find and replace. Tiendo a usar un carácter especial en ambos extremos para ayudar a distinguirlo.I tend to use a special character at both ends to help distinguish it.

Recientemente, he descubierto una nueva forma de abordar esto.I recently found a new way to approach this. Decidí dejar esta sección aquí porque se trata de un patrón que se suele usar.I decided to leave this section in here because this is a pattern that is commonly used.

Reemplazar varios tokensReplace multiple tokens

Cuando dispongo de una lista de tokens que es necesario reemplazar, adopto un enfoque más genérico.When I have a list of tokens that I need to replace, I take a more generic approach. Los colocaría en una tabla hash y procesaría una iteración en ellos para realizar el reemplazo.I would place them in a hashtable and iterate over them to do the replace.

$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.Those tokens could be loaded from JSON or CSV if needed.

ExecutionContext ExpandStringExecutionContext ExpandString

Existe una forma inteligente de definir una cadena de sustitución con comillas simples y expandir las variables más adelante.There's a clever way to define a substitution string with single quotes and expand the variables later. Fíjese en este ejemplo:Look at this example:

$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.The call to .InvokeCommand.ExpandString on the current execution context uses the variables in the current scope for substitution. Lo clave aquí es que $message se puede definir muy pronto, antes incluso de que las variables existan.The key thing here is that the $message can be defined very early before the variables even exist.

Si ampliamos eso un poco, podemos realizar esta sustitución una y otra vez con diferentes valores.If we expand on that just a little bit, we can perform this substitution over and over wih different values.

$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.To keep going on this idea; you could be importing a large email template from a text file to do this. Tengo que agradecer a Mark Kraus esta sugerencia.I have to thank Mark Kraus for this suggestion.

Lo que más le convengaWhatever works the best for you

Me encanta el enfoque de cadena de formato.I'm a fan of the format string approach. Definitivamente hago esto con las cadenas más complicadas o si hay varias variables.I definitely do this with the more complicated strings or if there are multiple variables. Puedo usar cualquiera en todo lo que sea muy corto.On anything that is very short, I may use any one of these.

¿Algo más?Anything else?

He abarcado mucho con esto.I covered a lot of ground on this one. Espero que al irse se lleve consigo este aprendizaje.My hope is that you walk away learning something new.