Linux generic

psycho 2012. 10. 8. 13:36

 Windows와는 다르게, 많은 Unix 플랫폼에서는 파일 이름에 문자 제한이 따로 없다. 이는 Linux도 마찬가지로, 대부분의 파일시스템에서 Null 문자(0x00)를 제외한 어떤 문자든 파일 이름으로 사용할 수 있다. 그런데, 이 때문에 가끔씩 골치아픈 상황에 처하기도 한다. 특히 Script 상에서 공백문자를 포함한 파일들을 반복문으로 처리할 때 문제가 되기 쉽다.


 BASH에는 반복문을 포함하여 여러 입력을 받아 변수에 할당해주는 기능이 있다. 그런데, C에서의 배열과는 다르게 이 녀석들은 기본적으로 항목을 구분하는 데 사용하는 구분자가 Null이 아니라 공백문자1이다. 덕분에 중간에 공백문자가 포함된 파일 이름이 입력으로 들어가면 그것이 한 덩어리로 해석되는 것이 아니라 공백문자를 기준으로 토막이 나 버린다. 당연히 제대로 동작할 리가 없다.


 BASH에는 IFS라는 변수가 있다. 이 변수에 등록되어 있는 문자만이 구분자의 역할을 하게 되며, 당연히 이것을 적절하게 바꿔주면 파일 이름이 토막나는 것을 방지할 수 있다. 대부분의 경우 이것을 개행문자 정도로만 바꿔줘도 충분하지만, 이 역시 문제가 발생할 소지가 있다. 명령이나 함수에 여러 인자를 넘길 때도 이 변수의 내용을 참조하여 해석하기 때문에, 당연히 구분되어 인식되어야 할 내용이 한 덩어리로 입력되어 오동작하는 경우가 발생한다. 그렇다고 인자 사이에 공백 대신 개행문자 같은 걸 집어넣을 실력이 되는 독자라면 이런 글을 읽지도 않을 것이다. 덧붙여, IFS에 Null 문자를 집어넣는다는 발상이 얼마나 어리석은 것인지는 굳이 말하지 않겠다.


그러나, 구글신(?)님에게 열심히 문의를 해본 결과, 해결책이 있었다. 물론 대부분이 IFS 변수를 이용하는 방법만을 제시했지만, 사용하기에 따라 완벽한 방법이 될 수 있는 해결책을 제시해놓은 곳2이 있었다. 바로 find와 while, 그리고 read를 조합한 것인데, 이는 파일 이름으로 허용되지 않는(실질적으로 문자열 구분의 표준인) Null 문자를 구분자로 하는 방법이다. 다음은 그 예제이다.


find -name '*.sh' -print0 | while read -d $'\0' file

do

    cat "$file"

done


구분자를 고려하지 않고 일반적으로 사용되는 for문에 대응되는 형태는 다음과 같다.

 

for file in `find -name '*.sh'`

do

    cat "$file"

done


 여기서 read -d $'\0'이 핵심이다. $'' 형식으로 개행 문자 등 일반적으로 입력 불가능한 제어 문자들을 집어넣을 수 있으며, read의 -d 옵션은 해석에 사용할 구분자를 설정하는 것이다. 따라서, Null 문자가 나올 때까지 file 변수에 입력을 받아들이고 끊어주게 되며, while에 의해 더 이상 입력이 없을 때까지 반복된다. file 변수를 참조할 때 어째서 ""를 반드시 붙여야 하는지에 대해서는 굳이 말하지 않아도 될 것이다.

※ 본 예제같이 간단한 경우에는 find 명령의 -exec option을 이용할 수 있다. 물론 -exec를 사용할 때는 find 자체에서 파일의 원래 이름대로 처리를 해 주기 때문에 구분자를 신경쓰지 않아도 된다.


※2012-10-09 12:32 수정사항 : 예제에  빠진 -print0 추가. 이 옵션은 find가 파일 이름을 출력할 때 개행문자(\n) 대신 Null 문자(\0)를 사용하도록 한다.

※2012-10-14 23:44 수정사항 : 주석 수정, 설명 추가.

각주 1

여기서의 공백문자는 기본적으로 Space, Tab, 개행문자 등 ASCII 코드 범위 내에서 '공백'의 형태로 출력되는 모든 문자를 가리킨다. 자세한 것은 BASH 문서를 참조하라.

각주 2

http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html -- 이곳에 가면 여기서 소개된 방법을 포함하여 IFS 변수의 활용에 대해 많은 것을 배울 수 있다.

  1. 여기서의 공백문자는 기본적으로 Space, Tab, 개행문자 등 ASCII 코드 범위 내에서 '공백'의 형태로 출력되는 모든 문자를 가리킨다. 자세한 것은 BASH 문서를 참조하라. [본문으로]
  2. http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html -- 이곳에 가면 여기서 소개된 방법을 포함하여 IFS 변수의 활용에 대해 많은 것을 배울 수 있다. [본문으로]
find와 함께 IFS에서 Null 문자를 사용하면 되지 않나요?
그렇게 간단하게 해결되었다면 이런 글 안 썼을 겁니다. 본문에 'IFS에 Null을 집어넣는다는 발상이 얼마나 어리석은 것인지는 굳이 말하지 않겠다'라고 적어뒀는데, 이는 IFS라는 변수가 단지 여러 개의 이어진 입력을 토큰으로 분리하기 위한 문자를 설정하기 위해 있는 것이 아니라 Shell 내부적으로 입력된 명령행에서 단어(대표적으로 명령어 이름과 인자 등)를 구분하기 위해 사용하는 문자들을 설정하기 위해 있는 변수이기 때문입니다. 따라서 섣불리 잘못 손댔다간 명령 해석에 문제가 생길 수 있습니다. 실험이 동반되지 않으면 이해하기가 쉽지 않기 때문에 일부러 그렇게 적어둔 겁니다. 제 경우도 완전히 이해했다고는 생각하지 않기 때문에 어설프게 이야기했다가 정확하지 않은 정보를 전달하게 된다면 그것도 골치아프거든요.