bash 파일을 표준으로 리디렉션하는 것은 Linux의 셸(`sh`)과 어떻게 다릅니까?

bash 파일을 표준으로 리디렉션하는 것은 Linux의 셸(`sh`)과 어떻게 다릅니까?

실행 중에 사용자를 전환하는 스크립트를 작성하고 표준 입력으로 파일 리디렉션을 사용하여 실행했습니다 user-switch.sh.

#!/bin/bash

whoami
sudo su -l root
whoami

그리고 그것을 실행하면 bash내가 기대하는 동작이 제공됩니다.

$ bash < user-switch.sh
vagrant
root

그러나 을 사용하여 스크립트를 실행하면 sh다른 결과가 나타납니다.

$ sh < user-switch.sh 
vagrant
vagrant

bash < user-switch.sh와 다른 출력을 제공하는 이유는 무엇입니까 sh < user-switch.sh?

노트:

  • Debian Jessie를 실행하는 두 개의 다른 상자에서 발생합니다.

답변1

가 없는 유사한 스크립트 sudo이지만 유사한 결과는 다음과 같습니다.

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

를 사용하면 bash스크립트의 나머지 부분이 에 대한 입력으로 이동하고 sed를 사용하면 dash쉘이 이를 해석합니다.

실행 strace: dash스크립트 블록(여기서는 8kB, 전체 스크립트를 담기에 충분함)을 읽은 후 다음을 생성합니다 sed.

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

이는 파일 핸들이 파일 끝에 있으며 sed입력이 표시되지 않음을 의미합니다. 나머지 부분은 dash. (스크립트가 블록 크기 8kB보다 길면 나머지 부분은 에서 읽습니다 sed.)

반면 Bash는 마지막 명령의 끝 부분을 찾습니다.

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

입력이 파이프에서 오는 경우 다음과 같습니다.

$ cat script.sh | bash

파이프와 소켓은 검색할 수 없으므로 되감기를 수행할 수 없습니다. 이 경우 Bash는 입력 읽기로 돌아갑니다.한 번에 한 문자씩과독을 피하기 위해. (fd_to_buffered_stream()~에input.c) 각 바이트에 대해 전체 시스템 호출을 수행하는 것은 원칙적으로 그다지 효과적이지 않습니다. 실제로는 쉘이 수행하는 대부분의 작업이 완전히 새로운 프로세스를 생성하는 것과 관련되어 있다는 사실에 비해 읽기가 큰 오버헤드가 될 것이라고 생각하지 않습니다.

비슷한 상황은 이렇습니다.

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

read서브쉘은 첫 번째 줄바꿈만 읽어서 head다음 줄을 볼 수 있도록 해야 합니다 . (이것은 에서도 작동합니다 dash.)


즉, Bash는 스크립트 자체와 스크립트에서 실행되는 명령에 대해 동일한 소스 읽기를 지원하기 위해 추가 길이를 사용합니다. dash그렇지 않습니다. 데비안에 패키지된 zsh, 및 는 ksh93Bash와 함께 사용됩니다.

답변2

쉘은 표준 입력에서 스크립트를 읽고 있습니다. 스크립트 내에서 표준 입력을 읽으려는 명령을 실행합니다. 어떤 입력이 어디로 갈까요?확실하게 말할 수는 없습니다.

쉘이 작동하는 방식은 소스 코드 덩어리를 읽고 구문 분석하고 완전한 명령을 찾으면 명령을 실행한 다음 나머지 청크와 파일의 나머지 부분을 진행하는 것입니다. 청크에 완전한 명령이 포함되어 있지 않으면 (끝에 종료 문자가 있음 - 모든 쉘이 줄 끝까지 읽는다고 생각합니다) 쉘은 다른 청크 등을 읽습니다.

스크립트의 명령이 쉘이 스크립트를 읽는 것과 동일한 파일 설명자에서 읽으려고 하면 명령은 읽은 마지막 청크 뒤에 오는 모든 항목을 찾습니다. 이 위치는 예측할 수 없습니다. 셸이 선택한 청크 크기에 따라 다르며 셸 및 해당 버전뿐만 아니라 시스템 구성, 사용 가능한 메모리 등에 따라 달라질 수 있습니다.

Bash는 명령을 실행하기 전에 스크립트에서 명령 소스 코드의 끝을 찾습니다. 이는 다른 쉘에서는 수행되지 않을 뿐만 아니라 쉘이 일반 파일에서 읽는 경우에만 작동하기 때문에 믿을 수 있는 것이 아닙니다. 쉘이 파이프(예: ssh remote-host.example.com <local-script-file.sh)에서 읽는 경우 읽은 데이터는 읽혀지며 읽지 않을 수 없습니다.

스크립트의 명령에 입력을 전달하려면 일반적으로 다음을 사용하여 명시적으로 전달해야 합니다.여기 문서. (여기 문서는 일반적으로 여러 줄 입력에 가장 편리하지만 어떤 방법이든 가능합니다.) 작성한 코드는 스크립트가 일반 파일에서 셸에 입력으로 전달되는 경우에만 몇 가지 셸에서만 작동합니다. 두 번째 whoami입력이 에 입력으로 전달될 것으로 예상했다면 sudo …대부분의 경우 스크립트가 셸의 표준 입력으로 전달되지 않는다는 점을 염두에 두고 다시 생각해 보세요.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

이번 10년에는 sudo -i root. 달리기는 sudo su과거의 해킹입니다.

관련 정보