Я создаю установщик для одного из моих проектов, который загружает двоичные файлы проекта (в ZIP-файле), а затем распаковывает его в каталог в папке программ ОС, и я хочу добавить этот каталог в PATH
.
Мой скрипт представляет собой установщик с пользовательским интерфейсом, и его можно скомпилировать в .exe
/исполняемый файл.
Я попробовал это:
import os
os.environ['PATH'] += os.pathsep + install_dir # `install_dir` is the installation directory
Но это не навсегда и не остается в конце сценария.
@chepner Можете ли вы объяснить больше?
Например, возможно, файл конфигурации оболочки включает PATH=/some/directory:$PATH
, и когда ваш скрипт Python запускается, значение PATH
уже содержит каталог, необходимый для любого исполняемого файла, который будет запускаться вашим скриптом. Это тот, кого вы настраиваете не во время выполнения, а во время установки.
@chepner Мой скрипт предназначен для загрузки двоичных файлов моего проекта и добавления каталога, в котором находятся двоичные файлы, в PATH
.
Меня беспокоит, что другие программы влияют на конфигурацию моей среды. Сообщите мне, куда загружаются файлы (или, еще лучше, позвольте мне сообщить сценарию, куда их установить после загрузки), и я настрою любую среду, которая должна знать, где находятся двоичные файлы.
Мой скрипт загружает ZIP-файл, содержащий двоичные файлы, во временную папку системы, а затем разархивирует его в C:/Progam Files/MyProject
в Windows, /Applications/MyProject
в macOS и /usr/local/bin/MyProject
в Linux (используя MyProject
в качестве имени проекта), а затем удаляет ZIP-файлы из временной папки. .
Ваш скрипт не может повлиять на переменную PATH
процесса, который он сам не создает. Лучшее, что он может сделать, — это отредактировать любой файл конфигурации, который используется для установки PATH
для данного процесса, а я говорю, что вам не следует этого делать. Пусть тот, кому нужно использовать двоичные файлы, правильно настроит свою среду; вам достаточно сделать их доступными.
Я делаю установщик, чтобы упростить процесс установки двоичных файлов. Это похоже на установщик Python для Windows (только для примера: на самом деле это не одно и то же, но цель та же).
Монтажники устанавливают; они не настраивают другие программы. В идеале тот, кому нужны бинарники, сам решает, куда им идти, а установщик помещает их туда, куда ему сказано. (То есть вы не прописываете /usr/local/bin/MyProject
жестко, а позволяете тому, кто запустит ваш установщик, решать, куда MyProject
идти.)
Почему вы чувствуете, что вам нужно изменить PATH? В современных ОС с графическим интерфейсом люди запускают программы с помощью значков/ярлыков, которые обычно предоставляют полный путь к exe-файлу. Вероятно, вам нужно будет добавить PATH только в том случае, если это инструмент командной строки. Это?
@chepner Этот путь является путем установки по умолчанию, я работаю над тем, как его настроить, но прямо сейчас, в этом вопросе, я хочу, как добавить путь установки в системную переменную среды PATH
.
@001 Это инструмент CLI.
Вы не знаете. Системы PATH
нет, по крайней мере, в UNIX. Существует множество способов, которыми любой конкретный дистрибутив может определить файл конфигурации для процесса 1, из которого любой процесс может унаследовать значение PATH
, но обычно PATH
изначально определяется более локально, с помощью оболочки входа.
@chepner Под «системой PATH
» я просто имел в виду переменную среды.
Я тоже. Нестандартно иметь одно значение для PATH
, наследуемое всеми процессами.
Мне нужен тот, который хотя бы использовался для оболочки.
Не существует независимого от платформы способа сделать это. В типичных дистрибутивах Linux вам, вероятно, удастся отредактировать /etc/profile
(чего не должен делать ваш установщик), а в macOS вам придется добавить соответствующий файл в /etc/paths.d
. В конечном итоге рекомендуется документировать, где ваш установщик будет устанавливать программу, а пользователи этой программы будут следить за тем, чтобы их переменная PATH
обновлялась соответствующим образом.
Так как же обстоят дела у других установщиков?
Многие сценарии установки заканчиваются шагом, добавляющим команду к сценарию запуска оболочки пользователя. Или они предоставляют инструмент настройки, который пользователи могут использовать для этого. Или они просто помещают в документацию что-то, что советует пользователям делать это вручную.
@Barmar Теперь я знаю, посмотрите ответы, которые вы увидите, что я написал скрипт, чтобы добавить путь к PATH
(а также удалить их).
Вы не должны изменять свой вопрос так, чтобы он лишал законной силы существующие ответы. Должен ли я удалить свой сейчас?
@tripleee Извините, извините. Мне очень жаль.
Вот что я сделал (знаю, это не лучшее решение, но оно работает):
import os
import subprocess
import sys
def add_to_path_win(new_path):
new_path = new_path.strip().replace('/', '\\').removesuffix("\\")
key = win32api.RegOpenKeyEx(
win32con.HKEY_CURRENT_USER,
r"Environment",
0,
win32con.KEY_ALL_ACCESS
)
current_path, _ = win32api.RegQueryValueEx(key, "Path")
if new_path in current_path.split(os.pathsep) or \
new_path + '\\' in current_path.split(os.pathsep):
return
new_path_value = new_path + os.pathsep + current_path
win32api.RegSetValueEx(key, "Path", 0, win32con.REG_EXPAND_SZ, new_path_value)
win32api.RegCloseKey(key)
refresh_path()
def edit_environment_variables_file(file_path, new_path):
with open(file_path, 'a') as environment_variables_file:
environment_variables_file.write(f'PATH = "{new_path}{os.pathsep}$PATH"\n')
refresh_path()
def add_to_path_bash(new_path):
new_path = new_path.strip().replace('\\', '/').removesuffix('/')
if os.path.exists(os.path.expanduser('~/.bash_profile')):
file_path = os.path.expanduser('~/.bash_profile')
elif os.path.expanduser('~/.profile'):
file_path = os.path.expanduser('~/.profile')
else:
raise FileNotFoundError("Couldn't find the .bash_profile/.profile")
edit_environment_variables_file(file_path, new_path)
def add_to_path_sh(new_path):
new_path = new_path.strip().replace('\\', '/').removesuffix('/')
if os.path.expanduser('~/.profile'):
file_path = os.path.expanduser('~/.profile')
else:
raise FileNotFoundError("Couldn't find the .profile")
edit_environment_variables_file(file_path, new_path)
def add_to_path_zsh(new_path):
new_path = new_path.strip().replace('\\', '/').removesuffix('/')
if os.path.expanduser('~/.zshenv'):
file_path = os.path.expanduser('~/.zshenv')
else:
raise FileNotFoundError("Couldn't find the .zshenv")
edit_environment_variables_file(file_path, new_path)
def add_to_path_unix(shell):
if shell == 'bash':
return add_to_path_bash
elif shell == 'sh':
return add_to_path_sh
elif shell == 'zsh':
return add_to_path_zsh
# ################################################################################
def remove_from_path_win(new_path):
new_path = new_path.strip().replace('/', '\\').removesuffix("\\")
key = win32api.RegOpenKeyEx(
win32con.HKEY_CURRENT_USER,
r"Environment",
0,
win32con.KEY_ALL_ACCESS
)
current_path, _ = win32api.RegQueryValueEx(key, "Path")
current_path = current_path.split(os.pathsep)
if new_path not in current_path or \
new_path + '\\' not in current_path:
return
if new_path + '\\' in current_path:
new_path = new_path + '\\'
current_path.remove(new_path)
win32api.RegSetValueEx(key, "Path", 0, win32con.REG_EXPAND_SZ, os.pathsep.join(current_path))
win32api.RegCloseKey(key)
refresh_path()
def remove_from_environment_variables_file(file_path, new_path):
with open(file_path, 'r+') as environment_variables_file:
content = environment_variables_file.read()
if f'export PATH = "{new_path}{os.pathsep}$PATH"\n' in content:
content = content.replace(f'export PATH = "{new_path}{os.pathsep}$PATH"\n', '')
if f'PATH = "{new_path}{os.pathsep}$PATH"\n' in content:
content = content.replace(f'PATH = "{new_path}{os.pathsep}$PATH"\n', '')
refresh_path()
def remove_from_path_bash(new_path):
new_path = new_path.strip().replace('\\', '/').removesuffix('/')
if os.path.exists(os.path.expanduser('~/.bash_profile')):
file_path = os.path.expanduser('~/.bash_profile')
elif os.path.expanduser('~/.profile'):
file_path = os.path.expanduser('~/.profile')
else:
raise FileNotFoundError("Couldn't find the .bash_profile/.profile")
remove_from_environment_variables_file(file_path, new_path)
def remove_from_path_sh(new_path):
new_path = new_path.strip().replace('\\', '/').removesuffix('/')
if os.path.expanduser('~/.profile'):
file_path = os.path.expanduser('~/.profile')
else:
raise FileNotFoundError("Couldn't find the .profile")
remove_from_environment_variables_file(file_path, new_path)
def remove_from_path_zsh(new_path):
new_path = new_path.strip().replace('\\', '/').removesuffix('/')
if os.path.expanduser('~/.zshenv'):
file_path = os.path.expanduser('~/.zshenv')
else:
raise FileNotFoundError("Couldn't find the .zshenv")
remove_from_environment_variables_file(file_path, new_path)
def remove_from_path_unix(shell):
if shell == 'bash':
return remove_from_path_bash
elif shell == 'sh':
return remove_from_path_sh
elif shell == 'zsh':
return remove_from_path_zsh
# ################################################################################
def refresh_path_win():
key = win32api.RegOpenKeyEx(
win32con.HKEY_CURRENT_USER,
r"Environment",
0,
win32con.KEY_ALL_ACCESS
)
current_path, _ = win32api.RegQueryValueEx(key, "Path")
os.environ['PATH'] = current_path
win32api.RegCloseKey(key)
def refresh_from_environment_variables_file(shell, file_path):
proc = subprocess.Popen(
[shell],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
proc.stdin.write(f"source {file_path}\n")
proc.stdin.write(f"echo $PATH\n")
path, _ = proc.communicate()
os.environ['PATH'] = path
def refresh_path_bash():
if os.path.exists(os.path.expanduser('~/.bash_profile')):
file_path = '~/.bash_profile'
elif os.path.expanduser('~/.profile'):
file_path = '~/.profile'
else:
raise FileNotFoundError("Couldn't find the .bash_profile/.profile")
refresh_from_environment_variables_file(shell, file_path)
def refresh_path_sh():
if os.path.expanduser('~/.profile'):
file_path = '~/.profile'
else:
raise FileNotFoundError("Couldn't find the .profile")
refresh_from_environment_variables_file(shell, file_path)
def refresh_path_zsh():
if os.path.expanduser('~/.zshenv'):
file_path = '~/.zshenv'
else:
raise FileNotFoundError("Couldn't find the .zshenv")
refresh_from_environment_variables_file(shell, file_path)
def refresh_path_unix(shell):
if shell == 'bash':
return refresh_path_bash
elif shell == 'sh':
return refresh_path_sh
elif shell == 'zsh':
return refresh_path_zsh
if sys.platform in ("win32", 'cygwin'):
import win32api
import win32con
add_to_path = add_to_path_win
remove_from_path = remove_from_path_win
refresh_path = refresh_path_win
elif sys.platform.startswith("linux") or sys.platform == "darwin":
shell = os.environ['SHELL'].removeprefix('/bin/')
if shell in ('bash', 'sh', 'zsh'):
add_to_path = add_to_path_unix(shell)
remove_from_path = remove_from_path_unix(shell)
refresh_path = refresh_path_unix(shell)
else:
raise NotImplementedError(f"Unsupported shell: {shell}")
else:
raise NotImplementedError(f"Unsupported platform: {sys.platform}")
Я опубликовал это на PyPI в пакете под названием pathenv
, исходный код см. https://github.com/foxypiratecove37350/pathenv.
remove_from_path()
обрабатывает только два из бесконечно возможных случаев.Похоже, вы хотите навязать интерфейс Windows на платформах, где это не будет оценено по достоинству, а иногда даже невозможно.
Изначально вопрос был задан о системе PATH
, поэтому здесь есть один раздел, посвященный этому.
PATH
Во многих современных Unix-подобных системах система PATH
настраивается с помощью файлов /etc
.
systemd
вы можете добавить файл /etc/environ.d
, который обновит систему PATH
./etc/paths.d
Однако обычно этого делать не следует. См. раздел «Альтернативы» ниже.
PATH
Для этого не существует стандартного механизма; но некоторые установщики, такие как rbenv
, добавляют несколько строк в конфигурацию оболочки пользователя с комментарием, указывающим, какой компонент добавил изменение и почему.
Неудивительно, что это имеет тенденцию вызывать ошибки, когда пользователи вручную меняют строки, удаляют, а затем переустанавливают или запускают установщик несколько раз, когда что-то не удалось в первый раз, или они не поняли, что он сделал.
Как и в предыдущем разделе, менее навязчивый подход состоит в том, чтобы предоставить пользователю возможность сначала установить ваше программное обеспечение в разумном месте и просто задокументировать, как он может изменить свои личные PATH
при необходимости.
Системный администратор несет ответственность за поддержание PATH
, и вы не хотите саботировать его усилия. Просто установите свои двоичные файлы в /usr/local/bin
(или, если вы собираете, например, пакет .deb
, в usr/bin
в иерархии пакетов) и позвольте пользователю убедиться, что PATH
является нормальным.
Если вы настаиваете на создании собственного установщика, убедитесь, что он соответствует Стандарту иерархии файловой системы, который по сути представляет собой то же самое в отношении двоичных файлов, но также содержит подробные инструкции для других файлов (файлы общей конфигурации, файлы данных , библиотеки и т. д.).
Более плодотворный подход — предоставить пакеты для популярных платформ (пакеты Debian будут работать на Ubuntu, Mint и т. д.; RPM для Red Hat, CentOS, Rocky и т. д.; у Alpine и Arch есть свои собственные системы и т. д. и т. п.) и предоставить .tar.gz
обычный configure; make; make install
как запасной вариант (или, может быть, в вашем случае просто пакет PyPI?)
Менеджеры пакетов обычно устанавливают все в масштабе всей системы и даже не имеют четко определенного понятия «текущий пользователь». Если необходима нетривиальная конфигурация пользователя, возможно, упакуйте отдельный инструмент, который создаст необходимые файлы конфигурации для каждого пользователя, который его вызывает.
На компьютерах Mac, если у вас есть пакет приложений, его следует установить в /Applications
; но для чего-то, что не является простым графическим интерфейсом, просто вернитесь к традиционным соглашениям Unix. Для этого не существует стандартной структуры, но большинство пользователей, у которых есть такая необходимость, вероятно, уже используют Homebrew.
(Существуют конкурирующие менеджеры пакетов, которые вы также можете поддерживать; но это становится стандартом де-факто.)
Пожалуйста, принесите извинения по двум причинам: - Я уже ответил, но не могу принять его как принятый, потому что Stack Overflow требует, чтобы я подождал 2 дня, чтобы это сделать - И я забыл указать, что отказался от идеи использования системы PATH
пользователь PATH
.
Кроме того, это установщик с графическим интерфейсом, но мой проект можно установить, загрузив ZIP-файл или скомпилировав его из исходного кода, так что это просто для упрощения установки и использования моего проекта.
Это то, что вы обычно настраиваете в оболочке/процессе, который будет выполнять ваш скрипт Python, а не в самом скрипте. В лучшем случае вы можете отредактировать модуль или файл конфигурации, который будет использовать ваш скрипт.