Правильное использование ssh и sed в скрипте python с os.system
Я пытаюсь запустить команду ssh в скрипте Python, используя os.systemдля добавления 0в конец полностью совпадающей строки на удаленном сервере, используя sshи sed.
У меня есть файл, который называется 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'
Это правильный / желаемый результат; забавная '"'"'идиома заключается в том, как безопасно вводить буквальную одинарную кавычку внутри строки в одинарных кавычках в POSIX-совместимой оболочке.
Что изменилось? Лоты:
- Мы генерируем команды, которые хотим запускать как массивы , и позволяем Python выполнять работу по преобразованию этих массивов в строки, где это необходимо. Это позволяет избежать атак путем внедрения оболочки - очень распространенного класса уязвимостей системы безопасности.
- Поскольку мы сами создаем списки, мы можем изменить то, как мы цитируем каждый из них: мы можем использовать f-строки, когда это уместно, необработанные строки, когда это необходимо, и т. Д.
- Мы не переходим
~к удаленному серверу: это избыточно и ненужно, потому что~это место по умолчанию для запуска сеанса SSH; и меры безопасности, которые мы используем (чтобы значения не были проанализированы как код оболочкой), не дают ему иметь никакого эффекта (поскольку замена~на активное значениеHOMEвыполняется неsedсама по себе, а вызывающей его оболочкой ; поскольку мы вообще не вызываем никакую локальную оболочку, нам также нужно было использовать,os.path.expanduserчтобы заставить~in~/.ssh/my-ssh-keyвыполняться). - Поскольку мы не используем необработанную строку, нам нужно удвоить обратную косую черту,
\bчтобы гарантировать, что они обрабатываются Python как буквальные, а не синтаксические. - Важно отметить, что мы никогда не передаем данные в контексте, где они могут быть проанализированы как код любой оболочкой, локальной или удаленной .