Search code examples
powershellsshsed

Can't use sed when using ssh with ms powershell to linux host


This is working in linux

sed "s/#\?\(PasswordAuthentication\s*\).*$/\1 no/" /etc/ssh/sshd_config;

But in MS Powershell in windows when I using

pwsh -c ssh root@161.97.108.95 -p 22 sed "s/#\?\(PasswordAuthentication\s*\).*$/\1 no/" /etc/ssh/sshd_config;

I get following error

sed: -e expression #1, char 35: unterminated `s' command

Before I fix encoding issue in powershell so \1 look right. So for now I can't solve this.

$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding

BodyName : utf-8
EncodingName : Unicode (UTF-8)
HeaderName : utf-8
WebName : utf-8
WindowsCodePage : 1200
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.EncoderReplacementFallback
DecoderFallback : System.Text.DecoderReplacementFallback
IsReadOnly : True
CodePage : 65001

Edit more info

enter image description here

Is ssh in powershell th issue?

I see the command is changed in debug info

enter image description here


Solution

    • It seems that ssh isn't capable of relaying arbitrary commands with separate arguments that contain \ characters to the remote shell, and simply escaping \ as \\ also does not work.

    • The solution is to pass the entire sed command line as a single argument to ssh:

    # From PowerShell:
    # Note the use of '...''...''...'
    ssh root@161.97.108.95 -p 22 'sed ''s/#\?\(UsePAM\s*\).*$/\1 no/'' /etc/ssh/sshd_config'
    

    Note:

    • Because PowerShell itself interpolates $-prefixed tokens inside "..." (expandable strings), it is better to use '...' (verbatim strings).

      • Unlike in POSIX-compatible shells such as Bash, you can directly embed ' characters inside '...' by escaping them as '', as shown above.
    • In cases where you do need up-front string interpolation by PowerShell, be sure to escape any pass-through $ characters as `$, i.e. using the so-called backtick, PowerShell's escape character.

      • Note that \ has no special meaning in PowerShell, whereas it functions as the escape character in POSIX-compatible shells, for instance.

      • As an aside: $ chars. that aren't followed by an identifier (e.g., $foo) or start a subexpression (e.g., $(1 + 2)) are left untouched, so there is no strict need to `-escape them, though it's probably better to do so for conceptual reasons; e.g., "5$/" is treated the same as "5`$/" by PowerShell and yields verbatim 5$/ ($ sign retained).
        The same applies analogously to POSIX-compatible shells (except that escaping $ requires \$ there).

    • Your question shows an attempt to call ssh via pwsh -c, i.e. via pwsh, the PowerShell (Core) 7 CLI, which isn't necessary: just invoke ssh directly, as shown above.

      • If you were to call directly from cmd.exe, you'd use "..." for enclosing the sed command line as a whole (cmd.exe only recognizes double-quoting, and doesn't interpret $), and just '...' for the embedded sed script.

    Calling your command from Python, via subprocess.run():

    • As noted, there is no need to call via the PowerShell CLI.

    • From Python, you have to option to call ssh directly, without involvement of a shell (be it PowerShell or cmd.exe).

    • On Windows only, you may still pass a whole command line to execute, in which case the quoting rules are in essence the same as in cmd.exe: only double-quoting ("...") is recognized.

    • Quoting in the case at hand gets tricky, because of the nested quoting that is necessary: embedded "..." quoting around the pass-through sed command, and, inside that, '...' quoting around the sed script. Additionally, the \ characters should be preserved as-is.

    • The simplest solution is to use a raw, triple-quoted string literal, r'''...''', inside of which ', ", and \ can all be embedded unescaped.

    Therefore:

    import subprocess
    
    # Note: Shell-less invocation (`shell=False` is implied).
    #       Passing a whole command line as a single string in a shell-less
    #       invocation is supported on Windows only.
    subprocess.run(
      r'''ssh root@161.97.108.95 -v -p 22 "sed 's/#\?\(UsePAM\s*\).*$/\1 no/' /etc/ssh/sshd_config"''', 
      encoding="utf-8"
    )