Powershell: Out-String -Width 10 does not truncate a string

Created on 2 Jun 2018  路  7Comments  路  Source: PowerShell/PowerShell

I apologize for this if it is expected behavior however based on the documentation, I believe that setting width should cause the output to be truncated.

-Width
Specifies the number of characters in each line of output. Any additional characters are truncated, not wrapped. If you omit this parameter, the width is determined by the characteristics of the host program. The default value for the Windows PowerShell console is 80 (characters).

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-string?view=powershell-6#optional-parameters

Steps to reproduce


$x=''; 1..100 | %{ $x += 'x' }; $x | Out-String -Width 10

Expected behavior

# string should be truncated to 10 characters
xxxxxxxxxx

Actual behavior

# String is not truncated
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Environment data

> $PSVersionTable
$PSVersionTable
Name                           Value
----                           -----
PSVersion                      6.0.2
PSEdition                      Core
GitCommitId                    v6.0.2
OS                             Darwin 17.6.0 Darwin Kernel Version 17.6.0: Fri Apr 13 19:57:44 PDT 2018; root:xnu-4570.60.17.0.1~3/RELEASE_X86_64
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Question Resolution-Answered WG-Engine

Most helpful comment

@BrucePay: Small correction: [0..$len] -> [0..($len-1)].

Tangent alert:

Out of curiosity I compared the performance of various approaches and made the following surprising discoveries:

  • Even letting .Substring() fail and handling the exception with try / catch makes for faster execution than placing conditionals around it (and the latter are in turn faster than the -join (([string]$_)[0..($len-1)]) approach).

  • Unexpectedly, in Windows PowerShell and PS Core v6.0.2 the following arcane regex solution is fastest: $_ -replace "(?<=.{$len}).+"

  • In PS Core v6.1.0-preview2 - the current preview - the regex solution is more than _twice as slow as before_ (and then slowest overall) - any ideas why? A change in PowerShell or in CoreFx? See #6976.


Test code (uses script Time-Command):

$len = 3  # how many chars. to return
$count = 1000 # how many runs to average

'foooooooooooooooo' | Time-Command -Count $count `
    { $_.Substring(0, [Math]::Min($len, $_.Length)) },
    { try { $_.Substring(0, $len) } catch { $_ } },
    { if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) }},
    { -join (([string]$_)[0..($len-1)]) },
    { $_ -replace "(?<=.{$len}).+" }

Here are example results from a W10 VM (note that Core is slower in general, and note how the regex solution went from fastest to slowest between v6.0.2 and v6.1.0-preview.2):

# Windows PowerShell v5.1 on W10

Command                                                        TimeSpan        
-------                                                        --------        
 $_ -replace "(?<=.{$len}).+"                                  00:00:00.0000226
 try { $_.Substring(0, $len) } catch { $_ }                    00:00:00.0000256
 $_.Substring(0, [Math]::Min($len, $_.Length))                 00:00:00.0000283
 if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) } 00:00:00.0000294
 -join (([string]$_)[0..($len-1)])                             00:00:00.0000346

# PS Core v6.0.2 on W10

Command                                                        TimeSpan        
-------                                                        --------        
 $_ -replace "(?<=.{$len}).+"                                  00:00:00.0000327
 try { $_.Substring(0, $len) } catch { $_ }                    00:00:00.0000424
 $_.Substring(0, [Math]::Min($len, $_.Length))                 00:00:00.0000479
 if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) } 00:00:00.0000511
 -join (([string]$_)[0..($len-1)])                             00:00:00.0000591

# PS Core v6.1.0-preview.2 on W10

Command                                                        TimeSpan
-------                                                        --------
 try { $_.Substring(0, $len) } catch { $_ }                    00:00:00.0000429
 $_.Substring(0, [Math]::Min($len, $_.Length))                 00:00:00.0000473
 if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) } 00:00:00.0000486
 -join (([string]$_)[0..($len-1)])                             00:00:00.0000595
 $_ -replace "(?<=.{$len}).+"                                  00:00:00.0000959

All 7 comments

Hi @jasonchester When you use the -Width parameter on Out-String you're overriding the screen width used when _formatting objects_. For example, dir *.md | Out-String -Width 20 will give you output that looks like:

PS >  ls *.md | out-string -width 20
    Directory: D:\gi
    t\PowerShell_bru
    cepay

Mode    LastWriteTim
        e
----    ------------
-a----     5/20/18
        11:30 AM
-a----     5/20/18
        11:30 AM
-a----     5/20/18
        11:30 AM

But that only applies to objects that are being formatted. Strings are treated as "already formatted" so they just pass through to the output unchanged. This is why your example, which uses a string, doesn't appear to work.

Oh - and if you want to create a long string, multiplication works on strings:

PS >  "x" * 100
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

This is not clear from the current documentation. We should update the documentation to make this distinction more clear.

@brucepay, Thanks for the quick answer, the explanation about out string applying to un-formatted inputs but not strings was exactly what I was missing.

More importantly, thanks for the string multiplication tip. I鈥檒l use that for sure.

Do you have any posh-fu for truncating strings to a specified length? I am planning to use Write-ProgressEx to display the progress of a script executing batches of gremlin queries. I am setting the status of the loop to the gremlin query but it is longer than my display is wide so it ends up wrapping which makes it much harder to scan as it scrolls by.

Of course there is "...".Substring(0,$width) but, it just doesn't feel very posh in my pipeline which is gets even more cluttered when combined with the (get-host).stuff.ui.junk.width needed to handle console resizing gracefully

@jasonchester Yes - this proposal #6753 _Add string cmdlets that emulate operator functionality on pipeline_ should be what you want. If you can, please take a few minutes to read the proposal and provide feedback on scenarios, etc. Thanks!

For now, to truncate strings in the pipeline, you could use foreach with the substring() method as follows:

PS > "foobar", "foobaz", "foobuz" | foreach substring 0 3
foo
foo
foo

Unfortunately Substring() throws if the string isn't long enough. An alternate approach would be a function like this:

function TruncateStr ($len) { process { -join (([string]$_)[0..$len]) }}

This little function will take strings from the pipeline and truncate them to the desired length. If the string isn't long enough, there's no error - the string is just passed through.

@BrucePay: Small correction: [0..$len] -> [0..($len-1)].

Tangent alert:

Out of curiosity I compared the performance of various approaches and made the following surprising discoveries:

  • Even letting .Substring() fail and handling the exception with try / catch makes for faster execution than placing conditionals around it (and the latter are in turn faster than the -join (([string]$_)[0..($len-1)]) approach).

  • Unexpectedly, in Windows PowerShell and PS Core v6.0.2 the following arcane regex solution is fastest: $_ -replace "(?<=.{$len}).+"

  • In PS Core v6.1.0-preview2 - the current preview - the regex solution is more than _twice as slow as before_ (and then slowest overall) - any ideas why? A change in PowerShell or in CoreFx? See #6976.


Test code (uses script Time-Command):

$len = 3  # how many chars. to return
$count = 1000 # how many runs to average

'foooooooooooooooo' | Time-Command -Count $count `
    { $_.Substring(0, [Math]::Min($len, $_.Length)) },
    { try { $_.Substring(0, $len) } catch { $_ } },
    { if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) }},
    { -join (([string]$_)[0..($len-1)]) },
    { $_ -replace "(?<=.{$len}).+" }

Here are example results from a W10 VM (note that Core is slower in general, and note how the regex solution went from fastest to slowest between v6.0.2 and v6.1.0-preview.2):

# Windows PowerShell v5.1 on W10

Command                                                        TimeSpan        
-------                                                        --------        
 $_ -replace "(?<=.{$len}).+"                                  00:00:00.0000226
 try { $_.Substring(0, $len) } catch { $_ }                    00:00:00.0000256
 $_.Substring(0, [Math]::Min($len, $_.Length))                 00:00:00.0000283
 if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) } 00:00:00.0000294
 -join (([string]$_)[0..($len-1)])                             00:00:00.0000346

# PS Core v6.0.2 on W10

Command                                                        TimeSpan        
-------                                                        --------        
 $_ -replace "(?<=.{$len}).+"                                  00:00:00.0000327
 try { $_.Substring(0, $len) } catch { $_ }                    00:00:00.0000424
 $_.Substring(0, [Math]::Min($len, $_.Length))                 00:00:00.0000479
 if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) } 00:00:00.0000511
 -join (([string]$_)[0..($len-1)])                             00:00:00.0000591

# PS Core v6.1.0-preview.2 on W10

Command                                                        TimeSpan
-------                                                        --------
 try { $_.Substring(0, $len) } catch { $_ }                    00:00:00.0000429
 $_.Substring(0, [Math]::Min($len, $_.Length))                 00:00:00.0000473
 if ($_.Length -lt $len) { $_ } else { $_.Substring(0, $len) } 00:00:00.0000486
 -join (([string]$_)[0..($len-1)])                             00:00:00.0000595
 $_ -replace "(?<=.{$len}).+"                                  00:00:00.0000959
Was this page helpful?
0 / 5 - 0 ratings