Модуль subprocess¶
Модуль subprocess дозволяє створювати нові процеси. При цьому він може підключатися до стандартних потоків введення/виведення та отримувати код повернення.
За допомогою subprocess можна, наприклад, виконувати будь-які команди Linux зі скрипту. І залежно від ситуації отримувати виведення чи лише перевіряти, що команда виконалася без помилок.
Note
Цей модуль призначений для заміни кількох старих модулів і функцій:
os.system
os.spawn*
Рекомендований варіант роботи з модулем, це використання функції run для всіх випадків використання, які вона може обробляти. Для більш складних випадків можна використовувати Popen (функція run всередині також використовує Popen).
Функція subprocess.run
¶
Функція subprocess.run
– основний спосіб роботи з модулем subprocess.
Синтаксис функції run:
In [5]: subprocess.run?
Signature:
subprocess.run(
*popenargs,
input=None,
capture_output=False,
timeout=None,
check=False,
**kwargs,
)
Функція subprocess.run
виконує команду описану аргументами popenargs, чекає,
поки команда завершиться і повертає екземпляр класу CompletedProcess.
У сигнатурі функції subprocess.run зазначена тільки частина параметрів для
налаштування роботи функції. Всі інші параметри заховані в **kwargs
- тут
можна вказувати будь-які параметри Popen.
В документації в описі функції зазначено більше параметрів, але для повного переліку все ж треба звернутися до опису Popen:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs)
Найпростіший варіант використання функції – запуск її таким чином:
In [1]: import subprocess
In [2]: result = subprocess.run('ls')
ipython_as_mngmt_console.md README.md version_control.md
module_search.md useful_functions
naming_conventions useful_modules
У змінній результат тепер міститься спеціальний об'єкт CompletedProcess. З цього об'єкта можна отримати інформацію про виконання процесу, наприклад, про код повернення (це код, який вказує статус завершення роботи, в найпростішому випадку, вдалий чи ні):
In [3]: result
Out[3]: CompletedProcess(args='ls', returncode=0)
In [4]: result.returncode
Out[4]: 0
Код 0 означає, що програма виконалася успішно.
Warning
Всі команди які передаються subprocess виконуються на Debian. Якщо ви використовуєте іншу ОС, потрібно змінити команди на відповідні для ОС.
Якщо потрібно викликати команду з аргументами, її потрібно передавати таким чином (як список або будь-який інший ітерований об'єкт):
In [15]: subprocess.run(['ls', '-l'])
total 32
-rw-r--r-- 1 vagrant vagrant 66 Jun 26 2023 basics_01_ping.py
-rw-r--r-- 1 vagrant vagrant 359 Jun 26 2023 basics_02_ping_func.py
-rw-r--r-- 1 vagrant vagrant 693 Jun 26 2023 basics_03_popen_ping_func.py
-rw-r--r-- 1 vagrant vagrant 758 Jun 26 2023 basics_04_popen_ping_func_zip.py
-rw-r--r-- 1 vagrant vagrant 688 Jun 26 2023 basics_05_popen_ping_func_zip.py
-rw-r--r-- 1 vagrant vagrant 384 Jun 26 2023 basics_06_rich_progress_ping_func.py
-rw-r--r-- 1 vagrant vagrant 487 Jun 26 2023 basics_07_rich_colors_ping_func.py
drwxr-xr-x 2 vagrant vagrant 4096 Jun 26 2023 ipaddress
Out[15]: CompletedProcess(args=['ls', '-l'], returncode=0)
При спробі виконати команду з використанням wildcard-виразів, наприклад, якщо
використати *
, виникне помилка:
In [20]: subprocess.run(['ls', '-ls', '*rich*py'])
ls: cannot access '*rich*py': No such file or directory
Out[20]: CompletedProcess(args=['ls', '-ls', '*rich*py'], returncode=2)
Для того щоб викликати команди, в яких використовуються wildcard-вирази, потрібно додавати аргумент shell і викликати команду таким чином (всю команду пишемо в середині одного рядка):
In [22]: subprocess.run('ls -ls *rich*py', shell=True)
4 -rw-r--r-- 1 vagrant vagrant 384 Jun 26 2023 basics_06_rich_progress_ping_func.py
4 -rw-r--r-- 1 vagrant vagrant 487 Jun 26 2023 basics_07_rich_colors_ping_func.py
Out[22]: CompletedProcess(args='ls -ls *rich*py', returncode=0)
Ще одна особливість функції run - вона очікує на завершення виконання команди. Якщо спробувати, наприклад, запустити команду ping, цей аспект буде більш помітний:
In [26]: subprocess.run(['ping', '-c', '3', '-n', '8.8.8.8'])
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=23.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=114 time=24.6 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=114 time=29.8 ms
--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2012ms
rtt min/avg/max/mdev = 23.467/25.974/29.836/2.770 ms
Out[26]: CompletedProcess(args=['ping', '-c', '3', '-n', '8.8.8.8'], returncode=0)
Отримання результату виконання команди¶
За замовчуванням функція run виводить результат виконання команди на стандартний потік виведення.
In [5]: subprocess.run('ls')
basics_01_ping.py basics_05_popen_ping_func_zip.py
basics_02_ping_func.py basics_06_rich_progress_ping_func.py
basics_03_popen_ping_func.py basics_07_rich_colors_ping_func.py
basics_04_popen_ping_func_zip.py ipaddress
Out[5]: CompletedProcess(args='ls', returncode=0)
In [7]: result = subprocess.run('ls')
basics_01_ping.py basics_05_popen_ping_func_zip.py
basics_02_ping_func.py basics_06_rich_progress_ping_func.py
basics_03_popen_ping_func.py basics_07_rich_colors_ping_func.py
basics_04_popen_ping_func_zip.py ipaddress
Якщо результат виконання команди треба зберегти для подальшої обробки, можна
використовувати параметр capture_output
:
Тепер можна отримати результат виконання команди таким чином:
In [9]: result.stdout
Out[9]: b'basics_01_ping.py\nbasics_02_ping_func.py\nbasics_03_popen_ping_func.py\nbasics_04_popen_ping_func_zip.py\nbasics_05_popen_ping_func_zip.py\nbasics_06_rich_progress_ping_func.py\nbasics_07_rich_colors_ping_func.py\nipaddress\n'
Зверніть увагу на літеру b
перед рядком. Вона означає, що в атрибуті stdout
вивід у вигляді байтового рядка. Для переведення байтового рядка у звичайний
рядок є два варіанти:
- використовувати метод decode
- вказати аргумент encoding
Warning
Ні в якому разі не перетворюйте байтовий рядок у звичайний конвертацією через str
!
Варіант із decode:
In [11]: result.stdout.decode("utf-8")
Out[11]: 'basics_01_ping.py\nbasics_02_ping_func.py\nbasics_03_popen_ping_func.py\nbasics_04_popen_ping_func_zip.py\nbasics_05_popen_ping_func_zip.py\nbasics_06_rich_progress_ping_func.py\nbasics_07_rich_colors_ping_func.py\nipaddress\n'
Варіант із encoding:
In [12]: result = subprocess.run('ls', capture_output=True, encoding="utf-8")
In [13]: result.stdout
Out[13]: 'basics_01_ping.py\nbasics_02_ping_func.py\nbasics_03_popen_ping_func.py\nbasics_04_popen_ping_func_zip.py\nbasics_05_popen_ping_func_zip.py\nbasics_06_rich_progress_ping_func.py\nbasics_07_rich_colors_ping_func.py\nipaddress\n'
Також перехоплення стандартних потоків виведення та помилок можна робити за допомогою параметрів stdout/stderr (параметри capture_output та stdout/stderr не можна використовувати одночасно):
In [14]: result = subprocess.run('ls', stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8")
In [15]: result.stdout
Out[15]: 'basics_01_ping.py\nbasics_02_ping_func.py\nbasics_03_popen_ping_func.py\nbasics_04_popen_ping_func_zip.py\nbasics_05_popen_ping_func_zip.py\nbasics_06_rich_progress_ping_func.py\nbasics_07_rich_colors_ping_func.py\nipaddress\n'
Робота зі стандартним потоком помилок¶
Якщо команда була виконана з помилкою або не відпрацювала коректно, вивід команди потрапить на стандартний потік помилок.
Отримати цей вивід можна, якщо перехопити його через capture_output
або
stderr
, а в результаті звернутись до атрибуту stderr:
In [16]: result = subprocess.run(['ping', '-c', '3', '-n', 'a'], stderr=subprocess.PIPE, encoding='utf-8')
In [17]: result = subprocess.run(['ping', '-c', '3', '-n', 'a'], capture_output=True, encoding='utf-8')
In [18]: result
Out[18]: CompletedProcess(args=['ping', '-c', '3', '-n', 'a'], returncode=2, stdout='', stderr='ping: a: Name or service not known\n')
В обох випадках у result.stdout буде порожній рядок, а в result.stderr стандартний потік виводу:
In [19]: result.stderr
Out[19]: 'ping: a: Name or service not known\n'
In [21]: result.stdout
Out[21]: ''
In [22]: result.returncode
Out[22]: 2
Якщо потрібно перехопити та об'єднати обидва потоки (stdout та stderr) в один,
можна використовувати stdout=subprocess.PIPE
і stderr=subprocess.STDOUT
.
Приклад об'єднання потоків для команди з неуспішним статусом виконання (зверніть увагу на returncode):
In [24]: result = subprocess.run(
...: ['ping', '-c', '3', '-n', 'a'], encoding='utf-8',
...: stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
...: )
In [25]: result
Out[25]: CompletedProcess(args=['ping', '-c', '3', '-n', 'a'], returncode=2, stdout='ping: a: Name or service not known\n')
In [26]: result.stdout
Out[26]: 'ping: a: Name or service not known\n'
In [27]: result.stderr
Приклад об'єднання потоків для команди з успішним статусом виконання:
In [28]: result = subprocess.run(
...: ['ping', '-c', '3', '-n', '8.8.8.8'], encoding='utf-8',
...: stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
...: )
In [29]: result
Out[29]: CompletedProcess(args=['ping', '-c', '3', '-n', '8.8.8.8'], returncode=0, stdout='PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=24.3 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=114 time=24.8 ms\n64 bytes from 8.8.8.8: icmp_seq=3 ttl=114 time=25.9 ms\n\n--- 8.8.8.8 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2006ms\nrtt min/avg/max/mdev = 24.288/24.991/25.851/0.647 ms\n')
In [30]: result.stdout
Out[30]: 'PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=24.3 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=114 time=24.8 ms\n64 bytes from 8.8.8.8: icmp_seq=3 ttl=114 time=25.9 ms\n\n--- 8.8.8.8 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2006ms\nrtt min/avg/max/mdev = 24.288/24.991/25.851/0.647 ms\n'
In [31]: result.returncode
Out[31]: 0
Приклади використання модуля¶
Приклад використання модуля subprocess
import subprocess
reply = subprocess.run(['ping', '-c', '3', '-n', '8.8.8.8'])
if reply.returncode == 0:
print('Alive')
else:
print('Unreachable')
Результат виконання буде таким:
$ python subprocess_run_basic.py
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=43 time=54.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=54.4 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=43 time=53.9 ms
--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2005ms
rtt min/avg/max/mdev = 53.962/54.145/54.461/0.293 ms
Alive
Тут результат виконання команди виводиться на стандартний потік виведення.
Функція ping_ip перевіряє доступність IP-адреси та повертає True та stdout, якщо адреса доступна, або False та stderr, якщо адреса недоступна:
import subprocess
def ping_ip(host):
"""
Ping IP address and return:
On success - True
On failure - False
"""
reply = subprocess.run(['ping', '-c', '3', '-n', host],
capture_output=True,
encoding='utf-8')
if reply.returncode == 0:
return True
else:
return False
print(ping_ip('8.8.8.8'))
print(ping_ip('a'))
Результат виконання буде таким: