
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 写脚本的话基本不影响使用。
另外需要注意安全问题。
1 ps1aniuge 2019-06-05 19:17:52 +08:00 楼主这什么玩意?不知所谓!!! 楼主的 win,powershell 慢( mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe ) 你折腾 linux ( wsl )干嘛??! 1 130ms,启动 powershell 不算慢。 2 可以买高速 ssd,装在 c 盘上。想快速尽量不能贫穷。 3 可以经常手动运行 。net 优化,来提速。 任务计划 -》 \Microsoft\Windows\.NET Framework\.NET Framework NGEN v4.0.30319 64 4 可以启动 Superfetch 服务,即内存缓存程序来提速。 5 可以运行 linux 版( wsl 版) powershell,来提速 linux 中使用 pwsh。不过这个和 win 中的 powershell 没啥关系。 |
3 ps1aniuge 2019-06-05 19:47:56 +08:00 你来回折腾,到底要干嘛? 你要在你的 wsl 发行版的 linux 中,使用 powershell,不需要远程去使用, 只需要在 wsl 发行版 linux 中,安装 powershell 6.2.1。 装好后,在 linux 本地使用 /usr/bin/pwsh 即可。 不需要联网 服务器 /客户机这样使用。 就算是服务器 /客户机这样使用。用 ssh-ps-remoting 也行,也不需要自己开发。 你这是过度开发。会写 c,会写锤子,看啥都是钉子。 |