How to check if stdout has been redirected to NUL

2019-02-25 00:11发布

问题:

How can I check if my program's stdout has been redirected to NUL?
That way I can avoid outputting data since it's pointless.

I mainly need this for Windows, but if you have a Linux solution, it might be helpful for others in the future, so feel free to post that as well.

回答1:

There are probably other ways to do this (and it wouldn't be a surprise if there turns out to be a proper function for it I've overlooked), but here's one way:

enum
{
    Output_Console,
    Output_File,
    Output_NUL,
};

bool GetOutputHandleType(int* piType)
{
    HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    if (h)
    {
        BY_HANDLE_FILE_INFORMATION fi;
        if (GetFileInformationByHandle(h, &fi))
        {
            *piType = Output_File;
            return true;
        }
        if (GetLastError() == ERROR_INVALID_FUNCTION)
        {
            *piType = Output_NUL;
            return true;
        }
        if (GetLastError() == ERROR_INVALID_HANDLE)
        {
            *piType = Output_Console;
            return true;
        }
    }
    return false;
}


回答2:

I figured it out myself. It's annoying.

#include <Windows.h>
#include <io.h>
#pragma comment(lib, "ntdll.lib")  // can instead use GetProcAddress (below)
extern "C" NTSTATUS __stdcall NtQueryVolumeInformationFile(
    HANDLE FileHandle, struct _IO_STATUS_BLOCK *IoStatusBlock,
    void *FsInformation, unsigned long Length,
    enum _FSINFOCLASS FsInformationClass);
bool isdevnull(FILE *file)
{
    struct FILE_FS_DEVICE_INFORMATION
    { unsigned long DeviceType, Characteristics; } fsinfo;
    struct { void *info, *status; } iosb;
    typedef NTSTATUS (__stdcall *PNTQIF)(
        HANDLE FileHandle, struct _IO_STATUS_BLOCK *IoStatusBlock,
        void *FsInformation, unsigned long Length,
        enum _FSINFOCLASS FsInformationClass);
    PNTQIF const ntqif =
        true  // True if you have ntdll.lib, false otherwise
        ? NtQueryVolumeInformationFile
        : (PNTQIF) GetProcAddress(
            GetModuleHandle(TEXT("ntdll.dll")),
            "NtQueryVolumeInformationFile");
    return ntqif(
        (HANDLE) _get_osfhandle(_fileno(stdout)),
        (struct _IO_STATUS_BLOCK *)&iosb,
        &fsinfo, sizeof(fsinfo),
        (enum _FSINFOCLASS)4
    ) == 0 && fsinfo.DeviceType == 0x00000015 /*FILE_DEVICE_NULL*/;
}
int main()
{
    bool b = isdevnull(stdout);
}