【问题标题】:Output binary data on PowerShell pipeline在 PowerShell 管道上输出二进制数据
【发布时间】:2014-09-02 17:39:49
【问题描述】:

我需要将一些数据传送到程序的stdin

  1. 前 4 个字节是一个 32 位的unsigned int,表示数据的长度。这 4 个字节与 C 在内存中存储 unsigned int 完全相同。我将其称为二进制数据。
  2. 剩余字节为数据。

在 C 中,这是微不足道的:

WriteFile(h, &cb, 4);  // cb is a 4 byte integer
WriteFile(h, pData, cb);

fwrite(&cb, sizeof(cb), 1, pFile);
fwrite(pData, cb, 1, pFile);

或者在 C# 中你会使用 BinaryWriter(我认为这段代码是正确的,我现在没有 C#...)

Bw.Write((int)Data.Length);
Bw.Write(Data, 0, Data.Length);

在 PowerShell 中,我确信这是可能的,但这是我所能得到的。这显然是将 4 个字节的大小打印为 4 个人类可读的数字:

$file = "c:\test.txt"
Set-content $file "test data" -encoding ascii
[int]$size = (Get-ChildItem $file).Length
$bytes = [System.BitConverter]::GetBytes($size)
$data = Get-content $file
$bytes
$data
11
0
0
0
test data

我需要在管道上发送的二进制数据看起来像这样(\xA 是不可打印字符的转义表示,我不希望在我的输出中出现 '\',我想要 @987654333 的 BYTE @ 代表在输出中):

\xA\x0\x0\0test data

我不知道如何以二进制格式将字节数组写入管道。我也不知道如何摆脱回车。

编辑: 我发现我可以这样做:

$file = "c:\test.txt"
Set-content $file "test data" -encoding ascii
"File: ""{0}""" -f (Get-content $file)
[int]$size = (Get-ChildItem $file).Length
"Size: " + $size
$bytes = [System.BitConverter]::GetBytes($size)
"Bytes: " + $bytes
$data = Get-content $file
$file1 = "c:\test1.txt"
Set-content $file1 $bytes -encoding byte
Add-Content $file1 $data -encoding ASCII
"File: ""{0}""" -f (Get-content $file1)
"Size: " + (Get-ChildItem $file1).Length
File: "test data"
Size: 11
Bytes: 11 0 0 0
File: "   test data"
Size: 15

但这需要我建立一个临时文件。一定有更好的办法!

编辑: 上述解决方案会破坏任何字符代码 > 127。管道没有“二进制”编码模式。

编辑: 我终于找到了一种将BinaryWriter 连接到应用程序的stdin 的迂回方法。见my answer

【问题讨论】:

  • sigh 投反对票的原因是什么?我有 2063 分的贡献,所以我不是寄生虫。这不是一个家庭作业问题,主要是因为我已经离开学校 20 年了。那么是什么给了?
  • 前4个字节是什么长度? “二进制长度”是什么意思?
  • 前 4 个字节是接下来数据的长度。数据的长度包含在一个 32 位整数中。长度必须以二进制编码。所以如果传输的数据长度是10,那么前4个字节就是0A 00 00 00,接下来是10个数据字节。
  • 啊,那么是十六进制,不是二进制和大端(即,它将是 0A 00 00 00,而不是 00 00 00 0A)?
  • 大端。但不是十六进制。前 4 个字节的“十六进制转储”为 0A 00 00 00。

标签: powershell stdout pipeline binary-data


【解决方案1】:

Bill_Stewart 是正确的,您不能通过管道传输二进制数据。当您使用| 运算符时,PowerShell 使用$OutputEncoding 规定的编码。我找不到不会损坏数据的编码。

我发现了一些有用的东西,BinaryWriter

这是我的测试代码,以C:\foo.exe 开头,它只是输出它接收到的数据:

#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); 
    BYTE aBuf[0x100];
    int nRet;
    DWORD cbRead;
    if (!(nRet = ReadFile(hInput, aBuf, 256, &cbRead, NULL)))
        return printf("err: %u %d %d", cbRead, nRet, GetLastError());
    for (int i=0 ; i<256 ; ++i)
        printf("%d ", aBuf[i]);
    return 0;
}

此 PowerShell 脚本演示了“损坏”:

$data = [Byte[]] (0..255)

$prefix = ($data | ForEach-Object {
  $_ -as [Char]
}) -join ""
"{0}" -f $prefix
$OutputEncoding = [System.Text.Encoding]::GetEncoding("us-ascii")
$prefix | c:\foo.exe

这是输出。首先你看到$prefix 确实有完整的字符集。其次,您会看到到达foo.exe 的数据已被转换。

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
 ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 5
0 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 9
7 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 63 63 63 63 63 63 63 
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63

使用BinaryWriter 有效:

$data = [Byte[]] (0..255)

$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo 
$ProcessInfo.FileName = "C:\foo.exe"
$ProcessInfo.RedirectStandardInput = $true 
$ProcessInfo.RedirectStandardOutput = $true 
$ProcessInfo.UseShellExecute = $false 
$Proc = New-Object System.Diagnostics.Process 
$Proc.StartInfo = $ProcessInfo 
$Proc.Start() | Out-Null 

$Writer = New-Object System.IO.BinaryWriter($proc.StandardInput.BaseStream);
$Writer.Write($data, 0, $data.length)
$Writer.Flush()
$Writer.Close()

$Proc.WaitForExit()
$Proc.StandardOutput.ReadToEnd()
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 5
0 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 9
7 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 1
33 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 16
8 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

所以,我在写入数据文件之前以二进制形式写入长度的最终脚本如下所示:

$data = [Byte[]] (0..255)

$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo 
$ProcessInfo.FileName = "C:\foo.exe"
$ProcessInfo.RedirectStandardInput = $true 
$ProcessInfo.RedirectStandardOutput = $true 
$ProcessInfo.UseShellExecute = $false 
$Proc = New-Object System.Diagnostics.Process 
$Proc.StartInfo = $ProcessInfo 
$Proc.Start() | Out-Null 

$Writer = New-Object System.IO.BinaryWriter($proc.StandardInput.BaseStream);
$Writer.Write([Int32]$data.length)
$Writer.Write($data, 0, $data.length)
$Writer.Flush()
$Writer.Close()

$Proc.WaitForExit()
$Proc.StandardOutput.ReadToEnd()

您可以看到前 4 个字节 0 1 0 0 是等于 256[Int32] 的原始二进制表示:

0 1 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 1
31 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 16
6 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251

【讨论】:

  • 干杯,这让我很生气。无论我采用哪种方法,二进制数据都会被破坏(始终如一,所以它显然遵循某种逻辑)。
【解决方案2】:

我需要将一些数据通过管道传输到程序的标准输入。

使用不同的编码确实会导致很多问题。这是一种不同的方法,Get-/Set-Content 未应用任何编码。

您实际上可以使用Start-Process cmdlet 将二进制数据通过管道传输到外部程序:

Start-Process my.exe -RedirectStandardInput my.bin

至少从 PowerShell 2.0 开始工作。

【讨论】:

  • 正如这里只创建一个进程所证明的那样,-RedirectStandardInput 没有任何管道;文件my.bin 被馈送到新my.exe 进程的stdin。如果您已经有或不介意创建一个文件以用作新进程的stdin,这可能是最好的解决方案。如果像问题一样,您希望一个进程向另一个进程的stdin 提供数据而不涉及中间文件,那么这将行不通。 johnnycrash's answer 展示了如何使用重定向将任意数据提供给子进程的 stdin
【解决方案3】:

这对你有用吗?

$fileName = "C:\test.txt"
$data = [IO.File]::ReadAllText($fileName)
$prefix = ([BitConverter]::GetBytes($data.Length) | foreach-object {
  "\x{0:X2}" -f $_
}) -join ""
"{0}{1}" -f $prefix,$data

如果您希望$prefix 包含字节的原始数据表示,您可以将"\x{0:X2}" -f $_ 替换为$_ -as [Char]

【讨论】:

  • 不,抱歉,因为我希望流的前 4 个字节是 32 位数字的原始机器表示。 2 的赞美。使用 Set-Content $file $bytearray -encoding byte 得到什么。
  • 如何将它通过管道传输到另一个程序的标准输入?假设数据长度为 10 (0A)。这是命令解释器的换行符。
  • 我无法控制将数据传输到预期的程序。我只是想自动化一个大规模的测试。这是接收程序正在执行的操作: ReadFile(m_hInput,&ulMsgLen,sizeof(ulMsgLen),&cbBytes,NULL); m_strMsg.PszAllocate(ulMsgLen+1) ; ReadFile(m_hInput,m_strMsg,m_strMsg.Cch(),&cbBytes,NULL))
  • “我无法控制将数据传输到的程序所期望的内容”——这就是我想要表达的观点。如果您尝试将数据通过管道传输到另一个程序的标准输入并且该数据包含控制字符(从 shell 的角度来看),那么 shell 将对这些控制字符(例如,在我的示例中的换行符)进行操作,而不是将它们传递给程序的标准输入。
  • 我给出了您的解决方案并评论了 +1,因为我朝着正确的方向前进,即“|”正在使用编码来处理输出。没有“原始”编码,因此您似乎无法使用“|”在管道上输出二进制数据。它似乎不受 ctrl 字符的影响。
【解决方案4】:
[System.Console]::OpenStandardOutput().Write($bytes, 0, $bytes.Length)

【讨论】:

  • 请通过您的解决方案提供一些信息。代码是如何工作的等等。
【解决方案5】:

使用 binaryWriter 的更短示例:

$file = 'c:\temp\test.txt'
$test = [byte[]](0..255)
$mode = [System.IO.FileMode]::Create
$stream = [System.IO.File]::Open($file, $mode)
$bw = [System.IO.BinaryWriter]::new($stream)
$bw.Write($test)
$bw.Flush()
$bw.Dispose()
$stream.Dispose()

【讨论】:

  • 您正在向test.txt 写入一个测试数组(即使它是二进制数据,而不是文本数据)。然后呢?它如何通过管道输送到管道中的下一个流程?这个问题特别想避免使用临时文件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-15
相关资源
最近更新 更多