Steps to reproduce
Create a file a.cpp with the following content:
#include<cstdio>
int main()
{
char buffer[1024];
size_t rc;
while ((rc = fread(buffer, 1, 1024, stdin)) != 0)
{
fwrite(buffer, 1, rc, stdout);
}
// There is a blank line on purpose!
return 0;
}
// The file does not end with "\n" on purpose!
Then, in bash, compile it with g++ (or any C++ compiler you find on macOS) and test redirection in bash and PowerShell:
g++ a.cpp
./a.out < a.cpp > bash_redir.cpp
exec pwsh
Then in PowerShell:
Start-Process a.out -RedirectStandardInput a.cpp -RedirectStandardOutput pwsh_redir.cpp -Wait
Get-FileHash *.cpp
Expected behavior
All the three .cpp files have the same hash (are the same).
Actual behavior
The file pwsh_redir.cpp is different from the other two. The on-purpose blank line is eaten, and the on-purpose no-"\n" is broken. Specifically, pwsh_redir.cpp is the following:
#include<cstdio>
int main()
{
char buffer[1024];
size_t rc;
while ((rc = fread(buffer, 1, 1024, stdin)) != 0)
{
fwrite(buffer, 1, rc, stdout);
}
// There is a blank line on purpose!
return 0;
}
// The file does not end with "\n" on purpose!
Note that an extra "\n" has been appended at the end.
Environment data
Name Value
---- -----
PSVersion 6.1.2
PSEdition Core
GitCommitId 6.1.2
OS Darwin 18.2.0 Darwin Kernel Version 18.2.0: Mo...
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
The full value of OS property is Darwin 18.2.0 Darwin Kernel Version 18.2.0: Mon Nov 12 20:24:46 PST 2018; root:xnu-4903.231.4~2/RELEASE_X86_64. The full table of PSCompatibleVersions is
Major Minor Build Revision
----- ----- ----- --------
1 0 -1 -1
2 0 -1 -1
3 0 -1 -1
4 0 -1 -1
5 0 -1 -1
5 1 10032 0
6 1 2 -1
Comments
The standard stream might not be textual and they are binary in many cases. E.g., the curl might write the response stream to stdout.
PowerShell for macOS does the redirection by using ForkAndExecProcess, which creates pipes connecting the parent and the child. The parent, which is PowerShell, will then copy the stream from the pipe to file in a faulty way -- it assumes textual stream (as it's using StreamReader and StreamWriter).
There are TWO faults:
- Assuming the streams are textual.
- Copying text streams with
ReadLine and WriteLine carelessly, which might create extraneous or missing line breaks.
The fix is to access the underlying pipe/file stream when doing the copy, and do a binary copy (resembling the C++ example code above).
This problem does not reproduce on Windows PowerShell, because Windows PowerShell implements Start-Process stream redirection by opening the actual files, inheriting the handles and setting the correct handles in STARTUPINFO.
This problem is not related to the long-discussed broken native-piping in PowerShell.
Steps to reproduce
Create a file
a.cppwith the following content:Then, in bash, compile it with
g++(or any C++ compiler you find on macOS) and test redirection in bash and PowerShell:Then in PowerShell:
Expected behavior
All the three
.cppfiles have the same hash (are the same).Actual behavior
The file
pwsh_redir.cppis different from the other two. The on-purpose blank line is eaten, and the on-purpose no-"\n" is broken. Specifically,pwsh_redir.cppis the following:Note that an extra "\n" has been appended at the end.
Environment data
The full value of
OSproperty isDarwin 18.2.0 Darwin Kernel Version 18.2.0: Mon Nov 12 20:24:46 PST 2018; root:xnu-4903.231.4~2/RELEASE_X86_64. The full table ofPSCompatibleVersionsisComments
The standard stream might not be textual and they are binary in many cases. E.g., the
curlmight write the response stream tostdout.PowerShell for macOS does the redirection by using
ForkAndExecProcess, which creates pipes connecting the parent and the child. The parent, which is PowerShell, will then copy the stream from the pipe to file in a faulty way -- it assumes textual stream (as it's usingStreamReaderandStreamWriter).There are TWO faults:
ReadLineandWriteLinecarelessly, which might create extraneous or missing line breaks.The fix is to access the underlying pipe/file stream when doing the copy, and do a binary copy (resembling the C++ example code above).
This problem does not reproduce on Windows PowerShell, because Windows PowerShell implements
Start-Processstream redirection by opening the actual files, inheriting the handles and setting the correct handles inSTARTUPINFO.This problem is not related to the long-discussed broken native-piping in PowerShell.