Jak uruchamiać skrypty bash za pomocą Pythona?

Użytkownicy systemu Linux z pewnością docenią potęgę poleceń powłoki.

Osoby pracujące z językiem Python często poszukują sposobów na automatyzację zadań, co pozwala zaoszczędzić cenny czas. Czasem do tego celu wykorzystywane są skrypty bash.

Jednak Python oferuje bardziej efektywne narzędzia do tworzenia skryptów niż bash. Ponadto, zarządzanie skryptami w Pythonie jest znacznie prostsze, szczególnie gdy projekty rozrastają się. Utrzymanie dużych skryptów bash może stać się skomplikowane.

Co jednak zrobić, gdy posiadamy już gotowe skrypty bash, które chcemy wywoływać z poziomu Pythona?

Czy istnieje metoda na uruchamianie poleceń i skryptów bash w programach napisanych w Pythonie?

Oczywiście! Python dysponuje wbudowanym modułem o nazwie `subprocess`, który umożliwia wykonywanie poleceń i skryptów bezpośrednio w skryptach Pythona. Przyjrzyjmy się bliżej, jak to działa.

Realizacja poleceń Bash

Jak już wspomniano, moduł `subprocess` jest kluczowy do uruchamiania poleceń i skryptów bash. Oferuje on różnorodne metody i klasy, które to umożliwiają.

W kontekście modułu `subprocess` warto zwrócić uwagę na jedną metodę i jedną klasę: `run` oraz `Popen`. To właśnie one umożliwiają nam wywoływanie poleceń bash z poziomu skryptów Pythona. Przeanalizujmy je po kolei.

`subprocess.run()`

Metoda `subprocess.run()` przyjmuje listę ciągów znaków jako argument pozycyjny. Lista ta zawiera polecenie bash wraz z jego ewentualnymi argumentami. Pierwszy element listy to nazwa polecenia, a pozostałe to argumenty.

Spójrzmy na prosty przykład:

import subprocess
subprocess.run(["ls"])

Powyższy skrypt wyświetli zawartość bieżącego katalogu roboczego, co jest zgodne z działaniem polecenia `ls`. W tym przypadku nie podaliśmy żadnych argumentów, jedynie samo polecenie. Możemy jednak rozszerzyć to o argumenty polecenia `ls`, takie jak `-l`, `-a`, `-la` itp.

Oto przykład z argumentami:

import subprocess
subprocess.run(["ls", "-la"])

Powyższe polecenie wyświetli wszystkie pliki i katalogi, w tym ukryte, wraz z dodatkowymi informacjami o uprawnieniach. Argument `-la` rozszerza działanie polecenia `ls` o wyświetlanie plików ukrytych i szczegółów.

Podczas pisania poleceń mogą pojawić się błędy. Domyślnie, błędy te spowodują przerwanie działania programu. Jak jednak je przechwycić i wykorzystać? Możemy to zrobić za pomocą argumentu słowa kluczowego `stderr`.

Spójrzmy na ten przykład:

import subprocess
result = subprocess.run(["cat", "sample.txt"], stderr=subprocess.PIPE, text=True)
print(result.stderr)

Upewnij się, że w katalogu roboczym nie istnieje plik o nazwie `sample.txt`. Wartość argumentu `stderr` ustawiona na `PIPE` sprawia, że błąd zostanie zwrócony w obiekcie. Możemy uzyskać do niego dostęp za pomocą atrybutu `stderr`. Argument tekstowy `text=True` zapewnia, że wynik będzie ciągiem znaków.

Podobnie, możemy przechwycić standardowe wyjście polecenia, wykorzystując argument słowa kluczowego `stdout`:

import subprocess
result = subprocess.run(["echo", "Hello, World!"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.stdout)

`subprocess.run()` – przekazywanie wejścia

Możliwe jest przekazywanie danych wejściowych do poleceń za pomocą argumentu słowa kluczowego `input`. Dane te przekazywane są w formacie ciągu znaków, dlatego ważne jest, aby ustawić `text=True`. Domyślnie dane wejściowe są traktowane jako bajty.

Oto przykład:

import subprocess
subprocess.run(["python3", "add.py"], text=True, input="2 3")

W tym przypadku, skrypt Pythona `add.py` oczekuje dwóch liczb jako danych wejściowych. Przekazaliśmy je za pomocą argumentu `input`.

`subprocess.Popen()`

Klasa `subprocess.Popen()` oferuje bardziej zaawansowane możliwości niż metoda `subprocess.run()`. Umożliwia większą kontrolę nad procesem, na przykład sprawdzenie stanu wykonania polecenia, uzyskanie jego wyjścia czy podawanie wejścia.

Klasa `subprocess.Popen()` posiada kilka metod, które warto poznać. Przeanalizujmy je krok po kroku, na przykładach:

`wait()`

Metoda `wait()` służy do zatrzymania wykonania skryptu do czasu zakończenia polecenia. Kolejne linie kodu nie zostaną wykonane, dopóki polecenie nie zakończy swojej pracy. Spójrzmy na przykład:

import subprocess
process = subprocess.Popen(["ls", "-la"])
print("Completed!")

Jeśli uruchomimy powyższy kod, zobaczymy, że komunikat „Completed!” zostanie wypisany przed wyświetleniem wyniku polecenia `ls`. Możemy uniknąć tej sytuacji, korzystając z metody `wait()`:

import subprocess
process = subprocess.Popen(["ls", "-la"])
process.wait()

print("Completed!")

Teraz, instrukcja `print` zostanie wykonana dopiero po zakończeniu wykonywania polecenia `ls`.

`communicate()`

Metoda `communicate()` służy do uzyskiwania danych wyjściowych i błędów polecenia oraz do przekazywania mu danych wejściowych. Zwraca krotkę, zawierającą odpowiednio standardowe wyjście i standardowy strumień błędów. Spójrzmy na przykład:

import subprocess
process = subprocess.Popen(["echo", "Hello, World!"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result = process.communicate()
print(result)

`subprocess.Popen()` – przekazywanie wejścia

W przypadku klasy `Popen`, nie możemy bezpośrednio przekazać danych wejściowych. Musimy użyć argumentu kluczowego `stdin`, który udostępnia obiekt `stdin`. Ten obiekt posiada metodę `write`, służącą do wprowadzania danych.

Jak wspomniano wcześniej, domyślnie, dane wejściowe są traktowane jako obiekty bajtowe. Dlatego musimy pamiętać o ustawieniu `text=True` podczas tworzenia instancji `Popen`.

Spójrzmy na przykład:

import subprocess
process = subprocess.Popen(["python3", "add.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
process.stdin.write("2 3")
process.stdin.close()
print(process.stdout.read())

`poll()`

Metoda `poll()` służy do sprawdzenia, czy polecenie zakończyło już swoje działanie. Jeśli polecenie wciąż jest wykonywane, metoda zwróci wartość `None`. Spójrzmy na przykład:

import subprocess
process = subprocess.Popen(['ping', '-c 5', 'geekflare.com'], stdout=subprocess.PIPE, text=True)
while True:
    output = process.stdout.readline()
    if output:
    	print(output.strip())
    result = process.poll()
    if result is not None:
        break

Powyższy kod wykorzystuje polecenie `ping` z pięcioma żądaniami. Pętla `while` iteruje do momentu, gdy polecenie zostanie zakończone. Metoda `poll` sprawdza status wykonania polecenia. Gdy metoda `poll` zwróci wartość inną niż `None`, pętla zostaje przerwana.

Wykonywanie skryptów Bash

Omówiliśmy już dwa sposoby wykonywania poleceń. Teraz przejdźmy do uruchamiania skryptów bash w skryptach Pythona.

Moduł `subprocess` udostępnia metodę o nazwie `call`, która służy do wykonywania skryptów bash. Metoda ta zwraca kod wyjścia ze skryptu bash. Domyślny kod wyjścia to `0`. Spójrzmy na przykład:

Utwórz skrypt bash o nazwie `practice.sh` z następującą zawartością:

#!/bin/bash

echo "Hello, World!"
exit 1

Teraz napisz skrypt Pythona, który uruchomi powyższy skrypt bash:

import subprocess
exit_code = subprocess.call('./practice.sh')
print(exit_code)

Po uruchomieniu skryptu Pythona, uzyskasz następujący rezultat:

Hello, World!
1

Podsumowanie

W artykule omówiliśmy, jak wykonywać polecenia i skrypty bash w języku Python. Dzięki tym metodom, proces automatyzacji zadań stanie się o wiele efektywniejszy.

Udanej pracy z kodem! 👨‍💻