Como executar o comando shell usando o perfil do shell e os ganchos de diretório atuais (por exemplo, direnv)

9

Estou tentando obter shell-commande async-shell-commandintegrar perfeitamente com alguns programas no meu .bashrcarquivo, especificamente o direnv neste exemplo.

Descobri que, se eu personalizasse shell-command-switch, poderia obter processos de shell para carregar meu perfil como se fosse um shell de logon interativo regular:

(setq shell-command-switch (purecopy "-ic"))
(setq explícito-bash-args '("-ic" "exporta EMACS =; eco stty; bash"))

Eu também estou usando exec-path-from-shell .

Digamos que eu tenho um ~/.bashrcarquivo com:

...

avaliação "$ (gancho direnv $ 0)"
eco "foo"

Dentro ~/code/foo, tenho um .envrcarquivo com:

exportar PATH = $ PWD / bin: $ PATH
eco "bar"

Se eu executar M-x shellcom default-directoryset ~/code/foo, um shell bash carregará meu perfil corretamente e executará o gancho direnv para adicioná-lo ao meu caminho:

direnv: carregando .envrc
Barra
direnv: exportação ~ PATH
~ / code / foo $ echo $ PATH
/ Usuários / nome de usuário / código / foo / bin: / usr / local / bin: ... # rest of $ PATH

No entanto, se default-directoryainda está ~/code/fooe eu corro M-! echo $PATH, ele carrega corretamente meu .bashrc, mas não executa o gancho direnv do diretório atual:

foo
/ usr / local / bin: ... # resto de $ PATH sem ./bin

Eu obtenho o mesmo resultado se eu executar M-! cd ~/code/foo && echo $PATH.

Existe uma maneira de aconselhar, conectar shell-commandou start-processfazer com que ele se comporte como se estivesse sendo enviado de um buffer de shell interativo?

waymondo
fonte
Como é o seu gancho direnv? Se estiver definido em ~ / .bashrc e você avaliar (setq shell-command-switch "-ic"), deverá ser avaliado juntamente com todos os outros comandos em ~ / .bashrc.
rekado 25/10
A linha no meu ~ / .bashrc é eval "$(direnv hook $0)". Isso está sendo executado, mas o mecanismo que deve ser acionado quando você está em um diretório específico com um .envrcarquivo não está.
waymondo
Nada no .envrcarquivo é avaliado? Ou são apenas variáveis ​​de ambiente que não são exportadas? Poderia, por favor, fornecer um exemplo completo para que eu possa tentar reproduzir isso?
rekado
@rekado - Obrigado por dar uma olhada. Atualizei minha pergunta para ser mais explícita na reprodução.
waymondo

Respostas:

5

Isso não parece ser um problema com o Emacs, mas com o bash. shell-commandapenas executa call-processno shell e passa argumentos. Eu tentei isso em um shell regular:

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

~/.bashrcé originário, mas o gancho direnv não é executado. Quando direnv hook bashé executada, uma função _direnv_hooké enviada e anexada a PROMPT_COMMAND.

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

Eu suspeito que PROMPT_COMMANDsimplesmente não vai funcionar. Porém, isso não é um problema, porque _direnv_hooké realmente simples. Podemos simplesmente acrescentar eval $(direnv export bash)o comando shell e ele funcionará:

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

Isso imprimirá o caminho aumentado para o buffer de mensagens. Como alternativa, execute com M-!:

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

Você não precisa fazer (setq shell-command-switch "-ic")isso funcionar.

rekado
fonte
Obrigado, isso realmente ajudou a me colocar no caminho certo. Eu estava procurando uma solução que não envolvesse adicionar o eval "$(direnv export bash)"in elisp, mas isso foi extremamente útil e me colocou no caminho certo.
waymondo
5

A execução eval "$(direnv hook $0)"define uma função que se conecta $PROMPT_COMMAND, que nunca é chamada quando o bash é executado, bash -icporque não há prompt. Você pode alterar a linha:

eval "$(direnv hook $0)"

para:

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

chamar explicitamente a função de gancho.

Edit: Acabei de perceber que o rekado deu uma resposta muito semelhante.

Erik Hetzner
fonte
Esta é a resposta mais concisa, apontando meu desconhecimento de como o direnv trabalha $PROMPT_COMMAND.
waymondo
4

Agradecemos a Rekado e Erik por apontarem como o gancho direnv funciona usando $PROMPT_COMMAND. Como shell-commandnão usa um prompt, isso não estava sendo executado.

Embora a resposta de Erik funcione no meu exemplo de chamada de um comando shell M-!com default-directoryset, ela não funcionaria no exemplo a seguir:

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

Depois de pesquisar no Google, encontrei uma maneira de criar um gancho preexec no bash para executar algo antes de um comando ser executado. Eu peguei essa ideia e modifiquei para atender minha necessidade de direnv. Aqui está a aparência da parte relevante do meu ~ / .bashrc agora:

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
fonte
0

hoje em dia você provavelmente gostaria de usar https://github.com/wbolster/emacs-direnv

funciona de forma semelhante ao gancho que o direnv instala no seu shell. o ambiente emacs é atualizado mediante solicitação (ou automaticamente ao alternar buffers) para corresponder a quais estados direnv é o ambiente correto para o diretório atual.

modificando exec-pathe o process-environmentemacs se comportará como o seu shell faria: execute programas dos caminhos certos e com o ambiente certo.

wouter bolsterlee
fonte