2014-07-12 10 views
7

Potrzebuję przesłać niektóre dane do stdin programu.Wyjściowe dane binarne na potoku PowerShell

  1. Pierwsze 4 bajty to 32-bitowa liczba całkowita bez znaku reprezentująca długość danych. Te 4 bajty są dokładnie takie same, jak C, które zapisałoby w pamięci unsigned int. Odnoszę się do tego jako danych binarnych.
  2. Pozostałe bajty są danymi.

w C, to jest trywialne:

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

lub

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

lub C# byłoby użyć BinaryWriter (myślę, że ten kod jest w porządku, nie mam C# leżący w tej chwili ...)

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

W PowerShell jestem pewien, że to możliwe, ale t jego jest tak blisko, jak tylko mogłem. To jest oczywiście drukując 4 bajty wielkości jako 4 ludzkich numerów czytelnych:

$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 

muszę danych binarnych wysyłanych na rurze wyglądać następująco (\ xA jest uciekł reprezentacja nieprzestrzegania znaku wydruku , nie chcę „\” w mojej mocy, chcę bajt że „\ xa” oznacza na wyjściu):

\xA\x0\x0\0test data 

nie wiem jak napisać tablicę bajtów z rurociągu w format binarny. Nie wiem też, jak pozbyć się powrotów karetki.

EDIT: Odkryłem, że mogę to zrobić:

$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 

Wymaga to jednak mnie zbudować plik tymczasowy. Musi istnieć lepszy sposób!

EDYTOWANIE: Powyższe rozwiązanie powoduje uszkodzenie dowolnego kodu znaku> 127. Nie ma trybu kodowania "binarnego" dla potoku.

EDIT: I wreszcie odkrył okrężną drogą, aby uzyskać BinaryWriter przewodowy do danej aplikacji stdin. Zobacz moją odpowiedź.

+0

* Westchnienie * za co spadł? Mam 2063 punktów za przyczynianie się, więc nie jestem pasożytem. To nie jest zadanie domowe, głównie dlatego, że przez 20 lat nie byłem w szkole. Co daje? – johnnycrash

+0

Pierwsze 4 bajty to długość czego? A co rozumiesz przez "długość w binarnej"? –

+0

Pierwsze 4 bajty to długość kolejnych danych. Długość danych jest zawarta w 32-bitowej liczbie całkowitej. Długość musi być zakodowana w systemie binarnym. Jeśli więc długość przesyłanych danych wynosi 10, pierwsze 4 bajty będą wynosić 0 00 00 00. Następnie pojawi się 10 bajtów danych. – johnnycrash

Odpowiedz

5

Bill_Stewart jest poprawny, ponieważ nie można potokować danych binarnych. Kiedy używasz | operator, powershell używa kodowania podyktowanego przez $ OutputEncoding. Nie mogłem znaleźć kodowania, które nie spowodowałoby uszkodzenia danych.

Znalazłem coś, co działa, ale BinaryWriter.

Oto mój kod testowy, zaczynając od c: \ foo.exe, które po prostu wysyła dane otrzymane:

#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; 
} 

Ten skrypt ps pokazuje "Korupcja":

$file = "c:\foo.bin" 
Set-Content $file ([Byte[]](0..255)) -encoding byte 
$data = (Get-content $file -encoding byte) 
$prefix = ($data | foreach-object { 
    $_ -as [char] 
}) -join "" 
"{0}" -f $prefix 
$OutputEncoding = [System.text.Encoding]::GetEncoding("us-ascii") 
$prefix | c:\foo.exe 

Oto wynik. Najpierw zobaczysz, że prefiks $ ma pełny zestaw znaków. Po drugie, dane, które dostałeś do foo.exe, zostały przekonwertowane.

!"#$%&'()*+,-./:;<=>[email protected][\]^_`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 

Korzystanie BinaryWriter działa:

$file = "c:\foo.bin" 
Set-Content $file ([Byte[]](0..255)) -encoding byte 
$data = (Get-content $file -encoding byte) 

$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 

Tak, mój ostateczny scenariusz, który pisze długość binarnie przed zapisaniem pliku danych, wyglądałby mniej więcej tak:

$file = "c:\foo.bin" 
Set-Content $file ([Byte[]](0..255)) -encoding byte 
$data = (Get-content $file -encoding byte) 

$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([int]$data.length) 
$Writer.Write($data, 0, $data.length) 
$Writer.flush() 
$writer.close() 

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

można zobacz pierwsze 4 bajty 0 1 0 0 są nieprzetworzoną binarną reprezentacją [int] równą 256

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 
1

Czy ta praca jest dla Ciebie?

$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 

Można zastąpić "\x{0:X2}" -f $_ z $_ -as [Char] jeśli chcesz $prefix zawierać surowe reprezentacje danych bajtów.

+0

Nie, przepraszam, ponieważ chcę, aby pierwsze 4 bajty strumienia były nieprzetworzoną reprezentacją 32-bitową. Komplement 2. Co otrzymujesz z bajtu kodowania Set-Content $ file $ bytearray -encoding. – johnnycrash

+0

Jak możesz podłączyć to do standardowego wejścia innego programu? Załóżmy, że długość danych wynosi 10 (0A). To jest posuw liniowy do interpretera poleceń. –

+0

Zobacz zaktualizowaną odpowiedź - chociaż nie wiem, jak sobie poradzisz z tym, że interpreter poleceń nie pozwoli ci wysłać niektórych znaków sterujących na standardowe wejście innego programu. –