在 python 脚本中正确使用 ssh 和 sed os.system

Using ssh and sed within a python script with os.system properly

我正在尝试 运行 python 脚本中的 ssh 命令,使用 os.system 在远程完全匹配的字符串末尾添加 0服务器使用 sshsed.

我在远程服务器中有一个名为 nodelist 的文件,它是一个如下所示的列表。

test-node-1
test-node-2
...
test-node-11
test-node-12
test-node-13
...
test-node-21

我想用 sed 做以下修改,我想搜索 test-node-1,当找到完全匹配时我想在末尾添加一个 0,文件必须看起来像这个。

test-node-1 0
test-node-2
...
test-node-11
test-node-12
test-node-13
...
test-node-21

然而,当我运行第一个命令时,

hostname = 'test-node-1'
function = 'nodelist'

os.system(f"ssh -i ~/.ssh/my-ssh-key username@serverlocation \"sed -i '/{hostname}/s/$/ 0/' ~/{function}.txt\"")

结果变成这样,

test-node-1 0
test-node-2
...
test-node-11 0
test-node-12 0
test-node-13 0
...
test-node-21

我试过像这样在命令中添加 \b,

os.system(f"ssh -i ~/.ssh/my-ssh-key username@serverlocation \"sed -i '/\b{hostname}\b/s/$/ 0/' ~/{function}.txt\"")

该命令根本不起作用。

我必须手动输入节点名称,而不是像这样使用变量,

os.system(f"ssh -i ~/.ssh/my-ssh-key username@serverlocation \"sed -i '/\btest-node-1\b/s/$/ 0/' ~/{function}.txt\"")

让我的命令生效。

我的命令有什么问题,为什么我不能做我想让它做的事?

此代码存在严重的安全问题;修复它们需要从头开始重新设计。让我们在这里做:

#!/usr/bin/env python3
import os.path
import shlex  # note, quote is only here in Python 3.x; in 2.x it was in the pipes module
import subprocess
import sys

# can set these from a loop if you choose, of course
username = "whoever"
serverlocation = "whereever"
hostname = 'test-node-1'
function = 'somename'

desired_cmd = ['sed', '-i',
               f'/\b{hostname}\b/s/$/ 0/',
               f'{function}.txt']
desired_cmd_str = ' '.join(shlex.quote(word) for word in desired_cmd)
print(f"Remote command: {desired_cmd_str}", file=sys.stderr)

# could just pass the below direct to subprocess.run, but let's log what we're doing:
ssh_cmd = ['ssh', '-i', os.path.expanduser('~/.ssh/my-ssh-key'),
           f"{username}@{serverlocation}", desired_cmd_str]
ssh_cmd_str = ' '.join(shlex.quote(word) for word in ssh_cmd)
print(f"Local command: {ssh_cmd_str}", file=sys.stderr)  # log equivalent shell command
subprocess.run(ssh_cmd) # but locally, run without a shell

如果你运行这个(除了最后的subprocess.run,它需要一个真正的SSH密钥,主机名等),输出看起来像:

Remote command: sed -i '/\btest-node-1\b/s/$/ 0/' somename.txt
Local command: ssh -i /home/yourname/.ssh/my-ssh-key whoever@whereever 'sed -i '"'"'/\btest-node-1\b/s/$/ 0/'"'"' somename.txt'

那是correct/desired输出;有趣的 '"'"' 习语是如何在 POSIX-compliant shell.

中的 single-quoted 字符串中安全地注入文字单引号

有什么不同?手数:

  • 我们正在生成我们想要 运行 作为数组 的命令,并在必要时让 Python 完成将这些数组转换为字符串的工作.这避免了 shell 注入攻击,这是一种非常常见的 class 安全漏洞。
  • 因为我们自己生成列表,所以我们可以更改引用每个列表的方式:我们可以在适当的时候使用 f-strings,在适当的时候使用原始字符串,等等。
  • 我们不会将 ~ 传递给远程服务器:这是多余且不必要的,因为 ~ 是 SSH 会话开始的默认位置;以及我们正在使用的安全预防措施(以防止值被 shell 解析为代码)防止它产生任何影响(作为 ~ 的替换为 [=17= 的活动值] 不是由 sed 本身完成的,而是由调用它的 shell 完成的;因为我们根本没有调用任何本地 shell,所以我们还需要使用 os.path.expanduser使 ~/.ssh/my-ssh-key 中的 ~ 得到尊重)。
  • 因为我们没有使用原始字符串,所以我们需要将 \b 中的反斜杠加倍,以确保 Python.[=41= 将它们视为文字而非句法。 ]
  • 至关重要的是,我们绝不会在任何 shell 本地或远程 .[=41= 都可以将数据解析为代码的上下文中传递数据]