Bash-commando’s uitvoeren in Python

Op mijn lokale computer voer ik een python-script uit dat deze regel bevat

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

Dit werkt prima.

Vervolgens voer ik dezelfde code uit op een server en krijg ik de volgende foutmelding

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

Dus wat ik toen deed, is dat ik een print bashCommandinvoegde die me dan het commando in de terminal afdrukt voordat het wordt uitgevoerd met os.system().

Natuurlijk krijg ik opnieuw de fout (veroorzaakt door os.system(bashCommand)) maar voor die fout wordt de opdracht in de terminal afgedrukt. Daarna heb ik die uitvoer gekopieerd en in de terminal gekopieerd en op enter gedrukt en het werkt…

Heeft iemand een idee wat er aan de hand is?


Antwoord 1, autoriteit 100%

Gebruik os.systemniet. Het is afgeschaft ten gunste van subprocess. Uit de docs: “Deze module is bedoeld om verschillende oudere modules en functies te vervangen: os.system, os.spawn“.

Zoals in jouw geval:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

Antwoord 2, autoriteit 83%

Om de eerdere antwoorden hier enigszins uit te breiden, zijn er een aantal details die vaak over het hoofd worden gezien.

  • Voorkeur subprocess.run()boven subprocess.check_call()en vrienden boven subprocess.call()boven subprocess.Popen()over os.system()over os.popen()
  • Begrijp en gebruik text=True, ook bekend als universal_newlines=True.
  • Begrijp de betekenis van shell=Trueof shell=Falseen hoe het citeren en de beschikbaarheid van shell-gemakken verandert.
  • Begrijp de verschillen tussen shen Bash
  • Begrijp hoe een subproces los staat van zijn bovenliggende proces en in het algemeen het bovenliggende proces niet kan veranderen.
  • Vermijd het uitvoeren van de Python-interpreter als een subproces van Python.

Deze onderwerpen worden hieronder in meer detail behandeld.

Voorkeur voor subprocess.run()of subprocess.check_call()

De functie subprocess.Popen()is een werkpaard op laag niveau, maar het is lastig om het correct te gebruiken en je kopieert/plakt meerdere regels code … die handig al bestaan ​​in de standaardbibliotheek als een set wrapperfuncties op een hoger niveau voor verschillende doeleinden, die hieronder in meer detail worden gepresenteerd.

Hier is een paragraaf uit de documentatie:

De aanbevolen benadering voor het aanroepen van subprocessen is om de functie run()te gebruiken voor alle gebruikssituaties die het aankan. Voor meer geavanceerde toepassingen kan de onderliggende Popen-interface direct worden gebruikt.

Helaas verschilt de beschikbaarheid van deze wrapper-functies tussen Python-versies.

  • subprocess.run()is officieel geïntroduceerd in Python 3.5. Het is bedoeld om al het volgende te vervangen.
  • subprocess.check_output()werd geïntroduceerd in Python 2.7 / 3.1. Het is in principe gelijk aan subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call()is geïntroduceerd in Python 2.5. Het is in principe gelijk aan subprocess.run(..., check=True)
  • subprocess.call()is geïntroduceerd in Python 2.4 in de originele subprocess-module (PEP-324). Het is in principe gelijk aan subprocess.run(...).returncode

API op hoog niveau versus subprocess.Popen()

De gerefactoreerde en uitgebreide subprocess.run()is logischer en veelzijdiger dan de oudere legacy-functies die het vervangt. Het retourneert een CompletedProcess-object dat heeft verschillende methodes waarmee u de exitstatus, de standaardoutput en enkele andere resultaten en statusindicatoren uit het voltooide subproces kunt opvragen.

subprocess.run()is de juiste keuze als je gewoon een programma nodig hebt om te draaien en de controle terug te geven aan Python. Voor meer betrokken scenario’s (achtergrondprocessen, misschien met interactieve I/O met het Python-ouderprogramma) moet je nog steeds subprocess.Popen()gebruiken en zelf voor al het loodgieterswerk zorgen. Dit vereist een vrij ingewikkeld begrip van alle bewegende delen en mag niet lichtvaardig worden ondernomen. Het eenvoudigere Popen-objectvertegenwoordigt de (mogelijk nog steeds actief) proces dat moet worden beheerd vanuit uw code voor de rest van de levensduur van het subproces.

Misschien moet worden benadrukt dat alleen subprocess.Popen()slechts een proces creëert. Als je het daarbij laat, heb je een subproces dat gelijktijdig met Python draait, dus een “achtergrond”-proces. Als het geen invoer of uitvoer hoeft te doen of anderszins met u moet coördineren, kan het nuttig werk doen parallel met uw Python-programma.

Vermijd os.system()en os.popen()

Sinds de eeuwige tijd (nou ja, sinds Python 2.5) de osmodule documentatiebevat de aanbeveling om subprocesste verkiezen boven os.system():

De module subprocessbiedt krachtigere faciliteiten voor het spawnen van nieuwe processen en het ophalen van hun resultaten; het gebruik van die module heeft de voorkeur boven het gebruik van deze functie.

De problemen met system()zijn dat het duidelijk systeemafhankelijk is en geen manieren biedt om met het subproces te communiceren. Het werkt gewoon, met standaarduitvoer en standaardfout buiten het bereik van Python. De enige informatie die Python terugkrijgt is de exit-status van de opdracht (nul betekent succes, hoewel de betekenis van niet-nulwaarden ook enigszins systeemafhankelijk is).

PEP-324(die hierboven al werd genoemd) bevat een meer gedetailleerde reden waarom os.systemproblematisch is en hoe subprocessdeze problemen probeert op te lossen.

os.popen()was vroeger nog meer sterk afgeraden:

Verouderd sinds versie 2.6:Deze functie is verouderd. Gebruik de module subprocess.

Sinds ergens in Python 3 is het echter opnieuw geïmplementeerd om eenvoudig subprocesste gebruiken, en wordt omgeleid naar de subprocess.Popen()-documentatie voor details.

Begrijp en gebruik check=True

usually

Je zult ook merken dat subprocess.call()veel van dezelfde beperkingen heeft als os.system(). Bij regelmatig gebruik moet u over het algemeen controleren of het proces met succes is voltooid, wat subprocess.check_call()en subprocess.check_output()doen (waarbij de laatste ook de standaarduitvoer retourneert van het voltooide subproces). Op dezelfde manier moet u gewoonlijk check=Truegebruiken met subprocess.run(), tenzij u specifiek moet toestaan ​​dat het subproces een foutstatus retourneert.

In de praktijk, met check=Trueof subprocess.check_*, gooit Python een CalledProcessErroruitzonderingals het subproces een niet-nul exit-status retourneert.

Een veel voorkomende fout bij subprocess.run()is om check=Trueweg te laten en verrast te zijn wanneer downstream-code faalt als het subproces faalt.

Aan de andere kant was een veelvoorkomend probleem met check_call()en check_output()dat gebruikers die deze functies blindelings gebruikten, verrast waren toen de uitzondering werd gemeld, b.v. wanneer grepgeen overeenkomst heeft gevonden. (Je moet waarschijnlijk toch grepvervangen door native Python-code, zoals hieronder beschreven.)

Alles meegerekend, je moet begrijpen hoe shell-commando’s een exit-code retourneren, en onder welke voorwaarden ze een exit-code die niet nul is (fout), en een bewuste beslissing nemen hoe het precies moet worden afgehandeld.

p>

Begrijp en gebruik waarschijnlijk text=Trueoftewel universal_newlines=True

Sinds Python 3 zijn strings die intern zijn in Python Unicode-strings. Maar er is geen garantie dat een subproces Unicode-uitvoer of tekenreeksen genereert.

(Als de verschillen niet meteen duidelijk zijn, wordt Pragmatic Unicodevan Ned Batchelder aanbevolen, zo niet ronduit verplicht, lezen. Er is een videopresentatie van 36 minuten achter de link als je dat liever hebt, hoewel het zelf lezen van de pagina waarschijnlijk aanzienlijk minder tijd kost.)

Diep van binnen moet Python een bytesbuffer ophalen en op de een of andere manier interpreteren. Als het een klodder binaire gegevens bevat, moet het nietworden gedecodeerd in een Unicode-tekenreeks, omdat dat foutgevoelig en bug-inducerend gedrag is – precies het soort vervelende gedrag dat veel Python 2-scripts doorzeefde , voordat er een manier was om goed onderscheid te maken tussen gecodeerde tekst en binaire gegevens.

Met text=Truevertel je Python dat je in feite tekstuele data terug verwacht in de standaard codering van het systeem, en dat deze het beste gedecodeerd moet worden in een Python (Unicode) string van het vermogen van Python (meestal UTF-8 op elk redelijk up-to-date systeem, behalve misschien Windows?)

Als dat nietis wat je terugvraagt, geeft Python je gewoon bytesstrings in de stdouten stderrsnaren. Misschien weet je op een later moment weldat het toch tekststrings waren, en ken je hun codering. Vervolgens kun je ze decoderen.

normal = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True,
    text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))

Python 3.7 introduceerde de kortere, meer beschrijvende en begrijpelijke alias textvoor het trefwoordargument dat voorheen enigszins misleidend werd genoemd universal_newlines.

Begrijp shell=Trueversus shell=False

Met shell=Truegeef je een enkele string door aan je shell, en de shell neemt het van daaruit over.

Met shell=Falsegeef je een lijst met argumenten door aan het besturingssysteem, waarbij je de shell omzeilt.

Als je geen shell hebt, sla je een proces op en verwijder je een redelijk aanzienlijke hoeveelheid verborgen complexiteit, die al dan niet bugs of zelfs beveiligingsproblemen kan bevatten.

Aan de andere kant, als je geen shell hebt, heb je geen omleiding, wildcard-uitbreiding, taakbeheer en een groot aantal andere shell-functies.

Een veelgemaakte fout is om shell=Truete gebruiken en dan toch een lijst met tokens door te geven aan Python, of omgekeerd. Dit werkt in sommige gevallen, maar is echt slecht gedefinieerd en kan op interessante manieren breken.

# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
    shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    # Probably don't forget these, too
    check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
    shell=True,
    # Probably don't forget these, too
    check=True, text=True)

Het algemene antwoord “maar het werkt voor mij” is geen nuttig weerwoord, tenzij je precies begrijpt onder welke omstandigheden het zou kunnen stoppen met werken.

Voorbeeld van refactoring

Heel vaak kunnen de functies van de shell worden vervangen door native Python-code. Eenvoudige Awk- of sed-scripts moeten waarschijnlijk in plaats daarvan eenvoudig naar Python worden vertaald.

Om dit gedeeltelijk te illustreren, is hier een typisch maar enigszins dwaas voorbeeld dat veel shell-functies omvat.

cmd = '''while read -r x;
   do ping -c 3 "$x" | grep 'round-trip min/avg/max'
   done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
    cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
    for host in hosts:
        host = host.rstrip('\n')  # drop newline
        ping = subprocess.run(
             ['ping', '-c', '3', host],
             text=True,
             stdout=subprocess.PIPE,
             check=True)
        for line in ping.stdout.split('\n'):
             if 'round-trip min/avg/max' in line:
                 print('{}: {}'.format(host, line))

Enkele dingen om hier op te letten:

  • Met shell=Falseheb je de aanhalingstekens die de shell nodig heeft rond strings niet nodig. Het plaatsen van aanhalingstekens is waarschijnlijk een fout.
  • Het is vaak logisch om zo min mogelijk code in een subproces uit te voeren. Dit geeft je meer controle over de uitvoering vanuit je Python-code.
  • Dat gezegd hebbende, complexe shell-pipelines zijn vervelend en soms een uitdaging om opnieuw te implementeren in Python.

De geherstructureerde code illustreert ook hoeveel de shell echt voor je doet met een zeer beknopte syntaxis — ten goede of ten kwade. Python zegt dat expliciet beter is dan implicietmaar de Python-code isnogal uitgebreid en ziet er waarschijnlijk complexer uit dan dit in werkelijkheid is. Aan de andere kant biedt het een aantal punten waar je de controle kunt grijpen in het midden van iets anders, zoals triviaal geïllustreerd door de verbetering dat we gemakkelijk de hostnaam kunnen opnemen samen met de uitvoer van de shell-opdracht. (Dit is ook geenszins een uitdaging om in de shell te doen, maar ten koste van weer een andere afleiding en misschien een ander proces.)

Algemene Shell-constructies

Voor de volledigheid volgt hier een korte uitleg van enkele van deze shell-functies en enkele opmerkingen over hoe ze misschien kunnen worden vervangen door native Python-faciliteiten.

  • Globbing of wildcard-uitbreiding kan worden vervangen door glob.glob()of heel vaak met eenvoudige Python-tekenreeksvergelijkingen zoals for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash heeft verschillende andere uitbreidingsfaciliteiten zoals .{png,jpg}brace-uitbreiding en {1..100}evenals tilde-uitbreiding (~breidt uit naar uw homedirectory, en meer in het algemeen ~accountnaar de homedirectory van een andere gebruiker)
  • Shell-variabelen zoals $SHELLof $my_exported_varkunnen soms eenvoudig worden vervangen door Python-variabelen. Geëxporteerde shell-variabelen zijn beschikbaar als b.v. os.environ['SHELL'](de betekenis van exportis om de variabele beschikbaar te maken voor subprocessen — een variabele die niet beschikbaar is voor subprocessen zal uiteraard niet beschikbaar zijn naar Python als een subproces van de shell, of vice versa. Met het env=trefwoordargument to subprocessmethoden kunt u de omgeving van het subproces definiëren als een woordenboek, dus dat is een manier om een ​​Python-variabele zichtbaar te maken voor een subproces). Met shell=Falsemoet je weten hoe je aanhalingstekens verwijdert; bijvoorbeeld, cd "$HOME"is gelijk aan os.chdir(os.environ['HOME'])zonder aanhalingstekens rond de mapnaam. (Heel vaak is cdsowieso niet nuttig of nodig, en veel beginners laten de dubbele aanhalingstekens rond de variabele weg en komen ermee weg tot op een dag …)
  • Redirection stelt je in staat om uit een bestand te lezen als je standaardinvoer, en je standaarduitvoer naar een bestand te schrijven. grep 'foo' <inputfile >outputfileopent outputfileom te schrijven en inputfileom te lezen, en geeft de inhoud ervan als standaardinvoer door aan grep, waarvan de standaarduitvoer dan in outputfileterechtkomt. Dit is over het algemeen niet moeilijk te vervangen door native Python-code.
  • Pijpenlijnen zijn een vorm van omleiding. echo foo | nlvoert twee subprocessen uit, waarbij de standaarduitvoer van echode standaardinvoer is van nl(op OS-niveau, in Unix-achtige systemen is dit een enkel bestand handvat). Als je een of beide uiteinden van de pijplijn niet kunt vervangen door native Python-code, overweeg dan misschien om toch een shell te gebruiken, vooral als de pijplijn meer dan twee of drie processen heeft (kijk echter naar de pipes-module in de Python-standaardbibliotheekof een aantal modernere en veelzijdigere concurrenten van derden ).
  • Met taakbeheer kun je taken onderbreken, op de achtergrond uitvoeren, naar de voorgrond terugzetten, enz. De basis Unix-signalen om een ​​proces te stoppen en voort te zetten, zijn natuurlijk ook beschikbaar vanuit Python. Maar jobs zijn een abstractie op een hoger niveau in de shell waarbij procesgroepen enz. betrokken zijn die je moet begrijpen als je zoiets vanuit Python wilt doen.
  • Citaten in de shell is mogelijk verwarrend totdat je begrijpt dat allesin feite een string is. Dus ls -l /is gelijk aan 'ls' '-l' '/'maar het citeren rond letterlijke waarden is volledig optioneel. Strings zonder aanhalingstekens die shell-metatekens bevatten ondergaan parameteruitbreiding, witruimte-tokenisatie en wildcard-uitbreiding; dubbele aanhalingstekens voorkomen witruimte-tokenisatie en wildcard-uitbreiding, maar laten parameteruitbreidingen toe (variabele vervanging, opdrachtvervanging en backslash-verwerking). Dit is in theorie eenvoudig, maar kan verwarrend zijn, vooral wanneer er meerdere interpretatielagen zijn (bijvoorbeeld een shell-opdracht op afstand).

Begrijp de verschillen tussen shen Bash

subprocessvoert uw shell-opdrachten uit met /bin/sh, tenzij u specifiek anders verzoekt (behalve natuurlijk op Windows, waar het de waarde van de COMSPECvariabele). Dit betekent dat verschillende Bash-only functies zoals arrays, [[etcniet beschikbaar zijn.

Als u alleen Bash-syntaxis moet gebruiken, kunt u:
geef het pad naar de shell door als executable='/bin/bash'(waarbij je natuurlijk het pad moet aanpassen als je Bash ergens anders is geïnstalleerd).

subprocess.run('''
    # This for loop syntax is Bash only
    for((i=1;i<=$#;i++)); do
        # Arrays are Bash-only
        array[i]+=123
    done''',
    shell=True, check=True,
    executable='/bin/bash')

Een subprocessstaat los van zijn bovenliggende proces en kan dit niet wijzigen

Een wat veel voorkomende fout is om iets te doen als

subprocess.run('cd /tmp', shell=True)
subprocess.run('pwd', shell=True)  # Oops, doesn't print /tmp

Hetzelfde zal gebeuren als het eerste subproces probeert een omgevingsvariabele in te stellen, die natuurlijk verdwenen zal zijn als je een ander subproces uitvoert, enz.

Een onderliggend proces draait volledig los van Python, en wanneer het klaar is, heeft Python geen idee wat het deed (afgezien van de vage indicatoren die het kan afleiden uit de exit-status en uitvoer van het onderliggende proces). Een kind kan de omgeving van de ouder over het algemeen niet veranderen; het kan geen variabele instellen, de werkdirectory wijzigen of, met zoveel woorden, communiceren met zijn ouder zonder medewerking van de ouder.

De directe oplossing in dit specifieke geval is om beide commando’s in een enkel subproces uit te voeren;

subprocess.run('cd /tmp; pwd', shell=True)

hoewel deze specifieke use case natuurlijk niet erg handig is; gebruik in plaats daarvan het cwdtrefwoordargument, of gewoon os.chdir()voordat u het subproces uitvoert. Op dezelfde manier kun je voor het instellen van een variabele de omgeving van het huidige proces (en dus ook de kinderen) manipuleren via

os.environ['foo'] = 'bar'

of geef een omgevingsinstelling door aan een onderliggend proces met

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(om nog maar te zwijgen van de voor de hand liggende refactoring van subprocess.run(['echo', 'bar']); maar echois een slecht voorbeeld van iets om in te draaien een subproces natuurlijk).

Python niet uitvoeren vanuit Python

Dit is een enigszins dubieus advies; er zijn zeker situaties waarin het zinvol is of zelfs een absolute vereiste is om de Python-interpreter uit te voeren als een subproces vanuit een Python-script. Maar heel vaak is de juiste aanpak gewoon om de andere Python-module te importin je aanroepende script en de functies ervan rechtstreeks aan te roepen.

Als je het andere Python-script onder jouw controle hebt en het is geen module, overweeg dan om er een een. (Dit antwoord is al te lang, dus ik zal hier niet in details treden.)

Als je parallellisme nodig hebt, kun je Python-functies in subprocessen uitvoeren met de multiprocessing-module.Er is ook threadingdie meerdere taken in één proces uitvoert (wat lichter is en u meer controle geeft, maar ook meer beperkt omdat threads binnen een proces nauw zijn gekoppeld en gebonden aan een enkel GIL.)


Antwoord 3, autoriteit 13%

Noem het met subproces

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

De fout die u krijgt, lijkt te zijn omdat er geen swap-module op de server is, u moet swap op de server installeren en het script opnieuw uitvoeren


Antwoord 4, autoriteit 5%

Je kunt subprocessgebruiken, maar ik heb altijd het gevoel gehad dat het geen ‘pythonische’ manier was om het te doen. Dus heb ik Sultan (schaamteloze plug) gemaakt die het gemakkelijk maakt om commandoregelfuncties uit te voeren.

https://github.com/aeroxis/sultan


Antwoord 5, autoriteit 5%

Het is mogelijk dat je het bash-programma gebruikt, met de parameter -c om de commando’s uit te voeren:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])

Antwoord 6, autoriteit 2%

Volgens de fout mist u een pakket met de naam swapop de server. Deze /usr/bin/cwmvereist dit. Als u Ubuntu/Debian gebruikt, installeer dan python-swapmet aptitude.


Antwoord 7, autoriteit 2%

U kunt ook ‘os.popen’ gebruiken.
Voorbeeld:

import os
command = os.popen('ls -al')
print(command.read())
print(command.close())

Uitvoer:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py
None

Antwoord 8

Als u de opdracht zonder shell wilt uitvoeren, geeft u de opdracht door als een lijsten implementeert u de omleiding in Python met behulp van [subprocess]:

#!/usr/bin/env python
import subprocess
with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)

Opmerking: geen > test.ntaan het einde. stdout=fileimplementeert de omleiding.


Als u de opdracht wilt uitvoeren met de shell in Python, geeft u de opdracht door als een stringen schakelt u shell=Truein:

#!/usr/bin/env python
import subprocess
subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

Hier is de shell die verantwoordelijk is voor de uitvoeromleiding (> test.ntstaat in het commando).


Als u een bash-opdracht wilt uitvoeren die bashisms gebruikt, moet u het uitvoerbare bestand bash expliciet specificeren, bijvoorbeeld om bash-procesvervanging te emuleren:

#!/usr/bin/env python
import subprocess
subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')

Antwoord 9

subprocess.Popen()heeft de voorkeur boven os.system()omdat het meer controle en zichtbaarheid biedt. Als u subprocess.Popen()echter te uitgebreid of complex vindt, is peasyshelleen kleine wrapper die ik erboven heb geschreven, waardoor het gemakkelijk is om met bash van Python te communiceren.

https://github.com/davidohana/peasyshell


Antwoord 10

De pythonische manier om dit te doen is het gebruik van subprocess.Popen

subprocess.Popenneemt een lijst waarin het eerste element de opdracht is die moet worden uitgevoerd, gevolgd door eventuele opdrachtregelargumenten.

Als voorbeeld:

import subprocess
args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running `echo Hello!` on cmd line
args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes