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

Posted

技术标签:

【中文标题】在带有 os.system 的 python 脚本中正确使用 ssh 和 sed【英文标题】:Using ssh and sed within a python script with os.system properly 【发布时间】:2020-12-31 02:09:19 【问题描述】:

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

我在远程服务器中有一个名为 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 '/\bhostname\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\"")

让我的命令生效。

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

【问题讨论】:

MAC 和 Linux 都试过了,都不能用花括号 使用os.system本身就是不合适的;它不可避免地会将您的内容通过 shell 解析,并带来所有安全问题。它已被弃用多年。独占使用subprocess 模块。 可能\b 在创建命令字符串的过程中已经被消耗掉了。你试过\\bhostname\\b 吗?你也试过hostname$这对你的情况也有好处吗?无论如何尝试子进程。 您的回答非常详细!这是正确的方法。我只是想知道谁吃了\b?如果它可能是ssh,但正如预期的那样是os.system(f",我测试了用echo替换ssh 是的;需要在 Python 中使用原始字符串来防止反斜杠被解释。例如,r'/\b%s\b/s/$/ 0/' % hostname 会按预期工作。 【参考方案1】:

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

#!/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'/\\bhostname\\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'

这是正确/期望的输出;有趣的'"'"' 习惯用法是如何在符合 POSIX 的 shell 中安全地在单引号字符串中注入文字单引号。


有什么不同?很多:

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

【讨论】:

文件的位置在~/Documents/function.txt,你是说我不应该解析~,那我应该改用/home/username/Documents/function.txt吗? 或者只是Documents/function.txt

以上是关于在带有 os.system 的 python 脚本中正确使用 ssh 和 sed的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 LibreOffice 宏将 os.system() 命令写入 python 脚本并执行它?

python os.system()和os.popen()

Python执行脚本方法

使用 os.system("bash code") 在 Python 脚本中调用 bash 命令是一种好的风格吗? [关闭]

克服 Python 2.3 中的 os.system() 限制

python 使用 os.system() 时没有返回正常的结果,返回了256 ubuntu16.04系统下 python版本 2.7.11