如何在PowerShell中输出不带换行符的文本?

214

我希望我的PowerShell脚本可以打印出这样的东西:

Enabling feature XYZ......Done
Write-Output "Enabling feature XYZ......."
Enable-SPFeature...
Write-Output "Done"

但是 Write-Output 总是在结尾打印一个换行符,所以我的输出不在同一行上。有没有办法解决这个问题?


21个回答

211

Write-Host -NoNewline "正在启用 XYZ 功能......."


111
因为 OP 的示例特别使用了 Write-Output,而它的功能与 Write-Host 大不相同,所以被投下了反对票。读者在复制/粘贴答案之前应该注意到这个巨大的差异。 - NathanAldenSr
7
我同意@NathanAldenSr的观点,如果你想要输出到文件等其他情况下,Write-Host就没什么用了。 - stevethethread
10
“Write-Host” 几乎从来不是正确的答案。它相当于在Unix环境中执行“>/dev/tty”。 - Mark Reed
2
在某些情况下,可以使用“Write-Progress”,请参见以下示例。 - Thomas B in BDX
3
Write-HostWrite-Information 是相同的,可以重定向为输出流 6。因此,您可以将其重定向到文件中,如 6>output.txt。它没有自己的“思想”,只是您无法从 Unixland 中带来的知识,因此很多人会误解它。 - Naikrovek
显示剩余3条评论

66

很不幸的是,正如几个答案和评论中所指出的那样,Write-Host可能存在危险,并且无法传递到其他进程,而Write-Output没有-NoNewline标志。

但这些方法是"*nix"显示进度的方式,而在PowerShell中,显示进度的方法似乎是Write-Progress:它在PowerShell窗口顶部显示一个带有进度信息的进度条,可从PowerShell 3.0及更高版本中使用,请参阅手册获取详细信息。

# Total time to sleep
$start_sleep = 120

# Time to sleep between each notification
$sleep_iteration = 30

Write-Output ( "Sleeping {0} seconds ... " -f ($start_sleep) )
for ($i=1 ; $i -le ([int]$start_sleep/$sleep_iteration) ; $i++) {
    Start-Sleep -Seconds $sleep_iteration
    Write-Progress -CurrentOperation ("Sleep {0}s" -f ($start_sleep)) ( " {0}s ..." -f ($i*$sleep_iteration) )
}
Write-Progress -CurrentOperation ("Sleep {0}s" -f ($start_sleep)) -Completed "Done waiting for X to finish"

而且以OP的例子为例:

# For the file log
Write-Output "Enabling feature XYZ"

# For the operator
Write-Progress -CurrentOperation "EnablingFeatureXYZ" ( "Enabling feature XYZ ... " )

Enable-SPFeature...

# For the operator
Write-Progress -CurrentOperation "EnablingFeatureXYZ" ( "Enabling feature XYZ ... Done" )

# For the log file
Write-Output "Feature XYZ enabled"

4
我认为这是展示状态的最佳解决方案。如果您需要日志或其他内容,您必须接受Write-Output的换行符。 - fadanner
1
同意,而且渐进式显示的点只是为了实时安装“花哨”,在日志文件中没有必要:打印“开始做某事”,然后“完成做某事”。 - Thomas B in BDX

10

尽管在您向用户提供信息输出的情况下可能不适用,但可以创建一个字符串来附加输出。 在输出时,只需输出该字符串即可。

当然,在您的情况下,忽略这个示例是愚蠢的,但在概念上非常有用:

$output = "Enabling feature XYZ......."
Enable-SPFeature...
$output += "Done"
Write-Output $output

显示:

Enabling feature XYZ.......Done

2
这在特定的示例中可能有效,但Write-Output仍会产生额外的换行符。这是一个合理的解决方法,但并不是最终解决方案。 - Slogmeister Extraordinaire
14
既然安装了该功能,整个输出都会出现,所以这不是重点。 - majkinetor
19
我不理解,为什么会有超过1个点赞给这个问题,因为“这不是重点,因为整个输出是在安装该功能后出现的”。 - maxkoryukov
3
当然,忽略这个例子在你的情况下可能有些荒谬,但是它在概念上很有用。 - shufler
1
@shufler 这个概念上完全没有用处。从功能上讲,它与一开始就将整个值分配给 $output 没有任何区别。 - JLRishe
显示剩余2条评论

5

要写入文件,您可以使用字节数组。以下示例创建一个空的ZIP文件,您可以向其中添加文件:

[Byte[]] $zipHeader = 80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
[System.IO.File]::WriteAllBytes("C:\My.zip", $zipHeader)

或者使用:

[Byte[]] $text = [System.Text.Encoding]::UTF8.getBytes("Enabling feature XYZ.......")
[System.IO.File]::WriteAllBytes("C:\My.zip", $text)

1
这是一个很棒的例子! - Peter Mortensen

5

是的,正如其他答案所述,使用Write-Output无法完成此操作。当PowerShell无法胜任时,请转向.NET。这里甚至有几个.NET答案,但它们比必要的复杂。

只需使用:

[Console]::Write("Enabling feature XYZ.......")
Enable-SPFeature...
Write-Output "Done"

虽然不是最纯粹的PowerShell,但它可以工作。


13
因为这个行为与Write-Host一模一样,只是人们没想到会这样,所以被踩了。 - JBert
"[Console]::Write"比"Write-Host"还要糟糕,如果你知道它是fd 6,至少可以拦截它。这将绕过所有标准打开的文件描述符,直接打印到控制台。 - Mark Reed

5

在PowerShell中似乎没有办法做到这一点。所有之前的答案都不正确,因为它们的行为方式与Write-Output不同,更像是Write-Host,而后者无论如何都没有这个问题。

最接近的解决方案似乎是使用带有-NoNewLine参数的Write-Host。通常情况下,您无法将其管道化,但可以通过在Write-Host => Export to a file中描述的方式覆盖此函数,以便轻松地使其接受输出文件的参数。但这仍然远非一个好的解决方案。使用Start-Transcript可以更好地实现这一点,但该cmdlet在本机应用程序方面存在问题。

Write-Output在一般情况下无法满足您的需求。

编辑于2023年:

你甚至可以为了疯狂而做出这样的事情:

function log ($msg, [switch] $NoNewLine) { $script:_msg+=$msg; if ($NoNewLine) { Write-Host -NoNewline $msg } else { Write-Host $msg; $script:_msg >>output.txt; $script:_msg = '' } }

测试:

log '启用功能xyx' -NoNewLine; 1..3 | % { sleep 1; log . -NoNewLine }; log '完成'; cat output.txt

启用功能xyx...完成


3

shufler的回答是正确的。换句话说:不要使用数组形式将值传递给Write-Output,

Write-Output "Parameters are:" $Year $Month $Day

或通过多次调用Write-Output实现等效功能,

Write-Output "Parameters are:"
Write-Output $Year
Write-Output $Month
Write-Output $Day
Write-Output "Done."

首先将您的组件连接到一个字符串变量中:

$msg="Parameters are: $Year $Month $Day"
Write-Output $msg

这将防止由于多次调用Write-Output(或ARRAY FORM)而导致的中间CRLFs,但当然不会抑制Write-Output命令的最终CRLF。为此,您将需要编写自己的命令,使用此处列出的其他复杂解决方法之一,或等待Microsoft支持Write-Output的-NoNewline选项。
您希望在控制台上提供文本进度条(即“....”),而不是写入日志文件,也可以通过使用Write-Host来满足。您可以通过将msg文本收集到变量中以写入日志文件并使用Write-Host来提供控制台进度来实现两者。这些功能可以组合成一个命令,以实现最大的代码重用。

我更喜欢这个答案而不是其他的。如果你正在调用对象的属性,你不能将它们括在引号中,所以我使用了:Write-Output ($msg = $MyObject.property + "我想要包含的一些文本" + $Object.property)。 - Lewis
2
@Lewis,你可以在字符串中包含对象属性!使用$()表达式来包围任何变量,例如"$($MyObject.Property) 我想要包含的一些文本 $($Object.property)"。 - shufler
这在特定的示例中可能有效,但Write-Output仍会产生额外的换行符,只是因为它是最后写入的东西,所以你看不见它。这是一个合理的解决方法,但不是一个解决方案。可能有某些内容正在使用无法处理尾随换行符的结果输出。 - Slogmeister Extraordinaire
1
不正确。这个解决方案不能用单个命令完成。 - majkinetor
这并没有解决问题。日志文件输出应该显示即将尝试的操作,以便可以看到和跟踪失败。连接字符串并不能实现这一点。 - durette

3

我作弊了,但我相信这是唯一符合所有要求的答案。也就是说,避免了尾部的CRLF,提供了另一个操作在此期间完成的位置,并根据需要正确地重定向到标准输出(stdout)。

$c_sharp_source = @"
using System;
namespace StackOverflow
{
   public class ConsoleOut
   {
      public static void Main(string[] args)
      {
         Console.Write(args[0]);
      }
   }
}
"@
$compiler_parameters = New-Object System.CodeDom.Compiler.CompilerParameters
$compiler_parameters.GenerateExecutable = $true
$compiler_parameters.OutputAssembly = "consoleout.exe"
Add-Type -TypeDefinition $c_sharp_source -Language CSharp -CompilerParameters $compiler_parameters

.\consoleout.exe "Enabling feature XYZ......."
Write-Output 'Done.'

1
pwsh (powershell core) 中,Add-Type 没有 -CompilerParameters 选项,但你可以使用其他语言来实现相同的功能:python3 -c "import sys; print(sys.argv[1], end='')" "Enabling feature XYZ......." - Carl Walsh

3

我遇到的问题是,当使用PowerShell v2时,Write-Output实际上会在输出时进行换行,至少是对于stdout而言。我试图将XML文本写入stdout,但没有成功,因为它会在第80个字符处自动换行。

解决方法是使用:

[Console]::Out.Write($myVeryLongXMLTextBlobLine)

在PowerShell v3中,这不是一个问题。Write-Output 在那里似乎正常工作。

根据PowerShell脚本的调用方式,您可能需要使用

[Console]::BufferWidth =< length of string, e.g. 10000)

在将内容写入标准输出之前,请先注意一些事项。

2
表现类似于Write-Host,但更糟糕。例如,你无法将其导入到文件中。 - majkinetor

2
FrinkTheBrave的回答可以简化为以下内容:
[System.IO.File]::WriteAllText("c:\temp\myFile.txt", $myContent)

1
这根本没有回答问题。 - NathanAldenSr
2
但这正是我所寻找的,也是我从问题标题中期望的。 - Patrick Roocks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接