Python subprocess / Popen พร้อมสภาพแวดล้อมที่ถูกดัดแปลง


285

ฉันเชื่อว่าการรันคำสั่งภายนอกที่มีสภาพแวดล้อมที่ปรับเปลี่ยนเล็กน้อยเป็นกรณีทั่วไป นั่นเป็นวิธีที่ฉันมักจะทำ:

import subprocess, os
my_env = os.environ
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)

ฉันมีความรู้สึกว่ามีวิธีที่ดีกว่า; มันดูไม่เป็นไร


10
นอกจากนี้ยังต้องการใช้os.pathsepแทน ":" สำหรับเส้นทางที่ทำงานข้ามแพลตฟอร์ม ดูstackoverflow.com/questions/1499019/…
am

8
@phaedrus ฉันไม่แน่ใจว่ามันมีความเกี่ยวข้องมากเมื่อเขาใช้เส้นทางเช่น/usr/sbin:-)
Dmitry Ginzburg

คำตอบ:


405

ฉันคิดว่าos.environ.copy()ดีกว่าถ้าคุณไม่ต้องการแก้ไข os.environ สำหรับกระบวนการปัจจุบัน:

import subprocess, os
my_env = os.environ.copy()
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)

>>> env = os.environ.copy >>> env ['foo'] = 'แถบ' Traceback (การโทรล่าสุดครั้งล่าสุด): ไฟล์ "<stdin>" บรรทัดที่ 1 ใน <โมดูล> TypeError: 'instancemethod' วัตถุไม่รองรับการกำหนดรายการ
user1338062

5
@ user1338062 คุณกำลังกำหนดวิธีการที่เกิดขึ้นจริงos.environ.copyกับenvตัวแปร แต่คุณต้องกำหนดผลของการเรียกวิธีการที่จะos.environ.copy() env
chown

4
การแก้ไขตัวแปรสภาพแวดล้อมใช้งานได้จริงถ้าคุณใช้shell=Trueในsubprocess.Popenการเรียกใช้ โปรดทราบว่าอาจมีผลกระทบด้านความปลอดภัยในการทำเช่นนั้น
danielpops

Inside subprocess.Popen (my_command, env = my_env) - "my_command" คืออะไร
avinash

@avinash - my_commandเป็นเพียงคำสั่งให้เรียกใช้ อาจเป็นเช่น/path/to/your/own/programหรือคำสั่ง "ปฏิบัติการ" อื่น ๆ
kajakIYD

64

ขึ้นอยู่กับว่าปัญหาคืออะไร ถ้าเป็นการโคลนและแก้ไขสภาพแวดล้อมวิธีหนึ่งก็คือ:

subprocess.Popen(my_command, env=dict(os.environ, PATH="path"))

แต่นั่นค่อนข้างขึ้นอยู่กับว่าตัวแปรที่ถูกแทนที่นั้นเป็นตัวบ่งชี้ python ที่ถูกต้องซึ่งส่วนใหญ่มักเป็น (บ่อยแค่ไหนที่คุณเรียกใช้ชื่อตัวแปรสภาพแวดล้อมที่ไม่ใช่ตัวอักษรและตัวเลข + ขีดล่างหรือตัวแปรที่ขึ้นต้นด้วยตัวเลข)

มิฉะนั้นคุณจะสามารถเขียนสิ่งที่ชอบ:

subprocess.Popen(my_command, env=dict(os.environ, 
                                      **{"Not valid python name":"value"}))

ในกรณีที่แปลกมาก (คุณใช้รหัสควบคุมหรืออักขระที่ไม่ใช่ ASCII บ่อยครั้งในชื่อตัวแปรสภาพแวดล้อมหรือไม่) ว่าคีย์ของสภาพแวดล้อมคือbytesสิ่งที่คุณไม่สามารถทำได้ (ใน python3) แม้แต่ใช้โครงสร้างนั้น

ในขณะที่คุณสามารถดูเทคนิคต่าง ๆ (โดยเฉพาะอย่างยิ่งคนแรก) ที่ใช้ประโยชน์ที่นี่ในคีย์ของสภาพแวดล้อมตามปกติคือตัวบ่งชี้หลามที่ถูกต้องและยังเป็นที่รู้จักล่วงหน้า (ในเวลาการเข้ารหัส) แนวทางที่สองมีปัญหา ในกรณีที่ที่ไม่ได้เป็นกรณีที่คุณอาจจะมองหาวิธีการอื่น


3
upvote ฉันไม่รู้ว่าคุณสามารถเขียนdict(mapping, **kwargs)ได้ ฉันคิดว่ามันเป็นอย่างใดอย่างหนึ่งหรือ หมายเหตุ: คัดลอกos.environโดยไม่แก้ไขตาม@Daniel Burke ที่แนะนำในคำตอบที่ยอมรับในปัจจุบันแต่คำตอบของคุณสั้นกว่า ในหลาม 3.5 dict(**{'x': 1}, y=2, **{'z': 3})ขึ้นไปคุณยังสามารถทำ ดูห้าวหาญ 448
jfs

1
คำตอบนี้จะอธิบายวิธีการที่ดีกว่า (และเหตุใดวิธีนี้จึงไม่ดีนัก) เพื่อรวมพจนานุกรมสองเล่มเข้าเป็นหนึ่งใหม่: stackoverflow.com/a/26853961/27729
krupan

@krupan: คุณเห็นข้อเสียอะไรสำหรับกรณีการใช้งานเฉพาะนี้ (รวม dicts โดยพลการและคัดลอก / ปรับปรุงสภาพแวดล้อมเป็นงานที่แตกต่างกัน)
jfs

1
@ krupan สิ่งแรกของทุกกรณีปกติคือตัวแปรสภาพแวดล้อมจะเป็นตัวบ่งชี้หลามที่ถูกต้องซึ่งหมายถึงการสร้างครั้งแรก สำหรับกรณีนี้ไม่มีการคัดค้านของคุณ สำหรับกรณีที่สองการคัดค้านหลักของคุณยังคงล้มเหลว: จุดเกี่ยวกับคีย์ที่ไม่ใช่สตริงไม่สามารถใช้ได้ในกรณีนี้เนื่องจากคีย์นั้นจำเป็นต้องใช้เป็นสตริงในสภาพแวดล้อมโดยทั่วไป
skyking

@JFSebastian คุณถูกต้องสำหรับกรณีนี้เทคนิคนี้ดีและฉันควรจะอธิบายตัวเองดีกว่า ขอโทษด้วย. ฉันแค่ต้องการช่วยคนเหล่านั้น (เช่นตัวฉันเอง) ที่อาจถูกล่อลวงให้ใช้เทคนิคนี้และนำไปใช้กับกรณีทั่วไปของการรวมพจนานุกรมสองประโยคโดยพลการ
krupan

24

คุณอาจใช้my_env.get("PATH", '')แทนmy_env["PATH"]ในกรณีที่PATHไม่ได้กำหนดไว้ในสภาพแวดล้อมดั้งเดิม แต่อย่างใดนอกจากนั้นมันดูดี


21

ด้วย Python 3.5 คุณสามารถทำสิ่งนี้ได้:

import os
import subprocess

my_env = {**os.environ, 'PATH': '/usr/sbin:/sbin:' + os.environ['PATH']}

subprocess.Popen(my_command, env=my_env)

ที่นี่เราจบลงด้วยการคัดลอกos.environและแทนที่PATHค่า

มันเป็นไปได้โดยPEP 448 (ภาพรวมการเปิดออกเพิ่มเติม)

ตัวอย่างอื่น. หากคุณมีสภาพแวดล้อมเริ่มต้น (เช่นos.environ) และ dict ที่คุณต้องการแทนที่ค่าเริ่มต้นด้วยคุณสามารถแสดงได้ดังนี้:

my_env = {**os.environ, **dict_with_env_variables}

@avinash ตรวจสอบsubprocess.Popenเอกสาร มันคือ "ลำดับของข้อโต้แย้งของโปรแกรมหรือสตริงอื่น"
skovorodkin

10

หากต้องการตั้งค่าตัวแปรสภาพแวดล้อมเป็นการชั่วคราวโดยไม่ต้องคัดลอกวัตถุ os.envrion เป็นต้นฉันทำสิ่งนี้:

process = subprocess.Popen(['env', 'RSYNC_PASSWORD=foobar', 'rsync', \
'rsync://username@foobar.com::'], stdout=subprocess.PIPE)

4

พารามิเตอร์ env ยอมรับพจนานุกรม คุณก็สามารถใช้ os.environ เพิ่มคีย์ (ตัวแปรที่คุณต้องการ) (สำเนา Dict หากคุณต้อง) Popenไปที่และใช้มันเป็นพารามิเตอร์ไป


นี่เป็นคำตอบที่ง่ายที่สุดหากคุณต้องการเพิ่มตัวแปรสภาพแวดล้อมใหม่ os.environ['SOMEVAR'] = 'SOMEVAL'
Andy Fraley

1

ฉันรู้ว่านี่ได้รับคำตอบมาระยะหนึ่งแล้ว แต่ก็มีบางประเด็นที่บางคนอาจต้องการทราบเกี่ยวกับการใช้ PYTHONPATH แทน PATH ในตัวแปรสภาพแวดล้อม ฉันได้อธิบายคำอธิบายของการเรียกใช้สคริปต์ python กับ cronjobs ที่เกี่ยวข้องกับสภาพแวดล้อมที่ได้รับการแก้ไขในวิธีที่แตกต่างกัน (ดูที่นี่ ) คิดว่ามันจะเป็นของดีสำหรับผู้ที่ต้องการฉันเพียงเล็กน้อยมากกว่าคำตอบนี้ให้


0

ในบางสถานการณ์คุณอาจต้องการส่งผ่านตัวแปรสภาพแวดล้อมที่ความต้องการย่อยของคุณเท่านั้น แต่ฉันคิดว่าคุณมีความคิดที่ถูกต้องโดยทั่วไป (นั่นคือสิ่งที่ฉันทำด้วย)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.