← 返回列表
🔗 原文
Windows上Unix命令简史:CoreUtils(再探)
A Brief History of Unix Commands On Windows: CoreUtils (Again)

If you use Windows today and type ls, cat, grep, or awk in a terminal, there is a good chance something useful will happen. That was not always true. For most of the history of personal computing, Unix/Linux and Windows lived on opposite sides of a cultural border. Unix people had pipes, small composable tools, shell scripts, make, sed, awk, grep, tar, and the idea that everything was a file. Windows people had drive letters, backslashes, COMMAND.COM or cmd.exe, and an API that did not care much about what POSIX thought.

Yet there has always been a demand for Unix tools on Windows. Some of it came from programmers who wanted the same build scripts everywhere. Some came from administrators who missed grep and awk. Some came from companies trying to port big Unix applications to NT without rewriting them all. The result is a long, strange history of Unix-on-Windows layers, toolkits, compromises, and almost-but-not-quite compatibility.

Easy?

The simplest version of the problem sounds trivial. How hard can cat be? Open a file, copy bytes to standard output, done. Writing ls is a little more work, but Windows has directory APIs. Common commands like cp, mv, rm, mkdir are not very mysterious. Even pipes are not foreign to Windows. A lot of the everyday Unix command set can be ported as ordinary Win32 console programs with some path handling and enough patience.

But not all of Unix or Linux translates cleanly to Windows. The big issue is fork(). On Unix, a process can clone itself. The child gets a copy of the parent’s address space, open file descriptors, environment, signal state, and so on. Modern kernels make this efficient with copy-on-write memory, but the programming model is old and deeply baked into Unix. Shells use it constantly. Servers use it. Build systems use it. Scripting languages assume it exists, or at least that the surrounding environment behaves as though it does.

Windows process creation is different. Windows has CreateProcess(), which starts a new program. That is a perfectly reasonable model, but it is not fork() (more like fork()+exec()). If you are just launching notepad.exe, no problem. If you are trying to implement a POSIX shell that forks, redirects file descriptors, adjusts the environment, and then starts another program, the mismatch is extreme and you’ll have to do some strange things to fake things out.

One of the early commercial answers was the MKS Toolkit, originally from Mortice Kern Systems. MKS gave Windows users a pile of familiar commands, shells, and development tools. It was not just ls and friends; it included things like ksh, vi, grep, find, awk, make, and many of the utilities needed to move scripts and build procedures between Unix and Windows. The current PTC MKS documentation still describes it in exactly that spirit: Unix shells and hundreds of commands for interoperability with Windows.

MKS was attractive because it treated Windows as Windows. You were not necessarily pretending your machine was a Unix workstation. You were getting a Unix-flavored toolbox that could operate in a Windows environment. For many people, that was enough. You could write scripts, process text, drive builds, and avoid learning three different syntaxes for the same job.

AT&T Returns

A more ambitious attempt came from AT&T Bell Labs: UWIN, a pet project of David Korn of KornShell fame. UWIN stood for Unix for Windows, and that is more or less what it tried to be: a Unix interface layer on top of Windows NT and Windows 95, with libraries, headers, and commands. Korn’s 1997 USENIX paper describes the goal as building an open environment rich enough to serve as both a development and an execution environment, with nearly all of the X/Open Release 4 headers, interfaces, and commands.

UWIN is one of those projects that feels both obvious and heroic. Obvious, because if you have a bunch of Unix software, you want a compatibility layer. Heroic, because every POSIX semantic eventually has to land on some Windows behavior that was designed with different assumptions. File permissions, symbolic links, signals, terminals, process groups, device files, pathnames, and fork() all have edge cases. You can make the common case work and still spend years fighting the uncommon cases.

Cygwin

Then there is Cygwin, probably the most famous of the classic solutions. Cygwin became the go-to answer for people who wanted GNU tools and a fairly complete POSIX environment on Windows. The model is different from simply compiling ls.exe as a native Windows program. Cygwin provides a POSIX compatibility DLL. Programs built for Cygwin call into that layer, and the layer translates Unix expectations into Windows operations.

That is both Cygwin’s strength and its weakness. It is strong because a lot of Unix software can be rebuilt and made to run. The Cygwin project is explicit that it is not a way to run unmodified Linux binaries; you rebuild from source. It is also not a way to magically make ordinary Windows programs understand Unix signals and ptys. But for source-level portability, it has been remarkably useful.

The weak spot, again, is where Unix semantics do not map cleanly. Cygwin’s own documentation calls out fork() as “particularly interesting” because it does not map well onto Win32, making it difficult to implement correctly. Cygwin’s fork() is not copy-on-write in the usual Unix sense; it has to do substantial work to recreate the parent process state in a new Windows process.

There were other branches on the family tree, too. Microsoft had POSIX and Interix efforts, later wrapped into Services for UNIX. MinGW and MSYS gave developers lighter-weight GNU-ish environments aimed at building native Windows programs. Git for Windows brought a useful subset of MSYS2-derived tools to an audience that did not necessarily know or care what MSYS2 was. But the broad pattern remained: either you had native Windows tools that looked like Unix commands, or you had a compatibility layer that tried to preserve Unix behavior more deeply.

WSL

Then Microsoft changed the game with WSL, the Windows Subsystem for Linux. WSL said, in effect: instead of endlessly translating Unix expectations into Windows expectations, let’s run a Linux userland. The first version translated Linux system calls. WSL 2 went further and used a real Linux kernel inside a managed lightweight VM, which improved compatibility at the cost of making the boundary between Windows and Linux more explicit. Microsoft’s own documentation describes WSL as a way to run a GNU/Linux environment, including most command-line tools and applications, directly on Windows without a traditional VM or dual boot. The WSL 1 versus WSL 2 comparison notes that WSL 2 uses the actual Linux kernel in a managed VM for fuller system call compatibility.

This wasn’t a new idea. Things like coLinux and similar “kernels under Windows” had done this with varying degrees of success, but without the ability to fully integrate with Windows. Having it part of the “official” Windows landscape worked better, as you might expect.

The More Things Change…

But now the pendulum has swung again. Microsoft has introduced Coreutils for Windows, a Microsoft-maintained package based on the Rust uutils project. It provides native Windows builds of familiar Unix-style tools, packaged as a single multi-call binary, including coreutils, findutils, and a GNU-compatible grep. Microsoft describes the goal as making movement between Linux, macOS, WSL, containers, and Windows more frictionless, so the same commands, flags, and pipelines work more consistently.

That is not a replacement for WSL, and it is not trying to be Cygwin. It is closer in spirit to the old desire that made MKS attractive: sometimes you are in Windows, you are staying in Windows, and you still want tools that behave the way your fingers expect.

Of course, as we’ve seen, this is hardly a new idea. In fact, it is practically full circle back to the MKS toolkit. The only difference is the official support.

There is a lesson here. Running Unix commands on Windows is not one problem. There are at least three problems masquerading as one. First, there is the user-interface problem: people like the commands and want them available. That is easy enough. Second, there is the scripting problem: people want pipelines, quoting, globbing, exit statuses, and command options to behave consistently. That is harder. Third, there is the operating-system semantics problem: people want Unix programs to run as though Windows were Unix. That’s where there is the most friction.

So, yes, basic utilities are easy. But the Unix command line is not just a bag of executables. It is an ecosystem built around process inheritance, file descriptors, signals, terminals, pipes, path conventions, and fifty years of assumptions. Every attempt to bring Unix tools to Windows has chosen a different place to draw the line.

MKS drew it at commercial interoperability. UWIN tried to give Windows a serious Unix personality. Cygwin built a POSIX world in a DLL. WSL brought in Linux itself. Microsoft’s new Coreutils for Windows brings the everyday commands back into native Windows space. Sometimes things make a full circle.

But it all starts with a user thinking: Why can’t I list files on Windows with ls? We’ve used MKS, Cygwin, coLinux, UWIN, and even WSL. We’ve also thrown hardware at the problem.

🤖 AI 总结
如今Windows终端已支持ls、cat等Unix命令,回顾其演变历程,CoreUtils的引入是关键转折。

If you use Windows today and type ls, cat, grep, or awk in a terminal, there is a good chance something useful will happen. That was not always true. For most of the history of personal computing, Unix/Linux and Windows lived on opposite sides of a cultural border. Unix people had pipes, small composable tools, shell scripts, make, sed, awk, grep, tar, and the idea that everything was a file. Windows people had drive letters, backslashes, COMMAND.COM or cmd.exe, and an API that did not care much about what POSIX thought.

Yet there has always been a demand for Unix tools on Windows. Some of it came from programmers who wanted the same build scripts everywhere. Some came from administrators who missed grep and awk. Some came from companies trying to port big Unix applications to NT without rewriting them all. The result is a long, strange history of Unix-on-Windows layers, toolkits, compromises, and almost-but-not-quite compatibility.

Easy?

The simplest version of the problem sounds trivial. How hard can cat be? Open a file, copy bytes to standard output, done. Writing ls is a little more work, but Windows has directory APIs. Common commands like cp, mv, rm, mkdir are not very mysterious. Even pipes are not foreign to Windows. A lot of the everyday Unix command set can be ported as ordinary Win32 console programs with some path handling and enough patience.

But not all of Unix or Linux translates cleanly to Windows. The big issue is fork(). On Unix, a process can clone itself. The child gets a copy of the parent’s address space, open file descriptors, environment, signal state, and so on. Modern kernels make this efficient with copy-on-write memory, but the programming model is old and deeply baked into Unix. Shells use it constantly. Servers use it. Build systems use it. Scripting languages assume it exists, or at least that the surrounding environment behaves as though it does.

Windows process creation is different. Windows has CreateProcess(), which starts a new program. That is a perfectly reasonable model, but it is not fork() (more like fork()+exec()). If you are just launching notepad.exe, no problem. If you are trying to implement a POSIX shell that forks, redirects file descriptors, adjusts the environment, and then starts another program, the mismatch is extreme and you’ll have to do some strange things to fake things out.

One of the early commercial answers was the MKS Toolkit, originally from Mortice Kern Systems. MKS gave Windows users a pile of familiar commands, shells, and development tools. It was not just ls and friends; it included things like ksh, vi, grep, find, awk, make, and many of the utilities needed to move scripts and build procedures between Unix and Windows. The current PTC MKS documentation still describes it in exactly that spirit: Unix shells and hundreds of commands for interoperability with Windows.

MKS was attractive because it treated Windows as Windows. You were not necessarily pretending your machine was a Unix workstation. You were getting a Unix-flavored toolbox that could operate in a Windows environment. For many people, that was enough. You could write scripts, process text, drive builds, and avoid learning three different syntaxes for the same job.

AT&T Returns

A more ambitious attempt came from AT&T Bell Labs: UWIN, a pet project of David Korn of KornShell fame. UWIN stood for Unix for Windows, and that is more or less what it tried to be: a Unix interface layer on top of Windows NT and Windows 95, with libraries, headers, and commands. Korn’s 1997 USENIX paper describes the goal as building an open environment rich enough to serve as both a development and an execution environment, with nearly all of the X/Open Release 4 headers, interfaces, and commands.

UWIN is one of those projects that feels both obvious and heroic. Obvious, because if you have a bunch of Unix software, you want a compatibility layer. Heroic, because every POSIX semantic eventually has to land on some Windows behavior that was designed with different assumptions. File permissions, symbolic links, signals, terminals, process groups, device files, pathnames, and fork() all have edge cases. You can make the common case work and still spend years fighting the uncommon cases.

Cygwin

Then there is Cygwin, probably the most famous of the classic solutions. Cygwin became the go-to answer for people who wanted GNU tools and a fairly complete POSIX environment on Windows. The model is different from simply compiling ls.exe as a native Windows program. Cygwin provides a POSIX compatibility DLL. Programs built for Cygwin call into that layer, and the layer translates Unix expectations into Windows operations.

That is both Cygwin’s strength and its weakness. It is strong because a lot of Unix software can be rebuilt and made to run. The Cygwin project is explicit that it is not a way to run unmodified Linux binaries; you rebuild from source. It is also not a way to magically make ordinary Windows programs understand Unix signals and ptys. But for source-level portability, it has been remarkably useful.

The weak spot, again, is where Unix semantics do not map cleanly. Cygwin’s own documentation calls out fork() as “particularly interesting” because it does not map well onto Win32, making it difficult to implement correctly. Cygwin’s fork() is not copy-on-write in the usual Unix sense; it has to do substantial work to recreate the parent process state in a new Windows process.

There were other branches on the family tree, too. Microsoft had POSIX and Interix efforts, later wrapped into Services for UNIX. MinGW and MSYS gave developers lighter-weight GNU-ish environments aimed at building native Windows programs. Git for Windows brought a useful subset of MSYS2-derived tools to an audience that did not necessarily know or care what MSYS2 was. But the broad pattern remained: either you had native Windows tools that looked like Unix commands, or you had a compatibility layer that tried to preserve Unix behavior more deeply.

WSL

Then Microsoft changed the game with WSL, the Windows Subsystem for Linux. WSL said, in effect: instead of endlessly translating Unix expectations into Windows expectations, let’s run a Linux userland. The first version translated Linux system calls. WSL 2 went further and used a real Linux kernel inside a managed lightweight VM, which improved compatibility at the cost of making the boundary between Windows and Linux more explicit. Microsoft’s own documentation describes WSL as a way to run a GNU/Linux environment, including most command-line tools and applications, directly on Windows without a traditional VM or dual boot. The WSL 1 versus WSL 2 comparison notes that WSL 2 uses the actual Linux kernel in a managed VM for fuller system call compatibility.

This wasn’t a new idea. Things like coLinux and similar “kernels under Windows” had done this with varying degrees of success, but without the ability to fully integrate with Windows. Having it part of the “official” Windows landscape worked better, as you might expect.

The More Things Change…

But now the pendulum has swung again. Microsoft has introduced Coreutils for Windows, a Microsoft-maintained package based on the Rust uutils project. It provides native Windows builds of familiar Unix-style tools, packaged as a single multi-call binary, including coreutils, findutils, and a GNU-compatible grep. Microsoft describes the goal as making movement between Linux, macOS, WSL, containers, and Windows more frictionless, so the same commands, flags, and pipelines work more consistently.

That is not a replacement for WSL, and it is not trying to be Cygwin. It is closer in spirit to the old desire that made MKS attractive: sometimes you are in Windows, you are staying in Windows, and you still want tools that behave the way your fingers expect.

Of course, as we’ve seen, this is hardly a new idea. In fact, it is practically full circle back to the MKS toolkit. The only difference is the official support.

There is a lesson here. Running Unix commands on Windows is not one problem. There are at least three problems masquerading as one. First, there is the user-interface problem: people like the commands and want them available. That is easy enough. Second, there is the scripting problem: people want pipelines, quoting, globbing, exit statuses, and command options to behave consistently. That is harder. Third, there is the operating-system semantics problem: people want Unix programs to run as though Windows were Unix. That’s where there is the most friction.

So, yes, basic utilities are easy. But the Unix command line is not just a bag of executables. It is an ecosystem built around process inheritance, file descriptors, signals, terminals, pipes, path conventions, and fifty years of assumptions. Every attempt to bring Unix tools to Windows has chosen a different place to draw the line.

MKS drew it at commercial interoperability. UWIN tried to give Windows a serious Unix personality. Cygwin built a POSIX world in a DLL. WSL brought in Linux itself. Microsoft’s new Coreutils for Windows brings the everyday commands back into native Windows space. Sometimes things make a full circle.

But it all starts with a user thinking: Why can’t I list files on Windows with ls? We’ve used MKS, Cygwin, coLinux, UWIN, and even WSL. We’ve also thrown hardware at the problem.

原文
A Brief History of Unix Commands On Windows: CoreUtils (Again)

If you use Windows today and type ls, cat, grep, or awk in a terminal, there is a good chance something useful will happen. That was not always true. For most of the history of personal computing, Unix/Linux and Windows lived on opposite sides of a cultural border. Unix people had pipes, small composable tools, shell scripts, make, sed, awk, grep, tar, and the idea that everything was a file. Windows people had drive letters, backslashes, COMMAND.COM or cmd.exe, and an API that did not care much about what POSIX thought.

Yet there has always been a demand for Unix tools on Windows. Some of it came from programmers who wanted the same build scripts everywhere. Some came from administrators who missed grep and awk. Some came from companies trying to port big Unix applications to NT without rewriting them all. The result is a long, strange history of Unix-on-Windows layers, toolkits, compromises, and almost-but-not-quite compatibility.

Easy?

The simplest version of the problem sounds trivial. How hard can cat be? Open a file, copy bytes to standard output, done. Writing ls is a little more work, but Windows has directory APIs. Common commands like cp, mv, rm, mkdir are not very mysterious. Even pipes are not foreign to Windows. A lot of the everyday Unix command set can be ported as ordinary Win32 console programs with some path handling and enough patience.

But not all of Unix or Linux translates cleanly to Windows. The big issue is fork(). On Unix, a process can clone itself. The child gets a copy of the parent’s address space, open file descriptors, environment, signal state, and so on. Modern kernels make this efficient with copy-on-write memory, but the programming model is old and deeply baked into Unix. Shells use it constantly. Servers use it. Build systems use it. Scripting languages assume it exists, or at least that the surrounding environment behaves as though it does.

Windows process creation is different. Windows has CreateProcess(), which starts a new program. That is a perfectly reasonable model, but it is not fork() (more like fork()+exec()). If you are just launching notepad.exe, no problem. If you are trying to implement a POSIX shell that forks, redirects file descriptors, adjusts the environment, and then starts another program, the mismatch is extreme and you’ll have to do some strange things to fake things out.

One of the early commercial answers was the MKS Toolkit, originally from Mortice Kern Systems. MKS gave Windows users a pile of familiar commands, shells, and development tools. It was not just ls and friends; it included things like ksh, vi, grep, find, awk, make, and many of the utilities needed to move scripts and build procedures between Unix and Windows. The current PTC MKS documentation still describes it in exactly that spirit: Unix shells and hundreds of commands for interoperability with Windows.

MKS was attractive because it treated Windows as Windows. You were not necessarily pretending your machine was a Unix workstation. You were getting a Unix-flavored toolbox that could operate in a Windows environment. For many people, that was enough. You could write scripts, process text, drive builds, and avoid learning three different syntaxes for the same job.

AT&T Returns

A more ambitious attempt came from AT&T Bell Labs: UWIN, a pet project of David Korn of KornShell fame. UWIN stood for Unix for Windows, and that is more or less what it tried to be: a Unix interface layer on top of Windows NT and Windows 95, with libraries, headers, and commands. Korn’s 1997 USENIX paper describes the goal as building an open environment rich enough to serve as both a development and an execution environment, with nearly all of the X/Open Release 4 headers, interfaces, and commands.

UWIN is one of those projects that feels both obvious and heroic. Obvious, because if you have a bunch of Unix software, you want a compatibility layer. Heroic, because every POSIX semantic eventually has to land on some Windows behavior that was designed with different assumptions. File permissions, symbolic links, signals, terminals, process groups, device files, pathnames, and fork() all have edge cases. You can make the common case work and still spend years fighting the uncommon cases.

Cygwin

Then there is Cygwin, probably the most famous of the classic solutions. Cygwin became the go-to answer for people who wanted GNU tools and a fairly complete POSIX environment on Windows. The model is different from simply compiling ls.exe as a native Windows program. Cygwin provides a POSIX compatibility DLL. Programs built for Cygwin call into that layer, and the layer translates Unix expectations into Windows operations.

That is both Cygwin’s strength and its weakness. It is strong because a lot of Unix software can be rebuilt and made to run. The Cygwin project is explicit that it is not a way to run unmodified Linux binaries; you rebuild from source. It is also not a way to magically make ordinary Windows programs understand Unix signals and ptys. But for source-level portability, it has been remarkably useful.

The weak spot, again, is where Unix semantics do not map cleanly. Cygwin’s own documentation calls out fork() as “particularly interesting” because it does not map well onto Win32, making it difficult to implement correctly. Cygwin’s fork() is not copy-on-write in the usual Unix sense; it has to do substantial work to recreate the parent process state in a new Windows process.

There were other branches on the family tree, too. Microsoft had POSIX and Interix efforts, later wrapped into Services for UNIX. MinGW and MSYS gave developers lighter-weight GNU-ish environments aimed at building native Windows programs. Git for Windows brought a useful subset of MSYS2-derived tools to an audience that did not necessarily know or care what MSYS2 was. But the broad pattern remained: either you had native Windows tools that looked like Unix commands, or you had a compatibility layer that tried to preserve Unix behavior more deeply.

WSL

Then Microsoft changed the game with WSL, the Windows Subsystem for Linux. WSL said, in effect: instead of endlessly translating Unix expectations into Windows expectations, let’s run a Linux userland. The first version translated Linux system calls. WSL 2 went further and used a real Linux kernel inside a managed lightweight VM, which improved compatibility at the cost of making the boundary between Windows and Linux more explicit. Microsoft’s own documentation describes WSL as a way to run a GNU/Linux environment, including most command-line tools and applications, directly on Windows without a traditional VM or dual boot. The WSL 1 versus WSL 2 comparison notes that WSL 2 uses the actual Linux kernel in a managed VM for fuller system call compatibility.

This wasn’t a new idea. Things like coLinux and similar “kernels under Windows” had done this with varying degrees of success, but without the ability to fully integrate with Windows. Having it part of the “official” Windows landscape worked better, as you might expect.

The More Things Change…

But now the pendulum has swung again. Microsoft has introduced Coreutils for Windows, a Microsoft-maintained package based on the Rust uutils project. It provides native Windows builds of familiar Unix-style tools, packaged as a single multi-call binary, including coreutils, findutils, and a GNU-compatible grep. Microsoft describes the goal as making movement between Linux, macOS, WSL, containers, and Windows more frictionless, so the same commands, flags, and pipelines work more consistently.

That is not a replacement for WSL, and it is not trying to be Cygwin. It is closer in spirit to the old desire that made MKS attractive: sometimes you are in Windows, you are staying in Windows, and you still want tools that behave the way your fingers expect.

Of course, as we’ve seen, this is hardly a new idea. In fact, it is practically full circle back to the MKS toolkit. The only difference is the official support.

There is a lesson here. Running Unix commands on Windows is not one problem. There are at least three problems masquerading as one. First, there is the user-interface problem: people like the commands and want them available. That is easy enough. Second, there is the scripting problem: people want pipelines, quoting, globbing, exit statuses, and command options to behave consistently. That is harder. Third, there is the operating-system semantics problem: people want Unix programs to run as though Windows were Unix. That’s where there is the most friction.

So, yes, basic utilities are easy. But the Unix command line is not just a bag of executables. It is an ecosystem built around process inheritance, file descriptors, signals, terminals, pipes, path conventions, and fifty years of assumptions. Every attempt to bring Unix tools to Windows has chosen a different place to draw the line.

MKS drew it at commercial interoperability. UWIN tried to give Windows a serious Unix personality. Cygwin built a POSIX world in a DLL. WSL brought in Linux itself. Microsoft’s new Coreutils for Windows brings the everyday commands back into native Windows space. Sometimes things make a full circle.

But it all starts with a user thinking: Why can’t I list files on Windows with ls? We’ve used MKS, Cygwin, coLinux, UWIN, and even WSL. We’ve also thrown hardware at the problem.

中文翻译
Windows上Unix命令简史:CoreUtils(再探)

如果你今天使用 Windows,并在终端中输入 lscatgrepawk,很可能会有一些有用的东西出现。但情况并非一直如此。在个人计算的大部分历史中,Unix/Linux 和 Windows 一直处于文化边界的两侧。Unix 用户有管道、小型可组合工具、shell 脚本、makesedawkgreptar,以及“一切都是文件”的理念。Windows 用户则有驱动器盘符、反斜杠、COMMAND.COM 或 cmd.exe,以及一个并不太在意 POSIX 想法的 API。

然而,对 Windows 上 Unix 工具的需求一直存在。其中一些来自希望在所有地方使用相同构建脚本的程序员;一些来自怀念 grepawk 的管理员;还有一些来自试图将大型 Unix 应用程序移植到 NT 而不全部重写的公司。结果便是一段漫长而奇怪的历史——各种 Unix-on-Windows 层、工具包、妥协和几近但不完全兼容的解决方案。

简单?

这个问题最简单的版本听起来微不足道。cat 能有多难?打开文件,将字节复制到标准输出,完成。编写 ls 需要多一些工作,但 Windows 有目录 API。像 cpmvrmmkdir 这样的常见命令并不神秘。即使是管道对 Windows 来说也并不陌生。日常 Unix 命令集中的许多命令,加上一些路径处理和足够的耐心,都可以作为普通的 Win32 控制台程序移植过来。

但并非所有的 Unix 或 Linux 都能干净地迁移到 Windows。一个大问题是 fork()。在 Unix 上,进程可以克隆自身。子进程获得父进程地址空间、打开的文件描述符、环境、信号状态等的副本。现代内核通过写时复制内存使其高效,但这种编程模型很古老,并深深植根于 Unix 中。Shell 经常使用它。服务器使用它。构建系统使用它。脚本语言假定它存在,或者至少假定周围的环境表现得像它存在一样。

Windows 的进程创建方式不同。Windows 有 CreateProcess(),用于启动新程序。这是一个非常合理的模型,但它不是 fork()(更像是 fork()+exec())。如果你只是启动 notepad.exe,没有问题。但如果你试图实现一个 POSIX shell,它需要 fork、重定向文件描述符、调整环境,然后启动另一个程序,那么这种不匹配是极端的,你将不得不做一些奇怪的事情来模拟。

早期的一个商业解决方案是 MKS Toolkit,最初来自 Mortice Kern Systems。MKS 为 Windows 用户提供了一大堆熟悉的命令、shell 和开发工具。它不仅仅是 ls 和它的朋友们;还包括像 kshvigrepfindawkmake 以及许多在 Unix 和 Windows 之间迁移脚本和构建流程所需的工具。当前的 PTC MKS 文档仍然用同样的精神描述它:Unix shell 和数百个命令,用于与 Windows 互操作。

MKS 的吸引力在于它将 Windows 视为 Windows。你并不需要假装你的机器是一台 Unix 工作站。你得到的是一个可以在 Windows 环境中运行的 Unix 风格工具箱。对许多人来说,这已经足够了。你可以编写脚本、处理文本、驱动构建,而无需为同一工作学习三种不同的语法。

AT&T 回归

一个更大胆的尝试来自 AT&T 贝尔实验室:UWIN,这是 KornShell 的创作者 David Korn 的个人项目。UWIN 代表 Unix for Windows,它的目标也大致如此:在 Windows NT 和 Windows 95 之上构建一个 Unix 接口层,包含库、头文件和命令。Korn 在 1997 年的 USENIX 论文中将目标描述为构建一个足够丰富的开放环境,既可以作为开发环境也可以作为执行环境,包含几乎所有 X/Open Release 4 头文件、接口和命令。

UWIN 是那种既显得理所当然又充满英雄主义的项目之一。理所当然,因为如果你有一堆 Unix 软件,你就需要一个兼容层。英雄主义,因为每一个 POSIX 语义最终都必须落在某个基于不同假设设计的 Windows 行为上。文件权限、符号链接、信号、终端、进程组、设备文件、路径名和 fork() 都有边界情况。你可以让常见情况正常工作,但仍然需要花费多年时间与不常见的情况作斗争。

Cygwin

然后是 Cygwin,可能是经典解决方案中最著名的一个。Cygwin 成为了那些想要在 Windows 上获得 GNU 工具和相当完整 POSIX 环境的人的首选答案。这种模式不同于简单地将 ls.exe 编译为原生 Windows 程序。Cygwin 提供了一个 POSIX 兼容性 DLL。为 Cygwin 构建的程序调用该层,该层将 Unix 的预期转换为 Windows 操作。

这既是 Cygwin 的优势也是它的弱点。其优势在于许多 Unix 软件可以被重新构建并运行。Cygwin 项目明确表示,它不是运行未经修改的 Linux 二进制文件的方式;你需要从源代码重新构建。它也不是一种魔法,让普通 Windows 程序理解 Unix 信号和 pty。但对于源代码级别的可移植性,它非常有用。

弱点仍然在于 Unix 语义无法干净映射的地方。Cygwin 自己的文档将 fork() 称为“特别有趣”,因为它无法很好地映射到 Win32,导致正确实现变得困难。Cygwin 的 fork() 不是通常 Unix 意义上的写时复制;它需要做大量工作才能在新的 Windows 进程中重新创建父进程状态。

这个家族树上还有其他分支。微软曾有过 POSIX 和 Interix 项目,后来整合到 Services for UNIX 中。MinGW 和 MSYS 为开发者提供了更轻量的类 GNU 环境,旨在构建原生 Windows 程序。Git for Windows 将 MSYS2 衍生工具的一个有用子集带给那些不一定知道或关心 MSYS2 是什么的用户。但总体模式仍然不变:要么拥有看起来像 Unix 命令的原生 Windows 工具,要么拥有更深入保留 Unix 行为的兼容层。

WSL

随后,微软通过 WSL(Windows Subsystem for Linux)改变了游戏规则。WSL 实际上在说:与其无休止地将 Unix 的期望翻译成 Windows 的期望,不如直接运行一个 Linux 用户空间。第一个版本翻译了 Linux 系统调用。WSL 2 更进一步,在一个受管理的轻量级虚拟机中使用了真正的 Linux 内核,这提高了兼容性,但代价是使 Windows 和 Linux 之间的界限更加明确。微软自己的文档将 WSL 描述为一种在 Windows 上直接运行 GNU/Linux 环境(包括大多数命令行工具和应用程序)的方式,无需传统的虚拟机或双启动。WSL 1 与 WSL 2 的比较指出,WSL 2 在受管理的虚拟机中使用实际的 Linux 内核,以实现更充分的系统调用兼容性。

这并非新想法。像 coLinux 和类似的“Windows 下的内核”已经以不同程度的成功做到了这一点,但无法与 Windows 完全集成。将其作为“官方”Windows 生态的一部分效果更好,正如你所预期的那样。

万物皆变…

但如今,钟摆再次摆回。微软推出了 Coreutils for Windows,这是一个基于 Rust uutils 项目并由微软维护的软件包。它提供了熟悉的 Unix 风格工具的原生 Windows 构建,打包成单个多调用二进制文件,包括 coreutils、findutils 和一个 GNU 兼容的 grep。微软将目标描述为使在 Linux、macOS、WSL、容器和 Windows 之间的迁移更加顺畅,从而使相同的命令、标志和管道能够更一致地工作。

当然,正如我们所看到的,这几乎不是一个新想法。事实上,它几乎又回到了 MKS 工具包的起点。唯一的区别是官方的支持。

这里有一个教训。在 Windows 上运行 Unix 命令并非单一问题。至少有三个问题伪装成一个。首先,是用户界面问题:人们喜欢这些命令并希望它们可用。这很容易。其次,是脚本问题:人们希望管道、引号、通配符、退出状态和命令选项的行为保持一致。这更难。第三,是操作系统语义问题:人们希望 Unix 程序能像 Windows 就是 Unix 一样运行。这是摩擦最大的地方。

所以,是的,基础工具很简单。但 Unix 命令行不仅仅是一个可执行文件的集合。它是一个围绕进程继承、文件描述符、信号、终端、管道、路径约定和五十年假设构建的生态系统。每一次将 Unix 工具带到 Windows 的尝试都选择了不同的分界线。

MKS 将其划定在商业互操作性上。UWIN 试图给 Windows 一个严肃的 Unix 个性。Cygwin 在 DLL 中构建了一个 POSIX 世界。WSL 引入了 Linux 本身。微软的新 Coreutils for Windows 将日常命令带回了原生的 Windows 空间。有时,事情会绕一个完整的圈。

但这一切都始于一个用户的想法:为什么我不能在 Windows 上用 ls 列出文件?我们使用过 MKS、Cygwin、coLinux、UWIN,甚至 WSL。我们还用硬件来解决问题