Subproses Python / Popen dengan lingkungan yang dimodifikasi

284

Saya percaya bahwa menjalankan perintah eksternal dengan lingkungan yang sedikit dimodifikasi adalah kasus yang sangat umum. Begitulah cara saya cenderung melakukannya:

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

Saya punya firasat bahwa ada cara yang lebih baik; apakah ini terlihat baik-baik saja?

Oren_H
sumber
10
Juga lebih suka menggunakan os.pathsepdaripada ":" untuk jalur yang berfungsi lintas platform. Lihat stackoverflow.com/questions/1499019/…
amit
8
@ phaedrus Saya tidak yakin ini sangat relevan ketika dia menggunakan jalur seperti /usr/sbin:-)
Dmitry Ginzburg

Jawaban:

403

Saya pikir os.environ.copy()lebih baik jika Anda tidak bermaksud memodifikasi os.environ untuk proses saat ini:

import subprocess, os
my_env = os.environ.copy()
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)
Daniel Burke
sumber
>>> env = os.environ.copy >>> env ['foo'] = 'bar' Traceback (panggilan terakhir terakhir): File "<stdin>", baris 1, dalam <module> TypeError: 'instancemethod' objek tidak mendukung penugasan item
user1338062
5
@ user1338062 Anda menetapkan metode yang sebenarnya os.environ.copyke envvariabel tetapi Anda perlu untuk menetapkan hasil memanggil metode os.environ.copy()untuk env.
chown
4
Resolusi variabel lingkungan hanya benar-benar berfungsi jika Anda menggunakannya shell=Truedalam subprocess.Popenpermohonan Anda . Perhatikan bahwa ada implikasi keamanan yang berpotensi terjadi.
danielpops
Di dalam subprocess.Popen (my_command, env = my_env) - apa itu "my_command"
avinash
@avinash - my_commandhanya perintah untuk dijalankan. Ini bisa berupa misalnya /path/to/your/own/programatau pernyataan "dieksekusi" lainnya.
kajakIYD
64

Itu tergantung pada apa masalahnya. Jika ingin mengkloning dan memodifikasi lingkungan, salah satu solusinya adalah:

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

Tapi itu agak tergantung pada variabel yang diganti adalah pengidentifikasi python yang valid, yang paling sering adalah (seberapa sering Anda mengalami nama variabel lingkungan yang bukan garis bawah + alfanumerik atau variabel yang dimulai dengan angka?).

Kalau tidak, Anda bisa menulis sesuatu seperti:

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

Dalam kasus yang sangat aneh (seberapa sering Anda menggunakan kode kontrol atau karakter non-ascii dalam nama variabel lingkungan?) Bahwa kunci-kunci lingkungan bytesAnda tidak dapat (pada python3) bahkan menggunakan konstruksi itu.

Seperti yang Anda lihat teknik (terutama yang pertama) yang digunakan di sini manfaat pada kunci lingkungan biasanya adalah pengidentifikasi python yang valid, dan juga dikenal sebelumnya (pada waktu pengkodean), pendekatan kedua memiliki masalah. Dalam kasus di mana bukan itu masalahnya Anda mungkin harus mencari pendekatan lain .

meroket
sumber
3
Suara positif. Saya tidak tahu Anda bisa menulis dict(mapping, **kwargs). Saya pikir itu atau. Catatan: ini disalin os.environtanpa memodifikasinya seperti yang disarankan oleh @Daniel Burke dalam jawaban yang saat ini diterima tetapi jawaban Anda lebih ringkas. Di Python 3.5+ Anda bahkan bisa melakukannya dict(**{'x': 1}, y=2, **{'z': 3}). Lihat pep 448 .
jfs
1
Jawaban ini menjelaskan beberapa cara yang lebih baik (dan mengapa cara ini tidak terlalu bagus) untuk menggabungkan dua kamus menjadi yang baru: stackoverflow.com/a/26853961/27729
krupan
@krupan: kerugian apa yang Anda lihat untuk kasus penggunaan khusus ini? (menggabungkan dicts sewenang-wenang dan menyalin / memperbarui lingkungan adalah tugas yang berbeda)
jfs
1
@krupan Pertama-tama kasus normal adalah variabel lingkungan akan menjadi pengidentifikasi python yang valid, yang berarti konstruk pertama. Untuk kasus itu tidak ada keberatan Anda berlaku. Untuk kasus kedua, keberatan utama Anda masih gagal: titik tentang kunci non-string tidak berlaku dalam kasus ini karena kunci pada dasarnya harus berupa string dalam lingkungan.
meroket
@ JSFSebastian Anda benar bahwa untuk kasus khusus ini teknik ini baik-baik saja dan saya harus menjelaskan diri saya lebih baik. Permintaan maaf saya. Saya hanya ingin membantu mereka (seperti saya) yang mungkin tergoda untuk menggunakan teknik ini dan menerapkannya pada kasus umum penggabungan dua kamus sewenang-wenang (yang memiliki beberapa gotcha, sebagai jawaban yang saya tunjukkan).
krupan
24

Anda dapat menggunakannya my_env.get("PATH", '')sebagai pengganti my_env["PATH"]jika dalam kasus PATHentah bagaimana tidak didefinisikan di lingkungan asli, tetapi selain itu terlihat baik-baik saja.

SilentGhost
sumber
20

Dengan Python 3.5 Anda bisa melakukannya dengan cara ini:

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

Di sini kita berakhir dengan salinan os.environdan PATHnilai yang diganti .

Itu dimungkinkan oleh PEP 448 (Generalisasi Pembongkaran Tambahan).

Contoh lain. Jika Anda memiliki lingkungan default (yaitu os.environ), dan dikt Anda ingin mengganti default dengan, Anda dapat mengekspresikannya seperti ini:

my_env = {**os.environ, **dict_with_env_variables}
skovorodkin
sumber
@avinash, lihat dokumentasi subprocess.Popen . Ini "urutan argumen program atau string tunggal".
skovorodkin
10

Untuk sementara mengatur variabel lingkungan tanpa harus menyalin objek os.envrion dll, saya melakukan ini:

process = subprocess.Popen(['env', 'RSYNC_PASSWORD=foobar', 'rsync', \
'rsync://[email protected]::'], stdout=subprocess.PIPE)
MFB
sumber
4

Parameter env menerima kamus. Anda cukup mengambil os.environ, menambahkan kunci (variabel yang Anda inginkan) (ke salinan dikt jika Anda harus) untuk itu dan menggunakannya sebagai parameter untuk Popen.

Noufal Ibrahim
sumber
Ini adalah jawaban paling sederhana jika Anda hanya ingin menambahkan variabel lingkungan baru. os.environ['SOMEVAR'] = 'SOMEVAL'
Andy Fraley
1

Saya tahu ini telah dijawab untuk beberapa waktu, tetapi ada beberapa poin yang beberapa mungkin ingin tahu tentang menggunakan PYTHONPATH daripada PATH dalam variabel lingkungan mereka. Saya telah menjabarkan penjelasan tentang menjalankan skrip python dengan cronjobs yang berhubungan dengan lingkungan yang dimodifikasi dengan cara yang berbeda ( ditemukan di sini ). Kupikir itu akan bermanfaat bagi mereka yang, seperti aku, membutuhkan sedikit lebih banyak daripada jawaban yang diberikan.

memenuhi syarat
sumber
0

Dalam keadaan tertentu Anda mungkin hanya ingin mewariskan variabel lingkungan yang dibutuhkan subproses Anda, tetapi saya pikir Anda memiliki ide yang tepat secara umum (itulah cara saya melakukannya juga).

Andrew Aylett
sumber