PowerShell icon indicating copy to clipboard operation
PowerShell copied to clipboard

On Linux, only the `help` function respects the `PAGER` env var.

Open sebastiancarlos opened this issue 1 month ago • 3 comments

Prerequisites

Steps to reproduce

I think ideally, Out-Host -Paging should use the same logic as the help function.

For what it's worth, I think the help implementation has the correct logic:

`help` implementation
PS> (Get-Command help).Definition

    {
        # By default use more on Windows and less on Linux.
        $pagerCommand = 'less'
        $pagerArgs = '-s','-P','Page %db?B of %D:.\. Press h for help or q to quit\.'

        # Respect PAGER environment variable which allows user to specify a custom pager.
        # Ignore a pure whitespace PAGER value as that would cause the tokenizer to return 0 tokens.
        if (![string]::IsNullOrWhitespace($env:PAGER)) {
            if (Get-Command $env:PAGER -ErrorAction Ignore) {
                # Entire PAGER value corresponds to a single command.
                $pagerCommand = $env:PAGER
                $pagerArgs = $null
            }
            else {
                # PAGER value is not a valid command, check if PAGER command and arguments have been specified.
                # Tokenize the specified $env:PAGER value. Ignore tokenizing errors since any errors may be valid
                # argument syntax for the paging utility.
                $errs = $null
                $tokens = [System.Management.Automation.PSParser]::Tokenize($env:PAGER, [ref]$errs)

                $customPagerCommand = $tokens[0].Content
                if (!(Get-Command $customPagerCommand -ErrorAction Ignore)) {
                    # Custom pager command is invalid, issue a warning.
                    Write-Warning "Custom-paging utility command not found. Ignoring command specified in `$env:PAGER: $env:PAGER"
                }
                else {
                    # This approach will preserve all the pagers args.
                    $pagerCommand = $customPagerCommand
                    $pagerArgs = if ($tokens.Count -gt 1) {$env:PAGER.Substring($tokens[1].Start)} else {$null}
                }
            }
        }

        $pagerCommandInfo = Get-Command -Name $pagerCommand -ErrorAction Ignore
        if ($pagerCommandInfo -eq $null) {
            $help
        }
        elseif ($pagerCommandInfo.CommandType -eq 'Application') {
            # If the pager is an application, format the output width before sending to the app.
            $consoleWidth = [System.Math]::Max([System.Console]::WindowWidth, 20)

            if ($pagerArgs) {
                $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand $pagerArgs
            }
            else {
                $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand
            }
        }
        else {
            # The pager command is a PowerShell function, script or alias, so pipe directly into it.
            $help | & $pagerCommand $pagerArgs
        }
    }

Expected behavior

$ PAGER='bat --paging=always' pwsh # or any other custom pager
PS> help help
# output using the bat pager
<CTRL-D>

$ PAGER='bat --paging=always' pwsh
PS> Get-Process | Out-Host -Paging
# output using the bat pager

# Note how 'more' and 'less' are hardcoded applications. This might be fine
# actually
PS> (Get-Command more).Definition
/usr/bin/more
PS> (Get-Command less).Definition
/usr/bin/less

Actual behavior

$ PAGER='bat --paging=always' pwsh # or any other custom pager
PS> help help
# output using the bat pager
<CTRL-D>
$ PAGER='bat --paging=always' pwsh

PS> Get-Process | Out-Host -Paging
# The main pager always use 'more' no matter what (!!)
# output using 'more' pager

# Note how 'more' and 'less' are hardcoded applications. This might be fine
# actually
PS> (Get-Command more).Definition
/usr/bin/more
PS> (Get-Command less).Definition
/usr/bin/less

Error details


Environment data

Name                           Value
----                           -----
PSVersion                      7.5.4
PSEdition                      Core
GitCommitId                    7.5.4-0-g7c8d9e7e0ed2fc1f2caf50746fe9fef720ca2a0a
OS                             Arch Linux
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

sebastiancarlos avatar Nov 04 '25 13:11 sebastiancarlos

I think it should respect $env:PAGER if it's defined. If it's not defined, it should work as it does now:

Using Paging is similar to using the more command.

237dmitry avatar Nov 04 '25 18:11 237dmitry

I think it should respect $env:PAGER if it's defined. If it's not defined, it should work as it does now

I agree. For what it's worth, I think the help implementation has the correct logic:

`help` implementation
PS> (Get-Command help).Definition

    {
        # By default use more on Windows and less on Linux.
        $pagerCommand = 'less'
        $pagerArgs = '-s','-P','Page %db?B of %D:.\. Press h for help or q to quit\.'

        # Respect PAGER environment variable which allows user to specify a custom pager.
        # Ignore a pure whitespace PAGER value as that would cause the tokenizer to return 0 tokens.
        if (![string]::IsNullOrWhitespace($env:PAGER)) {
            if (Get-Command $env:PAGER -ErrorAction Ignore) {
                # Entire PAGER value corresponds to a single command.
                $pagerCommand = $env:PAGER
                $pagerArgs = $null
            }
            else {
                # PAGER value is not a valid command, check if PAGER command and arguments have been specified.
                # Tokenize the specified $env:PAGER value. Ignore tokenizing errors since any errors may be valid
                # argument syntax for the paging utility.
                $errs = $null
                $tokens = [System.Management.Automation.PSParser]::Tokenize($env:PAGER, [ref]$errs)

                $customPagerCommand = $tokens[0].Content
                if (!(Get-Command $customPagerCommand -ErrorAction Ignore)) {
                    # Custom pager command is invalid, issue a warning.
                    Write-Warning "Custom-paging utility command not found. Ignoring command specified in `$env:PAGER: $env:PAGER"
                }
                else {
                    # This approach will preserve all the pagers args.
                    $pagerCommand = $customPagerCommand
                    $pagerArgs = if ($tokens.Count -gt 1) {$env:PAGER.Substring($tokens[1].Start)} else {$null}
                }
            }
        }

        $pagerCommandInfo = Get-Command -Name $pagerCommand -ErrorAction Ignore
        if ($pagerCommandInfo -eq $null) {
            $help
        }
        elseif ($pagerCommandInfo.CommandType -eq 'Application') {
            # If the pager is an application, format the output width before sending to the app.
            $consoleWidth = [System.Math]::Max([System.Console]::WindowWidth, 20)

            if ($pagerArgs) {
                $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand $pagerArgs
            }
            else {
                $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand
            }
        }
        else {
            # The pager command is a PowerShell function, script or alias, so pipe directly into it.
            $help | & $pagerCommand $pagerArgs
        }
    }

sebastiancarlos avatar Nov 04 '25 20:11 sebastiancarlos

The WG discussed this. We agree that when -paging is used with Out-Host, it should respect use of $env:PAGER if it's defined. This should be true for both Windows and non-Windows.

SteveL-MSFT avatar Dec 03 '25 17:12 SteveL-MSFT