Cara menjalankan shell-command menggunakan profil shell dan direktori hook saat ini (mis. Direnv)

9

Saya mencoba untuk mendapatkan shell-commanddan async-shell-commandmengintegrasikan secara mulus dengan beberapa program dalam .bashrcfile saya , khususnya direnv dalam contoh ini.

Saya menemukan bahwa jika saya mengkustomisasi shell-command-switch, saya bisa mendapatkan proses shell untuk memuat profil saya seolah-olah itu adalah shell login interaktif reguler:

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

Saya juga menggunakan exec-path-from-shell .

Katakanlah saya punya ~/.bashrcfile dengan:

...

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

Di dalam ~/code/foosaya punya .envrcfile dengan:

export PATH = $ PWD / bin: $ PATH
gema "bar"

Jika saya menjalankan M-x shelldengan default-directoryset ke ~/code/foo, bash shell akan memuat profil saya dengan benar dan menjalankan kait direnv untuk menambahkannya ke jalur saya:

direnv: memuat .envrc
batang
direnv: ekspor ~ PATH
~ / code / foo $ echo $ PATH
/ Pengguna / nama pengguna / kode / foo / bin: / usr / local / bin: ... # sisa dari $ PATH

Namun jika default-directorymasih ~/code/foodan saya jalankan M-! echo $PATH, itu benar memuat .bashrc saya tetapi tidak menjalankan hook direnv dari direktori saat ini:

foo
/ usr / local / bin: ... # sisa $ PATH tanpa ./bin

Saya mendapatkan hasil yang sama jika saya berlari M-! cd ~/code/foo && echo $PATH.

Apakah ada cara saya dapat menyarankan atau menghubungkan ke shell-commandatau start-processuntuk membuatnya berperilaku seolah-olah sedang dikirim dari buffer shell interaktif?

waymondo
sumber
Seperti apa pengait direnv Anda? Jika itu didefinisikan dalam ~ / .bashrc dan Anda mengevaluasi (setq shell-command-switch "-ic")maka itu harus dievaluasi bersama dengan setiap perintah lain di ~ / .bashrc.
rekado
Baris di ~ / .bashrc saya adalah eval "$(direnv hook $0)". Ini sedang dijalankan, tetapi mekanisme yang harus dipecat ketika Anda berada di direktori tertentu dengan .envrcfile tidak.
waymondo
Apakah tidak ada dalam .envrcfile yang dievaluasi? Atau hanya variabel lingkungan yang tidak diekspor? Bisakah Anda memberikan contoh lengkap sehingga saya dapat mencoba mereproduksi ini?
rekado
@ Kunado - Terima kasih telah melihatnya. Saya memperbarui pertanyaan saya menjadi lebih eksplisit untuk mereproduksi.
waymondo

Jawaban:

5

Ini tampaknya tidak menjadi masalah dengan Emacs tetapi dengan bash. shell-commandhanya mengeksekusi call-processdi shell dan melewati argumen. Saya mencoba ini pada shell biasa:

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

~/.bashrcbersumber, tetapi kait direnv tidak berjalan. Ketika direnv hook bashdieksekusi, suatu fungsi _direnv_hookadalah output dan ditambahkan ke PROMPT_COMMAND.

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

Saya menduga itu PROMPT_COMMANDtidak akan berhasil. Itu bukan masalah, karena _direnv_hookini sangat sederhana. Kami hanya bisa menambahkan eval $(direnv export bash)ke perintah shell dan itu akan bekerja:

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

Ini akan mencetak jalur yang ditambah ke buffer pesan. Atau, jalankan dengan M-!:

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

Anda tidak perlu melakukan (setq shell-command-switch "-ic")ini untuk bekerja.

rekado
sumber
Terima kasih, ini sangat membantu menempatkan saya di jalur yang benar. Saya sedang mencari solusi yang tidak melibatkan penambahan eval "$(direnv export bash)"di elisp, tetapi ini sangat membantu dan menempatkan saya di jalan yang benar.
waymondo
5

Menjalankan eval "$(direnv hook $0)"mendefinisikan fungsi yang terhubung $PROMPT_COMMAND, yang tidak pernah dipanggil ketika bash dijalankan bash -ickarena tidak ada prompt. Anda dapat mengubah baris:

eval "$(direnv hook $0)"

untuk:

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

untuk secara eksplisit memanggil fungsi hook.

Sunting: Baru sadar rekado memberikan jawaban yang sangat mirip.

Erik Hetzner
sumber
Ini adalah jawaban paling ringkas yang menunjukkan ketidakbiasaan saya dengan cara direnv bekerja $PROMPT_COMMAND.
waymondo
4

Terima kasih kepada Rekado dan Erik karena menunjukkan bagaimana pengait direnv bekerja dengan menggunakan $PROMPT_COMMAND. Karena shell-commandtidak menggunakan prompt, ini tidak dijalankan.

Sementara jawaban Erik bekerja dalam contoh saya memanggil perintah shell M-!dengan default-directoryset, itu tidak akan berfungsi dalam contoh berikut:

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

Setelah beberapa googling, saya menemukan cara untuk membuat preexec hook di bash untuk mengeksekusi sesuatu sebelum perintah dieksekusi. Saya mengambil ide ini dan dimodifikasi agar sesuai dengan kebutuhan saya untuk direnv. Inilah bagian yang relevan dari ~ / .bashrc saya sekarang:

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

saat ini Anda mungkin ingin menggunakan https://github.com/wbolster/emacs-direnv

ia bekerja mirip dengan hook yang direnv instal di shell Anda. lingkungan emacs diperbarui berdasarkan permintaan (atau secara otomatis saat mengganti buffer) untuk mencocokkan dengan status direnv apa yang merupakan lingkungan yang benar untuk direktori saat ini.

dengan memodifikasi exec-pathdan process-environmentemacs akan berperilaku seperti yang dilakukan shell Anda: jalankan program dari jalur yang benar dan dengan lingkungan yang benar.

Wouter Bolsterlee
sumber