Подпроцесс python Popen Environment PATH?

Я не понимаю, как subprocess ищет исполняемый файл при использовании Popen(). Он работает, если заданы абсолютные пути к дочернему процессу, но я пытаюсь использовать относительные пути. Я обнаружил, что если я установлю переменную среды PYTHONPATH, тогда я могу получить импортированные модули с этого пути нормально, и PYTHONPATH присутствует в sys.path, но, похоже, это не помогает с поведением subprocess.Popen. Я также пробовал редактировать файл sitecustomize.py, добавляя PYTHONPATH к os.environ, вот так

# copy PYTHONPATH environment variable into PATH to allow our stuff to use
# relative paths for subprocess spawning
import os
if os.getenv('PYTHONPATH') is not None and os.getenv('PATH') is not none:
    os.environ['PATH'] = ':'.join([os.getenv('PATH'), os.getenv('PYTHONPATH')])

и подтвердили, что при запуске python в интерактивном режиме с помощью ipython или путем запуска сценария из командной строки этот PYTHONPATH успешно отображается в os.environ. Однако subrocess.Popen по-прежнему не ищет исполняемый файл. Я думал, он должен унаследовать родительскую среду, если не указан env kwarg? Затем я попытался указать env явно, во-первых, сделав копию os.getenv, а во-вторых, просто предоставив env={'PATH': '/explicit/path/to/search/from'}, но он все еще не находит исполняемый файл. Теперь я в тупике.

Надеюсь, пример поможет более четко объяснить мою проблему:

/ каталог / subdir1 / некоторый_исполняемый файл
/dir/subdir2/some_script.py

# some_script.py
from subprocess import Popen, PIPE
spam, eggs = Popen(['../subdir1/some_executable'], stdout=PIPE, stderr=PIPE).communicate()

Если у меня /dir/subdir2 и я запускаю python some_script.py, это работает, но если я в /dir и запускаю python subdir2/some_script.py, хотя /dir/subdir2 находится в os.environ['PATH'], то подпроцесс выбросит OSError: [Errno 2] No such file or directory.


person wim    schedule 14.04.2011    source источник
comment
Перечитывая вопрос, я думаю, что вижу проблему. В командной оболочке переключитесь на /dir и посмотрите, что произойдет, если вы наберете ../subdir1/some_executable.   -  person ncoghlan    schedule 14.04.2011
comment
хорошо, я понимаю, что вы говорите, мое недоразумение было предположением, что относительные пути будут искать точно так же, как простой вызов программы. Благодарность   -  person wim    schedule 14.04.2011


Ответы (4)


(заполнение деталей из комментария, чтобы дать отдельный ответ)

Во-первых, относительные пути (пути, содержащие косую черту) никогда не проверяются ни в каком PATH, что бы вы ни делали. Они относятся только к текущему рабочему каталогу. Если вам нужно разрешить относительные пути, вам придется искать PATH вручную или изменить PATH, чтобы включить подкаталоги, а затем просто использовать имя команды, как в моем предложении ниже.

Если вы хотите запустить программу относительно местоположения скрипта Python, используйте __file__ и перейдите оттуда, чтобы найти абсолютный путь к программе, а затем используйте абсолютный путь в Popen.

Поиск в переменной среды текущего процесса PATH

Во-вторых, существует проблема в системе отслеживания ошибок Python о том, как Python работает с голыми командами (без косой черты). Обычно в Unix / Mac Popen ведет себя как os.execvp, когда аргумент env=None (было замечено и отмечено в конце неожиданное поведение):

В POSIX класс использует os.execvp()-подобное поведение для выполнения дочерней программы.

На самом деле это верно как для shell=False, так и для shell=True при условии env=None. Что означает это поведение, объясняется в документации функции os.execvp. :

Варианты, которые содержат букву «p» в конце (execlp(), execlpe(), execvp() и execvpe()), будут использовать переменную среды PATH для поиска файла программы. Когда среда заменяется (с использованием одного из вариантов exec*e, обсуждаемых в следующем абзаце), новая среда используется в качестве источника переменной PATH.

Для execle(), execlpe(), execve() и execvpe() (обратите внимание, что все они заканчиваются на «e») параметр env должен быть отображением, которое используется для определения переменных среды для нового процесса (это используется вместо среды текущего процесса); все функции execl(), execlp(), execv() и execvp() заставляют новый процесс наследовать среду текущего процесса.

Второй цитируемый абзац подразумевает, что execvp будет использовать переменные среды текущего процесса. В сочетании с первым процитированным абзацем мы делаем вывод, что execvp будет использовать значение переменной среды PATH из среды текущего процесса. Это означает, что Popen смотрит на значение PATH , как это было при запуске Python (Python, который запускает Popen экземпляр), и никакие изменения os.environ не помогут вам это исправить.

Кроме того, в Windows с shell=False Popen вообще не обращает внимания на PATH и будет искать только относительно текущего рабочего каталога.

Что делает shell=True

Что произойдет, если мы передадим shell=True в Popen? В этом случае Popen просто вызывает оболочку:

Аргумент shell (по умолчанию False) указывает, следует ли использовать оболочку в качестве выполняемой программы.

То есть Popen выполняет эквивалент:

Popen(['/bin/sh', '-c', args[0], args[1], ...])

Другими словами, с shell=True Python будет напрямую выполнять /bin/sh, без какого-либо поиска (передача аргумента executable в Popen может изменить это, и кажется, что если это строка без косой черты, тогда она будет интерпретироваться Python как имя программы оболочки для поиска в значении PATH из среды текущего процесса, т. е. как он ищет программы в случае shell=False, описанном выше).

В свою очередь, /bin/sh (или наша оболочка executable) будет искать программу, которую мы хотим запустить, в PATH своей собственной среды, которая совпадает с PATH Python (текущий процесс), как следует из кода после фразы That is чтобы сказать ... выше (потому что этот вызов имеет shell=False, так что это уже обсуждалось ранее). Следовательно, поведение, подобное execvp, мы получаем как с shell=True, так и с shell=False, пока env=None.

Передача env в Popen

Итак, что произойдет, если мы передадим env=dict(PATH=...) в Popen (тем самым определив переменную среды PATH в среде программы, которая будет запускаться Popen)?

В этом случае новая среда используется для поиска программы для выполнения. Цитата из документации Popen:

Если env не None, это должно быть отображение, которое определяет переменные среды для нового процесса; они используются вместо поведения по умолчанию при наследовании среды текущего процесса.

В сочетании с приведенными выше наблюдениями и экспериментами с использованием Popen это означает, что Popen в этом случае ведет себя как функция _ 72_. Если shell=False, Python ищет данную программу во вновь определенном PATH. Как уже обсуждалось выше для shell=True, в этом случае программа либо /bin/sh, либо, если имя программы задано с аргументом executable, тогда эта альтернативная программа (оболочка) ищется во вновь определенном PATH.

Вдобавок, если shell=True, то внутри оболочки путь поиска, который оболочка будет использовать для поиска программы, указанной в args, является значением PATH, переданным в Popen через env.

Таким образом, с env != None, Popen ищет значение ключа PATH из env (если ключ PATH присутствует в env).

Распространение переменных среды, отличных от PATH, в качестве аргументов

Существует предостережение относительно переменных среды, отличных от PATH: если значения этих переменных необходимы в команде (например, в качестве аргументов командной строки для выполняемой программы), то даже если они присутствуют в env, заданном для Popen, они не будут интерпретированы без shell=True. Этого легко избежать, не изменяя shell=True: вставьте это значение непосредственно в list аргумент args, который передается в Popen. (Кроме того, если эти значения поступают из собственной среды Python, для получения их значений можно использовать метод os.environ.get).

Использование /usr/bin/env

Если вам ТОЛЬКО нужна оценка пути, и вы действительно не хотите запускать командную строку через оболочку и используете UNIX, я советую использовать env вместо shell=True, как в

path = '/dir1:/dir2'
subprocess.Popen(['/usr/bin/env', '-P', path, 'progtorun', other, args], ...)

Это позволяет передать другой PATH процессу env (с помощью параметра -P), который будет использовать это найти программу. Это также позволяет избежать проблем с метасимволами оболочки и потенциальных проблем безопасности при передаче аргументов через оболочку. Очевидно, что в Windows (практически единственной платформе без /usr/bin/env) вам нужно будет сделать что-то другое.

О shell=True

Цитата из Popen документации:

Если shell - True, рекомендуется передавать args как строку, а не как последовательность.

Примечание. Ознакомьтесь с соображениями безопасности. раздел перед использованием shell=True.

Неожиданные наблюдения

Наблюдалось следующее поведение:

  • Этот колл вызывает FileNotFoundError, как и ожидалось:

    subprocess.call(['sh'], shell=False, env=dict(PATH=''))
    
  • Этот вызов обнаруживает sh, что является неожиданным:

    subprocess.call(['sh'], shell=False, env=dict(FOO=''))
    

    Ввод echo $PATH внутри открывающейся оболочки показывает, что значение PATH не пусто, а также отличается от значения PATH в среде Python. Таким образом, кажется, что PATH действительно не был унаследован от Python (как и ожидалось в присутствии env != None), но, тем не менее, PATH непусто. Неизвестно, почему это так.

  • Этот колл вызывает FileNotFoundError, как и ожидалось:

    subprocess.call(['tree'], shell=False, env=dict(FOO=''))
    
  • Это обнаруживает tree, как и ожидалось:

    subprocess.call(['tree'], shell=False, env=None)
    
person Walter Mundt    schedule 14.04.2011
comment
+1 Кроме того, в Windows с shell = False он вообще не обращает внимания на PATH и будет искать только относительно текущего рабочего каталога. Только что помог мне с большой проблемой - спасибо! - person sparc_spread; 10.10.2013
comment
Простой способ, который должен работать и в Windows, - явно указать os.environ['PATH'] в качестве аргумента env для subprocess.Popen, как это сделано здесь: stackoverflow.com/a / 4453495/1959808 и там: stackoverflow.com/a/20669704/1959808. - person Ioannis Filippidis; 30.06.2016
comment
трюк /usr/bin/env не работает, по крайней мере, для системных команд, таких как useradd, и, по крайней мере, в CentOS (с пустым PATH из cron): /usr/bin/env: groupadd: No such file or directory - person grandrew; 05.07.2016
comment
Если PATH пуст, это не удивительно. AFAIK, в отличие от оболочки, / usr / bin / env не имеет PATH по умолчанию, к которому он возвращается. Честно говоря, я бы все равно не рекомендовал полагаться на PATH оболочки по умолчанию; если вы пишете задания cron, просто напишите полные пути к своим двоичным файлам или установите PATH самостоятельно. - person Walter Mundt; 14.07.2016
comment
У меня есть subprocess.Popen, который, похоже, ищет путь с помощью shell=False. Однако неэффективно расширение пути для включения местоположения исполняемого файла с помощью sys.path.append - я обнаружил, что это работает, только если %PATH% содержит путь к исполняемому файлу до запуска программы Python. - person starfry; 20.03.2017

Кажется, вы немного запутались в природе PATH и PYTHONPATH.

PATH - это переменная среды, которая сообщает оболочке ОС, где искать исполняемые файлы.

PYTHONPATH - это переменная среды, которая сообщает интерпретатору Python, где искать модули для импорта. Это не имеет ничего общего с subprocess поиском исполняемых файлов.

Из-за различий в базовой реализации subprocess.Popen по умолчанию будет искать путь только в системах, отличных от Windows (Windows имеет некоторые системные каталоги, которые она всегда ищет, но это отличается от обработки PATH). Единственный надежный кроссплатформенный способ сканирования пути - передача shell=True в вызов подпроцесса, но у этого есть свои проблемы (как подробно описано в _ 9_ документация)

Однако, похоже, ваша основная проблема в том, что вы передаете Popen фрагмент пути, а не простое имя файла. Как только у вас есть разделитель каталогов, вы собираетесь отключить поиск PATH даже на платформе, отличной от Windows (например, см. Документацию Linux для семейство функций exec).

person ncoghlan    schedule 14.04.2011
comment
Это не соответствует документации Python. В документации Popen указано, что программа выполняется через os.execvp - и этот вызов ДЕЙСТВИТЕЛЬНО принимает учитывать переменную среды PATH. Кроме того, если вам ТОЛЬКО нужна оценка пути, я советую использовать env вместо shell=True, как в Popen(['/usr/bin/env', 'progtorun', other, args], ...). Это позволяет избежать проблем с метасимволами оболочки и потенциальных проблем безопасности при передаче аргументов через оболочку. - person Walter Mundt; 14.04.2011
comment
Однако они оба относятся к * NIX - они не работают в Windows, поэтому я не люблю рекомендовать их как обходные пути для номинально кроссплатформенного модуля. Вы правы, что мой ответ неверен, как написано, хотя - отредактирую соответствующим образом. - person ncoghlan; 14.04.2011
comment
Обновлено, чтобы прояснить, что поиск в PATH по умолчанию не выполняется только в Windows, а также чтобы указать на настоящую проблему (разделитель каталогов в выполняемой команде). - person ncoghlan; 14.04.2011
comment
Небольшая переделка. subprocess.Popen подберет исполняемые файлы в C:\Windows\System32, которые (и мне было интересно это выяснить), если вы используете 32-битный питон в 64-битной Windows, на самом деле C:\Windows\SysWOW64 - person John Oxley; 06.10.2016
comment
@JohnOxley Я изменил ответ, чтобы упомянуть об этом, но знаете ли вы какие-либо хорошие ссылки для этого? Может где-нибудь на MSDN? - person ncoghlan; 10.10.2016
comment
Лично у меня не было проблем с окнами и путями ... не знаю почему. Полностью находит тесты на пути, например, в наших экземплярах Windows Jenkins virtualenv. - person Erik Aronesty; 02.05.2018

Относительный путь в subprocess.Popen действует относительно текущего рабочего каталога, а не элементов системного PATH. Если вы запустите python subdir2/some_script.py из /dir, то ожидаемое расположение исполняемого файла (переданное в Popen) будет /dir/../subdir1/some_executable, то есть /subdir1/some_executable, а не /dir/subdir1/some_executable.

Если вы определенно хотите использовать относительные пути от собственного каталога сценария к конкретному исполняемому файлу, лучшим вариантом было бы сначала построить абсолютный путь из части каталога глобальной переменной __file__.

#/usr/bin/env python
from subprocess import Popen, PIPE
from os.path import abspath, dirname, join
path = abspath(join(dirname(__file__), '../subdir1/some_executable'))
spam, eggs = Popen(path, stdout=PIPE, stderr=PIPE).communicate()
person Jeremy Fishman    schedule 14.04.2011
comment
Что? subdir2/some_script.py относительно /dir просто /dir/subdir2/some_script.py - person tripleee; 10.12.2020
comment
Сценарий python в subdir2/some_script.py выполняет Popen с исполняемым путем ../subdir1/some_executable. Это тот исполняемый путь, который разрешается относительно текущего рабочего директора /dir, в результате чего получается /dir/../subdir1/some_executable. См. Ответ Уолтера, в котором одно и то же говорится по-разному. Я мог бы сформулировать свой ответ лучше. Ваше здоровье! edit: похоже, что в моем ответе тоже была опечатка, использовав subdir2 в пути к исполняемому файлу, где я имел в виду subdir1. - person Jeremy Fishman; 11.12.2020

Путь к python равен пути, по которому выполняется интерпретатор python. Итак, во втором случае вашего примера путь установлен в / dir, а не в / dir / subdir2. Вот почему вы получаете сообщение об ошибке.

person c0da    schedule 14.04.2011
comment
Я не верю, что это правильно, потому что если я напишу простой скрипт для печати os.environ, тогда PYTHONPATH будет тем же самым, независимо от того, откуда я запускаю интерпретатор. PYTHONPATH был установлен в / etc / environment и используется для увеличения пути поиска модулей. - person wim; 14.04.2011
comment
Я хотел сказать, что каталог, из которого выполняется python, этот каталог добавляется в pythonpath. Во втором случае добавляется / dir, а не / dir / subdir2. Итак, вы можете либо изменить свой код, чтобы отразить изменения (один из способов - добавить / dir / subdir2 в os.path в вашем коде), либо запустить python из соответствующего каталога. - person c0da; 14.04.2011