WSL 配置指北:打造 Windows 最强命令行
原文发表在我的 博客 上,欢迎订阅。;)
在两年前的八月,Microsoft 正式发布了 Windows 10 Anniversary Update 周年更新(它还有着 RS1,Version 1607,Build 14393 等一大堆别名)。其中最让包括我在内的众多开发者感到兴奋的特性之一,就是 WSL(Windows Subsystem for Linux,当时还叫 Bash on Ubuntu on Windows)的正式加入。
在 Windows 上原生运行 Linux 可执行文件,牛逼疯了!
然而 Bug10 也不是浪得虚名,原本只提供给 Insider 的 WSL 在正式发布后依然问题多多(不仅 zsh、tmux 等工具无法使用,网络相关的操作更是一概欠奉,还有各种各样 奇妙的 BUG),基本没有可用性,我在尝鲜了一段时间后也不得不重回 Cygwin 的怀抱。不过好消息是,在之后的更新中,这些 BUG 都已被逐一消灭。
经过了两年的发展,WSL 已经足够成熟,我也是时候完成这篇一咕再咕的博文了。
1. 我理想中的命令行界面
既然违反广告法取了这么个标题,那我自然得先描述一下我的目标,也就是我理想中的命令行界面应该是什么样子的(如果你不清楚命令行的概念,可以看看我之前写的 这篇文章):
- 好看(配色、字体可以自由设定);
- 支持 UTF-8 字符的输入与显示;
- 支持常见的 *NIX 命令行工具(cat、grep、awk 等);
- 自动补全、语法高亮、历史记录;
- 完善的复制粘贴支持;
- 互操作性(共享文件系统、网络栈,可调用 Win32 程序);
- 支持常用的脚本语言(PHP、Python、Node.js 等);
- 包管理器,以及其他各种常用软件的支持;
- 快速呼出(快捷键、右键菜单入口)。
然而遗憾的是,Windows 上的命令行一直以来都很微妙。
2. 难用的 Windows 命令行
停停停,那边的 PowerShell 爱好者 ,咱别动粗成吗?
首先我要对标题做出一些订正,Windows 原生命令行其实也可以不那么难用。虽然 cmd.exe 是公认的难用到反人类(毕竟是用来兼容 DOS 的老古董),但后来推出的 PowerShell 已经足够强大且现代化,能够称得上是一个成熟的命令行 Shell 了。如果你愿意学习的话,PowerShell 几乎可以满足你对命令行的所有期待。这一点可以参见:Is PowerShell ready to replace my Cygwin shell on Windows?
但是,PowerShell 与 Bash 等类 Unix 系统上的 Shell 程序几乎是两个完全不同的世界。不仅语法不同,其平台上各类常用的命令行工具也基本不一致(比如类 Unix 系统中的 grep
对应 PowerShell 中的 Select-String
,uniq
对应 Select-Object -Unique
等)。往深了说,他们的系统设计理念都是不一样的,比如很多人推崇的 Unix 哲学,在 Windows 上就基本不见踪影;而 COM 等概念也是 Windows 独一份。
▲ 图片来源:シス管系女子 BEGINS 特別編 まんがでわかる WSL
当然,我无意在此挑起操作系统间的圣战。Windows 和类 Unix 系统中的命令行哪个好用,见仁见智。不过对于包括我在内的很多用户都认为 Windows 命令行不怎么好用,仅此而已。
回到正题。
虽然 Windows 的命令行一直遭人诟病,但是人家的图形界面牛逼啊。于是无数工程师前赴后继,试图在 Windows 上创造出不输给类 Unix 系统的命令行体验 —— 却绝大多数以失败告终。曾经努力过的人,或者回到可爱的 Linux 上,或者进入高贵冷艳的 macOS 的世界。其中有先辈留下了 Cygwin、GnuWin32 等工具集,让我们可以在 Windows 下使用类 Unix 系统中常见的命令行工具,成为了不少 Windows 用户的救赎。
然而,就当大家都觉得「也就这样了」的时候,Microsoft 出人意料地站了出来。
带着他新鲜出炉的 WSL。
3. Windows Subsystem for Linux,参上!
大家都把 WSL 吹得这么牛逼,那 WSL 究竟是个什么玩意儿呢?
简单来说,WSL 是一个 兼容层,有点像反过来的 Wine。
首先,我问个问题,为什么 Linux 上的程序无法在 Windows 上运行呢?
了解过一点操作系统原理的同学应该都知道,这是 Windows 与 Linux 的内核提供的接口不同(系统调用、API 等)导致的。举个栗子,我们想知道某目录下的内容,在 Linux 下我们会使用 ls
命令,而在 Windows 下我们会使用 dir
命令。
当我们在 Linux 上执行 ls
命令,ls
会调用 getdents
这个系统调用,Linux 内核收到请求,将目录的内容返回给应用程序;当我们在 Windows 上执行 dir
命令,dir
会调用 NtQueryDirectoryFile
这个 API,NT 内核收到请求,将目录的内容返回给应用程序。虽然系统不同,但基本上都是一个道理。
然而,当我们把 Linux 上的应用程序拿到 Windows 上运行时,应用程序和内核就双双懵逼了。比如 ls
会尝试调用 getdents
系统调用(理想化的情况下,暂不考虑可执行文件格式等问题),Windows 的 NT 内核一看,心说:「这他娘的什么东西,老子不认识啊,啥情况啊」,ls
也想:「尼玛,内核怎么不回话啊,咋回事儿啊」……两边语言不通,应用程序自然无法正确执行。
但是有了 WSL,情况就不一样了。
依然拿 ls
举例,当我们在 WSL 中运行 ls
命令时,ls
会调用 getdents
系统调用(这个系统调用接口是 WSL 提供的,Windows 本身并没有这个接口),WSL 收到这个请求,明白了应用程序是想要知道目录的内容,于是把 Linux 的系统调用转换为 NT API NtQueryDirectoryFile
。NT 内核收到 WSL 的请求,将目录的内容返回给 WSL,WSL 再把返回的内容包装好后返回给 ls
。
也就是说,WSL 在 Linux 应用程序与 Windows NT 内核之间起到了翻译者的作用。很简单的道理,既然 NT 内核无法理解 Linux 应用程序的 POSIX 系统调用,那就弄个翻译来将 POSIX 系统调用实时转换为 NT 内核能理解的 API 调用,突出一个见人说人话、见鬼说鬼话。
只要实现了足够多的系统调用翻译,那么理论上 WSL 可以完全模拟成一个 Linux 内核。
相信各位都听说过鼎鼎大名的 Cygwin。同样是能让 Linux 应用程序运行在 Windows 上,WSL 和 Cygwin 有什么不同呢?其实差别还是挺大的。
虽然 Cygwin 提供了完整的 POSIX 系统调用 API(以运行库 Cygwin*.dll
的形式提供),但其依然工作在 User Mode;而 WSL 中的 Linux 应用程序进程会被包裹在一个叫做 Pico Process 的东西里,这个东西里发出的所有系统调用请求都会被直接送往 Kernel Mode 中的 lxcore.sys
与 lxss.sys
处理。
同样是将 POSIX 系统调用转换为 Windows 中的 API,Cygwin 是转换成 Win32 API 的调用(因为它架设在 Win32 子系统上,很多内核操作受限于 Win32 的实现,比如 fork
),而 WSL 则是转换为更底层的 NT API 调用(WSL 是与 Win32 平行的子系统,直接架设在 NT 内核上,可以通过 NT API 原生实现 fork
等系统调用)。
▲ WSL 架构示意图。图片来源:Windows for Linux Nerds
最重要的一点:如果使用 Cygwin,Linux 应用程序的源码必须 link 至 Cygwin 运行库(Cygwin*.dll
),修改源码重新编译后才能在 Windows 下运行。这些重新编译后的 Linux 应用程序在调用 POSIX API 时不会直接去请求内核,而是会去调用 Cygwin 运行库,由运行库翻译成 Win32 API、执行调用后返回结果。这也就意味着,重新编译后的应用程序需要依赖 Cygwin 运行库才能正常运行(有时候你会碰到的「缺少 Cygwin1.dll
」报错就是这个原因),而且这样编译出来的可执行程序是纯正的 Win32 PE 格式封装,只能在 Windows 上运行。
而在 WSL 下,我们可以直接运行未经任何修改的 ELF 格式 Linux 可执行程序。
▲ Cygwin 目录下,被编译成 Win32 可执行程序的 Linux 应用程序们。
最后总结一波:
WSL 就像是一个翻译官,就算那些未经修改的 Linux 应用程序们操着一口纯正的 POSIX 系统调用语法,WSL 也能快速准确地将其翻译为 NT 内核能听懂的 API 调用;
而那些使用了 Cygwin 重新编译后的 Linux 应用程序,就像是改造人一样变成了 Win32 应用程序的形状,还被套了个翻译机。程序自己(源码中)说的是 POSIX,经过翻译机(Cygwin 运行库)之后就变成 Win32 API 调用了,这样 NT 内核也能听得懂。
但是每次添加新程序都要改造,多麻烦啊,还是 WSL 原生态更健康(笑)。
以上只是我对 WSL 的粗浅解释,其具体实现原理可以参考官方博客上的 这一系列文章。
4. 安装 WSL,拥抱可爱的 Linux
好了不说废话,让我们开始安装 WSL。注意,WSL 仅支持 64 位系统,且本文中所描述的安装方法仅适用于 Windows 10 Fall Creators Update(秋季创意者更新,RS3,Version 1709,Build 16299)及以上版本。
第一步,打开「控制面板」中的「程序与功能」,点击左侧边栏的「启用或关闭 Windows 功能」选项,在弹出的窗口中勾选「适用于 Linux 的 Windows 子系统」,然后点击确定(可能需要重启)。
如果你懒得用 GUI,也可以直接在 PowerShell 中以管理员权限执行命令:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
第二步,打开 Microsoft Store,搜索「WSL」。挑选一个你喜欢的 Linux 发行版,然后点击安装。(截至目前,商店中可用的发行版有 Ubuntu、openSUSE、SUSE Linux Enterprise Server、Debian 以及 Kali Linux。)
第三步,在开始菜单中找到你刚刚安装的发行版,打开它。等待几分钟的初始化过程,设定好用户名与密码后(不需要与 Windows 的相同,用过 Linux 的选手应该都懂的)就会自动进入 Linux 环境。
至此,你已经完成了 WSL 的安装。
你也可以同时安装多个发行版,它们的数据都是独立的,互不影响。
5. 使用更专业的终端模拟器
我猜你现在正在对上面那个窗口发呆。
—— 这个新宋体他娘的是个什么情况?
如果你正在使用中文 Windows 系统,而且之前并没有修改过 Win32 Console 的默认配置,那么你的 WSL 终端默认就会是这样的。新宋体,就是这么 Hardcore。惊不惊喜,意不意外?
好吧不开玩笑,Windows 这个控制台窗口就是很多人讨厌它的原因之一,难用又难看。丑这一点倒还有解决方法(经过一番设置后还算能看,我以前就写过一篇关于 自定义 Windows 控制台字体 的文章),难用却是实打实的。尽管 Win10 上的控制台已经改进了不少(可以看看 Microsoft 的官方博客:Windows Command Line Tools For Developers),但其依然是最难用的终端模拟器之一,或许没有之一。
因此,为了实现我们的目标,一个更强大的终端模拟器是必须的。
终端模拟器是什么?为了这个回答这个问题,我专门写了一篇文章,去看看吧。:P
我个人比较推荐的终端模拟器有:
- wsl-terminal
专门为 WSL 开发的终端模拟器,基于 mintty 与 wslbridge,稳定易用。
- ConEmu
Windows 上的老牌终端模拟器,功能极为强大,要啥有啥。
- Hyper
基于 Electron 的跨平台终端模拟器,好看和可扩展性是卖点,BUG 不少。
还有其他各种各样的终端模拟器,选个自己喜欢的就好。反正不管选哪个,都比默认的那玩意儿要好用。