生成式软件工程
搜索

环境变量、代理与 DNS

装完软件敲命令却提示找不到,或者浏览器能打开、命令行打不开,这两类怪事多半出在同三个地方。

这一页讲什么
  • 环境变量到底是什么,为什么有些程序离开它就找不到东西;
  • PATH 为什么单独拿出来讲,它和别的变量的地位不一样在哪;
  • 刚设完环境变量,为什么当前窗口还是不生效;
  • DNS 在做什么,代理在做什么,两者谁先谁后;
  • 网络不通时,怎么用三条命令判断卡在哪一段。

一、环境变量是一张随程序附带的便条

你在终端里敲 python,系统需要知道两件事:去哪个目录找这个程序,以及这个程序运行时该读哪些配置。第二件事靠的就是环境变量。

把它想成一张便条:启动一个程序时,系统会把这张便条一起递过去。程序自己决定读便条上的哪几行,不读也行。

生活类比 环境变量像出门前塞进包里的便利贴。你换了一家店,便利贴上写的还是上一家的地址,店员照着便利贴做事,就会找错地方。 程序同样如此:它只认便条上写的,不认你脑子里想的。

二、便条上的常见几行

零基础阶段真正会打交道的变量只有几个,先把它们认全。

变量名管什么写错时的典型症状
PATH敲命令时去哪几个目录找可执行文件,按顺序找,找到就停提示「找不到命令」或「不是内部或外部命令」,但文件明明存在
HOME / USERPROFILE你的用户主目录在哪。macOS 与 Linux 用前一个,Windows 用后一个程序把配置写到了别处,或者报「无法创建配置文件」
TEMP / TMPDIR临时文件放哪安装到一半报「磁盘不可写」,其实是临时目录没有权限
LANG / LC_ALL程序输出用什么语言、什么编码中文变问号,或者日志里全是英文你读不懂
SHELL当前用的是哪个终端程序写在配置文件里的语法另一套终端不认识

还有一类是某个软件自己认的变量,比如数据库的地址、某个工具的密钥存放位置。这类变量的名字由那个软件规定,得去它的文档里查,不要猜。

三、PATH 为什么单独拿出来讲

别的变量负责「配置」,PATH 负责「路由」。程序怎么运行由前者决定,敲下命令时系统去找哪个程序由后者决定。

PATH 的值是一串目录,用冒号隔开(Windows 上用分号)。系统从左到右依次找,第一个找到的胜出,后面的目录不再看。

flowchart LR
  C["你敲下 python"] --> P["读 PATH 这张目录清单"]
  P --> D1["目录一 有没有 python"]
  D1 -->|"有"| RUN["执行这个文件"]
  D1 -->|"没有"| D2["目录二 有没有 python"]
  D2 -->|"有"| RUN
  D2 -->|"没有"| NF["继续往后找"]
  NF -->|"全都没有"| ERR["报找不到命令"]
这一段的落点 「第一个找到的胜出」这一条解释了最常见的一类怪事:电脑上装了两份 Python,你敲 python 跑起来的永远是 PATH 里靠前的那份。 遇到版本对不上,第一件事是先看它到底跑了哪个文件,别急着重装。

四、看一眼当前的值

下面三条命令在 macOS 与 Linux 上都能用。Windows 的两条在 PowerShell 里跑。

macOS 与 Linux:

echo $PATH          # 看 PATH 这张清单
which python        # 看 python 落在了哪个文件上
env | sort          # 列出全部环境变量,按名字排序

Windows 的 PowerShell:

$env:PATH           # 看 PATH 这张清单
Get-Command python  # 看 python 落在了哪个文件上
Get-ChildItem Env:  # 列出全部环境变量

which 这一条最值得养成习惯。它把「我以为」换成「实际是」。版本冲突、命令莫名奇妙不生效,先跑它。

五、设置之后为什么当前窗口还不生效

环境变量不像全局共享的一块内存,它是在启动进程时被复制一份递过去的。你改了「源头」,已经开着的窗口拿到的还是旧的那份。

  1. 改完系统里的设置,要重新打开一个终端窗口,新窗口才拿到新的值。
  2. 在终端里用 export 设的值,只对当前窗口有效。关掉窗口就没了。
  3. 想让每次打开都生效,得写进启动脚本。macOS 与 Linux 通常是 ~/.zshrc 或 ~/.bashrc,Windows 是系统设置里的「环境变量」面板。
  4. 写进启动脚本之后,也要重开窗口,或者用 source ~/.zshrc 让当前窗口重读一遍。
踩过的坑 在编辑器里改完启动脚本就以为已经生效,是新手最常见的一幕。 判断方法很直接:新开一个窗口,跑 echo $PATH,看新加的目录在不在里面。不在,说明文件根本没被读到,或者那条路径写错了。

六、DNS 在做什么

你输入的是名字,网络实际传输要靠地址。把名字换成地址这件事,由 DNS 负责。

过程很短:程序问系统「这个域名对应哪个地址」,系统去看自己的缓存,缓存没有就去问配置好的 DNS 服务器,拿到结果后告诉程序,顺便缓存一会儿。

环节它做什么出错时的症状
本地缓存记住最近查过的结果,省一次网络往返对方换了地址,你还在用旧地址,表现为「只有你的电脑打不开」
DNS 服务器真正回答「名字对应哪个地址」所有域名都打不开,提示「找不到服务器」或「域名解析失败」
hosts 文件本机手写的对照表,优先级高于 DNS某一条被改过,那个域名会固定指向一个地址,别的域名正常

排查顺序就是从近到远:先清本地缓存,再看 hosts 文件有没有被改过,最后才怀疑 DNS 服务器。

macOS 与 Linux 上清缓存与查 hosts:

cat /etc/hosts                       # 看本机对照表
sudo dscacheutil -flushcache         # macOS 清 DNS 缓存
sudo systemd-resolve --flush-caches  # 部分 Linux 发行版

Windows 的 PowerShell:

notepad C:\Windows\System32\drivers\etc\hosts   # 看本机对照表
ipconfig /flushdns                                  # 清 DNS 缓存
nslookup example.com                                # 直接问一次 DNS

七、代理在做什么

代理是替你转发请求的中间人。程序把请求交给代理,代理替它去连目标,再把结果带回来。

它和 DNS 的分工可以这样分:DNS 解决「这个名字对应哪个地址」,代理解决「这次连接由谁替我发出去」。两件事互不替代,都可能单独出问题。

flowchart LR
  APP["你的程序"] --> Q["这次连接要不要走代理"]
  Q -->|"不走"| DNS1["查 DNS 拿地址"]
  Q -->|"走"| PX["把请求交给代理"]
  PX --> DNS2["代理那边查 DNS 拿地址"]
  DNS1 --> T["连到目标"]
  DNS2 --> T
这一段的落点 走代理时,域名是谁解析的取决于代理的配置:有的代理在你本机解析,有的把域名原样交给代理去解析。 所以「浏览器能开、命令行不能开」这类不对称,八成是代理设置只在图形界面里生效,命令行那几个程序没读到。

八、两条命令看清代理设置

代理设置也走环境变量,最常见的三个名字是 http_proxy、https_proxy、no_proxy。前两个写代理的地址,最后一个写「这些地址不要走代理」。

macOS 与 Linux:

echo $http_proxy $https_proxy $no_proxy
curl -v https://example.com 2>&1 | head -20   # 看它连的是谁

Windows 的 PowerShell:

$env:HTTP_PROXY; $env:HTTPS_PROXY; $env:NO_PROXY
curl.exe -v https://example.com 2>&1 | Select-Object -First 20

curl -v 的输出里有 Trying 1.2.3.4:443 这一行。它连的地址是不是你以为的那个,一眼就能看出来。这一行比任何猜测都可靠。

踩过的坑 no_proxy 里写本机地址时,写 localhost 往往不够,还要写 127.0.0.1。 少写一个,本机服务也跟着走代理,表现是「本地起的服务明明在跑,却连不上」。

九、三条命令定位卡在哪一段

网络不通时,不要一上来就重装或改 DNS。按顺序问三个问题,每一步都能把范围砍掉一半。

  1. 名字能不能换成地址? 用 nslookup 或 ping 试一个你确定存在过的域名。换不出来,问题在 DNS 或 hosts。
  2. 地址能不能连上? 拿到地址之后直接连那个地址。用地址能连、用名字连不上,问题还在 DNS 那一段。
  3. 连的是不是你以为的那个地方? 用 curl -v 看它实际连到哪。连到了一个不认识的地址,问题在代理设置。
现在就做 打开终端,依次跑 echo $PATH、which python、env | sort。 然后用 curl -v 访问一个你常用的网站,在输出里找到 Trying 那一行,看它连的地址对不对。 三分钟,你会对自己这台机器的网络配置有一次具体的认识。

十、小结不了的东西

这一页讲的三样东西,共同点是「看不见、又在生效」。它们出问题时不会报一个明确的错,只会表现成「明明装了却用不了」「明明能上网却打不开」。

所以对付它们的办法是先把隐藏的东西显出来:echo 出变量、which 出真实路径、curl -v 出真实连接。看见之后再判断,比凭感觉调设置快得多。

1. 装完一个工具,提示「找不到命令」,但文件确实在硬盘上。先查什么?
先看 PATH 里有没有它所在的目录。用 which 或 Get-Command 查,输出为空就说明 PATH 没包含那个目录,或者新加的值还没被当前窗口读到。重装通常解决不了这一类问题。
2. 刚在系统设置里改了 PATH,当前窗口仍然报旧的结果,为什么?
因为环境变量是在启动进程时复制一份递过去的。已经开着的窗口拿到的是旧那份,改了源头它也不更新。重开一个窗口,新窗口才会拿到新值。
3. 浏览器能打开某个网站,命令行里同一个命令却连不上。最可能的原因是什么?
代理只配在了图形界面里,命令行那几个程序没有读到 http_proxy 这类变量。浏览器读系统代理设置,终端程序读环境变量,这是两套入口。
4. DNS 出问题和代理出问题,症状上怎么区分?
DNS 出问题时,用域名访问全都失败,但直接连地址往往能通。代理出问题时,域名和地址都可能连,只是实际连到的地方不是你以为的那个。用 curl -v 看它连的地址,就能分开这两类。
5. 为什么 no_proxy 里要同时写 localhost 和 127.0.0.1?
因为它们是两个不同的字面量,程序按字符串匹配,不会替你做等价换算。只写了其中一个,另一种写法仍然会被送进代理,本机服务就表现为「明明在跑却连不上」。