Как заставить команду оболочки работать с использованием профиля оболочки и текущих хуков каталогов (например, direnv)

9

Я пытаюсь получить shell-commandи async-shell-commandлегко интегрировать с парой программ в моем .bashrcфайле, в частности, direnv в этом примере.

Я обнаружил, что если бы я настроил shell-command-switch, я мог заставить процессы оболочки загружать мой профиль, как если бы это была обычная интерактивная оболочка входа в систему:

(setq shell-command-switch (purecopy "-ic"))
(setqlicit-bash-args '("-ic" "export EMACS =; stty echo; bash"))

Я также использую exec-path-from-shell .

Скажем, у меня есть ~/.bashrcфайл с:

...

eval "$ (direnv hook $ 0)"
эхо "фу"

Внутри у ~/code/fooменя есть .envrcфайл с:

экспорт PATH = $ PWD / bin: $ PATH
эхо "бар"

Если я запускаю M-x shellс default-directoryустановленным на ~/code/foo, оболочка bash правильно загрузит мой профиль и запустит ловушку direnv, чтобы добавить это к моему пути:

direnv: загрузка .envrc
бар
direnv: экспорт ~ PATH
~ / code / foo $ echo $ PATH
/ Users / имя пользователя / код / ​​foo / bin: / usr / local / bin: ... # остаток $ PATH

Однако, если default-directoryвсе еще, ~/code/fooи я запускаю M-! echo $PATH, он корректно загружает мой .bashrc, но не выполняет ловушку direnv текущего каталога:

Foo
/ usr / local / bin: ... # остаток $ PATH без ./bin

Я получаю тот же результат, если я бегу M-! cd ~/code/foo && echo $PATH.

Есть ли способ, которым я могу посоветовать или подключиться shell-commandили start-processзаставить его вести себя так, как будто он отправляется из интерактивного буфера оболочки?

waymondo
источник
Как выглядит ваш хуй direnv? Если он определен в ~ / .bashrc и вы eval'd, (setq shell-command-switch "-ic")то он должен оцениваться вместе с любой другой командой в ~ / .bashrc.
rekado
Строка в моем ~ / .bashrc есть eval "$(direnv hook $0)". Это выполняется, но механизм, который должен срабатывать, когда вы находитесь в определенном каталоге с .envrcфайлом, не работает.
waymondo
Ничто в .envrcфайле не оценивается? Или это просто переменные среды, которые не экспортируются? Не могли бы вы привести полный пример, чтобы я мог попытаться воспроизвести это?
rekado
@rekado - Спасибо, что взглянули на это. Я обновил свой вопрос, чтобы быть более четким для воспроизведения.
waymondo

Ответы:

5

Это не проблема для Emacs, а для bash. shell-commandпросто выполняется call-processна оболочке и передает аргументы. Я попробовал это на обычной оболочке:

bash -ic "cd ~/code/foo && echo $PATH"

~/.bashrcИсточник, но ловушка direnv не запускается. Когда direnv hook bashвыполняется, функция _direnv_hookвыводится и добавляется к PROMPT_COMMAND.

_direnv_hook() {
  eval "$(direnv export bash)";
};
if ! [[ "$PROMPT_COMMAND" =~ _direnv_hook ]]; then
  PROMPT_COMMAND="_direnv_hook;$PROMPT_COMMAND";
fi

Я подозреваю, что это PROMPT_COMMANDпросто не сработает. Это не проблема, потому что _direnv_hookэто действительно просто. Мы можем просто добавить eval $(direnv export bash)команду shell, и она будет работать:

(let ((default-directory "~/code/foo/"))
  (shell-command "eval \"$(direnv export bash)\" && echo $PATH"))

Это распечатает расширенный путь к буферу сообщений. В качестве альтернативы выполните с M-!:

cd ~/code/foo && eval "$(direnv export bash)" && echo $PATH

Вам не нужно, чтобы (setq shell-command-switch "-ic")это работало.

rekado
источник
Спасибо, это действительно помогло поставить меня на правильный путь. Я искал решение, которое не eval "$(direnv export bash)"включало бы добавление в elisp, но это было чрезвычайно полезно и поставило меня на правильный путь.
waymondo
5

Выполнение eval "$(direnv hook $0)"определяет функцию, к которой подключается $PROMPT_COMMAND, которая никогда не вызывается при запуске bash, bash -icпоскольку отсутствует приглашение. Вы можете изменить строку:

eval "$(direnv hook $0)"

чтобы:

eval "$(direnv hook $0)" && _direnv_hook

явно вызвать функцию крючка.

Изменить: Только что понял, rekado дал очень похожий ответ.

Эрик Хецнер
источник
Это самый краткий ответ, указывающий на мое незнакомство с тем, как работает direnv $PROMPT_COMMAND.
waymondo
4

Спасибо Rekado и Erik за указание на то, как работает ловушка direnv с помощью $PROMPT_COMMAND. Поскольку shell-commandподсказка не используется, это не выполняется.

Хотя ответ Эрика работает в моем примере вызова команды оболочки с M-!помощью default-directoryset, он не будет работать в следующем примере:

(let ((default-directory "~/code/"))
  (shell-command "cd ~/code/foo && echo $PATH"))

После некоторого поиска в Google, я нашел способ создать хеш preexec в bash для выполнения чего-либо перед выполнением команды. Я взял эту идею и изменил, чтобы соответствовать моей потребности в direnv. Вот как выглядит соответствующая часть моего ~ / .bashrc:

eval "$(direnv hook $0)"
direnv_preexec () {
  [ -n "$COMP_LINE" ] && return # don't execute on completion
  [ "$BASH_COMMAND" \< "$PROMPT_COMMAND" ] && return # don't double execute on prompt hook
  eval "$(direnv export bash)"
}
trap "direnv_preexec && $BASH_COMMAND" DEBUG
waymondo
источник
0

в настоящее время вы, вероятно, захотите использовать https://github.com/wbolster/emacs-direnv

он работает аналогично хуку, который direnv устанавливает в вашей оболочке. среда emacs обновляется по запросу (или автоматически при переключении буферов), чтобы соответствовать тому, что указывает direnv, является правильной средой для текущего каталога.

путем изменения, exec-pathи process-environmentemacs будет вести себя так, как ваша оболочка: выполнять программы с правильных путей и в правильной среде.

воутер болстерлее
источник