PATH Environment Variable on Mac OS X & Emacs.app
有几次朋友问我晚上的时候在干什么,我开玩笑似地说我在“磨刀”,他或许一下子不解了——我把折腾Emacs当做是磨刀。呵呵,“刀是不能不磨的”……长话短说,本文将介绍Mac OS X和GNU Emacs中的PATH环境变量及其设置方法。
首先你可能需要先了解一下Emacs For Mac OS。在Mac上,可能很多人偏爱Aquamacs这个发行版,相比较GNU Emacs(Cocoa),它是标准的OS X应用,而且针对Mac系统做了很多的优化和配置,让你使用起来得心应手,非常方便。但是,可能因为我当年第一次接触Emacs遇到的就是GNU Emacs(Cocoa),到后来再接触了Aquamacs,感觉Aquamacs看起来很不顺眼,而且这种感觉到今天也没有消失掉,我一如既往地对GNU Emacs钟情1。
Shell Configuration
在类UNIX系统中,你可以通过编辑相应shell启动时执行的 "startup" 文件来自定义shell程序的初始化工作。这些文件一般会被称为 "dot files" ,因为它们的名字经常是以 .
(dot) 开始。用户自己的 "dot files" 一般都位于用户的根目录(User's Home folder)4下面,需要你自己使用一个文本编辑器(e.g. vim, nano, emacs etc.)来创建。因为它们的名字都是以 .
开始的,当你执行 ls
时,它们不会被显示,你需要加上 -a
参数,即使用 ls -a
来查看所有文件。如果你只需要列出以 .
开始的文件,可以使用命令 ls -ld .*
。
Shell startup文件当中经常包含下面一些设置:
- shell execution PATH (determines where the shell will look for executables)
- MANPATH (determines where the 'man' program will look for man pages)
- shell aliases and functions (used to save you typing)
- shell prompts
- other environment variables
至于哪些初始文件会被shell启动时所读取取决于shell类型(e.g. bash, tcsh etc.)以及其是 login shell 还是 non-login shell 。5
Login Shell and Non-Login Shell
这里我们简单翻译为登录shell和非登录shell,至于为什么称呼 login shell ,是由于历史原因,当用户启动到文本模式的UNIX系统,输入用户口令登录后,shell便会启动。另外, login shell 也指使用 --login
参数启动的shell(bash --login
)。以bash为例,下面的引用来自其man page6:
A login shell is one whose first character of argument zero is a -, or one started with the –login option.
对于bash,当一个登录shell启动时,它将按照下面的顺序读取初始化文件:
- /etc/profile
- ~/.bash_profile
- ~/.bash_login
- ~/.profile
首先如果 /etc/profile
文件存在,那么登录shell将首先读取它并且执行其中的命令,然后按照顺序搜索 ~/.bash_profile
~/.bash_login
~/.profile
,并且只读取执行其中的一个。
当一个非登录shell以交互方式启动时,将会读取 /etc/bashrc
,然后寻找并尝试读取执行 ~/.bashrc
。7
在Mac OS X中,你在Terminal程序中开启的shell都是登录shell,而当你打开一个xterm(在苹果的X11下)窗口时,你得到的则是非登录shell,这是Terminal和xterm在读取初始化文件方面的主要区别。
Maintain Shell Startup Files
更多时候,你不想为登录shell和非登录shell同时维护两个配置文件,比如当你改变PATH环境变量的时候,你可能会希望同时应用于两种shell。通过source命令来做到这点。比如,将PATH和其他设置都放到 .bashrc
里面,再让 .bash_profile
“引用” .bashrc
,只需要在 .bash_profile
中添加下面的代码:
if [ -f ~/.bashrc ]; then source ~/.bashrc fi
这样,当开启一个登录shell的时候, .bashrc
会被读取并执行,你在其中所作的设置将会生效。
Mac OS X Runtime Configuration
Mac OS X中,程序运行分为两种,一种是使用终端(console),一种是使用GUI(图形用户界面),环境变量的设置需要根据不同的程序运行情况来分别设置。
当你打开Terminal.app,默认你将启动一个bash shell8,它将像上文介绍到的那样读取初始化文件。
我们知道,一般系统级的环境变量包括 PATH
会在 /etc/profile
中设置,当bash启动的时候会被读取。在Mac OS X中, /etc/profile
会使用path_helper来设置PATH9:
if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi
path_helper 会读取 /etc/paths.d
和 /etc/manpaths.d
目录下的文件内容,将它们添加进相应的 PATH
和 MANPATH
环境变量,这些目录下的文件一般通过单独的一行来指定一个路径。在读取这些目录之前,默认的 PATH
和 MANPATH
的值会从文件 /etc/paths
和 /etc/manpaths
中获得。
OS X中的窗口程序则会从用户自己的environment.plist文件中获得环境变量,可以参考这里。文件 ~/.MacOSX/environment.plist
会由Xcode创建,如果你没有安装Xcode的话,可能需要手动创建(注意大小写敏感),之后你便可以通过Property List Editor10来编辑此文件,修改或者保存键值对。environment.plist可以被窗口程序包括Terminal使用来获得系统的各种环境变量,而你在 .bash_profile
或其他dot文件中做的设置将只会作用于bash或相应的shell。 defaults 11程序允许对此文件进行读写12,比如当你修改了enviroment.plist文件,并使当前shell使用其中 PATH
的值,可以这样:
# bash export PATH=$(defaults read "${HOME}/.MacOSX/environment" PATH) # tcsh set path=(`defaults read ~/.MacOSX/environment PATH | tr ':' ' '`)
如果你希望 /etc/profile
中将通过 path_helper
获取的PATH路径的值自动更新到 environment.plist
中去,可以在 /etc/profile
做下面的改动:
#/etc/profile begin if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` defaults write $HOME/.MacOSX/environment PATH "$PATH" fi
一般情况下,你只需要在 .bash_profile
中设置环境变量而不需要改动 .plist
文件。大多数的Mac OS X窗口程序都不需要任何自定义的环境变量,只在程序实际需要一个特定的环境变量的时候,你才可能有必要修改environment.plist。
Emacs.app set PATH
首先,根据上文的一系列介绍,我们可以得到关于Emacs如何从OS X中继承环境变量的三点总结:
- 当你从shell中启动emacs的时候,emacs将继承shell的环境变量13。
- 当你以GUI方式直接启动Emacs.app,emacs将不会从shell那里继承环境变量,而是从
~/.MacOSX/environment.plist
继承。 - 当你从shell中以GUI方式启动emacs14,emacs也会继承shell的环境变量。
下面将介绍如何为Emacs设置 PATH
。
Setting PATH within Emacs
你可以直接为Emacs设置PATH环境变量,而不用以在OS中设置再让Emacs继承的方式。
使用命令 (getenv "PATH")
将会得到PATH环境变量的值,而 (setenv "PATH" VALUE)
用来将 PATH
设置为指定的字符串。如果你安装了MacPorts,想将其可执行文件目录添加进Emacs的PATH环境变量,可以在Emacs初始化文件中中添加下面的代码:
(setenv "PATH" (concat (getenv "PATH") ":""/opt/local/bin" ":""/opt/local/sbin"))
Emacs's exec-path
Emacs拥有一个 exec-path
变量,其值是一个文件目录的列表,当需要在子进程中运行程序的时候,Emacs会通过 exec-path
来寻找可执行文件(e.g. ispell or aspell, diff, grep, shell etc.) 。如果Emacs警告说它无法找到找到 ispell 、 aspell 、 gzip 等可执行文件,可能就是 exex-path
变量的问题。
默认,Emacs会将 (getenv "PATH")
的值赋给 exec-path
,所以 PATH
和 exec-path
的值会一致。但是二者之间又有区别:
PATH
的值被Emacs使用如果你在Emacs中开启一个shell(类似于你在终端中运行shell),或者当Emacs调用一些外部命令时(比如通过M-x compile
执行编译)。exec-path
被Emacs自身使用用来寻找可执行文件当你需要使用它的某些特性的时候,例如拼写检查,diff等。
当你需要在Emacs中设置 PATH
时,你可能也想一起调整 exec-path
,比如你想要设置使emacs直接从shell那里得到 PATH
的值,并且设置 exec-path
,可以将下面的代码添加到Emacs初始化文件中:
(setq *is-a-mac* (eq system-type 'darwin)) (defun string-rtrim (str) "Remove trailing white-space from a string." (replace-regexp-in-string "[ \t\n]*$" "" str)) (defun set-exec-path-from-shell-PATH () (interactive) (let ((path-from-shell (string-rtrim (shell-command-to-string "$SHELL --login -i -c 'echo $PATH'")))) (setenv "PATH" path-from-shell) (setq exec-path (split-string path-from-shell path-separator)))) (when (and *is-a-mac* window-system) (set-exec-path-from-shell-PATH))
可以使用 purcell 写的exec-path-from-shell,做法与上面的代码类似,并进行了一些拓展。
PATH in Emacs shell mode
在Emacs中,当你通过 M-x shell
开启一个shell,比如你设定了 shell-file-name
的值为 /bin/bash
,以使Emacs开启bash shell,这时候,bash是一个非登录shell,所以它会在用户根目录下寻找 .bashrc
来进行初始化,你可能需要将一些自定义的设置放到 .bashrc
中。上文中我们提到,这个shell还会继承Emacs中设置的 PATH
,这样就可能使 PATH
中出现重复的目录,比如为了简化维护,你的 .bashrc
中可能仅仅是下面一行内容:
source ~/.profile
而你在Emacs初始化文件中像这样设置 PATH
:
(setenv "PATH" (shell-command-to-string "source $HOME/.profile && printf $PATH"))
然后,你用 echo $PATH
来查看 PATH
的值会发现其中很多目录是重复的,让人很不爽。你可以在 .profile
中编写一个函数用来将一个目录添加进 PATH
当且仅当这个目录不在当前的 PATH
中来消除一些重复,或者在 .bashrc
中复制 .profile
的内容,但是去掉相关的 PATH
设置。到目前为止,我还没有找到更好的方法。
Author's Words
本文很多内容参考了 EmacsWiki 、 StackOverFlow.com 和 Apple 开发文档的相关主题,转载请注明来源网址: http://blog.galeo.me/path-environment-variable-on-mac-os-x-emacs-app.html。
Footnotes:
Emacs 24.1马上就要来了,好期待。后面我可能会写一篇文章,介绍Lisp的词法变量和动态变量,因为elisp在Emacs 24.1版本开始支持词法变量,所以可能顺带着介绍一下Emacs 24.1版本的某些变化。
虽然Mac OS X经过的UNIX认证,但是一些你在 *nix 系统中习以为常的东西到了OS X里面需要换个脑筋,“Think Different”,然后你会发现OS X在很多方面领先业界。
GNU Emacs在Mac OS X上的发行版,详细请参考上文中的链接。
用 ~
来表示(designated by ~
)。
注意:在命令提示符之后输入shell名称(e.g. bash, tcsh),将会开启一个子shell(sub-shell),子shell将会继承父shell(parent shell)的环境变量,即使通过 --noprofile
参数来禁止shell读取初始化文件。
使用命令 man bash
。同样的,可以使用命令 man tcsh
查看tcsh的man page。
更多shell启动的细节,请参考man page。
可以在 Terminal -> Preferences 里面设置shell启动路径。
注意: path_helper 只能通过shell文件来使用,而不能直接手动调用。
Property List Editor comes as part of Apple's Developer Tools Xcode 3 package, as of Xcode 4 on Lion, Apple no longer supplies it with its developer tools,不过你可以直接使用Xcode来编辑.plist文件。如果想要在Lion中继续使用Property List Editor,请参考这里,当然还有其他的选择,比如免费的Pref Setter,或者收费的PlistEdit Pro。
In Terminal, type man defaults
for details.
注意:当你改动了environment.plist文件之后,你需要重启系统来生效。
这一点对于Windows,Linux,Mac均适用。
Like this: nohup /Applications/Emacs.app/Contents/MacOS/Emacs &
。