본문 바로가기
Linux/Ubuntu

Linux Shell #! /bin/bash #! /bin/sh 에 대한 고찰

by ahsung 2020. 6. 1.

 

보통 쉘스크립트에서 가장 첫줄에 오는

#! /bin/<shell>

<shell>자리에 있는 shell 프로그램으로 밑 라인의 명령어를 실행한다고 일반적으로 알고 있습니다.

 

그런데 이 원리가 사실은 exec계열의 SystemCall을 사용한다는 것을 알게되었고, 어떤 원리인지 고찰해보는 포스팅입니다.

 

exec가 #!를 어떻게 실행하는지, 그리고 #!가 쉘스크립트를 어떻게 실행하게 만드는지 포스팅이기 때문에

exec 시스템콜을 이미 알고있다고 가정하고 진행하겠습니다.

 

$ ./excuteFile

프롬프트에 위 커맨드라인 명령어를 치게되면, "excuteFile"이 실행되게 됩니다.

 

좀 더 구체적으로는 현재 실행되고 있는 프롬프트(쉘)가 fork를 통해 child process를 만들고, 

exec를 호출하여 excuteFile 프로그램으로 프로세스 내용을 바꾸며, 자식 프로세스로서 실행하게 됩니다.

 

이때 신기한점은 exec계열의 시스템콜은 보통 매개변수로 실행할 파일을 바이너리로 파일로 해석하지만,

#! 를 만날경우에는 그 다음 등장하는 절대경로에 있는 파일을 실행합니다!!

 

 

a.sh의 내용

 

etest의 코드 내용

 

이런 스크립트 파일을 작성한다면  실행 결과는 어떻게 될까요??

 

 

일단 첫번째!! sh를 통해서 인자로 a.sh를 넘겨 실행해보았습니다.

#!로 시작한 라인은 무시하고 그 다음줄 라인의 명령어들이 그대로 실행됬네요

#!에 무엇을 쓰든 상관없이 예상한대로 sh가 실행되었습니다.

 

 

그다음 두번째!!

$ chmod 755 a.sh

a.sh가 스스로 실행할 수 있게 execute 권한을 줍시다.

 

 

 

신기한 결과가 나왔습니다. 일단 a.sh를 실행했는데

argv[0]이 etest

argv[1]이 ./a.sh

로 나왔네요!!  argv[0]는 프로세스명을 따로 지정해주지 않는다면 실행된 파일명이 나오게 되는데

분명 저는 "a.sh"를 실행했는데 실행된 파일은 #!로 지정했던  /home/asung/etest가 나왔습니다!!

그리고 argv[1]으로는 오히려 제가 실행한 file인 ./a.sh가 나왔네요!!

 

그리고 혹시 a.sh 스크립트의 나머지 내용인

ls

ls -l

ls -a

는 어떻게 프로세스에서 처리할까 궁금해서 표준입출력을 넣어봤는데, 표준입출력으로도 전달이 안되서

제가 직접 입력을 하고 끝냈습니다.

프로세스의 인자로도 넘어가지 않고, 그냥 무시된 듯 보입니다.

 

이제 위 첫번째와 두번째 실행결과로 쉘스크립트의 !#와 shell 프로그램들의 원리가 조금은 감이 오시나요??

 

아직 이해가 오지 않는다면 몇가지 오류멘트를 통해서 더 짐작해볼 수 있습니다.

 

 

네 감이 오시나요?? Shell 프로그램들은 argv[1]로 들어온 인자들을 open하고 있습니다!!

 

자 이제 정리해보겠습니다.

 

리눅스가 프롬프트 명령을 통해 프로세스를 실행하는 방법입니다.

1. 프롬프트에서 file명을 치게되면 fork후 exec계열 시스템콜을 통해 file을 실행시킨다.

2. exec계열의 시스템콜은 #!를 첫줄에 만나게되면 binary로 해석하지 않고 #! 다음에 나오는 file을 실행시킨다.

3. 이때 원래 처음 실행시켰던 file명을 argv인자로 넘겨준다.

 

 

쉘이 작동하는 방식입니다.

1. 쉘 프로그램에 인자를 넘겨주어 실행하게되면, 인자에 해당하는 file을 오픈한다.

2. 오픈한 파일은 text 파일이며, shell은 #!에 해당하는 줄은 무시하고 나머지 줄을 인터프리터하여 실행한다.

 

 

#!는 exec계열의 시스템콜에게 이 줄에 해당하는 file을 실행하라는 flag로서 꼭 쉘프로그램만 쓸수 있는 문법은 아니였습니다.

하지만 exec계열의 시스템콜도 #! 다음줄에 나오는 file만 실행하고 원래 file을 프로세스 인자로 넘겨줄뿐 그 뒤 나오는 text는 무시했습니다. 그러므로 etest의 경우 인자를 넘겨받은거 말고는 딲히 직접 ./etest를 실행한 것과 차이가 없었습니다.

 

즉 현 a.sh는 #! /home/asung/etest 가 첫줄이므로

a.sh를 실행해봤자 etest만 실행하고 argv[1]로 a.sh가 넘어간거 말고는 아무일도 없었습니다.

 

하지만 위 실험에서

$ sh ./a.sh

이 명령의 경우 확실하게 sh는 a.sh를 open하고서 안의 내용 #!부분을 무시하고 나머지 스크립트를 읽어냈습니다.

 

즉 a.sh에  #! /home/asung/etest 대신  #! /bin/sh 로 수정한후 a.sh를 실행한다면

같은 원리로 sh를 실행하게되고, 인자로 a.sh를 넘겨줄 것 입니다.

 

$ sh ./a.sh

네 바로 이 명령과 똑같은 일을 하게 되는 것입니다.

 

a.sh를 수정

 

수정한 a.sh를 실행

 

#! 로 작성한 쉘로 쉘 스크립트를 직접 실행하게 되는 원리였습니다!!

 

 

댓글