使用PowerShell和 wkhtmltopdf生成PDF
我知道还有其他方法可以做这件事,但我真的在努力让自己在PowerShell方面变得更好,我怎么也搞不明白为什么这行不通。
我有一个HTML文件的目录,我用PowerShell遍历它们,把它们发送给wkhtmltopdf,并尝试把输出放到另一个目录作为PDF。下面是我尝试使用的命令:
get-childitem -r -attributes !directory -path test_input | % {$_.fullname} | foreach-object { & "wkhtmltopdf.exe" -O landscape --enable-local-file-access $_ "test_output\\$_.pdf" }
我怀疑问题出在 \$_.pdf这一部分,因为我已经把这一部分改来改去好几次了,如果我改成下面这样,而不是上面的:
get-childitem -r -attributes !directory -path test_input | % {$_.fullname} | foreach-object { & "wkhtmltopdf.exe" -O landscape --enable-local-file-access $_ "test_output\\$($_.length).pdf" }
它可以工作,但PDF的名称会是原始文件完整路径的长度,也就是像 "90.pdf" 这样的名字。
如果不再使用长度,而改用namestring:
get-childitem -r -attributes !directory -path test_input | % {$_.fullname} | foreach-object { & "wkhtmltopdf.exe" -O landscape --enable-local-file-access $_ "test_output\\$($_.namestring).pdf" }
PDF的名字将只有 ".pdf"——在那种情况下似乎完全忽略了变量。我尝试了很多不同的属性在 $_ 对象上,但只有length这个属性替换了值,所以我完全不知道出了什么问题。
解决方案
tl;dr
在你自己的回答的基础上:
Get-ChildItem -Recurse -File -LiteralPath test_input |
ForEach-Object {
& "wkhtmltopdf.exe" -O landscape --enable-local-file-access $_.FullName "test_output\$($_.BaseName).pdf"
}
注:
-
为了概念上的清晰,上述使用 [
ForEach-Object] 的完整名称来代替%,它是其中一个内置别名。 -
只有你把目标可执行文件放在引号中,语法上才需要通过
&调用它,即 调用运算符;如果不加引号(且没有变量引用),&就是可选的——请参阅 这个回答 了解更多。
换句话说:& "wkhtmltopdf.exe"可以仅用wkhtmltopdf.exe来替换。 -
-attributes !directory已被替换为-File,以请求只输出 文件,这更简洁、概念上也更直接。 -
由于输入路径,
test_input,不包含通配符字符,我假设它应该被逐字(逐字面)处理,在这种情况下,稳妥的做法是使用-LiteralPath而不是-Path,因为恰好包含[/]的字面输入路径可能会被误解;请参阅 [这个答案] 获取更多信息。
详细解释:
我怀疑问题出在
\\$_.pdf这一部分
确实:
-
顺便提一下,使用
\\代替\是不必要的,但也无害:在PowerShell本身中,\没有任何特殊含义,因此不需要转义——请参阅 [这个回答] 的底部部分了解更多信息。
然而,将字面量的\\作为路径分隔符通常也会被安静地容忍。 -
关键是,由于前面的
% { $_.fullname }管道段,$_包含当前处理的输入文件的完整路径,而你的意图是让$_指向基名,即输入文件没有扩展名的文件名。
我已经尝试了
$_对象的很多不同属性,只有length替换了值,我也不清楚到底是怎么回事。
-
% { $_.fullname }(%是内置别名之一,指向内置 [ForEach-Object])输出每个输入文件的完整路径,作为一个字符串,例如一个 [[string]] 的实例,其唯一个(.NET类型原生)属性是.Length -
在PowerShell中,默认情况下,任何尝试引用一个 不存在 的属性,都会安静地返回
$null。 -
在字符串插值的上下文中,即在一个可展开的(插值)字符串(双引号,即
"...")中,$null简单地变成空字符串,即不会向结果字符串添加任何字符,这解释了你试图使用一个(设想的).namestring属性的结果。 - 通过
Set-StrictMode-Version 2或以上,你可以指示PowerShell对尝试访问不存在的属性时报告错误,但这有陷阱——请参阅 this answer。
因此:
- Do not use
% { $_.fullname }, as it prevents you from (easily) deriving the base file name from each input file in subsequent pipeline segments. -
Using the input file objects emitted by
Get-ChildItem- which are of typeSystem.IO.FileInfo- as-is, allows you to conveniently reference the base file name as$_.BaseName -
Inside
"..."- as shown in your own answer - referencing$_.BaseNamerequires enclosure in$(...), for syntactic reasons explained in this answer.