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?


¿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.


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.


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

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.