. 파일에서 바꾸기

파일에서 바꾸기는 문자열을 검색하는 방법은 동일하지만 조건에 맞는 문자열이 발견되면 파일을 변경해야 한다는 점에서 조금 더 복잡하다. 검색결과 파일이 더 커질 수 있기 때문에 파일 크기만큼의 고정 길이 버퍼를 사용할 수 없으며 동적으로 버퍼 크기를 관리해야 한다. 예를 들어 우리나라대한민국이나 한국으로 바꾸면 별 문제가 없지만 옥떨메옥상에서 떨어진 메주로 바꾼다면 파일 크기가 늘어날 것이다.

에구 그까짓 거 늘어나 봐야 얼마나 늘어나겠느냐고 약간의 여유분을 주는 정도로 문제가 해결될 것으로 안일하게 생각해서는 안된다. 검색결과가 많으면 많을수록 대체되는 문자열이 길수록 필요한 버퍼 크기가 더 커진다. 버퍼가 커지는 정도가 문제가 아니라 얼마나 커질지 미리 예측할 수 없다는 점이 문제다. 극단적인 예를 들자면 애국가동해물과 백두산이 ..... 충성하세로 바꾸면 약간이 아니라 몇 배로 커져 버릴 것이다.

그래서 파일에서 바꾸기를 할 때는 일단 파일의 크기에 약간의 여유분을 줘 메모리를 할당하되 문자열이 대체될 때마다 남은 여유분이 충분한지 점검해야 한다. 이런 식의 메모리관리는 앞에서 여러 번 실습한 적이 있어서 이제는 아주 익숙할 것이다. 다음은 파일에서 바꾸기를 하는 함수이다. 이 함수도 FindInFiles 함수에 의해 호출되며 인수는 OnFindFile 함수와 동일하다.

 

int OnReplaceFile(TCHAR *Path, DWORD Attr, LPVOID pCustom)

{

     TCHAR what[256];

     TCHAR to[256];

     HANDLE hFile;

     DWORD size, dwRead;

     TCHAR *buf, *pbuf;

     TCHAR *p;

     int line,col;

     TCHAR Text[501];

     TCHAR Mes[512];

     LVITEM LI;

     int idx;

     BOOL bReplace=FALSE;

     int len1,len2;

     int extra, memsize;

     int tempoff;

 

     if ((Attr & FILE_ATTRIBUTE_DIRECTORY) || (Attr & FILE_ATTRIBUTE_READONLY)) {

          return 0;

     }

 

     if (FindChildWithFile(Path) != NULL) {

          wsprintf(Mes, "%s 파일은 열려 있으므로 바꾸기를 수행할 수 없습니다.",Path);

          MessageBox(g_hFrameWnd,Mes,"알림",MB_OK);

          return -1;

     }

 

     lstrcpy(what,arFind[0].Get(0));

     lstrcpy(to,arFind[1].Get(0));

     len1=lstrlen(what);

     len2=lstrlen(to);

 

     hFile=CreateFile(Path,GENERIC_READ,0,NULL,

          OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

     if (hFile==INVALID_HANDLE_VALUE) {

          return -1;

     }

     size=GetFileSize(hFile,NULL);

     if (size > 30*1048576) {

          CloseHandle(hFile);

          return -1;

     }

     extra=10000;

 

     memsize=size+extra+1;

     buf=(TCHAR *)malloc(memsize);

     ReadFile(hFile,buf,size,&dwRead,NULL);

     buf[size]=0;

 

     for (pbuf=buf;;) {

          if (extra < 256) {

              tempoff=pbuf-buf;

              memsize+=10000;

              extra+=10000;

              buf=(TCHAR *)realloc(buf,memsize);

              pbuf=buf+tempoff;

          }

 

          p=FindString(pbuf,0,what,TRUE,(FindFlag & AE_FIND_MATCHCASE)!=0);

          if (p == NULL)

              break;

 

          if (FindFlag & AE_FIND_WHOLEWORD) {

              if (!((p==buf || IsDelimiter(*(p-1))) && IsDelimiter(*(p+lstrlen(what))))) {

                   pbuf=p+lstrlen(what);

                   continue;

              }

          }

 

          bReplace=TRUE;

          memmove(p+len2,p+len1,lstrlen(p+len1)+1);

          memcpy(p,to,len2);

 

          TotalFind++;

          GetLineNumAndText(buf,p,Text,line,col);

 

          LI.mask=LVIF_TEXT;

          LI.state=0;

          LI.stateMask=0;

          LI.iSubItem=0;

          LI.iItem=ListView_GetItemCount(hList);

          itoa(TotalFind,Mes,10);

          LI.pszText=Mes;

          idx=ListView_InsertItem(hList,&LI);

 

          if (FindFlag & AE_FIND_SHORTPATH) {

              wsprintf(Mes,"%s(%d,%d)",Path+lstrlen(arFind[2].Get(0))+1,line,col);

          } else {

              wsprintf(Mes,"%s(%d,%d)",Path,line,col);

          }

          ListView_SetItemText(hList,idx,1,Mes);

 

          ListView_SetItemText(hList,idx,2,Text);

          UpdateWindow(hList);

 

          pbuf=p+len2;

          extra -= (len2-len1);

     }

 

     CloseHandle(hFile);

     if (bReplace) {

          hFile=CreateFile(Path,GENERIC_WRITE,0,NULL,

              CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

          if (hFile!=INVALID_HANDLE_VALUE) {

              WriteFile(hFile,buf,lstrlen(buf),&dwRead,NULL);

              CloseHandle(hFile);

          }

     }

 

     free(buf);

     return 0;

}

 

이 함수는 바꾸기를 시작하기 전에 몇 가지 조건 점검을 한다. 우선 폴더나 읽기전용 파일인 경우는 바꾸기를 할 수 없다. 이런 파일을 만나면 그냥 조용히 아무 일도 하지 않고 리턴해버리면 된다. 이미 편집중인 파일도 잠재적으로 데이터를 잃을 위험이 있으므로 바꾸기를 할 수 없다. 아주 위험하므로 메시지박스로 이 사실을 분명히 알려 주도록 했다. 사용자는 이 메시지를 받았을 때 편집하고 있는 파일이 바꾸기 대상이면 파일을 닫은 후 명령을 다시 실행해야 한다.

또한 파일의 크기가 30MB 이상일 때도 엄청난 시스템 리소스를 소모하므로 바꾸기를 거부하도록 하였다. 파일 찾기를 할 때는 파일을 읽기만 하므로 30MB까지만 검색하도록 했지만 바꾸기는 파일을 변경하기 때문에 30MB까지만 변경할 수 없어 아예 대상 파일에서 제외시켜 버렸다. 이런 큰 파일에 대해 바꾸기를 할 때는 두 개의 파일 맵핑 객체를 사용해야 시스템에 무리를 주지 않고 작업을 완료할 수 있다.

문자열을 검색하는 방법은 OnFileFind 함수와 완전히 동일하되 검색된 문자열을 대체 문자열로 바꾸는 추가 동작이 필요하다. 문자열을 대체하는 방법은 아주 간단한데 두 문자열의 길이 차이만큼 뒤쪽 메모리를 이동시켜 대체할 문자열이 들어갈 공간을 만든 후 이 공간에 대체 문자열을 쏙 집어 넣으면 된다.

한 파일에 대해 바꾸기를 완료한 후 이 파일을 다시 디스크에 기록한다. , 무조건 기록할 필요없이 바꾸기를 했을 때만 기록하면 된다. 변경되지 않은 파일을 다시 쓸 필요도 없을 뿐더러 이렇게 하자면 전체적인 바꾸기 효율이 엄청나게 떨어지게 되므로 꼭 필요할 때만 파일 쓰기를 해야 한다.