-->

How to Create Text Files from an Array of Values i

2020-03-31 02:58发布

问题:

I have a text file "list.txt" with a list of hundreds of URL's that I want to parse, along with some common-to-all config data, into individual xml files (config files) using each value in "list.txt", like so:

list.txt contains:

line_1
line_2
line_3

The boilerplate config data looks like (using line_1 as an example):

<?xml version="1.0"?>
<Website xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Url>line_1.mydomain.com</Url>
  <Title>line_1</Title>
  <Enabled>true</Enabled>
  <PluginInName>Tumblr</PluginInName>
</Website>

So if "list.txt" contains 100 items, I want 100 config files written with the URL and Title elements individualized.

I have fumbled with several posts on reading the array and on creating text files, but I haven't been able to make any of it work.

What I tried, although it's munged at this point. I'm not sure where I started or how I got to here:

$FileName = "C:\temp\list.txt"
$FileOriginal = Get-Content $FileName

# create an empty array
Foreach ($Line in $FileOriginal)
{    
    $FileModified += $Line

    if ($Line -match $pattern) 
    {
        # Add Lines after the selected pattern 
        $FileModified += 'add text'
        $FileModified += 'add second line text'
    } 
}
Set-Content $fileName $FileModified

This is way beyond my neophyte Powershell skills. Can anyone help out?

回答1:

You're looking for a string-templating approach, where a string template that references a variable is instantiated on demand with the then-current variable value:

# Define the XML file content as a *template* string literal
# with - unexpanded - references to variable ${line}
# (The {...}, though not strictly necessary here, 
# clearly delineates the variable name.)
$template = @'
<code>
<?xml version="1.0"?>
<Website xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Url>${line}.mydomain.com</Url>
  <Title>${line}</Title>
  <Enabled>true</Enabled>
  <PluginInName>Tumblr</PluginInName>
</Website>
'@


# Loop over all input lines.
Get-Content C:\temp\list.txt | ForEach-Object {
   $line = $_ # store the line at hand in $line.
   # Expand the template based on the current $line value.
   $configFileContent = $ExecutionContext.InvokeCommand.ExpandString($template)
   # Save the expanded template to an XML file.
   $configFileContent | Set-Content -Encoding Utf8 "$line.xml"
}

Notes:

  • I've chosen UTF-8 encoding for the output XML files, and to name them "$line.xml", i.e. to name them for each input line and to store them in the current location; adjust as needed.

  • The template expansion (interpolation) is performed via automatic variable $ExecutionContext, whose .InvokeCommand property provides access to the .ExpandString() method, which allows performing string expansion (interpolation) on demand, as if the input string were a double-quoted string - see this answer for a detailed example.


Ansgar Wiechers points out that a simpler alternative in this simple case - given that only a single piece of information is passed during template expansion - is to use PowerShell's string-formatting operator, -f to fill in the template:

# Define the XML file content as a *template* string literal
# with '{0}' as the placeholder for the line variable, to
# be instantiated via -f later.
$template = @'
<code>
<?xml version="1.0"?>
<Website xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Url>{0}.mydomain.com</Url>
  <Title>{0}</Title>
  <Enabled>true</Enabled>
  <PluginInName>Tumblr</PluginInName>
</Website>
'@


# Loop over all input lines.
Get-Content C:\temp\list.txt | ForEach-Object {
   # Expand the template based on the current $line value.
   $configFileContent = $template -f $_
   # Save the expanded template to an XML file.
   $configFileContent | Set-Content -Encoding Utf8 "$line.xml"
}

Optional reading: choosing between -f and $ExecutionContext.InvokeCommand.ExpandString() for template expansion:

Tip of the hat to Ansgar for his help.

Using -f:

  • Advantages:

    • It is made explicit on invocation what values will be filled in.

      • Additionally, it's easier to include formatting instructions in placeholders (e.g., {0:N2} to format numbers with 2 decimal places).

      • Passing the values explicitly allows easy reuse of a template in different scopes.

    • An error will occur by default if you accidentally pass too few or too many values.

  • Disadvantages:

    • -f placeholders are invariably positional and abstract; e.g., {2} simply tells you that you're dealing with the 3rd placeholder, but tells you nothing about its purpose; in larger templates with multiple placeholders, this can become an issue.

    • Even if you pass the right number of values, they may be in the wrong order, which can lead to subtle bugs.

Using $ExecutionContext.InvokeCommand.ExpandString():

  • Advantages:

    • If your variables have descriptive names, your template will be more readable, because the placeholders - the variable names - will indicate their purpose.

    • No need to pass values explicitly on invocation - the expansion simply relies on the variables available in the current scope.

  • Disadvantages:

    • If you use a template in multiple functions (scopes), you need to make sure that the variables referenced in the template are set in each.

    • At least by default, $ExecutionContext.InvokeCommand.ExpandString() will quietly ignore nonexistent variables referenced in the template - which may or may not be desired.

      • However, you can use Set-StrictMode -Version 2 or higher to report an error instead; using Set-StrictMode is good practice in general, though note that its effect isn't lexically scoped and it can disable convenient functionality.
    • Generally, you manually need to keep your template in sync with the code that sets the variables referenced in the template, to ensure that the right values will be filled in (e.g., if the name of a referenced variable changes, the template string must be updated too).