“ps ax”가“#!”없이 실행중인 bash 스크립트를 찾지 못하는 이유는 무엇입니까? 머리글?


13

이 스크립트를 실행할 때 죽을 때까지 실행되도록 ...

# foo.sh

while true; do sleep 1; done

... 다음을 사용하여 찾을 수 없습니다 ps ax.

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

...하지만 일반적인 " #!"헤더를 스크립트에 추가하면 ...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

... 그리고 같은 ps명령으로 스크립트를 찾을 수있게됩니다 ...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

왜 그렇습니까?
이것은 관련 질문 일 수 있습니다. " #"은 (는) 주석 접두사 일 뿐이라고 생각 하면 " #! /usr/bin/bash"자체는 주석에 지나지 않습니다. 그러나 " #!"은 단순한 의견보다 더 큰 의미를 지니고 있습니까?


어떤 유닉스를 사용하고 있습니까?
Kusalananda

@Kusalananda-Linux linuxbox 3.11.10-301.fc20.x86_64 # 1 SMP Thu 12 월 5 일 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

답변:


13

현재 대화식 쉘이 bash이고 #!라인 없이 스크립트를 bash실행하면 스크립트가 실행됩니다. 프로세스는 ps ax출력에 그대로 표시됩니다 bash.

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

다른 터미널에서 :

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

관련 :


관련 섹션은 bash매뉴얼을 구성합니다 .

파일이 실행 파일 형식이 아니고 파일이 디렉토리가 아니기 때문에이 실행이 실패하면 파일은 쉘 명령을 포함하는 파일 인 shell script로 간주됩니다 . 서브 쉘이 스폰 되어 실행됩니다. 이 서브 쉘은 자체적으로 다시 초기화되므로 , 부모가 기억하는 명령 위치 (SHELL BUILTIN COMMANDS 아래의 해시 참조)가 자식에 의해 유지되는 경우를 제외하고는 스크립트를 처리하기 위해 새 쉘이 호출 된 것처럼 효과가 나타 납니다.

프로그램이로 시작하는 파일 #!인 경우 첫 번째 행의 나머지 부분은 프로그램의 인터프리터를 지정합니다. 쉘은 이 실행 파일 형식 자체를 처리하지 않는 운영 체제 에서 지정된 인터프리터 를 실행합니다. [...]

./foo.sh, 명령 줄에서 실행 하는 경우-줄 foo.sh이없는 경우 #!파일에서 명령을 하위 셸에서 실행하는 것과 같습니다.

$ ( echo "$BASHPID"; while true; do sleep 1; done )

으로 적절한 #!예에 - 라인 포인팅 /bin/bash, 그것은 일을 같이하다

$ /bin/bash foo.sh

내가 따르는 것 같지만 두 번째 경우에도 마찬가지입니다. bash는 두 번째 경우에도 스크립트를 실행합니다. 스크립트가 ps" /usr/bin/bash ./foo.sh" 로 실행되는 것을 볼 때 볼 수 있습니다 . 따라서 첫 번째 경우, bash가 스크립트를 실행하지만 두 번째 경우와 같이 해당 스크립트를 분기 된 bash 실행 파일로 "전달"할 필요는 없습니까? (그렇다면 파이프로 그렙을 찾을 수있을 것이라고 상상할 수 있습니다 ...?)
StoneThrow

@StoneThrow 업데이트 된 답변보기.
Kusalananda

"... 새 프로세스를 얻는 것을 제외하고"-하위 프로세스의 경우 이전 프로세스 $$( echo $BASHPID/ bash -c 'echo $PPID') 를 여전히 제외하고 새 프로세스를 가져옵니다 .
Michael Homer

@MichaelHomer 아, 감사합니다! 업데이트됩니다.
Kusalananda

12

쉘 스크립트가로 시작 #!하면 첫 번째 줄은 쉘에 관한 한 주석입니다. 그러나 처음 두 문자는 시스템의 다른 부분 인 커널에 의미가 있습니다. 두 문자 #!shebang 이라고합니다 . shebang의 역할을 이해하려면 프로그램 실행 방법을 이해해야합니다.

파일에서 프로그램을 실행하려면 커널의 조치가 필요합니다. 이것은 execve시스템 호출의 일부로 수행됩니다 . 커널은 파일 권한을 확인하고, 현재 호출 프로세스에서 실행중인 실행 파일과 관련된 리소스 (메모리 등)를 비우고, 새로운 실행 파일에 대한 리소스를 할당하고, 새로운 프로그램에 제어권을 전달해야합니다. 나는 언급하지 않을 것이다). execve시스템 호출은 현재 실행중인 프로세스의 코드를 대체; fork새로운 프로세스를 만들기 위해 별도의 시스템 호출 이 있습니다.

이를 위해 커널은 실행 파일의 형식을 지원해야합니다. 이 파일에는 커널이 이해하는 방식으로 구성된 머신 코드가 포함되어 있어야합니다. 쉘 스크립트에는 머신 코드가 포함되어 있지 않으므로 이런 방식으로 실행할 수 없습니다.

shebang 메커니즘을 통해 커널은 코드를 다른 프로그램으로 해석하는 작업을 연기 할 수 있습니다. 커널이 실행 파일이로 시작하는 #!것을 발견하면 다음 몇 문자를 읽고 파일 의 첫 번째 줄 (선행 #!및 선택적 공백 제외)을 다른 파일의 경로 (및 여기에서 설명하지 않는 인수)로 해석합니다 . ). 커널이 파일을 실행하라는 명령을 받았을 때 /my/script파일이 행으로 시작하는 것을 알면 #!/some/interpreter커널은 /some/interpreter인수로 실행 합니다 /my/script. 그런 다음 실행해야하는 스크립트 파일 /some/interpreter을 결정 /my/script해야합니다.

파일이 커널이 이해하는 형식의 원시 코드를 포함하지 않고 shebang으로 시작하지 않으면 어떻게 될까요? 그러면 파일이 실행 가능하지 않고 execve시스템 호출이 오류 코드 ENOEXEC(실행 파일 형식 오류) 와 함께 실패합니다 .

이것은 이야기의 끝일 수 있지만 대부분의 쉘은 대체 기능을 구현합니다. 커널이을 반환 ENOEXEC하면, 셸은 파일의 내용을보고 셸 스크립트처럼 보이는지 확인합니다. 쉘이 파일이 쉘 스크립트처럼 보인다고 생각하면 자체적으로 실행합니다. 이를 수행하는 방법에 대한 세부 사항은 쉘에 따라 다릅니다. ps $$스크립트 에 추가하여 어떤 일이 발생했는지 확인할 수 있으며 strace -p1234 -f -eprocess, 1234가 쉘의 PID 인 프로세스를 보면 더 많은 것을 볼 수 있습니다 .

bash에서이 폴백 메커니즘은 호출 fork하지만 호출 하지 않음 으로써 구현됩니다 execve. 자식 bash 프로세스는 자체 내부 상태를 지우고 새 스크립트 파일을 열어 실행합니다. 따라서 스크립트를 실행하는 프로세스는 여전히 원래 bash 코드 이미지와 bash를 원래 호출했을 때 전달 된 원래 명령 행 인수를 사용합니다. ATT ksh는 같은 방식으로 동작합니다.

% bash --norc
bash-4.3$ ./foo.sh 
  PID TTY      STAT   TIME COMMAND
21913 pts/2    S+     0:00 bash --norc

반대로 Dash 는 인수로 전달 된 스크립트의 경로를 ENOEXEC호출 /bin/sh하여 반응합니다 . 즉, 대시에서 shebangless 스크립트를 실행하면 스크립트에가있는 shebang 줄이있는 것처럼 동작 #!/bin/sh합니다. Mksh와 zsh는 같은 방식으로 동작합니다.

% dash
$ ./foo.sh
  PID TTY      STAT   TIME COMMAND
21427 pts/2    S+     0:00 /bin/sh ./foo.sh

훌륭하고 이해하기 쉬운 대답. 한가지 질문 RE : 당신이 설명한 폴백 구현 : 자식 bash이 갈래로, argv[]부모 와 같은 배열에 액세스 할 수 있다고 가정 합니다. "원래 bash를 호출 할 때 전달 된 원래 명령 행 인수"를 알고있는 방법입니다. 그래서 이것이 자식에게 원래 스크립트를 명시 적 인수로 전달하지 않는 이유입니다 (그래서 grep에서 찾을 수없는 이유)-정확합니까?
StoneThrow

1
실제로 커널 shebang 동작을 끌 수 있습니다 ( BINFMT_SCRIPT일반적으로 커널에 정적으로 링크되어 있지만 모듈이 이것을 제어하고 제거 / 모듈화 할 수 있음).하지만 임베디드 시스템을 제외하고는 왜 원하는지 알 수 없습니다. . 이 가능성에 대한 임시 해결책으로, 보상 bash할 구성 플래그 ( HAVE_HASH_BANG_EXEC)가 있습니다!
ErikF 2016 년

2
@StoneThrow 자식 bash가 수정하지 않기 때문에 "원래 명령 줄 인수를 알고"있는 것은 그리 많지 않습니다. 프로그램은 ps명령 줄 인수로 보고서를 수정할 수 있지만 특정 시점까지만 가능합니다. 기존 메모리 버퍼를 수정해야하며이 버퍼를 확대 할 수 없습니다. 따라서 bash argv가 스크립트 이름을 추가 하기 위해 bash를 수정하려고 시도하면 항상 작동하지는 않습니다. 아이에게는 execve시스템 호출 이 없기 때문에 아이는“논쟁을 통과”하지 않습니다 . 계속 실행되는 것과 동일한 bash 프로세스 이미지입니다.
Gilles 'SO- 악의를 멈춰라'

-1

첫 번째 경우, 스크립트는 현재 쉘에서 분기 된 자식에 의해 실행됩니다.

먼저 실행 echo $$한 다음 쉘의 프로세스 ID가 상위 프로세스 ID 인 쉘을 살펴 봐야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.