Cách thực hiện lệnh shell shell bằng cách sử dụng shell profile và hook thư mục hiện tại (ví dụ: direnv)


9

Tôi đang cố gắng để có được shell-commandasync-shell-commandtích hợp hoàn hảo với một vài chương trình trong .bashrctệp của mình , cụ thể là direnv trong ví dụ này.

Tôi thấy rằng nếu tôi tùy chỉnh shell-command-switch, tôi có thể nhận các quy trình shell để tải hồ sơ của mình như thể đó là một vỏ đăng nhập tương tác thông thường:

(setq shell-lệnh-switch (purecopy "-ic"))
(setq Rõ ràng-bash-args '("-ic" "export EMACS =; stty echo; bash"))

Tôi cũng đang sử dụng exec-path-from-shell .

Nói rằng tôi có một ~/.bashrctập tin với:

...

eval "$ (direnv móc $ 0)"
tiếng vang "foo"

Bên trong ~/code/footôi có một .envrctập tin với:

xuất PATH = $ PWD / thùng: $ PATH
tiếng vang "thanh"

Nếu tôi chạy M-x shellvới default-directorythiết lập thành ~/code/foo, bash shell sẽ tải chính xác hồ sơ của tôi và chạy hook direnv để thêm nó vào đường dẫn của tôi:

direnv: đang tải .envrc
quán ba
direnv: xuất khẩu
~ / code / foo $ echo $ PATH
/ Người dùng / tên người dùng / mã / foo / bin: / usr / local / bin: ... # phần còn lại của $ PATH

Tuy nhiên, nếu default-directoryvẫn còn ~/code/foovà tôi chạy M-! echo $PATH, nó tải chính xác .bashrc của tôi nhưng không thực hiện hook direnv của thư mục hiện tại:

foo
/ usr / local / bin: ... # phần còn lại của $ PATH không có ./bin

Tôi nhận được kết quả tương tự nếu tôi chạy M-! cd ~/code/foo && echo $PATH.

Có cách nào tôi có thể tư vấn hoặc móc vào shell-commandhoặc start-processlàm cho nó hoạt động như thể nó được gửi từ bộ đệm vỏ tương tác không?


Móc direnv của bạn trông như thế nào? Nếu nó được định nghĩa trong ~ / .bashrc và bạn eval'd (setq shell-command-switch "-ic")thì nó sẽ được ước tính cùng với mọi lệnh khác trong ~ / .bashrc.
rekado

Dòng trong ~ / .bashrc của tôi là eval "$(direnv hook $0)". Điều này đang được thực thi, nhưng cơ chế sẽ bị hủy khi bạn đang ở trong một thư mục cụ thể với một .envrctệp thì không.
waymondo

Có gì trong .envrctập tin được đánh giá? Hay đó chỉ là các biến môi trường không được xuất? Bạn có thể vui lòng cung cấp một ví dụ đầy đủ để tôi có thể cố gắng tái tạo điều này?
rekado

@rekado - Cảm ơn bạn đã xem nó. Tôi cập nhật câu hỏi của tôi để rõ ràng hơn để sao chép.
waymondo

Câu trả lời:


5

Điều này dường như không phải là một vấn đề với Emacs nhưng với bash. shell-commandchỉ thực hiện call-processtrên shell và truyền đối số. Tôi đã thử điều này trên một vỏ thông thường:

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

~/.bashrccó nguồn gốc, nhưng hook direnv không chạy. Khi direnv hook bashđược thực thi, một chức năng _direnv_hooklà đầu ra và được thêm vào PROMPT_COMMAND.

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

Tôi nghi ngờ rằng PROMPT_COMMANDđơn giản là sẽ không làm việc. Tuy nhiên, đó không phải là vấn đề vì _direnv_hooknó thực sự đơn giản. Chúng ta chỉ cần thêm vào eval $(direnv export bash)lệnh shell và nó sẽ hoạt động:

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

Điều này sẽ in đường dẫn tăng lên bộ đệm tin nhắn. Ngoài ra, thực hiện với M-!:

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

Bạn không cần phải (setq shell-command-switch "-ic")làm việc này.


Cảm ơn, điều này thực sự đã giúp đưa tôi đi đúng hướng. Tôi đang tìm kiếm một giải pháp không liên quan đến việc thêm eval "$(direnv export bash)"vào elisp, nhưng điều này cực kỳ hữu ích và đưa tôi đi đúng hướng.
waymondo

5

Running eval "$(direnv hook $0)"xác định một hàm nối vào $PROMPT_COMMAND, hàm này không bao giờ được gọi khi bash được chạy bash -icvì không có dấu nhắc. Bạn có thể thay đổi dòng:

eval "$(direnv hook $0)"

đến:

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

để gọi một cách rõ ràng chức năng hook.

Chỉnh sửa: Chỉ cần nhận ra rekado đã đưa ra một câu trả lời rất giống nhau.


Đây là câu trả lời ngắn gọn nhất chỉ ra sự không quen thuộc của tôi với cách direnv làm việc với $PROMPT_COMMAND.
waymondo

4

Cảm ơn Rekado và Erik đã chỉ ra cách hook direnv hoạt động bằng cách sử dụng $PROMPT_COMMAND. Vì shell-commandkhông sử dụng dấu nhắc, nên điều này không được thực thi.

Mặc dù câu trả lời của Erik hoạt động trong ví dụ của tôi về việc gọi lệnh shell M-!với default-directoryset, nhưng nó sẽ không hoạt động trong ví dụ sau:

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

Sau một vài lần googling, tôi tìm thấy một cách để tạo ra một hook preexec trong bash để thực thi một cái gì đó trước khi một lệnh được thực thi. Tôi lấy ý tưởng này và sửa đổi để phù hợp với nhu cầu của tôi về direnv. Đây là phần có liên quan của ~ / .bashrc của tôi bây giờ:

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

ngày nay bạn có thể muốn sử dụng https://github.com/wbolster/emacs-direnv

nó hoạt động tương tự như hook mà direnv cài đặt trong shell của bạn. môi trường emacs được cập nhật theo yêu cầu (hoặc tự động khi chuyển đổi bộ đệm) để phù hợp với trạng thái direnv là môi trường chính xác cho thư mục hiện tại.

bằng cách sửa đổi exec-pathprocess-environmentemacs sẽ hoạt động giống như trình bao của bạn sẽ thực hiện: thực thi các chương trình từ đúng đường dẫn và với môi trường phù hợp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.