삽질하는플머

Synology NAS DS110j: SSH 접속시 한글 파일 제대로 표시하기.

이런저런잡다구리/Synology-nas

DS110j 에 SSH로 접속해보면, 한글로 된 파일명이 모두 '?' 로 출력되어 참 보기 숭하다. 




Synology+nas+locale 로 검색, DS-710+ 에 로케일 설정 방법이 설명된 게시물을 건졌다. 구글 만세!


http://www.jlancer.net/board/article_view.jsp?article_no=1591&board_no=37&table_cd=EPAR01&table_no=01

http://www.jlancer.net/board/article_view.jsp?article_no=1592&board_no=37&table_cd=EPAR01&table_no=01




위 글을 참조해 DS110j 에 로케일을 설정해보자. 먼저 Putty의 코드페이지는 UTF-8로 설정. 




Synology NAS GPL Source 페이지에 가서 자신의 DSM 버전에 맞는 툴체인을 내려받는다. 




위 경우는 3.1이므로 "Home/DSM 3.1/Marvell 88F628x Linux 2.6.32" 내의 gcc421_glibc25_88f628x.tgz 파일을 내려받아 압축을 푼다. 


# tar -xzf gcc421_glibc25_88f628x.tgz



이 툴체인은 x86 리눅스에서 NAS용 프로그램을 빌드할 수 있는 크로스 컴파일 환경이다. 

다음 기회에 해부해보도록 하고, 일단 arm_linux 용 localelocaledef 실행파일  및 i18n 디렉토리를 시스템으로 복사한다. 


# cp arm-none-linux-gnueabi/arm-none-linux-gnueabi/libc/usr/bin/locale* /bin/

# cp -R arm-none-linux-gnueabi/arm-none-linux-gnueabi/libc/usr/share/i18n/ /usr/share/



locale 명령을 실행시켜보자. 

# locale -a
C
POSIX


localedef 로 새 로케일을 추가하고 잘 되었는지 및 확인.

# localedef -c -f UTF-8 -i en_US en_US.UTF-8
# locale -a
C
en_US.utf8 
POSIX    


재부팅 후 다음 명령을 실행시키고 

# export LANG=en_US.utf8
# export LC_ALL=en_US.utf8


한글이름 파일을 만들어보자. 제대로 동작하면 성공. 

# touch 한글한글.txt
# ls -l



bash 셸을 설치했다면 위의 LANG, LC_ALL 설정을 /opt/etc/profile 파일에 넣어주면 재부팅해도 제대로 동작한다. 




유니코드 커맨드 라인 실행하고 결과값 받아오기

탐구생활/Delphi
소위 이야기하는 도스커맨드 실행하고 결과 받아오기. 김성동님의 코드를 살짝 바꿔 사용. 
커맨드라인 파라미터를 유니코드로 넘길 수 있게 하고, 중간 중간에 이루어지는 출력을 받아서 처리할 수 있는 콜백버전을 새로 만듦. 

다국어 INI를 읽어 UTF-8 로 뿌려주는 간단한  유틸리티를 만들었는데, 이 유틸리티를 호출하려면 INI 파일의 이름을 유니코드 파라미터로 넘겨야 하기 때문에 필요해짐. 


// 유니코드 커맨드라인 실행하고 출력값 얻어오자. .
// 주의! 콘솔로 출력되는 문자열은 안시문자열이다. 
// GetConsoleOutputCP(), SetConsoleOutputCP() API 참조. 

type
  TRunCmdCallback = procedure (const aRetStr: AnsiString; aParam: Pointer);

// 실행결과 문자열을 RunCmdCallback으로 조금씩 받아 조립할 때 사용.
// 콜백의 호출 주기는 상수 BUFFER_SIZE 의 값과 WaitForSingleObject() 의 두번째 인자인 시간값으로 결정된다. 
function RunWideCmd(Command: WideString; RunCmdCallback: TRunCmdCallback; Param: Pointer = nil): DWORD; overload;
const
  BUFFER_SIZE = 512; // 버퍼 크기가 작으면 RunCmdCallback이 좀 더 자주 일어남
var
  hReadPipe : THandle;
  hWritePipe : THandle;
  SI : TStartUpInfo;
  PI : TProcessInformation;
  SA : TSecurityAttributes;
  SD : TSecurityDescriptor;
  BytesRead : DWORD;
  Avail, wrResult : DWORD;
  RetStr: AnsiString;
begin
  { Dos Application }
  if Win32Platform = VER_PLATFORM_WIN32_NT then
  begin
    InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(@SD, True, nil, False);
    SA.nLength := SizeOf(SA);
    SA.lpSecurityDescriptor := @SD;
    SA.bInheritHandle := True;
    CreatePipe(hReadPipe, hWritePipe, @SA, BUFFER_SIZE);
  end else
    CreatePipe(hReadPipe, hWritePipe, nil, BUFFER_SIZE);

  try
    { STARTUPINFO를 설정한다. }
    FillChar(SI, SizeOf(SI), 0);
    SI.cb := SizeOf(TStartUpInfo);
    SI.wShowWindow := SW_HIDE;
    SI.dwFlags := STARTF_USESHOWWINDOW;
    { STARTF_USESTDHANDLES 를 빼면 PIPE로 입출력이 Redirect 되지 않는다. }
    SI.dwFlags := SI.dwFlags or STARTF_USESTDHANDLES;
    SI.hStdOutput := hWritePipe;
    SI.hStdError  := hWritePipe;
    { Process를 생성한다. }
    if CreateProcessW(nil, PWChar(Command), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, SI, PI) then
    begin
      Result := 0;
      while Result = 0 do
      begin
      // 대기시간이 길면 반응이 느리다. 짧으면 콜백이 그만큼 자주 일어남. 적당히 조절해 사용. 
        wrResult := WaitForSingleObject(PI.hProcess, 5); 
        { Pipe에 출력된 내용이 있는 지 조사한다. }
        if PeekNamedPipe(hReadPipe, nil, 0, nil, @Avail, nil) then
        begin
          if Avail > 0 then
          begin
            { Redirect된 화면 내용을 얻어낸다. }
            SetLength(RetStr, Integer(Avail));
            ReadFile(hReadPipe, PAnsiChar(RetStr)[0], Avail, BytesRead, nil);

            if Assigned(RunCmdCallback) then RunCmdCallback(RetStr, Param);
          end;
        end;
        { WAIT_TIMEOUT이 아니면 루프를 마친다. }
        if wrResult <> WAIT_TIMEOUT then Result := 1;
      end;
      { 실행이 끝났다.. }
      GetExitCodeProcess(PI.hProcess, Result);
      CloseHandle(PI.hProcess);
      CloseHandle(PI.hThread);
    end;
  finally
    CloseHandle(hReadPipe);
    CloseHandle(hWritePipe);
  end;
end;

// 실행결과 문자열 전체를 한꺼번에 리턴받을 때 사용.
function RunWideCmd(Command : WideString; var RetStr: AnsiString): DWORD; overload;
const
  BUFFER_SIZE = 512;
var
  hReadPipe : THandle;
  hWritePipe : THandle;
  SI : TStartUpInfo;
  PI : TProcessInformation;
  SA : TSecurityAttributes;
  SD : TSecurityDescriptor;
  BytesRead : DWORD;
  Avail, wrResult : DWORD;

  LastSize: Integer;
begin
  { Dos Application }
  if Win32Platform = VER_PLATFORM_WIN32_NT then
  begin
    InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(@SD, True, nil, False);
    SA.nLength := SizeOf(SA);
    SA.lpSecurityDescriptor := @SD;
    SA.bInheritHandle := True;
    CreatePipe(hReadPipe, hWritePipe, @SA, BUFFER_SIZE);
  end else
    CreatePipe(hReadPipe, hWritePipe, nil, BUFFER_SIZE);

  try
    { STARTUPINFO를 설정한다. }
    FillChar(SI, SizeOf(SI), 0);
    SI.cb := SizeOf(TStartUpInfo);
    SI.wShowWindow := SW_HIDE;
    SI.dwFlags := STARTF_USESHOWWINDOW;
    { STARTF_USESTDHANDLES 를 빼면 PIPE로 입출력이 Redirect 되지 않는다. }
    SI.dwFlags := SI.dwFlags or STARTF_USESTDHANDLES;
    SI.hStdOutput := hWritePipe;
    SI.hStdError  := hWritePipe;
    { Process를 생성한다. }
    if CreateProcessW(nil, PWideChar(Command), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, SI, PI) then
    begin
      Result := 0;
      while Result = 0 do
      begin
        wrResult := WaitForSingleObject(PI.hProcess, 5);
        { Pipe에 출력된 내용이 있는 지 조사한다. }
        if PeekNamedPipe(hReadPipe, nil, 0, nil, @Avail, nil) then
        begin
          if Avail > 0 then
          begin
            { Redirect된 화면 내용을 얻어낸다. }
            LastSize := Length(RetStr);
            SetLength(RetStr, LastSize + Integer(Avail));
            ReadFile(hReadPipe, PChar(RetStr)[LastSize], Avail, BytesRead, nil);
          end;
        end;
        { WAIT_TIMEOUT이 아니면 루프를 마친다. }
        if wrResult <> WAIT_TIMEOUT then Result := 1;
      end;
      { 실행이 끝났다.. }
      GetExitCodeProcess(PI.hProcess, Result);
      CloseHandle(PI.hProcess);
      CloseHandle(PI.hThread);
    end;
  finally
    CloseHandle(hReadPipe);
    CloseHandle(hWritePipe);
  end;
end;


두번째 함수는 사용법 간단. 대충 다음과 같이. 

procedure TForm1.Button5Click(Sender: TObject);
var
  RetStr: AnsiString;
begin
  RunWideCmd('cmd /c dir c:\', RetStr);
  ListBox1.Items.Text := RetStr;
  ListBox1.ItemIndex := ListBox1.Count -1;
end;


첫번째 함수는 실제 콘솔창처럼 변화하는 상태를 표현할 때 써먹기 위해 만들었는데 사용은 약간 귀찮다. 
(콜백의 호출 주기는 RunWideCmd 함수 내의 상수 BUFFER_SIZE 의 값과 WaitForSingleObject() 로 전달되는 시간값으로 결정된다.) 

var
  ugRetStr: AnsiString;

procedure RunCmdCallback (const aRetStr: AnsiString; aParam: Pointer);
begin
  ugRetStr := ugRetStr + aRetStr;

  TListBox(aParam).Items.BeginUpdate;
  TListBox(aParam).Items.Text := ugRetStr;
  TListBox(aParam).ItemIndex := TListBox(aParam).Count -1;
  TListBox(aParam).Items.EndUpdate;
  Application.ProcessMessages; 
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  ugRetStr := '';
  RunWideCmd('cmd /c dir c:\', RunCmdCallback, ListBox1);
end;


덤으로... 이렇게 호출당한 입장에서 유니코드 파라미터를 참고하는 방법은 다음과 같다. 

function GetWideParamStr(P: PWideChar; var Param: WideString): PWideChar;
var
  i, Len: Integer;
  Start, S, Q: PWideChar;
begin
  while True do
  begin
    while (P[0] <> #0) and (P[0] <= ' ') do
      P := CharNextW(P);
    if (P[0] = '"') and (P[1] = '"') then Inc(P, 2) else Break;
  end;
  Len := 0;
  Start := P;
  while P[0] > ' ' do
  begin
    if P[0] = '"' then
    begin
      P := CharNextW(P);
      while (P[0] <> #0) and (P[0] <> '"') do
      begin
        Q := CharNextW(P);
        Inc(Len, Q - P);
        P := Q;
      end;
      if P[0] <> #0 then
        P := CharNextW(P);
    end else
    begin
      Q := CharNextW(P);
      Inc(Len, Q - P);
      P := Q;
    end;
  end;

  SetLength(Param, Len);

  P := Start;
  S := Pointer(Param);
  i := 0;
  while P[0] > ' ' do
  begin
    if P[0] = '"' then
    begin
      P := CharNextW(P);
      while (P[0] <> #0) and (P[0] <> '"') do
      begin
        Q := CharNextW(P);
        while P < Q do
        begin
          S[i] := P^;
          Inc(P);
          Inc(i);
        end;
      end;
      if P[0] <> #0 then P := CharNextW(P);
    end else
    begin
      Q := CharNextW(P);
      while P < Q do
      begin
        S[i] := P^;
        Inc(P);
        Inc(i);
      end;
    end;
  end;

  Result := P;
end;

function WideParamStr(Index: Integer): WideString;
var
  P: PWideChar;
begin
  Result := '';
  if Index = 0 then
  begin
    SetLength(Result, MAX_PATH);
    SetLength(Result, GetModuleFileNameW(0, PWideChar(Result), MAX_PATH));
  end else
  begin
    P := GetCommandLineW;
    while True do
    begin
      P := GetWideParamStr(P, Result);
      if (Index = 0) or (Result = '') then Break;
      Dec(Index);
    end;
  end;
end;


위에 정의된 WideParamStr() 함수를 ParamStr() 함수 대신에 사용하면 됨. 


다국어를 지원하는 TWideIniFile

탐구생활/Delphi
내가 만들고자 하는 물건은 이런 것.



어떤 아이디어가 떠오를 때, 동시에 같은 생각을 하는 사람이 100명이고 그 중 10명은 이미 만들고 있고, 한명은 벌써 완성했다는 옛 이야기가 있다. 그것이 진리임을 다시 깨닫게 되는 링크.

http://simplyplay.googlecode.com/svn-history/r59/trunk/WideIniFiles.pas

외부 저장만 UTF-8 로 바꿔주면 아주아주 쓸만할 듯.

하지만... 이미 만들었는걸... 앞으로는 찾아보고 만들자. 
윈도에서 저장된 UTF-8 문서의 경우 앞부분에 BOM이 따라붙으므로 이 부분은 건너뛰도록 하는 코드도 추가. 
저장시에도 BOM을 붙여준다.