【问题标题】:Out-File output is missing line feeds between lines of dataOut-File 输出在数据行之间缺少换行符
【发布时间】:2019-03-12 16:35:02
【问题描述】:

我正在传递$users 的数组。

PS C:\> $users |英尺 ID DisplayName AdminID first last Password ---- ----------- ------- ----- ---- -------- Axyz Axyz,比尔 NBX_Admin 比尔 Axyz 秘密

代码:

$y = @()
$y = "Create Users process.  Run started at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
foreach ($x in $users) {
    $y += "User $($x.DisplayName) with NNN of $($x.ID)"
}
$y += "Completed at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
$y | Out-File "Log.txt"

$y 现在是一个未格式化的字符串数组。当我在屏幕上输入$y 时,它看起来很棒。

如果我将它定向到Format-Table,它看起来很棒(没有标题)。

当我将它输出到一个文件并在命令提示符 (cmd.exe) 处键入该文件时,它看起来很棒。

但是,当我在记事本中将其拉起时,所有输出都显示在一行上。准确地说,所有数据都在那里,没有丢失任何数据行,但没有 CR/LF,因此当使用 Notepad.exe 查看时,所有数据都显示在文件中的一行上。

【问题讨论】:

  • 请改用Add-ContentSet-Content。可能这只是一个编码问题。 out-file -Encoding ascii 应该也会为您提供您所寻求的结果。
  • 第二行$y = .... 搞砸了您在这里尝试做的事情。当您设置$y = @() 时,您将创建一个空数组。当您设置$y = "something" 时,您将类型从数组更改为字符串。您稍后执行的每个+= 操作仍在创建一个新的string 而不是一个新的array。如果要使用此方法,则需要将 $y 转换为数组 ([array]$y += "something"),或者在第一次声明后的每个赋值中使用 += 永远不要更改类型。
  • "out-file -encoding ascii" 试了下没有解决问题。但是现在我一定遇到了一些奇怪的事情,因为今天,我无法使用我附加的代码示例重现结果。但我发誓我有这个问题,否则我根本不需要发帖。
  • 确实,问题与编码无关;真正的问题已经在 @AdminOfThings 的评论中解释过了,并且很容易被复制:$y = @(); $y = 'first'; $y += 'second'; $y 输出 'firstsecond',即一个单个字符串,因为尽管有first$y 创建为array ($y = @()),下一个赋值$y = 'first' 将其转换为string,然后+= 执行字符串连接,而不是数组构建。

标签: arrays powershell newline


【解决方案1】:

正如AdminOfThings 正确指出的那样:

  • 虽然 $y = @() 将一个空数组分配给 $y,但它不会类型约束该变量,所以您的下一个分配 - @ 987654325@ - 将变量类型更改为字符串

    • 只需使用+= 而不是=,因为随后的分配可以避免问题:$y += "Create Users process ..."
    • 或者,type-constraining 变量创建 - [array] $y = @() - 即,将类型文字放置在分配的变量的左侧(类似于强制转换) - 也可以防止问题.
  • += 的后续使用因此执行简单的字符串连接,而不是所需的逐步构建数组,“行”之间没有添加分隔符。[1]

    • 相比之下,如果您按预期使用了 数组Out-FileSet-Content 都会自动插入适合平台的换行符[ 2] 元素之间,最后加一个,保存时(在 PSv5+ 中,您可以使用 -NoNewline 开关选择退出)。

也就是说,使用+=“扩展”数组效率低下,因为 PowerShell 必须在幕后创建一个包含旧元素和新元素的 new 数组,假设数组是固定大小数据结构。

虽然使用+= 在循环中“扩展”数组的性能损失仅在迭代次数较多时才真正重要,但让 PowerShell 为您创建数组更为简洁、方便和高效隐式,通过使用您的foreach 循环作为表达式

# Initialize the array and assign the first element.
# Due to the type constraint ([array]), the RHS string implicitly becomes
# the array's 1st element.
[array] $y = "Create Users process.  Run started at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"

# Add the strings output by the foreach loop to the array.
# PowerShell implicitly collects foreach output in an array when
# you use it in as an expression.
$y += foreach ($x in $users)
{
    "User $($x.displayname) with NNN of $($x.ID)"
}   

# Add the final string to the array.
$y += "Completed at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"

# Send the array to a file with Out-File, which separates 
# the elements with newlines and adds a trailing one.
# Windows PowerShell:
#   Out-File creates UTF-16LE-encoded files.
#   Set-Content, which can alternatively be used, creates "ANSI"-encoded files.
# PowerShell Core:
#   Both cmdlets create UTF-8-encoded files without BOM.
$y | Out-File "Log.txt"

请注意,您可以类似地使用forifdo / while / switch 语句作为表达式

然而,在所有情况下,从 PowerShell 7.0 开始,这些语句只能作为表达式它们自己;遗憾的是,将它们用作管道的第一段或将它们嵌入到更大的表达式中不起作用 - 请参阅this GitHub issue


[1] 一个简单的问题演示:

# The initialization of $y as @() is overridden by $y = 'first'.
PS> $y = @(); $y = 'first'; $y += 'second'; $y
firstsecond  # !! $y contains a single string built with string concatenation

因此,您的症状描述与您的代码不一致,因为您应该在所有情况下都看到单行输出字符串(直接打印到屏幕/通过Format-Table,发送到文件和type - 来自cmd.exe)。

[2] 适合平台的换行符反映在 [Environment]::NewLine 中,在 Windows 上是 "`r`n" (CRLF),在类 Unix 平台上只是 "`n" (LF)(在 PowerShell Core )。

【讨论】:

    【解决方案2】:

    由于使用+= 会在每次迭代时重新创建数组,我建议将ForEach-Object 的输出分配给带有-Begin-Process-End 部分的变量,也使用更常见的方法格式运算符。:

    $Log = $users | ForEach-Object -Begin {
        "Create Users process.  Run started at [{0:MM/dd/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
    } -Process {
        "User {0} with NNN of {1}" -f $_.DisplayName,$_.ID
    } -End {
        "Completed at [{0:MM/dd/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
    }
    $Log | Set-Content "Log.txt"
    

    【讨论】:

    • 我可能有 10 行数据,所以 += 的开销没有影响。但是,您的答案忽略了为什么这样做,并且需要进行大量代码更改,而不是仅将所需的 CR/LF 添加到组合中。
    • 由于他的 ForEach 的编写方式,每个脚本块都会输出一个字符串。这些字符串在它们自己的行上,其中包括换行符。 ForEach 命令本身输出一个数组对象,其中每个元素都是命令输出的一行。当然,在这种特殊情况下的性能可能无关紧要,但在另一个例子中可能。不过这里的关键点是,您想要的格式会自动为您处理,同时为您提供更高性能的代码块。
    • 好点,@AdminOfThings(我已经从字符串中删除了明确的换行符,因为 Jean-Claude 的代码没有包含它们)。但是,请注意,与数组构建性能无关,在管道中使用ForEach-Object cmdletforeach 循环 慢得多。也就是说,这对于小迭代计数可能无关紧要,绝对执行持续时间仍然很短,而且它肯定会在这里提供一个优雅的解决方案。
    猜你喜欢
    • 2021-02-01
    • 1970-01-01
    • 2022-07-25
    • 1970-01-01
    • 2017-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    相关资源
    最近更新 更多