如何使用Shell配置文件和当前目录挂钩(例如direnv)使shell命令运行


9

我正在尝试获取shell-commandasync-shell-command.bashrc文件中的几个程序无缝集成,在此示例中特别是direnv

我发现,如果我进行了自定义shell-command-switch,则可以使外壳程序进程像常规的交互式登录外壳程序一样加载我的配置文件:

(setq shell-command-switch(纯副本“ -ic”))
(setqexplicit-bash-args'(“ -ic”“ export EMACS =; stty echo; bash”))

我也在使用exec-path-from-shell

说我有一个~/.bashrc文件:

...

评估“ $(direnv hook $ 0)”
回声“ foo”

里面~/code/foo我有一个.envrc文件:

导出PATH = $ PWD / bin:$ PATH
回声“栏”

如果我运行M-x shelldefault-directory设置为~/code/foo,是bash shell会正确加载我的个人资料和运行direnv钩到添加到我的路径:

direnv:加载.envrc
酒吧
direnv:导出〜PATH
〜/ code / foo $ echo $ PATH
/用户/用户名/代码/ foo / bin:/ usr / local / bin:...#$ PATH的其余部分

但是如果default-directory仍然~/code/foo运行M-! echo $PATH,它将正确加载我的.bashrc,但不执行当前目录的direnv钩子:

富
/ usr / local / bin:...#$ PATH其余部分不带./bin

如果我跑步,我会得到相同的结果M-! cd ~/code/foo && echo $PATH

有什么方法可以建议或挂钩它,shell-commandstart-process使其表现得好像是从交互式外壳缓冲区发送的吗?


您的direnv挂钩是什么样的?如果它是在〜/ .bashrc中定义的,并且您已(setq shell-command-switch "-ic")进行评估,则应与〜/ .bashrc中的所有其他命令一起对其进行评估。
rekado 2014年

我的〜/ .bashrc中的行是eval "$(direnv hook $0)"。这正在执行,但是当您位于带有.envrc文件的特定目录中时,不会触发该机制。
waymondo 2014年

.envrc文件中没有任何内容吗?还是只是不导出环境变量?您能否提供一个完整的例子,以便我可以尝试重现?
rekado 2014年

@rekado-感谢您看一下。我更新了我的问题,以便更明确地再现。
waymondo 2014年

Answers:


5

这似乎不是Emacs的问题,而是bash。 shell-command只是call-process在shell上执行并传递参数。我在常规shell上尝试过:

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")为此工作。


谢谢,这确实使我走上了正轨。我一直在寻找不涉及添加eval "$(direnv export bash)"elisp 的解决方案,但这非常有帮助,使我走上了正确的道路。
waymondo 2014年

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 2014年

4

感谢Rekado和Erik指出使用,direnv挂钩是如何工作的$PROMPT_COMMAND。由于shell-command不使用提示,因此未执行。

虽然埃里克的回答工作在我调用shell命令使用的例子M-!default-directory集,那么,在下面的例子不工作:

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

经过一番谷歌搜索,我找到了一种在bash中创建preexec钩子的方法,以在执行命令之前执行某些操作。我采纳了这个想法,并进行了修改以满足我对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

0

如今,您可能想使用https://github.com/wbolster/emacs-direnv

它的工作原理与direnv安装在您的Shell中的挂钩类似。emacs环境将根据请求(或在切换缓冲区时自动更新)进行更新,以匹配direnv指出的是当前目录的正确环境。

通过修改exec-pathprocess-environmentemacs的行为将与您的shell一样:在正确的路径和正确的环境下执行程序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.