Hur kör man bash-skript med Python?

By rik

Om du är en Linux-användare, kommer du säkert att uppskatta kraften i skal-kommandon.

Och om du arbetar med Python, kanske du har utforskat möjligheterna till automatisering. Det är ett utmärkt sätt att spara tid. Du kanske även har använt bash-skript för att automatisera olika uppgifter.

Python är ofta ett mer smidigt verktyg för skriptskrivning jämfört med bash. Hantering av Python-skript upplevs som enklare än att underhålla bash-skript, speciellt när de blir mer komplexa.

Men, vad händer om du redan har befintliga bash-skript som du vill integrera med din Python-kod?

Finns det en möjlighet att köra bash-kommandon och skript direkt inifrån Python?

Absolut, Python erbjuder en inbyggd modul, `subprocess`, som är utformad för att hantera just detta – att köra kommandon och skript i Python-skript. Låt oss undersöka närmare hur man kan använda den för att exekvera bash-kommandon och skript.

Exekvera bash-kommandon

Som du kanske redan har gissat, är det `subprocess`-modulen som vi vänder oss till för att köra bash-kommandon och skript. Modulen tillhandahåller olika metoder och klasser för detta ändamål.

I huvudsak finns det en metod och en klass som är viktiga att känna till inom `subprocess`-modulen: `run` och `Popen`. Båda dessa gör det möjligt för oss att exekvera bash-kommandon direkt i våra Python-skript. Låt oss granska dem var för sig.

`subprocess.run()`

Metoden `subprocess.run()` accepterar en lista av strängar som ett positionsargument. Detta argument är obligatoriskt och innehåller själva bash-kommandot och eventuella argument till kommandot. Det första elementet i listan är namnet på kommandot, medan de övriga elementen är argumenten som skickas till kommandot.

Låt oss titta på ett enkelt exempel.

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

Det ovanstående skriptet listar alla objekt i den aktuella arbetskatalogen där skriptet finns. I det här fallet har kommandot inga extra argument. Vi har endast angett själva bash-kommandot. Men vi kan lika gärna lägga till argument till `ls`-kommandot, som till exempel `-l`, `-a`, `-la` osv.

Här är ett exempel som använder argument till kommandot:

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

Det här kommandot listar alla filer, inklusive dolda filer, och visar även filernas rättigheter. Genom argumentet `la` specificerar vi att vi vill ha detaljerad information och att även dolda filer ska inkluderas.

Ibland kan misstag ske när vi skriver kommandona. Fel kommer att dyka upp i takt med misstagen. Vad händer om vi vill fånga dessa fel och använda dem senare? Det är möjligt med hjälp av sökordsargumentet `stderr`.

Låt oss se på ett exempel:

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

Se till att filen med namnet `sample.txt` inte finns i din arbetskatalog. Värdet på nyckelordsargumentet `stderr` är `PIPE`, vilket gör att felet returneras i ett objekt. Vi kan sedan komma åt det senare med samma namn. Och nyckelordsargumentet `text` talar om för Python att utdata ska tolkas som en sträng.

På samma sätt kan vi även fånga resultatet av kommandot med hjälp av nyckelordsargumentet `stdout`.

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

`subprocess.run()` – Inmatning

Du kan mata in data till kommandona med hjälp av nyckelordsargumentet `input`. Vi kommer att ange inmatningen i strängformat. Därför måste vi också ställa in nyckelordsargumentet `text` till `True`, eftersom det annars hanteras som bytes.

Här är ett exempel:

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

I programmet ovan kommer Python-skriptet `add.py` att ta två siffror som inmatning. Vi skickar inmatningen till Python-skriptet med hjälp av nyckelordsargumentet `input`.

`subprocess.Popen()`

Klassen `subprocess.Popen()` är mer avancerad än metoden `subprocess.run()`. Den ger fler möjligheter för att hantera exekvering av kommandona. Vi kommer att skapa en instans av `subprocess.Popen()` och använda den för att utföra olika operationer, som att få information om kommandoexekveringens status, hämta utdata, ge inmatning, etc.

Det finns ett flertal metoder för klassen `subprocess.Popen()` som är viktiga att känna till. Låt oss gå igenom dem en efter en, med tillhörande kodexempel.

`wait()`

Denna metod används för att pausa exekveringen av Python-skriptet tills det anropade kommandot har slutfört sitt arbete. Följande rader i skriptet kommer inte att köras förrän kommandot, vars exekvering inväntas med `wait()`, är färdigt. Se exemplet nedan:

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

Om du kör koden ovan kommer du att märka att meddelandet ”Färdig!” skrivs ut innan kommandot är färdigt. Vi kan undvika det här beteendet genom att använda metoden `wait()`. Låt oss vänta på att kommandot ska avslutas:

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

print("Färdig!")

Om du nu ser utdata från koden, inser du att `wait()` fungerar som den ska. Utskriftssatsen exekveras efter att kommandot har slutförts.

`communicate()`

Metoden `communicate` används för att hämta utdata och fel från kommandot, samt för att ge inmatning. Den returnerar en tupel som innehåller utdatan och eventuella fel. Låt oss se ett exempel:

import subprocess
process = subprocess.Popen(["echo", "Hej, Världen!"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result = process.communicate()
print(result)

`subprocess.Popen()` – Inmatning

Vi kan inte skicka inmatning till klassen `Popen` direkt. Vi måste använda nyckelordsargumentet `stdin` för att ge inmatning till kommandot. Instansen av klassen `Popen` kommer då att förse oss med ett `stdin`-objekt. Detta objekt har en metod som heter `write` som används för att skicka inmatning till kommandot.

Som tidigare nämnt, accepterar `stdin` som standard inmatning i byte-format. Så glöm inte att ställa in nyckelordsargumentet `text` till `True` när du skapar instansen av `Popen`.

Låt oss se ett exempel:

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()`

Metoden `poll` används för att kontrollera om kommandots exekvering är klar eller inte. Den här metoden returnerar `None` om kommandot fortfarande körs. Låt oss se ett exempel:

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

I koden ovan har vi använt kommandot `ping` med 5 förfrågningar. Det finns en oändlig loop som fortsätter tills kommandots exekvering är klar. Vi har använt metoden `poll` för att kontrollera statusen på kommandoexekveringen. Om metoden `poll` returnerar ett annat värde än `None`, betyder det att exekveringen är klar. Då avbryts den oändliga loopen.

Exekvera Bash-skript

Vi har sett två sätt att utföra kommandon. Låt oss nu se hur man kan köra bash-skript i Python-skript.

Modulen `subprocess` innehåller en metod som kallas `call`. Denna metod används för att köra bash-skript. Metoden returnerar utgångskoden från bash-skriptet. Standard utgångskoden för bash-skript är 0. Låt oss se ett exempel.

Skapa ett bash-skript med namnet `practice.sh` enligt följande:

#!/bin/bash

echo "Hej, Världen!"
exit 1

Skriv nu ett Python-skript och kör bash-skriptet ovan:

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

När du kör Python-skriptet ovan kommer du att få följande utdata:

Hej, Världen!
1

Slutsats

Vi har nu undersökt hur man kör bash-kommandon och skript i Python. Du kan använda dessa tekniker för att automatisera dina arbetsflöden mer effektivt.

Lycka till med kodningen! 👨‍💻