subprocess в Python
В этой статье вы узнаете как выполнять команды Linux и Windows из кода на Python 3.
Создайте файл subprocess_lesson.py и копируйте туда код из примеров.
Запустить скрипт можно командой
Простой пример
Пример программы, которая выполняет Linux команду ls
import subprocess subprocess.run( ‘ls’ )
Простой пример Windows
Пример программы, которая выполняет в Windows команду dir
import subprocess subprocess.run(‘dir’, shell = True )
У меня пока что не работает
Bash команда с опциями
Чтобы выполнить Bash команду с опциями, например, ls — la нужно добавить shell = True
import subprocess subprocess.run( ‘ls -la’ , shell = True )
У использования shell = True есть одна важная особенность: нужно особенно внимательно следить за безопастностью.
Рекомендуется использовать shell = True только если вы передаёте параметры самостоятельно.
Избежать использования shell = True можно передав команду и параметры списком:
import subprocess subprocess.run([ ‘ls’ , ‘-la’ ])
Передать переменную в аргумент команды
По аналогии с предыдущим параграфом — в качестве аргумента можно использовать и переменную
import subprocess text = «Visit TopBicycle.ru to support my website» subprocess.run([ «echo» , text])
Visit TopBicycle.ru to support my website
args, returncode, stdout
Разберём subprocess более подробно
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ ]) print(«p1») print(p1) print(«p1.args») print(p1.args) print(«p1.returncode») print(p1.returncode) print(«p1.stdout») print(p1.stdout)
total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 17:57 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 17:57 .. -rw-rw-r— 1 andrei andrei 195 Nov 30 16:51 subprocess_lesson.py p1 CompletedProcess(args= ‘ls -la’ , returncode=0) p1.args ls -la p1.returncode 0 p1.stdout None
Чтобы не выводить результат в терминал а сохранить в переменную, нужно воспользоваться опцией capture_output = True — доступна, начиная с версии Python 3.7
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ ], capture_output = True ) print(p1.stdout)
b’total 12\ndrwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 .\ndrwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 ..\n-rw-rw-r— 1 andrei andrei 92 Nov 30 18:41 subprocess_lesson.py\n’
Если byte вывод вам не нравится его можно декодировать
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ ], capture_output = True ) print(p1.stdout.decode())
total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 .. -rw-rw-r— 1 andrei andrei 101 Nov 30 18:46 subprocess_lesson.py
Или можно использовать опцию text=True
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ ], capture_output = True , text=True) print(p1.stdout)
total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 .. -rw-rw-r— 1 andrei andrei 101 Nov 30 18:46 subprocess_lesson.py
Ещё один вариант перенаправления вывода stdout=subprocess.PIPE
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ ], stdout=subprocess.PIPE, text=True) print(p1.stdout)
total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 .. -rw-rw-r— 1 andrei andrei 101 Nov 30 18:46 subprocess_lesson.py
import subprocess with open(‘output.txt’, ‘w’) as f: p1 = subprocess.run([ ‘ls’ , ‘-la’ ], stdout=f, text=True)
Обработка ошибок
Добавим заведомо неверное условие в команду. Например, пусть листинг выполняется не для текущей директории а для несуществующей.
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ , ‘not_exist’], capture_output = True , text=True) print(p1.returncode) print(p1.stderr)
2 ls: cannot access ‘not_exist’: No such file or directory
Обратите внимане, что Python в этом примере не выдаёт никаких ошибок
Чтобы Python информировал об ошибках во внешних командах используйте опцию check = True
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ , ‘not_exist’], capture_output = True , text=True, check = True ) print(p1.returncode) print(p1.stderr)
Traceback (most recent call last): File «subprocess_lesson.py», line 3, in p1 = subprocess.run([ ‘ls’ , ‘-la’ , ‘not_exist’], capture_output = True , text=True, check = True ) File «/usr/lib/python3.8/subprocess.py», line 512, in run raise CalledProcessError(retcode, process.args, subprocess.CalledProcessError: Command ‘[ ‘ls’ , ‘-la’ , ‘not_exist’]’ returned non-zero exit status 2.
Обратите внимане, что теперь Python выдаёт ошибку, а до print(p1.returncode) и print(p1.stderr) дело уже не доходит
import subprocess p1 = subprocess.run([ ‘ls’ , ‘-la’ , ‘not_exist’], stderr=subprocess.DEVNULL) print(p1.stderr)
Передача аргументов в скрипт
Допустим, нужно вызвать скрипт с несколькими аргументами
import subprocess subprocess.call([‘./script.sh %s %s %s’ %(ip, username, test_time)], shell = True )
Ещё пример: из python скрипта вызвать sed и обрезать число строк, которое сам скрипт получает как аргумент
import subprocess LINES = int(sys.argv[1]) subprocess.call([‘sed -i -e 1,%sd 2023-07-17-log.txt’ %(LINES)], shell = True )
Эту задачу можно решить на чистом Python решение
with open(‘file_with_lines.txt’, ‘r’) as fin: data = fin.readlines()[3:] with open(‘file_with_lines.txt’, ‘w’) as fout: fout.writelines(data)
Логи с помощью subprocess
Если запускать код в какой-то среде, где лог в файл неудобен а лог с помощью print невозможен, можно использовать echo из bash
import subprocess text = «Andrei Log: robot/src/libraries/TestController.py is running» subprocess.run ([ «echo» , text ])
Сравнить два файла
Если запускать код в какой-то среде, где лог в файл неудобен а лог с помощью print невозможен, можно использовать echo из bash
import subprocess def compare (file1 , file2): subprocess.run([ «diff» , file1 , file2])
Определить версию Linux
С помощью subprocess можно в том числе определить версию Linux
import subprocess import sys CENTOS = < "os_name" : "CentOS" , "cmd" : "rpm --eval %" > REDHAT = < "os_name" : "Red" , "cmd" : "rpm --eval %" > ROCKY = < "os_name" : "Rocky" , "cmd" : "rpm --eval %" > UBUNTU = < "os_name" : "Ubuntu" , "cmd" : "cat /etc/issue" >OS_LIST = [CENTOS, REDHAT, ROCKY, UBUNTU] def find_os () -> object : try : p = subprocess.run([ «lsb_release» , «-a» ], capture_output= True , text= True ) except Exception as e: print (f «lsb_release -a call failed: » , file =sys.stderr) raise system_release = str (p.stdout) + str (p.stderr) system_release = system_release.split() for os in OS_LIST: name = os[ «os_name» ] if name in system_release: break else : os = None return os def get_name (os) -> str : name = os[ «os_name» ] return name def get_version (os) -> str : cmd = os[ «cmd» ] cmd = cmd.split() p = subprocess.run(cmd, capture_output= True , text= True ) version = str (p.stdout) try : version = int (version) except : version = version.split() version = version[ 1 ] return version def get_linux_version () -> tuple : os = find_os() if os is not None : name = get_name(os) version = get_version(os) linux_version = (name, version) else : print ( «os is not found» ) linux_version = ( None , None ) return linux_version if __name__ == ‘__main__’ : print (get_linux_version())