Comment exécuter la commande shell à l'aide du profil shell et des hooks de répertoires actuels (ex. Direnv)

9

J'essaie d'obtenir shell-commandet async-shell-commandd'intégrer de manière transparente avec quelques programmes dans mon .bashrcfichier, en particulier direnv dans cet exemple.

J'ai trouvé que si je personnalisais shell-command-switch, je pouvais obtenir des processus shell pour charger mon profil comme s'il s'agissait d'un shell de connexion interactif normal:

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

J'utilise également exec-path-from-shell .

Disons que j'ai un ~/.bashrcfichier avec:

...

eval "$ (crochet direnv $ 0)"
écho "foo"

A l'intérieur ~/code/fooj'ai un .envrcdossier avec:

export PATH = $ PWD / bin: $ PATH
écho "bar"

Si je lance M-x shellavec default-directoryset to ~/code/foo, un shell bash chargera correctement mon profil et exécutera le hook direnv pour l'ajouter à mon chemin:

direnv: chargement .envrc
bar
direnv: export ~ PATH
~ / code / foo $ echo $ PATH
/ Utilisateurs / nom d'utilisateur / code / foo / bin: / usr / local / bin: ... # reste de $ PATH

Cependant si default-directoryest toujours ~/code/fooet que je cours M-! echo $PATH, il charge correctement mon .bashrc mais n'exécute pas le hook direnv du répertoire courant:

foo
/ usr / local / bin: ... # reste de $ PATH sans ./bin

J'obtiens le même résultat si je cours M-! cd ~/code/foo && echo $PATH.

Existe-t-il un moyen de conseiller ou de connecter shell-commandou start-processde le faire se comporter comme s'il était envoyé à partir d'un tampon shell interactif?

waymondo
la source
À quoi ressemble votre crochet direnv? S'il est défini dans ~ / .bashrc et que vous l'évaluez, (setq shell-command-switch "-ic")il doit être évalué avec toutes les autres commandes dans ~ / .bashrc.
rekado
La ligne dans mon ~ / .bashrc est eval "$(direnv hook $0)". Cela s'exécute, mais le mécanisme qui devrait être déclenché lorsque vous êtes dans un répertoire spécifique avec un .envrcfichier ne l'est pas.
waymondo
Rien dans le .envrcfichier n'est-il évalué? Ou s'agit-il uniquement de variables d'environnement qui ne sont pas exportées? Pourriez-vous s'il vous plaît fournir un exemple complet afin que je puisse essayer de le reproduire?
rekado
@rekado - Merci de l'avoir regardé. J'ai mis à jour ma question pour être plus explicite pour la reproduction.
waymondo

Réponses:

5

Cela ne semble pas être un problème avec Emacs mais avec bash. shell-commands'exécute simplement call-processsur le shell et passe des arguments. J'ai essayé cela sur un shell régulier:

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

~/.bashrcest originaire, mais le hook direnv n'est pas exécuté. Quand direnv hook bashest exécuté, une fonction _direnv_hookest sortie et ajoutée au début PROMPT_COMMAND.

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

Je soupçonne que PROMPT_COMMANDcela ne fonctionnera tout simplement pas. Ce n'est pas un problème, cependant, car _direnv_hookc'est vraiment simple. Nous pouvons simplement ajouter eval $(direnv export bash)à la commande shell et cela fonctionnera:

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

Cela affichera le chemin augmenté vers le tampon des messages. Vous pouvez également exécuter avec M-!:

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

Vous n'en avez pas besoin pour (setq shell-command-switch "-ic")que cela fonctionne.

rekado
la source
Merci, cela m'a vraiment aidé à me mettre sur la bonne voie. Je cherchais une solution qui n'implique pas l'ajout de eval "$(direnv export bash)"in elisp, mais cela a été extrêmement utile et m'a mis sur la bonne voie.
waymondo
5

L'exécution eval "$(direnv hook $0)"définit une fonction qui se connecte $PROMPT_COMMAND, qui n'est jamais appelée lorsque bash est exécuté bash -iccar il n'y a pas d'invite. Vous pouvez changer la ligne:

eval "$(direnv hook $0)"

à:

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

pour appeler explicitement la fonction hook.

Edit: Je viens de réaliser que rekado a donné une réponse très similaire.

Erik Hetzner
la source
Ceci est la réponse la plus concise soulignant ma méconnaissance de la façon dont Direnv fonctionne $PROMPT_COMMAND.
waymondo
4

Merci à Rekado et Erik d'avoir souligné le fonctionnement du hook direnv en utilisant $PROMPT_COMMAND. Puisque shell-commandn'utilise pas d'invite, cela n'a pas été exécuté.

Bien que la réponse d'Erik fonctionne dans mon exemple d'appeler une commande shell avec M-!avec default-directoryset, cela ne fonctionnerait pas dans l'exemple suivant:

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

Après quelques recherches sur Google, j'ai trouvé un moyen de créer un hook preexec dans bash pour exécuter quelque chose avant l'exécution d'une commande. J'ai pris cette idée et modifié pour répondre à mon besoin de direnv. Voici à quoi ressemble la partie pertinente de mon ~ / .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
la source
0

de nos jours, vous voudrez probablement utiliser https://github.com/wbolster/emacs-direnv

cela fonctionne de manière similaire au hook que direnv installe dans votre shell. l'environnement emacs est mis à jour sur demande (ou automatiquement lors du changement de tampons) pour correspondre à ce que direnv indique est l'environnement correct pour le répertoire en cours.

en modifiant exec-pathet process-environmentemacs se comportera comme le ferait votre shell: exécutez les programmes à partir des bons chemins et avec le bon environnement.

wouter bolsterlee
la source