Markus H.
Markus H.

Reputation: 23

Why my Out-File does not write output into the file

$ready = Read-Host "How many you want?: "
$i = 0
do{
    (-join(1..12 | ForEach {((65..90)+(97..122)+(".") | % {[char]$_})+(0..9)+(".") | Get-Random}))
    $i++
} until ($i -match $ready) Out-File C:/numbers.csv -Append

If I give a value of 10 to the script - it will generate 10 random numbers and shows it on pshell. It even generates new file called numbers.csv. However, it does not add the generated output to the file. Why is that?

Upvotes: 2

Views: 652

Answers (3)

mklement0
mklement0

Reputation: 437177

Your Out-File C:/numbers.csv -Append call is a completely separate statement from your do loop, and an Out-File call without any input simply creates an empty file.[1]

You need to chain (connect) commands with | in order to make them run in a pipeline.

However, with a statement such as as a do { ... } until loop, this won't work as-is, but you can convert such a statement to a command that you can use as part of a pipeline by enclosing it in a script block ({ ... }) and invoking it with &, the call operator (to run in a child scope), or ., the member-access operator (to run directly in the caller's scope):

[int] $ready = Read-Host "How many you want?"
$i = 0
& { 
  do{
    -join (1..12 | foreach { 
      (65..90 + 97..122 + '.' | % { [char] $_ }) +(0..9) + '.' | Get-Random
    })
    $i++
  } until ($i -eq $ready)
} | Out-File C:/numbers.csv -Append

Note the [int] type constraint to convert the Read-Host output, which is always a string, to a number, and the use of the -eq operator rather than the text- and regex-based -match operator in the until condition; also, unnecessary grouping with (...) has been removed.

Note: An alternative to the use of a script block with either the & or . operator is to use $(...), the subexpression operator, as shown in MikeM's helpful answer. The difference between the two approaches is that the former streams its output to the pipeline - i.e., outputs objects one by one - whereas $(...) invariably collects all output in memory, up front.

For smallish input sets this won't make much of a difference, but the in-memory collection that $(...) performs can become problematic with large input sets, so the & { ... } / . { ... } approach is generally preferable.


Arno van Boven' answer shows a simpler alternative to your do ... until loop based on a for loop.

Combining a foreach loop with .., the range operator, is even more concise and expressive (and the cost of the array construction is usually negligible and overall still amounts to noticeably faster execution):

[int] $ready = Read-Host "How many you want?"
& { 
  foreach ($i in 1..$ready) {
    -join (1..12 | foreach { 
      ([char[]] (65..90 + 97..122)) + 0..9 + '.' | Get-Random 
    })
  }
} | Out-File C:/numbers.csv -Append

The above also shows a simplification of the original command via a [char[]] cast that directly converts an array of code points to an array of characters.

In PowerShell [Core] 7+, you could further simplify by taking advantage of Get-Random's -Count parameter:

[int] $ready = Read-Host "How many you want?"
& { 
  foreach ($i in 1..$ready) {
    -join (
      ([char[]] (65..90 + 97..122)) + 0..9 + '.' | Get-Random -Count 12
    )
  }
} | Out-File C:/numbers.csv -Append

And, finally, you could have avoided a statement for looping altogether, and used the ForEach-Object cmdlet instead (whose built-in alias, perhaps confusingly, is also foreach, but there'a also %), as you're already doing inside your loop (1..12 | foreach ...):

[int] $ready = Read-Host "How many you want?"
1..$ready | ForEach-Object {
  -join (1..12 | ForEach-Object { 
    ([char[]] (65..90 + 97..122)) + 0..9 + '.' | Get-Random 
  })
} | Out-File C:/numbers.csv -Append

[1] In Windows PowerShell, Out-File uses UTF-16LE ("Unicode") encoding by default, so even a conceptually empty file still contains 2 bytes, namely the UTF-16LE BOM. In PowerShell [Core] v6+, BOM-less UTF-8 is the default across all cmdlets, so there you'll truly get an empty (0 bytes) file.

Upvotes: 3

Rno
Rno

Reputation: 887

I personally avoid Do loops when I can, because I find them hard to read. Combining the two previous answers, I'd write it like this, because I find it easier to tell what is going on. Using a for loop instead, every line becomes its own self-contained piece of logic.

[int]$amount = Read-Host "How many you want?: "
& {
    for ($i = 0; $i -lt $amount; $i++) {
        -join(1..12 | foreach {((65..90)+(97..122)+(".") | foreach {[char]$_})+(0..9)+(".") | Get-Random})
    }   
 } | Out-File C:\numbers.csv -Append

(Please do not accept this as an answer, this is just showing another way of doing it)

Upvotes: 0

MikeM
MikeM

Reputation: 13631

Another way is to wrap the loop in a sub-expression and pipe it:

$ready = Read-Host "How many you want?: "
$i = 0
$(do{
    (-join(1..12 | ForEach {((65..90)+(97..122)+(".") | % {[char]$_})+(0..9)+(".") | Get-Random}))
    $i++
} until ($i -match $ready)) | Out-File C:/numbers.csv -Append

Upvotes: 1

Related Questions