mirror of
ssh://git.janware.com/janware/proj/jw-pkg
synced 2026-04-24 09:13:37 +02:00
lib.ec.ssh: Don't quote shell operators
Naively join()ing a command list to be executed remotely via SSH also quotes shell operators which doesn't work, of course. Work around that. The workaround will not always work but covers lots of cases. Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
61c1a628a1
commit
84375cd482
4 changed files with 50 additions and 7 deletions
|
|
@ -6,6 +6,8 @@ from ...log import *
|
|||
from ...ExecContext import Result
|
||||
from ..SSHClient import SSHClient as Base
|
||||
|
||||
from .util import join_cmd
|
||||
|
||||
_USE_DEFAULT_KNOWN_HOSTS = object()
|
||||
|
||||
class AsyncSSH(Base):
|
||||
|
|
@ -52,7 +54,7 @@ class AsyncSSH(Base):
|
|||
if not cmd:
|
||||
raise ValueError("cmd must not be empty")
|
||||
|
||||
inner = f"exec {shlex.join(cmd)}"
|
||||
inner = f"exec {join_cmd(cmd)}"
|
||||
if wd is not None:
|
||||
inner = f"cd {shlex.quote(wd)} && {inner}"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import shlex
|
||||
|
||||
from ...util import run_cmd
|
||||
from ..SSHClient import SSHClient as Base
|
||||
from .util import join_cmd
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...ExecContext import Result
|
||||
|
|
@ -41,5 +40,5 @@ class Exec(Base):
|
|||
|
||||
async def _run_ssh(self, cmd: list[str], cmd_input: str|None, *args, **kwargs) -> Result:
|
||||
self.__init_askpass()
|
||||
return await run_cmd(['ssh', self.hostname, shlex.join(cmd)], cmd_input=cmd_input)
|
||||
return await run_cmd(['ssh', self.hostname, join_cmd(cmd)], cmd_input=cmd_input)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import paramiko # type: ignore # error: Library stubs not installed for "paramiko"
|
||||
import shlex
|
||||
|
||||
from ...log import *
|
||||
from ...ExecContext import Result
|
||||
from ..SSHClient import SSHClient as Base
|
||||
|
||||
from .util import join_cmd
|
||||
|
||||
class Paramiko(Base):
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
|
|
@ -45,9 +46,9 @@ class Paramiko(Base):
|
|||
|
||||
async def _run_ssh(self, cmd: list[str], cmd_input: str|None, *args, **kwargs) -> Result:
|
||||
try:
|
||||
stdin, stdout, stderr = self.__ssh.exec_command(shlex.join(cmd), timeout=self.__timeout)
|
||||
stdin, stdout, stderr = self.__ssh.exec_command(join_cmd(cmd), timeout=self.__timeout)
|
||||
except Exception as e:
|
||||
log(ERR, f'Command failed for {self.uri}: "{shlex.join(cmd)}"')
|
||||
log(ERR, f'Command failed for {self.uri}: "{join_cmd(cmd)}"')
|
||||
raise
|
||||
if cmd_input is not None:
|
||||
stdin.write(cmd_input)
|
||||
|
|
|
|||
41
src/python/jw/pkg/lib/ec/ssh/util.py
Normal file
41
src/python/jw/pkg/lib/ec/ssh/util.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
import shlex
|
||||
|
||||
DEFAULT_SHELL_OPERATORS = {
|
||||
# redirections
|
||||
">", ">>", "<", "<<", "<<-", "<&", ">&", "<>", ">|",
|
||||
"1>", "1>>", "2>", "2>>",
|
||||
|
||||
# pipelines / control
|
||||
"|", "||", "&", "&&", ";",
|
||||
|
||||
# grouping
|
||||
"(", ")",
|
||||
}
|
||||
|
||||
def join_cmd(
|
||||
cmd: Iterable[str],
|
||||
operators: set[str] = DEFAULT_SHELL_OPERATORS,
|
||||
) -> str:
|
||||
"""
|
||||
Join a token list into a POSIX-shell command string.
|
||||
|
||||
Tokens in `operators` are emitted verbatim.
|
||||
Everything else is shell-quoted with shlex.quote().
|
||||
|
||||
Example:
|
||||
["echo", "hello world", ">", "/tmp/out file"]
|
||||
-> "echo 'hello world' > '/tmp/out file'"
|
||||
"""
|
||||
ret: list[str] = []
|
||||
for token in cmd:
|
||||
if not isinstance(token, str):
|
||||
token = str(token)
|
||||
if token in operators:
|
||||
ret.append(token)
|
||||
else:
|
||||
ret.append(shlex.quote(token))
|
||||
return ' '.join(ret)
|
||||
Loading…
Add table
Add a link
Reference in a new issue