Если вы когда-либо писали скрипты для автоматизации на Linux, то наверняка сталкивались с модулем subprocess. Это мощный инструмент, но его синтаксис часто называют громоздким, а обработку потоков вывода — неинтуитивной. Вы когда-нибудь ловили себя на мысли: «Почему нельзя просто вызвать команду как функцию?»
Хорошая новость: можно. Библиотека sh (также известная как python-sh) создана именно для этого. На текущий момент (февраль 2026 года) это одно из самых элегантных решений для интеграции системных команд в код Python.
В этой статье мы разберем, почему начинающим (да и опытным) разработчикам стоит обратить внимание на sh вместо subprocess, и как использовать её для повседневной работы с Linux-сервером.
Что такое sh и почему это лучше subprocess?
Библиотека sh — это не набор заново реализованных команд, а полноценная "обёртка" (wrapper). Она позволяет вызывать любые программы в вашем системном PATH так, словно это обычные Python-функции.
Сравните два подхода:
Классический subprocess:
import subprocess
result = subprocess.run(['ls', '-l', '/home'], capture_output=True, text=True)
print(result.stdout)Магия sh:
from sh import ls
print(ls("-l", "/home"))Уже на этом примере видно, что код стал чище и читаемее. Вы просто работаете с командой, а не с процессом.
Установка: всё стандартно
Поскольку мы говорим о работе на Linux (а sh поддерживает Linux, macOS и BSD, но не поддерживает Windows), установка проходит штатно через pip:
pip install shДля актуальных версий Python (3.8 — 3.12) библиотека полностью совместима и активно поддерживается.
Основы: вызываем команды как функции
После импорта любая системная команда становится доступной.
Простой пример с git:
from sh import git
# Вызов git status
status = git("status")
print(status)
# Вызов git log --oneline
log = git.log("--oneline")
print(log)Обратите внимание на второй пример: git.log("--oneline"). Точечная нотация позволяет обращаться к дочерним командам (субкомандам) — это невероятно удобно для работы с такими инструментами, как git, systemctl или docker.
Работа с аргументами и флагами
Одна из самых частых задач — передача аргументов. sh понимает и позиционные аргументы, и ключевые слова.
Длинные и короткие флаги
Допустим, нам нужно скачать файл с помощью curl. В bash это было бы:
curl -o page.html --silent http://example.comВ sh это превращается в:
from sh import curl
curl("http://example.com", o="page.html", silent=True)
# Или более традиционно:
curl("http://example.com", "-o", "page.html", "--silent")Правила просты:
- Короткий флаг (-o) передаётся как именованный аргумент o="имя".
- Длинный флаг (--silent) передаётся как silent=True.
Автоматическое преобразование имен
В Python имена переменных не могут содержать дефисы. Но команды Linux их обожают (например, --no-create-home). sh умно решает эту проблему: дефисы в именах аргументов заменяются на подчеркивания.
from sh import adduser
# В терминале: adduser amoffat --system --shell=/bin/bash --no-create-home
adduser("amoffat", system=True, shell="/bin/bash", no_create_home=True)Библиотека автоматически преобразует no_create_home в --no-create-home.
Пайпы (конвейеры) как композиция функций
В Shell символ | (пайп) передает вывод одной программы на ввод другой. В sh это реализовано через композицию функций с помощью аргумента _in.
Хотите узнать, сколько файлов в директории /etc?
from sh import ls, wc
# Аналог: ls -1 /etc | wc -l
count = wc(ls("-1", "/etc"), "-l")
print(count)Или более сложный пример: найти 10 самых больших файлов в текущей директории.
from sh import du, sort, head
# du -sb * | sort -rn | head -10
result = head(sort(du("-sb", "*"), "-rn"), "-n", "10")
print(result)Это читается почти как английский язык.
Перенаправление вывода (Redirection)
В subprocess чтобы сохранить вывод в файл, нужно открывать файл и работать с хендлерами. В sh это делается через специальные именованные аргументы: _out (stdout) и _err (stderr).
Сохраняем в файл
from sh import ls
# Сохраняем список файлов в documents.txt
ls("-l", "/home/user/documents", _out="documents.txt")Работа с файловыми объектами Python
Вы можете передать не только строку с именем файла, но и любой файлоподобный объект:
from sh import ifconfig
from io import StringIO
buffer = StringIO()
ifconfig(_out=buffer)
print("Команда выполнена, результат в буфере:", buffer.getvalue())Объединение потоков
Если нужно, чтобы stderr писался туда же, куда и stdout (как 2>&1 в bash), используйте _err_to_out=True:
from sh import find
find("/root", name="config", _out="search.log", _err_to_out=True)Фоновые задачи (Background Processes)
Если команда выполняется долго, можно запустить её в фоне с помощью _bg=True. Это позволяет скрипту не ждать её завершения, а делать другие дела.
from sh import sleep
# Запускаем таймер на 10 секунд в фоне
p = sleep(10, _bg=True)
print("Спим в фоне, а мы выполняем другой код...")
# Делаем что-то ещё
print("Считаем овец...")
# В какой-то момент ждем завершения фоновой задачи
p.wait()
print("10 секунд прошло!")Объект p, который возвращается, позволяет управлять процессом: ждать его (wait()), проверить, запущен ли он, или получить статус выхода.
Магия Baking: готовим команды заранее
"Выпекание" (baking) — одна из killer-фич библиотеки. Она позволяет зафиксировать часть аргументов для команды, создавая новый вызываемый объект. Это похоже на functools.partial, но для shell-команд.
Представьте, что вы часто подключаетесь по SSH к удаленному серверу:
from sh import ssh
# Обычный вызов (каждый раз писать порт и юзера - утомительно)
ssh("myserver.com", "-p", "2222", "whoami")
# Используем baking
remote = ssh.bake("myserver.com", p=2222)
# Теперь remote — это готовая команда ssh с запеченными параметрами
whoami = remote.whoami()
uptime = remote.uptime()
remote.tail("/var/log/nginx/access.log", n=50)Код стал не только короче, но и выразительнее. Мы работаем с объектом remote, вызывая его методы.
Ещё пример: работа в определенной директории
from sh import ls
ls_custom = ls.bake("-la") # Теперь ls_custom всегда вызывает ls -la
print(ls_custom("/var/log")) # Выполнится ls -la /var/logОбработка ошибок (Exit Codes)
В мире Linux ненулевой код возврата команды обычно означает ошибку. sh превращает это в исключения, что делает обработку ошибок более питоничной.
from sh import ls, ErrorReturnCode
try:
ls("/some/non/existent/folder")
except ErrorReturnCode as e:
print(f"Команда упала с кодом {e.exit_code}")
print(f"Stdout: {e.stdout}")
print(f"Stderr: {e.stderr}")Вы даже можете ловить конкретные коды ошибок, например ErrorReturnCode_2 (код 2 часто означает "нет такого файла").
Интерактивный режим и Foreground
Некоторые программы (текстовые редакторы, htop) требуют полного контроля над терминалом. Если вы попытаетесь запустить vim обычным способом, скрипт зависнет, пытаясь агрегировать вывод. Для таких случаев используйте _fg=True (foreground) .
from sh import vim
# Редактор откроется как обычно, а Python будет ждать его закрытия
vim("/home/user/script.py", _fg=True)Это передает управление терминалом дочернему процессу.
Итоговый пример: автоматизация бэкапа
Давайте соберем всё вместе и напишем небольшой скрипт для создания архива и его отправки на удаленный сервер.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sh
from datetime import datetime
# 1. Задаем переменные
BACKUP_DIR = "/var/www/site"
ARCHIVE_NAME = f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.tar.gz"
REMOTE_SERVER = "backup@example.com"
REMOTE_PATH = "/backups/"
# 2. Создаем архив (проверяем ошибки)
print("Создание архива...")
try:
# Используем команду tar с пайпом к gzip? Нет, просто tar с флагом -z
sh.tar("-czf", ARCHIVE_NAME, BACKUP_DIR, _out="archive.log", _err_to_out=True)
print(f"Архив {ARCHIVE_NAME} создан.")
except sh.ErrorReturnCode as e:
print(f"Ошибка при создании архива: {e.stderr}")
exit(1)
# 3. "Выпекаем" команду scp для удобства
print("Отправка архива на сервер...")
scp_to_remote = sh.scp.bake(ARCHIVE_NAME, f"{REMOTE_SERVER}:{REMOTE_PATH}")
try:
# Можно было бы добавить ключи, например, port=2222, если нужно
scp_to_remote()
print("Архив успешно отправлен.")
except sh.ErrorReturnCode:
print("Не удалось отправить файл. Проверьте соединение или пути.")
exit(1)
print("Бэкап завершен!")Согласитесь, такой код читается как хорошая инструкция, а не как набор низкоуровневых вызовов.
Заключение
Библиотека sh — это блестящий пример того, как можно улучшить взаимодействие Python с операционной системой. Для начинающих программистов она предлагает:
- Интуитивный синтаксис. Не нужно думать в терминах процессов и пайпов, думайте в терминах функций и объектов.
- Меньше кода. Забудьте про бесконечные
subprocess.Popenиcommunicate(). - Питоничный подход. Исключения, именованные аргументы, композиция — всё это делает код надежнее.
Конечно, subprocess останется стандартом и иногда нужен для очень специфичных сценариев. Но для 90% повседневных задач автоматизации на Linux библиотека sh (текущая версия 2.x) является более чистым и продуктивным выбором .
Попробуйте заменить сегодня один из ваших старых скриптов на sh, и вы почувствуете разницу. Установка простая (pip install sh), а документация на Read the Docs подробная и понятная.