Cómo hacer que el comando de shell se ejecute utilizando el perfil de shell y los ganchos de directorio actuales (ej. Direnv)

9

Estoy tratando de conseguir shell-commandy async-shell-commandpara integrarse perfectamente con un par de programas en mi .bashrcarchivo, específicamente direnv en este ejemplo.

Descubrí que si lo personalizaba shell-command-switch, podía obtener procesos de shell para cargar mi perfil como si fuera un shell de inicio de sesión interactivo normal:

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

También estoy usando exec-path-from-shell .

Digamos que tengo un ~/.bashrcarchivo con:

...

eval "$ (enlace directo $ 0)"
echo "foo"

Dentro ~/code/footengo un .envrcarchivo con:

RUTA de exportación = $ PWD / bin: $ RUTA
echo "bar"

Si ejecuto M-x shellcon default-directoryset to ~/code/foo, un shell bash cargará correctamente mi perfil y ejecutará el enlace directo para agregarlo a mi ruta:

direnv: cargando .envrc
bar
direnv: export ~ RUTA
~ / code / foo $ echo $ PATH
/ Usuarios / nombre de usuario / código / foo / bin: / usr / local / bin: ... # resto de $ PATH

Sin embargo, si default-directoryestá quieto ~/code/fooy ejecuto M-! echo $PATH, carga correctamente mi .bashrc pero no ejecuta el enlace direnv del directorio actual:

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

Obtengo el mismo resultado si corro M-! cd ~/code/foo && echo $PATH.

¿Hay alguna manera de aconsejar o conectar shell-commando start-processhacer que se comporte como si se enviara desde un búfer de shell interactivo?

waymondo
fuente
¿Cómo se ve tu gancho directo? Si está definido en ~ / .bashrc y lo evalúa, (setq shell-command-switch "-ic")entonces debe evaluarse junto con cualquier otro comando en ~ / .bashrc.
rekado
La línea en mi ~ / .bashrc es eval "$(direnv hook $0)". Esto se está ejecutando, pero el mecanismo que debería activarse cuando está en un directorio específico con un .envrcarchivo no lo está.
waymondo
¿No se .envrcevalúa nada en el archivo? ¿O son solo las variables de entorno que no se exportan? ¿Podría darme un ejemplo completo para que pueda intentar reproducir esto?
rekado
@rekado - Gracias por echarle un vistazo. Actualicé mi pregunta para que sea más explícita para la reproducción.
waymondo

Respuestas:

5

Esto no parece ser un problema con Emacs sino con bash. shell-commandsimplemente se ejecuta call-processen el shell y pasa argumentos. Intenté esto en un shell normal:

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

~/.bashrces de origen, pero el enlace directo no se ejecuta. Cuando direnv hook bashse ejecuta, _direnv_hookse emite una función y se antepone a PROMPT_COMMAND.

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

Sospecho que eso PROMPT_COMMANDsimplemente no funcionará. Sin embargo, eso no es un problema, porque _direnv_hookes realmente simple. Simplemente podemos anteponer eval $(direnv export bash)el comando de shell y funcionará:

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

Esto imprimirá la ruta aumentada al búfer de mensajes. Alternativamente, ejecute con M-!:

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

No es necesario que (setq shell-command-switch "-ic")esto funcione.

rekado
fuente
Gracias, esto realmente me ayudó a ponerme en el camino correcto. Estaba buscando una solución que no implique agregar el eval "$(direnv export bash)"in elisp, pero esto fue extremadamente útil y me puso en el camino correcto.
waymondo
5

Ejecutar eval "$(direnv hook $0)"define una función que se conecta $PROMPT_COMMAND, a la que nunca se llama cuando se ejecuta bash bash -icporque no hay ninguna solicitud. Puedes cambiar la línea:

eval "$(direnv hook $0)"

a:

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

para llamar explícitamente a la función de enlace.

Editar: Acabo de darme cuenta de que rekado dio una respuesta muy similar.

Erik Hetzner
fuente
Esta es la respuesta más concisa que señala mi falta de familiaridad con cómo funciona directamente $PROMPT_COMMAND.
waymondo
4

Gracias a Rekado y Erik por señalar cómo funciona el gancho direnv usando $PROMPT_COMMAND. Como shell-commandno usa un indicador, esto no se estaba ejecutando.

Si bien la respuesta de Erik funciona en mi ejemplo de llamar a un comando de shell M-!con default-directoryset, no funcionaría en el siguiente ejemplo:

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

Después de buscar en Google, encontré una manera de crear un enlace preexec en bash para ejecutar algo antes de ejecutar un comando. Tomé esta idea y la modifiqué para satisfacer mi necesidad de dirección. Así es como se ve la parte relevante de mi ~ / .bashrc ahora:

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

hoy en día es probable que desee utilizar https://github.com/wbolster/emacs-direnv

Funciona de manera similar al gancho que se instala directamente en su shell. el entorno de emacs se actualiza a pedido (o automáticamente al cambiar los buffers) para que coincida con lo que indica direnv es el entorno correcto para el directorio actual.

modificando exec-pathy process-environmentemacs se comportará como lo haría su shell: ejecute programas desde las rutas correctas y con el entorno correcto.

wouter bolsterlee
fuente