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 bashCommand
invoegde 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.system
niet. 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()
bovensubprocess.check_call()
en vrienden bovensubprocess.call()
bovensubprocess.Popen()
overos.system()
overos.popen()
- Begrijp en gebruik
text=True
, ook bekend alsuniversal_newlines=True
. - Begrijp de betekenis van
shell=True
ofshell=False
en hoe het citeren en de beschikbaarheid van shell-gemakken verandert. - Begrijp de verschillen tussen
sh
en 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 onderliggendePopen
-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 aansubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
is geïntroduceerd in Python 2.5. Het is in principe gelijk aansubprocess.run(..., check=True)
subprocess.call()
is geïntroduceerd in Python 2.4 in de originelesubprocess
-module (PEP-324). Het is in principe gelijk aansubprocess.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 os
module documentatiebevat de aanbeveling om subprocess
te verkiezen boven os.system()
:
De module
subprocess
biedt 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.system
problematisch is en hoe subprocess
deze 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 subprocess
te 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=True
gebruiken met subprocess.run()
, tenzij u specifiek moet toestaan dat het subproces een foutstatus retourneert.
In de praktijk, met check=True
of subprocess.check_*
, gooit Python een CalledProcessError
uitzonderingals het subproces een niet-nul exit-status retourneert.
Een veel voorkomende fout bij subprocess.run()
is om check=True
weg 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 grep
geen overeenkomst heeft gevonden. (Je moet waarschijnlijk toch grep
vervangen 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=True
oftewel 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 bytes
buffer 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=True
vertel 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 bytes
strings in de stdout
en stderr
snaren. 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 text
voor het trefwoordargument dat voorheen enigszins misleidend werd genoemd universal_newlines
.
Begrijp shell=True
versus shell=False
Met shell=True
geef je een enkele string door aan je shell, en de shell neemt het van daaruit over.
Met shell=False
geef 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=True
te 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=False
heb 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 zoalsfor 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~account
naar de homedirectory van een andere gebruiker) - Shell-variabelen zoals
$SHELL
of$my_exported_var
kunnen soms eenvoudig worden vervangen door Python-variabelen. Geëxporteerde shell-variabelen zijn beschikbaar als b.v.os.environ['SHELL']
(de betekenis vanexport
is 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 hetenv=
trefwoordargument tosubprocess
methoden 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). Metshell=False
moet je weten hoe je aanhalingstekens verwijdert; bijvoorbeeld,cd "$HOME"
is gelijk aanos.chdir(os.environ['HOME'])
zonder aanhalingstekens rond de mapnaam. (Heel vaak iscd
sowieso 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 >outputfile
opentoutputfile
om te schrijven eninputfile
om te lezen, en geeft de inhoud ervan als standaardinvoer door aangrep
, waarvan de standaarduitvoer dan inoutputfile
terechtkomt. Dit is over het algemeen niet moeilijk te vervangen door native Python-code. - Pijpenlijnen zijn een vorm van omleiding.
echo foo | nl
voert twee subprocessen uit, waarbij de standaarduitvoer vanecho
de standaardinvoer is vannl
(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 depipes
-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 sh
en Bash
subprocess
voert uw shell-opdrachten uit met /bin/sh
, tenzij u specifiek anders verzoekt (behalve natuurlijk op Windows, waar het de waarde van de COMSPEC
variabele). 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 subprocess
staat 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 cwd
trefwoordargument, 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 echo
is 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 import
in 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 threading
die 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 subprocess
gebruiken, 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/cwm
vereist dit. Als u Ubuntu/Debian gebruikt, installeer dan python-swap
met 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.nt
aan het einde. stdout=file
implementeert de omleiding.
Als u de opdracht wilt uitvoeren met de shell in Python, geeft u de opdracht door als een stringen schakelt u shell=True
in:
#!/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.nt
staat 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 peasyshell
een 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.Popen
neemt 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