이 세상에 하나는 남기고 가자

이 세상에 하나는 남기고 가자

세상에 필요한 소스코드 한줄 남기고 가자

PHP 에서 한글 파일명 사용시 pathinfo(), basename() 결과가 맞지 않는 문제 해결

아사마루

PHP에서 path에 대한 정보를 얻기 위해 pathinfo()basename()을 많이 사용한다. 그런데 path 내에 한글과 같이 UTF-8 문자열이 포함될 경우 결과가 이상하게 나올 수 있다. 일단 아래의 소스 코드와 실행 결과를 보자.

<?php
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";

$filepath = '/home/가 나 다.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';

echo '<br />';

$locales = explode("\n", trim(shell_exec('locale -a')));
foreach ($locales as $locale) {
    setlocale(LC_ALL, $locale);
    $info = pathinfo($filepath);
    if (strcmp($info['basename'], '가 나 다.txt') != 0) {
        echo $locale . ' -> ' . $info['basename'] . '<br />';
    }
}

/*
실행 결과

Current Location : C
C -> 나 다.txt

C -> 나 다.txt
POSIX -> 나 다.txt
*/

위 코드는 현재 system 내에서 사용 가능한 모든 locale에 대해 실행 결과를 테스트한 결과다. 결과를 보면 정상적으로 처리되지 않고 첫글자가 잘려나간 잘못된 결과를 반환하는 경우가 있다. 바로 locale이 C 또는 POSIX인 경우다.

서버 환경 구성에 따른 차이가 있겠지만 나의 경우는 nobody 권한으로 실행되는 웹서버 상에서 현재 locale이 C로 나왔다. 따라서 한글 파일명이 포함된 path에 대해서는 pathinfo()를 사용할 수 없다.

이 문제에 대해 검색하다 Make PHP pathinfo() return the correct filename if the filename is UTF-8 라는 글을 찾았다. 여기서 질문을 한 aä.pdf path에 대해서는 현재 시점(PHP 7)에서 locale CPOSIX에서도 문제가 없었다. 이 버그와 관련하여 패치가 되었지만 아직 한글에 대한 버그는 남아 있는 것으로 보인다. 혹시나 해서 아래와 같이 일본어로도 동일한 테스트를 해보니 역시나 문제가 발생한다.

<?php
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";

$filepath = '/home/うながす.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';

echo '<br />';

$locales = explode("\n", trim(shell_exec('locale -a')));
foreach ($locales as $locale) {
    setlocale(LC_ALL, $locale);
    $info = pathinfo($filepath);
    if (strcmp($info['basename'], 'うながす.txt') != 0) {
        echo $locale . ' -> ' . $info['basename'] . '<br />';
    }
}

/*
실행 결과

Current Location : C
C -> .txt

C -> .txt
POSIX -> .txt
ja_JP -> ��ながす.txt
ja_JP.eucjp -> ��ながす.txt
ja_JP.ujis -> ��ながす.txt
japanese -> ��ながす.txt
japanese.euc -> ��ながす.txt
ko_KR -> ��ながす.txt
ko_KR.euckr -> ��ながす.txt
korean -> ��ながす.txt
korean.euc -> ��ながす.txt
mt_MT -> ��ながす.txt
mt_MT.iso88593 -> ��ながす.txt
yi_US -> �ながす.txt
yi_US.cp1255 -> �ながす.txt
zh_CN -> .txt
zh_CN.gb2312 -> .txt
zh_HK -> �がす.txt
zh_HK.big5hkscs -> �がす.txt
zh_SG -> .txt
zh_SG.gb2312 -> .txt
zh_TW -> �がす.txt
zh_TW.big5 -> �がす.txt
zh_TW.euctw -> .txt
*/

결론적으로 이 문제를 해결하기 위해서는 아래와 같이 간단히 해결 가능하다.

<?php
setlocale(LC_ALL,'ko_KR.UTF-8');

$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";

$filepath = '/home/가 나 다.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';

/*
실행 결과

Current Location : ko_KR.UTF-8
ko_KR.UTF-8 -> 가 나 다.txt
*/

setlocale(LC_ALL,'ko_KR.UTF-8')와 같이 UTF-8을 사용하는 locale로 변경하면 한글 파일명을 가진 path에 대해서도 pathinfo는 정상적으로 동작한다(ko_KR임에도 불구하고 일본어도 정상 동작한다).

이 문제에서의 특징은 한가지가 더 있다. 파일명 앞에 영문자와 같이 한글이 아닌 글자가 있다면 이 문제가 발생하지 않는다는 것이다. 아래의 예시를 보자.

<?php
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";

$filepath = '/home/a가 나 다.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';

/*
실행 결과

Current Location : C
C -> a가 나 다.txt
*/

이러한 원리를 이용한 방법으로 Make PHP pathinfo() return the correct filename if the filename is UTF-8에서는 아래와 같이 처리하는 방법을 제시하고 있다.

<?php
function getFilename($path)
{
    // if there's no '/', we're probably dealing with just a filename
    // so just put an 'a' in front of it
    if (strpos($path, '/') === false)
    {
        $path_parts = pathinfo('a'.$path);
    }
    else
    {
        $path= str_replace('/', '/a', $path);
        $path_parts = pathinfo($path);
    }
    return substr($path_parts["filename"],1);
}

하지만 이 방법 보다는 아래의 방법이 조금 더 나아 보인다(urlencode()를 이용하는 방법이다).

<php
function _pathinfo($path, $options = null)
{
    $path = urlencode($path);
    $parts = null === $options ? pathinfo($path) : pathinfo($path, $options);
    foreach ($parts as $field => $value) {
        $parts[$field] = urldecode($value);
    }
    return $parts;
}

결론적으로는 위 두가지 방법 보다는 setlocale(LC_ALL,'ko_KR.UTF-8') 을 이용해서 locale을 변경하는 것을 권장한다. 사용하는 라이브러리 내부 등에서도 pathinfo를 사용하는 경우가 있을 수 있으므로 프로그램이 시작시 locale을 변경하는 것이 좋을 것이다(단, locale이 다른 프로그램 영역에 영향을 줄 수 있는지에 대해서는 검토를 해보는 것이 좋다).

comments powered by Disqus