텍스트처리_03 파일 앞머리의 셔뱅(shebang, "#!/bin/sh" 등)을 추출해서 스크립트에 따라 확장자 붙이기

 
  • 출처 : 유닉스 리눅스 쉘스크립트 예제사전_한빛미디어

명령어: head, mv
키워드: 셔뱅, shebang, 확장자, 첫줄
사용처: 확장자가 없는 스크립트 파일에 자동으로 확장자를 부여하고 싶을 때


실행예제

$ ./shebang.sh script
'script' -> 'script.sh'

$ ./shebang.sh sample1
'sample1' -> 'sample1.pl'

스크립트

#!/bin/sh

# 대상 스크립트 파일이 있는지 확인
if [ ! -f "$1" ]; then
    echo "지정한 파일을 찾지 못했습니다: $1" >&2
    exit 1
fi

# 파일 첫 줄 읽음
headline=$(head -n 1 "$1")

# 파일 첫 줄에 따라 확장자를 판정해서 부여함
case "$headline" in
    */bin/sh|*bash*)
        mv -v "$1" "${1}.sh"
        ;;

    *perl*)
        mv -v "$1" "${1}.pl"
        ;;

    *ruby*)
        mv -v "$1" "${1}.rb"
        ;;

    *)
        echo "Unknown Type: $1"
esac

   

해설

이 스크립트는 스크립트 파일 첫 줄을 읽어서 사용된 언어에 대응하는 확장자를 파일명에 부가합니다.

관례적으로 스크립트 파일은 해당 언어에 대응하는 확장자를 파일명에 사용합니다. 일반적으로 사용하는 확장자는 다음 표와 같습니다.

  • 일반적으로 사용하는 확장자

    확장자 언어
    sh sh, bash
    pl Perl
    rb Ruby
    py Python
    php PHP

하지만 확장자는 어디까지나 관례이므로 셸 스크립트를 포함해서 스크립트 파일은 확장자가 없더라도 동작합니다. 따라서 셸 스크립트를 작성할 때 .sh를 붙이지 않는 사람도 있습니다.

여러분이 관리하는 시스템에도 확장자가 없는 스크립트가 존재할지도 모릅니다. 파일명 만으로도 어떤 언어로 만들어졌는지 알 수 있도록 그런 스크립트의 확장자를 일괄적으로 추가하고 싶을 때가 있을 겁니다. 그럴 때 이 예제를 사용하면 좋습니다.

셸 스크립트의 첫 줄은 반드시 #!로 시작합니다.

#!/bin/sh

이를 셔뱅(Shebang)이라고 합합니다.유닉스에서는 파일을 실행할 때 그 파일이 기계어로 작성된 파일이라면 그대로 실행합니다. 그렇지 않으면 파일 첫 줄을 읽어서 #! 뒤에있는 명령어를 실행합니다. 위 예에서는 /bin/sh가 실행되므로 셸 스크립트로 동작하게 됩니다.

이런 셔뱅의 동작을 이해하기 위해 다음과 같은 실험을 해봅시다.

C언어 등으로 작성된 실행 파일(기계어 파일)과 셸 스크립트 파일은 실행하기 위한 최소 퍼미션이 다릅니다. C 언어 등으로 만든 실행 파일은 읽기 퍼미션이 없어도 실행 권한이 있으면 실행됩니다.  

  • C언어로 작성한 실행 파일은 실행 비트만으로도 실행 가능
    $ chmod 100 a.out
    $ ls -l a.out
    ---x------ 1 user1 user1 6425 Jan 3 20:12 a.out*
    $ ./a.out
    Hello, World.
    

한편, 셸 스크립트 파일은 일반 사용자로 실행할 때는 실행 권한만으로는 실행되지 않고 반드시 읽기 권한도 있어야 합니다.

  • 셸 스크립트로 작성한 실행 파일은 실행 비트만으로는 에러 발생
    $ chmod 100 ptest.sh
    $ ls -l ptest.sh
    ---x------ 1 user1 user1 29 Jan 3 20:14 ptest.sh*
    $ ./ptest.sh
    /bin/sh: ./ptest.sh: Permission denied
    

유닉스에서 셸 스크립트를 실행하면 제일 먼저 첫 줄에 있는 셔뱅을 확인해서 셔뱅에 지정된 명령어가 파일 내용을 읽는 형태로 실행하기 때문입니다. 즉 위 예에서는 $ /bin/sh ./ptest.sh라고 해석되므로 ptest.sh에 읽기 권한이 있어야만 합니다(단, root 사용자는 그대로 실행할 수 있습니다).

예제에서는 이 셔뱅을 봐서 파일 종류를 판별해 확장자를 부여합니다.

우선 1에서는 대상 스크립트 파일을 확인합니다. test 명령어 -f 연산자로 파일이 존재하는지 확인해서 부정 연산자 !를 사용해 파일이 존재하지 않을 때 에러를 표시하고 종료합니다.

이어서 2에서는 head 명령어로 파일 첫 줄을 추출해서 셸 변수 headline에 저장합니다. head 명령어는 파일 앞부분 부터 읽는 명령어로 -n 옵션을 사용하면 지정한 줄 수만 추출할 수 있습니다. 여기서 -n 1을 지정해서 첫 줄만 추출해서 셔뱅만 취득합니다.

3에서 case문을 사용해 파일 종류를 판단합니다. mv 명령어를 이용해서 */bin.sh | *bash*와 일치하면(sh 또는 bash 스크립트) 확장자를 .sh로, *perl*와 일치하면 .pl로, *ruby*와 일치하면 .rb로 파일명을 변경합니다. 명령어 뒤에 *를 붙이는 것은 옵션 지정을 위해 뒤에 스페이스나 옵션이 이어져도 일치하도록 하기 위함입니다.

그리고 확장자 변경 mv 명령어에 -v 옵션을 써서 변경 전후 파일명을 표시합니다.   3case문 마지막에서 *를 이용해 지금까지 일치하지 않은 그 외의 파일은 “Unknown Type:” 이라고 표시하고 파일명을 변경하지 않습니다.

   

주의사항

  • 이 스크립트는 펄, 루비, sh, bash에만 대응합니다. 만약 루비가 /usr/bin/perl/ruby 같은 경로에 설치되었다면 오동작하게 됩니다.

  • 이미 확장자가 설정되어 있는지 확인하지 않으므로 상황에 따라서는 ptest.pl.pl처럼 확장자가 이중으로 지정될 수도 있습니다.

  • 파일 종류를 알 수없을 때는file 명령어가 편리합니다. file 명령어는 다음처럼 파일을 인수로 지정해서 그 파일이 무엇인지 판단해서 표시합니다.

      $ file /usr/bin/startx
      /usr/bin/startx: POSIX shell script text executable
    
      $ file network.dat
      network.dat: tcpdump capture file (little-endian) - version 2.4(Ethernet, capture length 65535)
    

    위 예에서 첫 번째 startx 파일은 셸 스크립트, 두 번째 network.dat 파일은 tcpdump한 패킷 캡쳐 파일입니다. 그리고 file 명령어는 ‘매직 파일’이라는 일종의 ‘파일 사전’이 있어서 이것을 보고 파일 종류를 판단합니다. 자세한 내용은 man magic으로 확인해보기 바랍니다.  

  • head 명령어와 반대로 파일 끝에서부터 읽는 tail 명령어도 있습니다.