适用于 PowerShell 5.1 及以上版本
代码生成是提高效率的利器——从配置文件模板、项目脚手架、API 客户端,到重复性的 CRUD 代码,都可以用模板引擎自动生成。PowerShell 的字符串插值和 Here-String 天然适合模板渲染,结合哈希表的对象展开能力,可以构建灵活的模板引擎。无论是生成批量配置文件、搭建项目骨架,还是生成重复性代码,模板引擎都能大幅减少手工劳动。
本文将讲解 PowerShell 中的模板引擎技术和代码生成实践。
基础模板渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function Expand-Template { param( [Parameter(Mandatory)] [string]$Template,
[hashtable]$Variables )
$result = $Template foreach ($key in $Variables.Keys) { $result = $result -replace "\{\{$key\}\}", $Variables[$key] } return $result }
$configTemplate = @" server { listen {{port}}; server_name {{hostname}};
location / { proxy_pass http://{{backend_host}}:{{backend_port}}; proxy_set_header Host `$host; }
access_log /var/log/nginx/{{hostname}}.access.log; } "@
$nginxConfig = Expand-Template -Template $configTemplate -Variables @{ port = "8080" hostname = "api.example.com" backend_host = "127.0.0.1" backend_port = "3000" }
Write-Host $nginxConfig
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11
| server { listen 8080; server_name api.example.com;
location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; }
access_log /var/log/nginx/api.example.com.access.log; }
|
高级模板引擎
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| function Invoke-TemplateEngine { param( [Parameter(Mandatory)] [string]$Template,
[Parameter(Mandatory)] [hashtable]$Context )
$Template = [regex]::Replace($Template, '\{\{#if\s+(\w+)\}\}(.*?)\{\{/if\}\}', { param($m) $varName = $m.Groups[1].Value $content = $m.Groups[2].Value if ($Context[$varName]) { $content } else { "" } }, [System.Text.RegularExpressions.RegexOptions]::Singleline)
$Template = [regex]::Replace($Template, '\{\{#each\s+(\w+)\}\}(.*?)\{\{/each\}\}', { param($m) $varName = $m.Groups[1].Value $body = $m.Groups[2].Value $items = $Context[$varName] if ($items -is [array]) { ($items | ForEach-Object { $item = $_ $result = $body if ($item -is [hashtable]) { foreach ($key in $item.Keys) { $result = $result -replace "\{\{this\.$key\}\}", $item[$key] } } $result -replace '\{\{this\}\}', $item }) -join '' } else { "" } }, [System.Text.RegularExpressions.RegexOptions]::Singleline)
foreach ($key in $Context.Keys) { if ($Context[$key] -isnot [array] -and $Context[$key] -isnot [hashtable]) { $Template = $template -replace "\{\{$key\}\}", $Context[$key] } }
return $Template }
$dockerComposeTemplate = @" version: '3.8' services: webapp: image: {{registry}}/{{app_name}}:{{version}} ports: - "{{port}}:80" environment: - ASPNETCORE_ENVIRONMENT={{environment}} {{#if debug}} - ASPNETCORE_DETAILEDERRORS=true {{/if}} depends_on:{{#each services}} - {{this.name}} {{/each}} "@
$composed = Invoke-TemplateEngine -Template $dockerComposeTemplate -Context @{ registry = "registry.example.com" app_name = "myapp" version = "2.5.0" port = "8080" environment = "production" debug = $false services = @( @{ name = "redis" }, @{ name = "postgres" }, @{ name = "rabbitmq" } ) }
Write-Host $composed
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| version: '3.8' services: webapp: image: registry.example.com/myapp:2.5.0 ports: - "8080:80" environment: - ASPNETCORE_ENVIRONMENT=production
depends_on: - redis - postgres - rabbitmq
|
项目脚手架生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| function New-ProjectScaffold { param( [Parameter(Mandatory)] [string]$ProjectName,
[string]$BasePath = ".",
[string]$Author = $env:USERNAME,
[string]$Description = "PowerShell module project" )
$projectDir = Join-Path $BasePath $ProjectName $context = @{ ProjectName = $ProjectName Author = $Author Description = $Description Year = (Get-Date).Year Version = "0.1.0" Guid = [guid]::NewGuid().ToString() }
Write-Host "创建项目:$ProjectName" -ForegroundColor Cyan
$dirs = @( "source", "source\Public", "source\Private", "tests", "docs", "examples" )
foreach ($dir in $dirs) { $fullPath = Join-Path $projectDir $dir New-Item $fullPath -ItemType Directory -Force | Out-Null Write-Host " 创建目录:$dir" -ForegroundColor DarkGray }
$manifest = @" @{ RootModule = '$ProjectName.psm1' ModuleVersion = '$($context.Version)' GUID = '$($context.Guid)' Author = '$($context.Author)' Description = '$($context.Description)' FunctionsToExport = @() CmdletsToExport = @() VariablesToExport = @() AliasesToExport = @() PrivateData = @{ PSData = @{ Tags = @('$ProjectName') ProjectUri = 'https://github.com/user/$ProjectName' } } } "@ $manifest | Set-Content (Join-Path $projectDir "source\$ProjectName.psd1") -Encoding UTF8
$module = @" # 导入公共函数 `$public = Get-ChildItem -Path "`$PSScriptRoot\Public\*.ps1" -ErrorAction SilentlyContinue foreach (`$func in `$public) { . `$func.FullName Export-ModuleMember -Function `$func.BaseName }
# 导入私有函数 `$private = Get-ChildItem -Path "`$PSScriptRoot\Private\*.ps1" -ErrorAction SilentlyContinue foreach (`$func in `$private) { . `$func.FullName }
Write-Host '$ProjectName 模块已加载' -ForegroundColor Green "@ $module | Set-Content (Join-Path $projectDir "source\$ProjectName.psm1") -Encoding UTF8
$sb = [System.Text.StringBuilder]::new() $null = $sb.AppendLine("# $ProjectName") $null = $sb.AppendLine("") $null = $sb.AppendLine($context.Description) $null = $sb.AppendLine("") $null = $sb.AppendLine("## 安装") $null = $sb.AppendLine("") $null = $sb.AppendLine("``````powershell") $null = $sb.AppendLine("Install-Module $ProjectName -Scope CurrentUser") $null = $sb.AppendLine("``````") $null = $sb.AppendLine("") $null = $sb.AppendLine("## 使用") $null = $sb.AppendLine("") $null = $sb.AppendLine("``````powershell") $null = $sb.AppendLine("Import-Module $ProjectName") $null = $sb.AppendLine("``````") $null = $sb.AppendLine("") $null = $sb.AppendLine("## 许可证") $null = $sb.AppendLine("") $null = $sb.AppendLine("MIT License - $($context.Author)") $sb.ToString() | Set-Content (Join-Path $projectDir "README.md") -Encoding UTF8
Write-Host "`n项目脚手架已生成:$projectDir" -ForegroundColor Green Write-Host " 目录:$($dirs.Count) 个" -ForegroundColor Cyan Write-Host " 文件:3 个(psd1, psm1, README.md)" -ForegroundColor Cyan }
New-ProjectScaffold -ProjectName "MyUtils" -Description "实用的 PowerShell 工具函数模块"
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11
| 创建项目:MyUtils 创建目录:source 创建目录:source\Public 创建目录:source\Private 创建目录:tests 创建目录:docs 创建目录:examples
项目脚手架已生成:.\MyUtils 目录:6 个 文件:3 个(psd1, psm1, README.md)
|
批量配置文件生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| function New-ConfigFromTemplate { param( [Parameter(Mandatory)] [string]$TemplatePath,
[Parameter(Mandatory)] [string]$DataPath,
[Parameter(Mandatory)] [string]$OutputDir )
$template = Get-Content $TemplatePath -Raw $data = Import-Csv $DataPath -Encoding UTF8
New-Item $OutputDir -ItemType Directory -Force | Out-Null
foreach ($row in $data) { $context = @{} $row.PSObject.Properties | ForEach-Object { $context[$_.Name] = $_.Value }
$output = Invoke-TemplateEngine -Template $template -Context $context $fileName = "$($row.Name -replace '\s', '-').config" $outputPath = Join-Path $OutputDir $fileName
$output | Set-Content $outputPath -Encoding UTF8 Write-Host "已生成:$fileName" -ForegroundColor Green }
Write-Host "`n批量生成完成:$($data.Count) 个文件" -ForegroundColor Cyan }
$nginxTemplate = @" server { listen {{port}}; server_name {{domain}};
root /var/www/{{name}}; index index.html;
location / { try_files `$uri `$uri/ =404; }
access_log /var/log/nginx/{{name}}.log; } "@
$nginxTemplate | Set-Content "C:\Templates\nginx.txt" -Encoding UTF8
$sites = @" Name,Domain,Port site1,api.example.com,443 site2,web.example.com,80 site3,docs.example.com,8080 "@ | ConvertFrom-Csv
$sites | Export-Csv "C:\Data\sites.csv" -NoTypeInformation -Encoding UTF8 New-ConfigFromTemplate -TemplatePath "C:\Templates\nginx.txt" -DataPath "C:\Data\sites.csv" -OutputDir "C:\Output\nginx"
|
执行结果示例:
1 2 3 4 5
| 已生成:site1.config 已生成:site2.config 已生成:site3.config
批量生成完成:3 个文件
|
注意事项
- 转义字符:模板中包含 PowerShell 特殊字符(
$、反引号)时需要正确转义
- 安全性:不要用
Invoke-Expression 渲染模板,存在代码注入风险。使用字符串替换或正则
- 编码:生成的文件始终指定 UTF-8 编码,避免中文乱码
- 幂等性:脚手架生成器应该可以重复执行,已存在的文件不覆盖或提示用户
- 模板来源:模板可以存储在文件、数据库或远程仓库中,方便版本管理
- 复杂逻辑:如果模板需要复杂条件判断,考虑使用专门的模板引擎模块(如
PSTemplates)