Perl: system() 호출의 쉘 기호 해석이 변경되었습니까?

Perl: system() 호출의 쉘 기호 해석이 변경되었습니까?

오늘 저는 아마도 최근에 Perl에서 쉘 명령을 실행하는 방식에 몇 가지 변경 사항이 있음을 발견했습니다. 무엇이 바뀌었는지 설명해줄 수 있는 사람이 있나요? 나 자신도 답을 찾을 수 없고, 슬프게도 우리는 이 변화에 대해 가장 어려운 방법으로 배웠습니다. 일부 신규 사용자는 새로운 홈 디렉토리에서 흥미로운 콘텐츠를 얻었습니다...

간단한 명령/스크립트를 실행하고 있습니다.

#!/usr/bin/perl -w

system("ls -R /etc/skel/.[^.]*");

Debian 11에서 perl v5.32.1출력은 다음과 같습니다 /etc/skel(예상대로).

.  ..  .bash_logout  .bashrc  .face  .face.icon  .kshrc  .profile

하지만 데비안 12에서는 perl v5.36.0와일드카드를 무시하고 ^전체를 읽습니다./etc..무시당하지 않는다는 뜻이다.

^대체 기호로 변경했을 때 !: system("ls -R /etc/skel/.[!.]*");예상대로 다시 작동했습니다.

문제는 Perl의 기호 및 호출 처리 !에 무슨 일이 일어났는가 하는 것 입니다.^system()

편집자: 2023년 9월 29일 19:50

두 서버 모두에서 몇 가지 테스트를 했는데 뭔가 변경된 것 같나요 dash?

Debian 11: ( dash Version: 0.5.11+git20200708+dd9ef66-5대시에 플래그가 표시되지 않으므로 --versionAPT에서 가져온 것입니다.)

root@s:~# dash -c 'ls -R /etc/skel/.[^.]*'
/etc/skel/.bash_logout  /etc/skel/.bashrc  /etc/skel/.forward+spam  /etc/skel/.kshrc  /etc/skel/.profile
root@s:~# dash -c 'ls -R /etc/skel/.[!.]*'
/etc/skel/.bash_logout  /etc/skel/.bashrc  /etc/skel/.forward+spam  /etc/skel/.kshrc  /etc/skel/.profile

데비안 12:dash Version: 0.5.12-2

[students] ~ ➽ $ dash -c 'ls -R /etc/skel/.[^.]*' | more
/etc/skel/..:
a2ps.cfg
a2ps-site.cfg
adduser.conf
adjtime
aliases
aliases.db
alsa
alternatives

[students] ~ ➽ $ dash -c 'ls -R /etc/skel/.[!.]*'
/etc/skel/.bash_logout  /etc/skel/.bashrc  /etc/skel/.face  /etc/skel/.face.icon  /etc/skel/.kshrc  /etc/skel/.profile

감사합니다, 카밀

답변1

변경된 사항은 Perl이 아니라 시스템의 기본 셸입니다. Perl은 system()use를 호출합니다 /bin/sh. 최근 Debian 및 Debian 파생 제품에서 이는 dash기본 POSIX 셸에 대한 기호 링크입니다. 오래된 시스템과 데비안이 아닌 많은 시스템에서는 bash.

실제로 두 쉘은 다르게 동작합니다 [^.].

$ dash -c 'ls -R /etc/skel/.[^.]*' 2>/dev/null | wc
   2875    2572   45543
$ bash -c 'ls -R /etc/skel/.[^.]*' 2>/dev/null | wc
      5       5     103

다음을 수행하여 쉽게 테스트할 수도 있습니다.

$ cd /bin
$ sudo rm sh
$ sudo ln -s bash sh

그런 다음 Perl 스크립트를 다시 실행하십시오. 예상대로 작동하는 것을 볼 수 있습니다. 돌아가서 변경 사항을 취소하는 것을 잊지 마십시오.

$ cd /bin
$ sudo rm sh
$ sudo ln -s dash sh

답변2

perl함수 문서는 를 system()통해 찾을 수 있습니다 perldoc -f system. Perl 5.34를 사용하여 다음을 찾았습니다.

system LIST
system PROGRAM LIST
exec포크가 먼저 수행되고 상위 프로세스가 하위 프로세스가 종료될 때까지 기다리는 것을 제외하면 정확히 동일한 작업을 수행합니다 . 매개변수 처리는 매개변수 수에 따라 달라집니다. LIST에 여러 인수가 있거나 LIST가 여러 값을 가진 배열인 경우 목록의 첫 번째 요소에서 제공한 프로그램을 목록의 나머지 부분에서 제공한 인수로 시작합니다.스칼라 인수가 하나만 있는 경우 인수에 셸 메타 문자가 있는지 확인하고, 그렇다면 구문 분석을 위해 전체 인수가 시스템의 명령 셸에 전달됩니다(Unix 플랫폼에서는 "/bin/sh -c"이지만 다른 플랫폼에서는 다릅니다). ). 인수에 셸 메타 문자가 없으면 이를 단어로 분할하여 "execvp"에 직접 전달하는 것이 더 효율적입니다.

여기서 의 경우 system("ls -R /etc/skel/.[^.]*")다음과 같은 상황이 발생합니다.

  • 매개변수가 전달되었습니다.
  • 이 매개변수에는 셸 메타 문자, 즉 [*1( Thompson 셸과의 하위 호환성 별칭 ^인 Bourne 셸의 메타 문자 |이지만 최신 POSIX에는 더 이상 없음 sh)이 포함됩니다.

그래서 이것은 실제로 당신이 쓴 것과 같습니다:

system({"/bin/sh"} "sh", "-c", "ls -R /etc/skel/.[^.]*");

sh쉘 코드가 자식 프로세스에서 해석되고 ls -R /etc/skel/.[^.]*종료될 때까지 기다려야 합니다.

ls -R /etc/skel/.[^.]*유효한 POSIX 코드가 아닌 한 sh.

스펙을 보면경로명 확장이것은 또한 다음을 가리킨다.파일 이름 확장에 사용되는 패턴POSIX 사양 2018 버전에서는 특히 관련 부분을단일 문자와 일치하는 패턴, 당신은 발견 할 것이다:

[
XBD RE 브래킷 표현식에 표시된 것처럼 열린 브래킷이 브래킷 표현식을 도입하지만<느낌표> 문자( '!' )는 정규식 표기법에서 일치하지 않는 목록의 <circumflex> 문자( '^' )를 대체해야 합니다., 패턴 대괄호 표현식을 도입해야 합니다.따옴표가 없는 <circumflex> 문자로 시작하는 대괄호 표현식은 지정되지 않은 결과를 생성합니다.. 그렇지 않으면 "["는 문자 자체와 일치해야 합니다.

즉, 수행할 작업을 지정하지 않고 사용하는 컬렉션을 무효화하려면 동일 [!x]하거나 둘 중 하나 또는 (귀하의 것과 마찬가지로 ) 또는 POSIX와 관련된 모든 것과 일치할 수 있습니다.[^x][^x][!x]^xsh

따라서 귀하의 행동이 변한다면 이는 귀하가 sh이 영역에서 한 가지 행동 방식에서 다른 방식으로 행동했기 때문일 가능성이 높습니다.

dashAlmquist 쉘(Debian에서 사용되는 쉘, NetBSD 자체에서 파생되고 Almquist 쉘에서 파생됨)의 경우 sh동작에 영향을 주거나 영향을 줄 수 있는 많은 변경 사항이 있습니다.

수정 사항은 실제로 문제와 관련이 없지만 다음과 같은 더 많은 버그가 발생한다는 점에 유의하세요.

$ string='\' pattern='[\^x]' dash -c 'case $string in ($pattern) echo match; esac'
match

따라서 대시가 GNU libc에 연결되면 2020년 5월과 11월 사이에 ^별칭으로 인식되는 짧은 기간이 있으며 !0.5.11+git20200708+dd9ef66-5가 그 안에 포함됩니다.

^!(regexp에서) in glob 로 변경된 이유는 역사적입니다. 위에 표시된 대로 ^(원래 이 문자는 캐럿이 아닌 ASCII의 위쪽 화살표였습니다) Thompson 쉘 및 Bourne 쉘의 파이프 연산자이므로 현대 echo [^x]의 .echo [ | x]sh

^별칭은 |Korn 쉘에서 제거되었으며 POSIX에서는 ^이를 파이프로 취급하는 것을 금지했지만 Korn 쉘은 이전 버전과의 호환성을 유지하기 [!x]위해 이를 다시 변경하지 않았습니다. [^x]bash 또는 zsh와 같은 일부 다른 쉘(또는 Bourne 전통의 수하물이 전혀 없는 csh와 같은 쉘)이므로 POSIX는 이를 지정하지 않습니다.

따라서 코드는 다음과 같아야 합니다.

ls -R /etc/skel/.[!.]*

유효한 sh구문입니다. 이제 이 코드에는 더 많은 문제가 있습니다.

  • 목적은 및를 제외하고 숨겨진 파일과 디렉토리(및 그 내용)를 나열하는 것입니다 .( 거의 바람직하지 않지만 일부 쉘은 여전히 ​​​​전역으로 반환됩니다). 예를 들어 명명 문서 ..가 손실된다는 점에 유의하십시오 ...foo
  • 일치하는 파일이 없으면 호출된 파일이 /etc/skel/.[^.]*존재하지 않는다는 오류 메시지를 받게 됩니다.

perl는 보다 강력한 언어이고 구현이 하나뿐이기 때문에 이식성이 더 높으므로 에 전달할 숨겨진 파일을 찾도록 sh요청하는 대신 다음에서 수행할 수 있습니다 .sh/etclsperl

@hidden_files = grep {!m{/\.\.?\z}} </etc/skel/.*>;
if (@hidden_files) {
  system "ls", "-R", @hidden_files;
}

엄밀히 말하면 공백도 의 메타 문자이지만 sh펄 설명에서는 그렇게 간주되지 않습니다. 공백을 제외한 메타 문자가 없으면 펄은 를 호출하는 대신 자체적으로 공백을 분할합니다 sh.

답변3

아무것도 없습니다. 이러한 기호는 Perl이 아닌 쉘에 의해 해석됩니다.

system()생성이란 /bin/sh -c전체 명령 문자열을 매개변수로 사용한다는 의미입니다. 쉘은 해당 문자열 내의 다른 모든 것을 해석하는 역할을 담당합니다. 이것이 쉘이 호출되는 이유입니다.껍데기주문하다.

정규식(regex)과 달리 [^abc]이는 실제로 쉘 와일드카드(globs)의 표준 구문 요소가 아니며 [!abc]올바른 방식으로 작성되었습니다. 일부 셸(예: Bash)은 두 가지 형식을 모두 허용하지만 /bin/sh는 Bash이거나 Bash 관련 확장을 지원한다고 보장되지 않으며 셸에서 POSIX 요구 사항만 지원하면 됩니다.

따라서 데비안에서는 이제 /bin/sh가 더 간단한 쉘(성능에 최적화된)인 dash에 연결될 가능성이 더 높습니다.가능한여러 버전 이전에 기본이었던 Bash에 여전히 연결되어 있습니다. 한 가지 차이점은 대시는 대체 ^기호를 지원하지 않고 !.

(지난 달의 일이 어렴풋이 기억나는데, Bash 5.2에서도 "POSIX 쉘" 모드를 호출할 때 동일한 동작이 있었나요? 지금은 기억이 나지 않습니다.)


덧붙이자면 이것은 실제로 Perl을 통해 파일을 나열하는 좋은 방법은 아닙니다. 이미 그 자체의 glob()기능이 있습니다! 재귀적으로 사용하려면 표준 File::Find모듈을 사용하세요(또는 재귀적인 Perl 함수를 만드세요). system()을 사용해도 find제외가 필요하지 않으므로 이 문제를 피할 수 있습니다 ...

관련 정보