So führen Sie den Shell-Befehl mithilfe des Shell-Profils und der aktuellen Verzeichnis-Hooks aus (z. B. direnv)

9

Ich versuche , zu erhalten shell-commandund async-shell-commandmit ein paar Programme in meiner nahtlos zu integrieren .bashrcDatei, speziell direnv in diesem Beispiel.

Ich stellte fest, dass ich beim Anpassen shell-command-switchShell-Prozesse dazu bringen könnte, mein Profil so zu laden, als wäre es eine reguläre interaktive Login-Shell:

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

Ich benutze auch exec-path-from-shell .

Angenommen, ich habe eine ~/.bashrcDatei mit:

...

eval "$ (direnv hook $ 0)"
Echo "foo"

Im Inneren habe ~/code/fooich eine .envrcDatei mit:

export PATH = $ PWD / bin: $ PATH
Echo "Bar"

Wenn ich M-x shellmit default-directoryset to ~/code/fooausführe, lädt eine Bash-Shell mein Profil korrekt und führt den direnv-Hook aus, um dies meinem Pfad hinzuzufügen:

direnv: Laden von .envrc
Bar
direnv: export ~ PATH
~ / code / foo $ echo $ PATH
/ Benutzer / Benutzername / Code / foo / bin: / usr / local / bin: ... # Rest von $ PATH

Wenn dies default-directoryjedoch immer noch der Fall ist ~/code/foound ich ausgeführt werde M-! echo $PATH, wird meine .bashrc-Datei korrekt geladen, der direnv-Hook des aktuellen Verzeichnisses wird jedoch nicht ausgeführt:

foo
/ usr / local / bin: ... # Rest von $ PATH ohne ./bin

Ich bekomme das gleiche Ergebnis, wenn ich renne M-! cd ~/code/foo && echo $PATH.

Gibt es eine Weise , die ich beraten oder Haken in shell-commandoder start-processum es sich verhalten , als ob es aus einem interaktiven Shell - Puffer gesendet wurden?

Waymondo
quelle
Wie sieht dein direnv hook aus? Wenn es in ~ / .bashrc definiert ist und Sie (setq shell-command-switch "-ic")es ausgewertet haben, sollte es zusammen mit jedem anderen Befehl in ~ / .bashrc ausgewertet werden.
rekado
Die Zeile in meinem ~ / .bashrc ist eval "$(direnv hook $0)". Dies wird ausgeführt, aber der Mechanismus, der ausgelöst werden soll, wenn Sie sich in einem bestimmten Verzeichnis mit einer .envrcDatei befinden, ist dies nicht.
Waymondo
Wird nichts in der .envrcDatei ausgewertet? Oder werden nur Umgebungsvariablen nicht exportiert? Könnten Sie bitte ein vollständiges Beispiel angeben, damit ich versuchen kann, dies zu reproduzieren?
rekado
@rekado - Danke, dass du es dir angesehen hast. Ich habe meine Frage so aktualisiert, dass sie für die Reproduktion expliziter ist.
Waymondo

Antworten:

5

Dies scheint kein Problem mit Emacs zu sein, sondern mit Bash. shell-commandwird nur call-processauf der Shell ausgeführt und übergibt Argumente. Ich habe das auf einer normalen Shell versucht:

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

~/.bashrcwird bezogen, aber der direnv-Hook wird nicht ausgeführt. Wenn direnv hook bashausgeführt, wird eine Funktion _direnv_hookausgegeben und vorangestellt PROMPT_COMMAND.

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

Ich vermute, das wird PROMPT_COMMANDeinfach nicht funktionieren. Das ist aber kein Problem, denn das _direnv_hookist wirklich einfach. Wir können einfach eval $(direnv export bash)den Shell-Befehl voranstellen und es wird funktionieren:

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

Dadurch wird der erweiterte Pfad zum Nachrichtenpuffer gedruckt. Alternativ können Sie Folgendes ausführen M-!:

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

Sie müssen nicht, (setq shell-command-switch "-ic")damit dies funktioniert.

rekado
quelle
Danke, das hat mir wirklich geholfen, auf den richtigen Weg zu kommen. Ich suchte nach einer Lösung, bei der das eval "$(direnv export bash)"Elisp nicht hinzugefügt werden muss, aber dies war äußerst hilfreich und brachte mich auf den richtigen Weg.
Waymondo
5

Running eval "$(direnv hook $0)"definiert eine Funktion, die sich einhakt $PROMPT_COMMANDund die beim Ausführen von bash niemals aufgerufen wird, bash -icda keine Eingabeaufforderung vorhanden ist. Sie können die Zeile ändern:

eval "$(direnv hook $0)"

zu:

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

die Hook-Funktion explizit aufrufen.

Bearbeiten: Gerade realisiert, gab rekado eine sehr ähnliche Antwort.

Erik Hetzner
quelle
Dies ist die prägnanteste Antwort, die darauf hinweist, dass ich nicht weiß, wie direnv funktioniert $PROMPT_COMMAND.
Waymondo
4

Vielen Dank an Rekado und Erik für den Hinweis, wie der Direnv-Hook mit funktioniert $PROMPT_COMMAND. Da shell-commandkeine Eingabeaufforderung verwendet wird, wurde diese nicht ausgeführt.

Während Eriks Antwort in meinem Beispiel funktioniert, in dem ein Shell-Befehl M-!mit default-directoryset aufgerufen wird, funktioniert sie im folgenden Beispiel nicht:

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

Nach einigem googeln habe ich einen Weg gefunden, einen preexec-Hook in bash zu erstellen , um etwas auszuführen, bevor ein Befehl ausgeführt wird. Ich nahm diese Idee und modifizierte sie, um meinem Bedürfnis nach Direnv gerecht zu werden. So sieht der relevante Teil meines ~ / .bashrc jetzt aus:

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

Heutzutage möchten Sie wahrscheinlich https://github.com/wbolster/emacs-direnv verwenden

Es funktioniert ähnlich wie der Hook, den direnv in Ihrer Shell installiert. Die Emacs-Umgebung wird auf Anfrage (oder automatisch beim Wechseln der Puffer) aktualisiert, um den direkten Umgebungen für das aktuelle Verzeichnis zu entsprechen.

Durch Ändern exec-pathund process-environmentEmacs wird sich so verhalten, wie es Ihre Shell tun würde: Führen Sie Programme von den richtigen Pfaden und mit der richtigen Umgebung aus.

Wouter Polsterlee
quelle