LINUX.ORG.RU

python запуск команды в терминале.

 , , ,


2

2

Решил я переписать для тренировки и изучения питона кучу баш скриптов в питон.

И есть у меня одна проблема. Очень часто в моих баш скриптах есть команды терминала. И они настолько разнообразные, что никакого единого подхода питоном к ним нет. А зачастую и вообще можно только запустить bash код из Питона.

В связи с этим я решил, что почти все команды буду запускать саб процессом из питона.

Но и тут меня встретила жопа с тем, что мне нормально не отловить вывод команды или ее ошибку переменную или в print.

os.system - не отлавливает ошибки subprocess.check_output - не тоже не отдает то ли ошибку,то ли вывод.

Единственное, что более менее работает это

p = Popen(command)
p = Popen(cmd, stdout=subprocess.PIPE,stderr=PIPE,shell=True,
		universal_newlines=True)
	output, error = p.communicate()

но и тут проблемы.

Если я делаю return, то не вижу вывода и должен делать x=function_name(cmd) print(x)

А если я делаю print , то не могу засунуть output в переменную.

В результате у меня родилась уродливая по моему мнению функция, в которую помимо команды я вынужден запихивать метод, корым она мне будет отдавать stdout.

вот она:

def run_command(cmd,output="print",exit_on_error=False):
	p = Popen(cmd, stdout=subprocess.PIPE,stderr=PIPE,shell=True,
		universal_newlines=True)
	o, e = p.communicate()
	if p.returncode != 0:
		if output == "print" : print("%s%s" % (o, e))
		if exit_on_error:
			print("Error. Exit script")
			quit()
		if output == "return": return("%s%s" % (o, e))
	else:
		if output == "return": return("%s%s" % (o, e))
		print("%s%s" % (o, e))

И если я хочу вывод на экран, то

cmd='команда'
run_command(cmd,"print")

А если я хочу вывод в переменную, то

cmd='команда'
x=run_command(cmd,"return")

В если я хочу выйти из скрипта при ошибке, то

run_command(cmd, "print", True)

Тогда как в баше, я получаю вывод в переменную или на экран в зависимости от того просто я запуская команды или запускаю ее в переменную.

Например:

last_remote=$(ssh $backup_ip "/sbin/zfs list -t snapshot -o name | grep "$remote_dataset""@""$type" | sort -r | head -1")

или просто 

$(ssh $backup_ip "/sbin/zfs list -t snapshot -o name | grep "$remote_dataset""@""$type" | sort -r | head -1")

И не важно вернется команда с ошибкой или нет, я могу отловить и статус и текст и сделать это очень просто.

Вопрос, вот эта моя уродливая функция: так все мучаются? или есть нормальное решение?

★★★

1. os.system использовать не надо, он устарел.

2. для сабов используй или run, или когда нужен пайп Pipe.

3. ошибки можно отлавливать плейнтекстом в самом питоне или запрашивать экзиткоды у шела.

anonymous ()
Ответ на: комментарий от anonymous

для сабов используй или run

run тоже тестил, все плохо отдает.

И по посту можно понять, что я пишу после того, как провел часов 5 за гуглением и тестами, а не просто так.

запрашивать экзиткоды

Меня не очень интересует exit code. меня интересует output команды, причем как в случае удачного испольнения, так и неудачного.

Моя функция работает, но я считаю, что это коряво.

constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)

shell=True

старайся избегать по возможности

output="print"

не лучше ли просто добавить параметр echo=True, а возвращать вывод вообще всегда? (если тебе не нужен вывод просто не сохраняй его в переменную)

eternal_sorrow ★★★★★ ()

а ещё есть такой параметр stderr=stdout, с ним в stdout процесса будет весь его вывод (stderr и stdout) именно в том порядке, в котором они были выведены процессом а не просто склеены последовательно, как у тебя

eternal_sorrow ★★★★★ ()
Ответ на: комментарий от eternal_sorrow

output=«print»

Это детали, сначала было через true, я просто не в курсе, как у питонщиков принято для красивого кода, но конечно могу и так и так сделать.

а возвращать вывод вообще всегда? (если тебе не нужен вывод просто не сохраняй его в переменную)

В том и дело, что не работает. те print в функции не отдает ничего в переменную, он выводит на экран а в return Null


label = "test-test"
cmd='sed -i "" \'/lala%s/,/%s.cfg/d\' /path/test1' % (label, label)
print("1::::::: run error, print , not exit")
x=run_command(cmd,"print")
print("X", x)

output:

1::::::: run error, print , not exit
пошел print:
sed: /path/test1: No such file or directory

А переменная пустая:

X None

constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)

стилевые замечания:

if output == "print" : print("%s%s" % (o, e))

так делать не стоит, всегда выноси тело на отдельную строку, даже если там одна строка

return("%s%s" % (o, e))

return это не функция, в скобки значение брать необязательно

"%s%s" % (o, e)

старайся по возможности избегать форматирования через %, используй str.format() или f"" литералы

eternal_sorrow ★★★★★ ()
Последнее исправление: eternal_sorrow (всего исправлений: 1)
Ответ на: комментарий от eternal_sorrow

ничего не понял. в текущем варианте всё работает, но если убрать if output == «return»: то возвращает None?

У меня список тестов, я показал первый проход. Проход со специальной ошибкой команды, и выводом через print.

При этом я пытаюсь ( чтобы показать вам, что оно не работает ) записать вывод функции в пепременную.

Функиця в данном случае выходит с print(«%s%s» % (o, e))

Вызываем мы ее в переменную x=run_command()

Но при этом в переменную ничего не записывается, а вывод идет на экран.

Именно по этому мнен приходится танцевать и иногнда выводить через return, а иногда через print

constin ★★★ ()
Ответ на: комментарий от constin

я всё равно ничего не понял, что у тебя там происходит. по моему у тебя там какой то жутчайший быдлокод если даже в таком простом кейсе ты не можешь предсказуемый результат получить

eternal_sorrow ★★★★★ ()
Ответ на: комментарий от eternal_sorrow

иными словами

функция выводит  print
x=run(cmd)

результат: вывод на экран, переменная пустая

функция выводит  print
run(cmd)

результат: вывод на экран

функция выводит  return
x=run(cmd)

результат:  переменная ловится

Итог, нет общего выхода из функции, чтобы его можно было поймать и на экран и в переменную

constin ★★★ ()
Ответ на: комментарий от eternal_sorrow

я спросил изначально , есть ли какой-то нормальный способ решения проблемы запуска.

Ты написал, что лучше все выводить через return. Я ответил, что все выводить через return не выходит, и привел пример, когда это не выходит.

constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)

Ты хочеешь использовать питон для запуска баш скриптов? Может быть лучше наоборот, куски шелл скриптов заменять вызовами питоновских скриптов?

sin_a ★★★★★ ()
Ответ на: комментарий от sin_a

Ты хочеешь использовать питон для запуска баш скриптов?

Ну у меня прекрасные башевские скрипты, они стабильно и красиво работают. И тут я решил подучить питон. Учить язык без проекта -хреновая идея. Поэтому я решил попробовать переписать несколько баш скриптов на питон.

constin ★★★ ()
Ответ на: комментарий от eternal_sorrow

run это твоя функция run_command или subprocess.run (или что то ещё)?

да, я просто сократил. мы говорим все время об ОДНОЙ моей функции run_command

Я не хочу перегружать пост информацией и показывать мои попытки с subprocess.run и остальными вариантами, там было все плохо.

constin ★★★ ()
Ответ на: комментарий от constin

Ты написал, что лучше все выводить через return

я такого не писал. я написал, что print можно делать по необходимости и управлять этим с помощью булева параметра, а return делать всегда и отлавливать возвращаемое значение в переменную по необходимости

eternal_sorrow ★★★★★ ()
Последнее исправление: eternal_sorrow (всего исправлений: 1)
Ответ на: комментарий от Dred

и в чем проблема ? Это в любом случае того что ты нагородил.

Проблема в том, что если ошибка, то одно, если в переменную, то другое, если на экран, то третье. Слишком много состояний и условий, а я люблю когда красиво и мало кода.

То что написано мной - работает. Сейчас еще поменяю код, как eternal_sorrow посоветовал и будет чуть элегантнее.

constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)
Ответ на: комментарий от constin

так а в чём проблема?

сделать return

поймать текст ошибки команды

это не одно и то же?

остановить скрипт с выводом ошибки/причины

x = run_command(cmd, echo=True, exit_on_error=True)

echo - это параметр, который управляет тем, выводить результат выполнения на экран или нет

eternal_sorrow ★★★★★ ()
Ответ на: комментарий от constin
def run_command(...):
    ...
    return (result, exit_code, error)

В случае ошибки - в result None, в err - вывод stderr, иначе none в err.

res, code, err =  run_command(...)
if err:
    print('Error {} in command {}: {}'.format(code, comm, err), file=stderr)
    exit(code)

...
anonymous ()
Ответ на: комментарий от anonymous

if err:
print('Error {} in command {}: {}'.format(code, comm, err), file=stderr)
exit(code)

Почти ок, спасибо.

Жаль, что нужно постоянно писать обработчик в коде для каждой команды.

constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)
Ответ на: комментарий от constin

когда красиво

то что у тебя далеко от «красиво»

ты же в курсе, что можешь вернуть несколько значений кортежем ? Верни код ошибки заодно, снаружи проверяй, декомпозицию никто не отменял. А то у тебя ф-ция то возвращает значение, то нет, это жесть какая-то.

Dred ★★★★★ ()
Ответ на: комментарий от constin

Ну вот, спасибо. Стала поменьше и всегда отдает значение, кроме quit.

Сейчас попробую учесть стилевые замечания.

def run_command(cmd,echo=False,exit_on_error=False):
	p = Popen(cmd, stdout=subprocess.PIPE,stderr=PIPE,shell=True,
		universal_newlines=True)
	o, e = p.communicate()
	if echo:
		print("%s%s" % (o, e))
	if p.returncode != 0 and exit_on_error:
			quit("Error: %s%s. Exit script" % (o, e))
	return("%s%s" % (o, e))
constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)
Ответ на: комментарий от eternal_sorrow

def run_command(cmd,echo=False,exit_on_error=False):
	p = Popen(cmd, stdout=subprocess.PIPE,stderr=PIPE,shell=True,
		universal_newlines=True)
	o, e = p.communicate()
	if echo:
		print('{}{}'.format(o, e))
	if p.returncode != 0 and exit_on_error:
		quit('Error: {}{} Exit script'.format(o,e))
	return ('{}{}'.format(o, e), p.returncode)
constin ★★★ ()
Ответ на: комментарий от eternal_sorrow

Полная версия ( я знаю, что это никому не интересно и не нужно)


#!/usr/bin/env python3

import sys
import subprocess
from subprocess import Popen, PIPE
import re

# tools
def run_command(cmd, echo=True, exit_on_error=False):
	p = Popen(cmd, stdout=subprocess.PIPE,stderr=PIPE,shell=True,
		universal_newlines=True)
	o, e = p.communicate()
	if echo:
		print('{}{}'.format(o, e))
	if p.returncode != 0 and exit_on_error:
		quit('Error: {}{} Exit script'.format(o,e))
	return ('{}{}'.format(o, e), p.returncode)



def printhelp():
    pass


def delete_from_export(dataset):
    run_command(
        'grep -v {} /etc/exports > /tmp/exports &&  mv /tmp/exports \
		/etc/exports'.format(dataset), echo=False)
    run_command('exportfs -ra', exit_on_error=True)


def pxe_boot_config(ip, dataset, label):
    config_list = [
        "DEFAULT vesamenu.c32",
        "PROMPT 0",
        "MENU TITLE PXE BOOT " + label + " ZFS " + ip,
        "LABEL pxe-ro",
        "MENU LABEL PXE",
        "KERNEL vmlinuz",
        "APPEND root=/dev/nfs initrd=initrd.img nfsroot="+ip+":/"+dataset+" ip=dhcp ro quiet splash",
        "IPAPPEND 3",
        "LABEL pxe-rw",
        "MENU LABEL PXE Administrator",
        "KERNEL vmlinuz",
        "MENU PASSWD XXXX",
        "APPEND root=/dev/nfs initrd=initrd.img nfsroot=" +ip + ":/" + dataset + " ip=dhcp rw quiet",
        "IPAPPEND 3",
        "ONTIMEOUT pxe-ro",
        "TIMEOUT 50",
        "MENU COLOR border 30;44 #40ffffff #a0000000 std",
        "MENU COLOR title 1;36;44 #9033ccff #a0000000 std",
        "MENU COLOR sel 7;37;40 #e0000000 #20ffffff all",
        "MENU COLOR unsel 37;44 #50ffffff #a0000000 std",
        "MENU COLOR help 37;40 #c0ffffff #a0000000 std",
        "MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std",
        "MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std",
        "MENU COLOR msg07 37;40 #90ffffff #a0000000 std",
        "MENU COLOR tabmsg 37;40 #e0ffffff #a0000000 std",
        "MENU COLOR disabled 37;44 #50ffffff #a0000000 std",
        "MENU COLOR hotkey 1;30;47 #ffff0000 #a0000000 std",
        "MENU COLOR hotsel 1;7;30;47 #ffff0000 #20ffffff all",
        "MENU COLOR scrollbar 30;47 #ffff0000 #00000000 std",
        "MENU COLOR cmdmark 1;36;47 #e0ff0000 #00000000 std",
        "MENU COLOR cmdline 30;47 #ff000000 #00000000 none"
    ]
    f = open('/srv/tftp/pxelinux.cfg/{}.cfg'.format(label), "a")
    for line in config_list:
        f.write(line + "\n")
    f.close()


# main fuctions

def clone(snapname, dataset):
	run_command('zfs clone {} {}'.format(snapname,dataset), exit_on_error=True)
	if dataset in open('/etc/exports').read():
		print('{} already exist in /etc/exports'.format(dataset))
	else:
		f = open('/etc/exports', "a")
		f.write('/{} 192.168.0.0/24(rw,no_root_squash,async,insecure,no_subtree_check)\n'.format(dataset))
		f.flush()
		f.close()
		run, error= run_command('exportfs -ra')
		if error != 0:
			print("Rolling changes back.Exiting script")
			print("clean /etc/export")
			delete_from_export(dataset)
			print("destroy zfs clone")
			run_command('zfs destroy {}'.format(dataset))
			print("done")
			quit()
		# # TODO write reroll
		label = dataset.replace("/", "-")
		list = ["LABEL " + label, "MENU LABEL " + label, "KERNEL vesamenu.c32",
			"MENU PASSWD XXX", "APPEND pxelinux.cfg/" + label + ".cfg"]
		f = open('/srv/tftp/default.cfg', "r+")
		for line in f:
			if 'SEPARATOR' in line:
				for item in list:
					f.write(item + "\n")
				break
		f.close()
		pxe_ip, er =run_command("hostname -I| cut -d' ' -f1",echo=False,exit_on_error=True)

		pxe_boot_config(pxe_ip.strip(),dataset, label)


def destroy(dataset):
    run_command('zfs destroy {}'.format(dataset))
    delete_from_export(dataset)
    label = dataset.replace("/", "-")
    run_command('sed -i "/LABEL {}/,/{}.cfg/d" /srv/tftp/default.cfg'.format(label,label))
    run_command('rm /srv/tftp/pxelinux.cfg/{}.cfg'.format(label))



if sys.argv[1] == "clone":
	clone(sys.argv[2],sys.argv[3])
elif sys.argv[1] == "destroy":
	destroy(sys.argv[2])
else:
	printhelp()
constin ★★★ ()
Последнее исправление: constin (всего исправлений: 1)