Perl5 の system でシェルが実行される条件

Perl5 で組み込み関数 system を実行した場合、1引数であればシェルが実行される、と perldoc perlfunc には書いてある。

https://perldoc.perl.org/perlfunc#system-LIST https://perldoc.jp/func/system

ただし、よく読むと、以下のように記述されている。

If there is only one scalar argument, the argument is checked for shell metacharacters, and if there are any, the entire argument is passed to the system's command shell for parsing (this is /bin/sh -c on Unix platforms, but varies on other platforms). スカラの引数が一つだけの場合、引数はシェルのメタ文字をチェックされ、もし あればパースのために引数全体がシステムコマンドシェル (これは Unix プラットフォームでは /bin/sh -c ですが、他のプラットフォームでは 異なります)に渡されます。 シェルのメタ文字がなかった場合、引数は単語に分解されて直接 execvp に 渡されます

つまり、メタ文字が無ければシェルでの実行ではなく単純に execvp に渡されるということになる(日本語の方がちょっとわかりにくいかも)。

さて、ここのメタ文字というのが具体的に何を指しているのかは perlfunc には書いていない。

この具体的な実装は pp_sys.c の pp_system にある。 https://github.com/Perl/perl5/blob/v5.41.9/pp_sys.c#L4588-L4796

この中で、実際にメタ文字判定を行っているのは doio.c の Perl_do_exec3 である。 https://github.com/Perl/perl5/blob/v5.41.9/doio.c#L2483-L2611

より具体的にはここ。 https://github.com/Perl/perl5/blob/v5.41.9/doio.c#L2542-L2574

Image

つまり、以下のうちいずれかの条件にマッチした場合にシェルが起動される。

ここで、2>&1 が末尾にはいっているケースは特殊な最適化が入っていて自前で dup していることに注意。

挙動確認

この挙動を見るには strace を利用すれば確認できる。

463e9dbb03f5# strace -ff perl -e 'system(q{echo Hello})'  &> /tmp/x; grep echo /tmp/x
execve("/usr/bin/perl", ["perl", "-e", "system(q{echo Hello})"], 0xfffff3dcd858 /* 10 vars */) = 0
[pid   723] execve("/usr/local/sbin/echo", ["echo", "Hello"], 0xaaaae98c8a60 /* 10 vars */) = -1 ENOENT (No such file or directory)
[pid   723] execve("/usr/local/bin/echo", ["echo", "Hello"], 0xaaaae98c8a60 /* 10 vars */) = -1 ENOENT (No such file or directory)
[pid   723] execve("/usr/sbin/echo", ["echo", "Hello"], 0xaaaae98c8a60 /* 10 vars */) = -1 ENOENT (No such file or directory)
[pid   723] execve("/usr/bin/echo", ["echo", "Hello"], 0xaaaae98c8a60 /* 10 vars */) = 0

execve("/usr/bin/perl", ["perl", "-e", "system(q{. hello.sh})"], 0xffffdb0706f8 /* 10 vars */) = 0
[pid   794] execve("/bin/sh", ["sh", "-c", ". hello.sh"], 0xaaaac712da60 /* 10 vars */) = 0
[pid   794] newfstatat(AT_FDCWD, "/usr/local/sbin/hello.sh", 0xffffcc1c6028, 0) = -1 ENOENT (No such file or directory)
[pid   794] newfstatat(AT_FDCWD, "/usr/local/bin/hello.sh", 0xffffcc1c6028, 0) = -1 ENOENT (No such file or directory)
[pid   794] newfstatat(AT_FDCWD, "/usr/sbin/hello.sh", 0xffffcc1c6028, 0) = -1 ENOENT (No such file or directory)
[pid   794] newfstatat(AT_FDCWD, "/usr/bin/hello.sh", 0xffffcc1c6028, 0) = -1 ENOENT (No such file or directory)
[pid   794] newfstatat(AT_FDCWD, "/sbin/hello.sh", 0xffffcc1c6028, 0) = -1 ENOENT (No such file or directory)
[pid   794] newfstatat(AT_FDCWD, "/bin/hello.sh", 0xffffcc1c6028, 0) = -1 ENOENT (No such file or directory)
[pid   794] write(2, "hello.sh: not found", 19hello.sh: not found) = 19


463e9dbb03f5# strace -ff perl -e 'system(q{echo $SHELL})'  &> /tmp/x; grep echo /tmp/x
execve("/usr/bin/perl", ["perl", "-e", "system(q{echo $SHELL})"], 0xffffde4e9948 /* 10 vars */) = 0
[pid   717] execve("/bin/sh", ["sh", "-c", "echo $SHELL"], 0xaaaae3edea60 /* 10 vars */) = 0

463e9dbb03f5# strace -ff perl -e 'system(q{echo > /tmp/y})'  &> /tmp/x; grep echo /tmp/x
execve("/usr/bin/perl", ["perl", "-e", "system(q{echo > /tmp/y})"], 0xffffe70cc8d8 /* 10 vars */) = 0
[pid   759] execve("/bin/sh", ["sh", "-c", "echo > /tmp/y"], 0xaaab038bfa60 /* 10 vars */) = 0

463e9dbb03f5# strace -ff perl -e 'system(q{echo hello 2>&1})'  &> /tmp/x; grep echo /tmp/x
execve("/usr/bin/perl", ["perl", "-e", "system(q{echo hello 2>&1})"], 0xffffc4c9c328 /* 10 vars */) = 0
[pid   772] execve("/usr/local/sbin/echo", ["echo", "hello"], 0xaaaaef204a60 /* 10 vars */) = -1 ENOENT (No such file or directory)
[pid   772] execve("/usr/local/bin/echo", ["echo", "hello"], 0xaaaaef204a60 /* 10 vars */) = -1 ENOENT (No such file or directory)
[pid   772] execve("/usr/sbin/echo", ["echo", "hello"], 0xaaaaef204a60 /* 10 vars */) = -1 ENOENT (No such file or directory)
[pid   772] execve("/usr/bin/echo", ["echo", "hello"], 0xaaaaef204a60 /* 10 vars */) = 0

ちなみに検証は perl 5.38.2 で行った。

463e9dbb03f5# perl -v

This is perl 5, version 38, subversion 2 (v5.38.2) built for aarch64-linux-gnu-thread-multi
(with 44 registered patches, see perl -V for more detail)

Copyright 1987-2023, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at https://www.perl.org/, the Perl Home Page.

歴史

Perl 3.0 の時点ではすでにこうなっている。 https://github.com/Perl/perl5/blob/perl-3.000/doio.c#L807-L880

Perl 2.0 のコードで言うと eval.c からの arg.c に当たる部分だが、ここではこの最適化は取り入れられていない。

考察

Perl 3.0 がリリースされたのは 1989 年である。 1989年といえば、CPU が 386DX/33MHz で RAM が 5.5MB とかの時代である。 このブログを読んでる人のうち半分ぐらいは産まれてすらない。

ということを考えれば、このような実装がされるのもうなづける。 https://www.eonet.ne.jp/~building-pc/pc/pc1989.htm

しかしながら、長い歴史の中でここの互換性は捨てても良かった部分だとは思う。が、互換性を頑固にたもちつづけることで使われてきたというのもまた事実。 互換性を破壊しながら進んだ Python などとはまた違う道を歩んだ Perl なのであった。

Published: 2025-03-03(Tue) 07:36