在带有 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 命令,以在远程服务器中使用 ssh
和 sed
的完全匹配字符串的末尾添加 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 脚本并执行它?
使用 os.system("bash code") 在 Python 脚本中调用 bash 命令是一种好的风格吗? [关闭]
克服 Python 2.3 中的 os.system() 限制
python 使用 os.system() 时没有返回正常的结果,返回了256 ubuntu16.04系统下 python版本 2.7.11