
왜냐하면 그들 중 일부가 그렇게 하고 있기 때문입니다.
> echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le > /tmp/hallo
> chmod 755 /tmp/hallo
> dash /tmp/hallo
Hallo, Baby!
> bash /tmp/hallo
/tmp/hallo: /tmp/hallo: cannot execute binary file
> (echo '#'; echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le) > /tmp/hallo
> bash /tmp/hallo
Hallo, Baby!
> mksh /tmp/hallo
Hallo, Baby!
> cat -v /tmp/hallo
#
e^@c^@h^@o^@ ^@H^@a^@l^@l^@o^@,^@ ^@B^@a^@b^@y^@!^@
^@
실제로 이것은 호환성 문제입니까?필수의기준으로? 꽤 위험하고 예상치 못한 것처럼 보이기 때문입니다.
답변1
에 따라POSIX,
입력 파일은 줄 길이가 무제한이라는 점을 제외하고는 텍스트 파일이어야 합니다.¹
입력에 NUL 문자²텍스트가 아닌 것으로 만들어라, 따라서 POSIX에 관한 한 동작이 지정되지 않으므로 sh
구현은 원하는 것은 무엇이든 할 수 있습니다(그리고 POSIX 호환스크립트NUL을 포함해서는 안 됩니다.)
처음 몇 바이트를 0으로 스캔하고 실수로 스크립트가 아닌 파일을 실행하려고 시도했다는 가정하에 스크립트 실행을 거부하는 일부 쉘이 있습니다.
exec*p()
함수, env
명령 sh
, find -exec
...이 다음과 같기 때문에 유용합니다.필수의시스템이 ENOEXEC와 함께 반환되는 경우 명령을 해석하기 위해 쉘을 호출합니다 execve()
. 따라서 잘못된 아키텍처에 대해 명령을 실행하려고 하면바이너리를 실행하지 않습니다쉘 스크립트로 이해하려고 시도하는 것보다 쉘에서 파일 오류가 발생했습니다.
이는 POSIX에서 허용됩니다.
실행 파일이 텍스트 파일이 아닌 경우 쉘은 이 명령 실행을 우회할 수 있습니다.
표준의 다음 개정판에서는로 변경됩니다:
쉘은 실행될 파일이 스크립트일 수 있는지 확인하기 위해 경험적 검사를 적용할 수 있으며 파일이 스크립트일 수 없다고 판단되면 이 명령 실행을 우회할 수 있습니다. 이 경우 오류 메시지를 작성하고 종료 상태 126을 반환해야 합니다.
참고: 스크립트가 될 수 없는 파일을 거부하는 일반적인 경험적 방법은 고정 길이 내에서 <newline> 바이트 앞에 NUL 바이트를 찾는 것입니다. 파일의 접두사. sh는 줄 길이가 무제한인 입력 파일을 허용해야 하므로 경험적 검사는 줄 길이를 기반으로 할 수 없습니다.
이러한 동작은 셸 헤더와 바이너리 데이터를 포함하는 셸 자체 추출 가능 아카이브를 방해할 수 있습니다1.
쉘 zsh
은 입력에서 NUL을 지원하지만 NUL은 의 인수로 전달될 수 없으므로 의 execve()
인수 또는 이름에서만 사용할 수 있습니다.내장명령 또는 기능:
$ printf '\0() echo zero; \0\necho \0\n' | zsh | hd
00000000 7a 65 72 6f 0a 00 0a |zero...|
00000007
(여기서 NUL을 이름으로 사용하여 함수를 정의하고 호출하고 NUL 문자를 내장 echo
명령에 인수로 전달합니다).
일부는 이를 제거하는 것도 현명한 일입니다. NUL
s는 때때로 패딩으로 사용됩니다. 예를 들어 터미널에서는 무시됩니다(복잡한 제어 시퀀스(예: 캐리지 리턴(문자 그대로))를 처리할 시간을 주기 위해 터미널로 전송되는 경우도 있습니다. 파일의 구멍은 NUL 등으로 채워지는 것처럼 나타납니다.
텍스트가 아닌 것은 NUL 바이트로 제한되지 않습니다. 또한 로케일에서 유효한 문자를 형성하지 않는 바이트 시퀀스이기도 합니다. 예를 들어, 0xc1 바이트 값은 UTF-8로 인코딩된 텍스트에 나타날 수 없습니다. 따라서 문자 인코딩으로 UTF-8을 사용하는 로케일에서 이러한 바이트를 포함하는 파일은 유효한 텍스트 파일이 아니므로 유효한 sh
스크립트가 아닙니다.
실제로 yash
는 잘못된 입력에 대해 불평하는 내가 아는 유일한 쉘입니다.
¹ 표준의 다음 개정판에서는그것은 바뀔 것이다에게
입력 파일은 모든 유형이 될 수 있지만 쉘 문법(XREF - XSH 2.10.2 쉘 문법 규칙)에 따라 구문 분석하려는 파일의 초기 부분은 문자로 구성되어야 하며 NUL 문자를 포함해서는 안 됩니다. 쉘은 행 길이 제한을 적용하지 않습니다.
자체 추출 아카이브를 설명하기 위해 나머지 부분에 NUL이 포함되어 있더라도 NUL 바이트 없이 구문적으로 유효한 섹션으로 시작하는 입력을 지원하도록 쉘을 명시적으로 요구합니다.
² 및 문자는 로케일의 문자 인코딩에 따라 디코딩되도록 되어 있으며( 출력 참조 locale charmap
), POSIX 시스템에서는 NUL 문자(인코딩이 항상 바이트 0임)는 인코딩에 바이트 0이 포함된 유일한 문자입니다. 즉, UTF-16은 POSIX 로케일에서 사용할 수 있는 문자 인코딩에 포함되지 않습니다.
3 그러나 스크립트 내에서 로캘이 변경되는지(예: LANG
/ LC_CTYPE
/ LC_ALL
/ LOCPATH
변수가 할당될 때), 입력을 해석하는 셸에 변경 사항이 적용되는 시점에 대한 문제가 있습니다.
답변2
이 동작의 이유는 약간 복잡합니다.
첫째, 최신 셸에는 잠재적인 바이너리 파일(널 바이트 포함)에 대한 검사가 포함되어 있지만 이 검사는 파일의 첫 번째 줄만 확인합니다. 이것이 첫 번째 줄의 '#'이 동작을 변경하는 이유입니다. 역사적인 Bourne Shell에는 바이너리 검사가 없으며 언급한 방식으로 작동하는 데 '#'도 필요하지 않습니다.
그런 다음 멀티 바이트 문자를 지원하기 위해 Bourne Shell에서 사용하는 특정 방법은 null 바이트에 대해 문자 길이 0을 반환하고 이로 인해 루프가 다음 문자를 재시도하기 때문에 mbtowc()
모든 null 바이트를 건너뜁니다 .mbtowc()
Bourne Shell은 1988년경에 이러한 종류의 코드를 도입했으며 다른 쉘도 이 동작을 복사했을 수 있습니다.