PowerShell 功能很强大,但对 Linux Shell 用户来说易用性依然达不到满意程度(虽然几经改进后比以前强了许多)。那么就有一个办法是平时使用 WSL 中的 Shell,并通过 PowerShell 脚本实现一些功能。但问题是 PowerShell 启动时间很慢(在我这里要 130ms 以上)。那就意味着在 WSL 中调用 PowerShell 脚本会因为启动速度慢而体验极差。
看着功能强大的 PowerShell 却没办法舒服地使用还是比较遗憾的。不过有一个折衷的办法,运行一个常驻的 PowerShell Server,然后在 WSL 下运行命令时直接连接到这个 Server,这样就省去了 PowerShell 的启动时间。
% time o '$PSversionTable.PSVersion'
5.1.18362.1
o '$PSversionTable.PSVersion' 0.00s user 0.00s system 0% cpu 0.006 total
% time o '11+22+33'
66
o '11+22+33' 0.00s user 0.00s system 0% cpu 0.006 total
% cat test.ps1
#!/home/goreliu/.bin/o -f
$var=@{Name = "小明"; Age = "12"; sex = "男"}
echo $var["Name"]
% time ./test.ps1
小明
./test.ps1 0.00s user 0.02s system 249% cpu 0.006 total
% time bash -c ''
bash -c '' 0.00s user 0.02s system 193% cpu 0.008 total
% o
PS> 33*99
3267
PS> 12mb
12582912
PS>
重点在于运行时间,只有惊人的 6ms,要比 WSL 下直接运行 bash 脚本还快。
其中 o 是用于连接 Server 的 Client,代码见下文。
#!/bin/powershell
$endpoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Loopback, 12345)
$Listener = New-Object System.Net.Sockets.TcpListener $endpoint
$Listener.Start()
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 10)
$RunspacePool.Open()
while ($true) {
$client = $Listener.AcceptTcpClient()
$job = [PowerShell]::Create().AddScript({
Param($client, $buffer)
$encoding = new-object System.Text.Utf8Encoding
$stream = $client.GetStream()
$cmd = ''
$buffer = new-object byte[] 102400
while ($true) {
try {
$read_size = $stream.Read($buffer, 0, $buffer.Count)
$cmd = $encoding.GetString($buffer, 0, $read_size)
} catch {
echo $_
break
}
if ($cmd.length -eq 0) {
break
}
<#
if ($cmd -match '@gui@') {
$stream.Write([BitConverter]::GetBytes(0), 0, 4)
[PowerShell]::Create().AddScript($cmd).BeginInvoke()
break
} elseif ($cmd -match '@guip@') {
$stream.Write([BitConverter]::GetBytes(0), 0, 4)
# 用进程
Start-Job -ScriptBlock {iex $args[0]} -ArgumentList $cmd
break
}
#>
iex $cmd -OutVariable out -ErrorVariable err
$all = $err
if ($all.Count -eq 0) {
$all = $out
}
if ($all.Count -eq 0) {
$stream.Write([BitConverter]::GetBytes(0), 0, 4)
} else {
$result = $encoding.GetBytes(($all -join "`n") + "`n")
$stream.Write([BitConverter]::GetBytes($result.length) + $result,`
0, 4 + $result.length)
}
}
$stream.Close()
$client.Close()
}).AddParameter('client', $client).AddParameter('buffer', $buffer)
$job.RunspacePool = $RunspacePool
$job.BeginInvoke()
}
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define buf_size 1048576
char buf[buf_size] = "";
int main(int argc, char *argv[]) {
// -f file: run file
if (argc == 3 && argv[1][0] == '-' && argv[1][1] == 'f') {
int fd = open(argv[2], O_RDONLY);
if (fd < 0) {
printf("Failed to open %s.\n", argv[2]);
return 1;
}
if (read(fd, buf, buf_size) <= 1) {
printf("Failed to read %s.\n", argv[2]);
close(fd);
return 1;
}
close(fd);
} else {
for (int i = 1; i < argc; ++i) {
strcat(buf, argv[i]);
strcat(buf, " ");
}
}
struct sockaddr_in their_addr;
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(12345);
their_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(their_addr.sin_zero), 8);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) != 0) {
printf("Failed to connect.\n");
return 1;
}
int manual = 0;
if (argc == 1) {
readlink("/proc/self/fd/0", buf, buf_size);
// pipe:[...]
if (buf[0] != 'p') {
// run as a shell
manual = 1;
}
}
while (1) {
if (manual) {
write(1, "PS> ", 4);
}
int numbytes;
if (argc == 1) {
numbytes = read(0, buf, buf_size);
if (numbytes == 1) {
// 只读到一个换行符
continue;
} else if (numbytes < 1) {
if (manual) {
write(1, "\n", 1);
}
close(sockfd);
return 0;
}
} else {
numbytes = strlen(buf);
}
if (numbytes > 102400) {
printf("To long: %d.\n", numbytes);
close(sockfd);
return 0;
}
send(sockfd, buf, numbytes, 0);
int length;
if (recv(sockfd, &length, 4, 0) != 4) {
printf("Failed to parse length.\n");
if (argc == 1) {
break;
}
continue;
}
while (length > 0) {
numbytes = recv(sockfd, buf, length > buf_size ? buf_size : length, 0);
length -= numbytes;
write(1, buf, numbytes);
}
if (!manual) {
break;
}
}
close(sockfd);
return 0;
}
还有一个运行在 Windows 的 Client,功能很简单,只用来运行图形界面(或者不需要结果的脚本),可以关联到 .ps1 (或者用新扩展名)文件上。可以用 tcc 或者 MinGW 编译。
// tcc -lws2_32 wino.c
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#define buf_size 102400
char buf[buf_size] = "";
char *filename = NULL;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
filename = lpCmdLine;
if (filename[0] == '\0') {
MessageBoxA(0, "Usage:\n o filename", "Error", MB_ICONERROR);
return 1;
}
if (filename[0] == '"') {
++filename;
filename[strlen(filename) - 1] = '\0';
}
int fd = open(filename, O_RDONLY);
if (fd < 0) {
sprintf(buf, "Failed to open %s", lpCmdLine);
MessageBoxA(0, buf, "Error", MB_ICONERROR);
return 1;
}
int numbytes = read(fd, buf, buf_size);
if (numbytes <= 1) {
sprintf(buf, "Failed to read %s", lpCmdLine);
MessageBoxA(0, buf, "Error", MB_ICONERROR);
close(fd);
return 1;
}
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
int iResult;
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
sprintf(buf, "WSAStartup failed with error: %d", iResult);
MessageBoxA(0, buf, "Error", MB_ICONERROR);
return 1;
}
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
sprintf(buf, "socket failed with error: %ld", WSAGetLastError());
MessageBoxA(0, buf, "Error", MB_ICONERROR);
WSACleanup();
return 1;
}
iResult = connect(ConnectSocket, (const struct sockaddr *)&server_addr, sizeof(server_addr));
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
WSACleanup();
MessageBoxA(0, "Unable to connect to server", "Error", MB_ICONERROR);
return 1;
}
iResult = send(ConnectSocket, buf, numbytes, 0);
if (iResult == SOCKET_ERROR) {
sprintf(buf, "send failed with error: %d", WSAGetLastError());
MessageBoxA(0, buf, "Error", MB_ICONERROR);
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
sprintf(buf, "shutdown failed with error: %d", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
closesocket(ConnectSocket);
WSACleanup();
return 0;
}
端口都写死在了代码里( 12345 )。
其中 Server 是多线程的,最多 10 个线程( CreateRunspacePool(1, 10))。
但需要注意一个事情,运行命令的输出结果和直接在 PowerShell 终端中不同:
% o '$PSVersionTable'
System.Collections.Hashtable
% /init /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe '$PSVersionTable'
Name Value
---- -----
PSVersion 5.1.18362.1
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.18362.1
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
写脚本的话基本不影响使用。
另外需要注意安全问题。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.