cat 명령의 간단한 Windows 구현
Linux에는 cat
연결된 파일을 출력 하는 명령이 있지만 Windows에는 이러한 명령이 없습니다. 결과적으로 나는 그것의 간단한 버전을 다시 만들기로 결정했지만 C 런타임 라이브러리의 어떤 부분도 사용할 수 없다는 문제가 있었다.
#include <windows.h>
/* global variables */
HANDLE stdout = NULL;
HANDLE stdin = NULL;
char *input_buffer = NULL;
CONSOLE_READCONSOLE_CONTROL crc = { .nLength = sizeof(crc), .dwCtrlWakeupMask = 1 << '\n' };
char *output_buffer = NULL;
DWORD output_capacity = 0;
/* There is only CommandLineToArgvW so a version for ascii is needed */
LPSTR *CommandLineToArgvA(LPWSTR lpWideCmdLine, INT *pNumArgs)
{
int retval;
int numArgs;
LPWSTR *args;
args = CommandLineToArgvW(lpWideCmdLine, &numArgs);
if (args == NULL)
return NULL;
int storage = numArgs * sizeof(LPSTR);
for (int i = 0; i < numArgs; ++i) {
BOOL lpUsedDefaultChar = FALSE;
retval = WideCharToMultiByte(CP_ACP, 0, args[i], -1, NULL, 0, NULL, &lpUsedDefaultChar);
if (!SUCCEEDED(retval)) {
LocalFree(args);
return NULL;
}
storage += retval;
}
LPSTR *result = (LPSTR *)LocalAlloc(LMEM_FIXED, storage);
if (result == NULL) {
LocalFree(args);
return NULL;
}
int bufLen = storage - numArgs * sizeof(LPSTR);
LPSTR buffer = ((LPSTR)result) + numArgs * sizeof(LPSTR);
for (int i = 0; i < numArgs; ++i) {
BOOL lpUsedDefaultChar = FALSE;
retval = WideCharToMultiByte(CP_ACP, 0, args[i], -1, buffer, bufLen, NULL, &lpUsedDefaultChar);
if (!SUCCEEDED(retval)) {
LocalFree(result);
LocalFree(args);
return NULL;
}
result[i] = buffer;
buffer += retval;
bufLen -= retval;
}
LocalFree(args);
*pNumArgs = numArgs;
return result;
}
static void lmemcpy(char *dest, const char *src, DWORD len)
{
/* copy 4 bytes at once */
for (; len > 3; len -= 4, dest += 4, src += 4)
*(long *)dest = *(long *)src;
while (len--)
*dest++ = *src++;
}
static void catstdin(void)
{
DWORD chars_read = 0;
ReadConsoleA(stdin, input_buffer, 2048, &chars_read, &crc);
WriteConsoleA(stdout, input_buffer, chars_read, NULL, NULL);
}
static void catfile(char *filepath)
{
HANDLE filehandle = CreateFileA(filepath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (filehandle == INVALID_HANDLE_VALUE) {
WriteConsoleA(stdout, "Error could not open file: ", 27, NULL, NULL);
WriteConsoleA(stdout, filepath, lstrlenA(filepath), NULL, NULL);
ExitProcess(GetLastError());
}
DWORD filelength = GetFileSize(filehandle, NULL);
if (filelength > output_capacity) { /* see if we need to allocate more memory */
char *new_buffer = HeapAlloc(GetProcessHeap(), 0, filelength * 2); /* copy the data from the old memory to the new memory */
lmemcpy(new_buffer, output_buffer, output_capacity);
HeapFree(GetProcessHeap(), 0, output_buffer); /* free old memory */
output_capacity = filelength * 2;
output_buffer = new_buffer;
}
ReadFile(filehandle, output_buffer, filelength, NULL, NULL);
WriteConsoleA(stdout, output_buffer, filelength, NULL, NULL);
CloseHandle(filehandle); /* close file */
}
void __cdecl mainCRTStartup(void)
{
/* setup global variables */
stdout = GetStdHandle(STD_OUTPUT_HANDLE);
stdin = GetStdHandle(STD_INPUT_HANDLE);
input_buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2048);
output_buffer = HeapAlloc(GetProcessHeap(), 0, 2048);
output_capacity = 2048;
/* get argc and argv */
int argc;
char **argv = CommandLineToArgvA(GetCommandLineW(), &argc) + 1;
argc--; /* the first arg is always the program name */
switch (argc) {
case 0:
for (;;) catstdin();
break;
default:
for (int i = 0; i < argc; ++i) {
if (!lstrcmpA(argv[i], "-"))
catstdin();
else
catfile(argv[i]);
}
}
/* free memory */
HeapFree(GetProcessHeap(), 0, input_buffer);
HeapFree(GetProcessHeap(), 0, output_buffer);
LocalFree(argv);
/* exit */
ExitProcess(0);
}
```
답변
명령 줄 인수를 ASCII로 변환하지 마십시오.
명령 줄 인수를 ASCII로 변환 할 이유가 없습니다. ASCII 문자열에 대한 포인터를 사용하는 모든 함수에는 와이드 문자열을 처리하는 변형 (예 : lstrcmpW()
및)도 CreateFileW()
있습니다. 이렇게하면 CommandLineToArgvA()
.
stderr
오류보고에 사용
cat
구현 사용자가 표준 출력을 다른 파일로 리디렉션 할 가능성이 거의 없다는 점을 고려 하십시오. 오류가있는 경우 콘솔에 인쇄하는 대신 해당 파일에 오류 메시지를 작성하는 것입니다. 을 추가 stderr = GetStdHandle(STD_ERROR_HANDLE)
하고 오류 메시지에 사용하십시오.
각 입력 파일만큼 큰 버퍼를 할당하지 마십시오.
디스크 공간은 일반적으로 RAM보다 10 배 이상 더 큽니다. 사용 가능한 RAM 용량보다 큰 파일을 찾으려면 프로그램이 실패합니다. 64KiB의 고정 크기로 버퍼를 할당하고 ReadFile()
필요한 경우 최대 64KiB의 청크로 입력을 읽기 위해 여러 호출을 사용 하는 것이 좋습니다. 한편으로는 ReadFile()
에 대한 여러 호출에서 더 많은 오버 헤드 가 발생하고 다른 한편으로는 CPU의 L2 캐시 내에있을 가능성이 높습니다. 어쨌든 성능이 크게 변경되지는 않을 것으로 예상하지만 이제 프로그램은 임의의 크기의 파일을 처리합니다.
이것은 또한 코드를 단순화 할 것입니다. 더 이상 파일 크기를 얻고 필요한 경우 버퍼 크기를 조정할 필요가 없습니다. 대신 파일 의 끝에 도달 할 때까지 읽으십시오 .
루프를 사용하여 stdin
EOF에 도달 할 때까지 읽기
-
인수로 지정 stdin
하는 경우 다음 명령 줄 인수를 계속하기 전에 최대 2048 바이트 만 읽습니다 . 그리고 인수를 전혀 지정하지 않으면 stdin
더 이상 읽을 것이 없더라도 에서 읽는 무한 루프가 생깁니다.
명심 stdin
또한 리디렉션 될 수도 있고, 실제로 파일에서 읽을 것, 또는 다른 프로그램에서 출력을 읽습니다.
stdin
파일 과 동일한 버퍼 사용
파일 또는 stdin
한 번에 하나만 처리하므로 두 개의 별도 버퍼를 가질 필요가 없습니다 . 충분히 큰지 확인하십시오.
읽기 및 쓰기 오류 처리
일이 잘못 될 수 있습니다. 파일을 읽거나에 쓰는 중 stdout
오류가 발생하면 오류 메시지를에 인쇄 한 stderr
다음 0이 아닌 종료 코드로 즉시 종료해야합니다. 사용자에게 오류를 알립니다. 또한 cat
구현이 배치 스크립트에서 사용되는 경우 0이 아닌 종료 코드를 사용하면 해당 스크립트가 잘못된 데이터를 맹목적으로 계속하는 대신 오류를 감지 할 수 있습니다.