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

しかし、Debian 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

Debian 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の派生製品では、これはデフォルトのdashPOSIXシェルへのシンボリックリンクです。古いシステムとDebianではない多くのシステムでは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が複数の値を持つ配列の場合は、リストの最初の要素によって提供されたプログラムをリストの残りの部分で指定された引数で始めます。スカラー引数が1つしかない場合は、引数にシェルメタ文字が含まれていることを確認します。そうであれば、解析のために引数全体がシステムのコマンドシェルに渡されます(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はより強力な言語であり、実装が1つしかないため、移植性が高いため、渡す隠しファイルを見つけるように要求するsh代わりに、次のようにすることができます。sh/etclsperl

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

厳密に言えば、空白度のメタ文字ですが、shPerlの説明ではそうではありません。スペース以外のメタ文字がない場合、Perlはそれを呼び出す代わりにスペースを分割しますsh

答え3

何もありません。これらのシンボルはPerlではなくシェルによって解釈されます。

system()生成とは、コマンド/bin/sh -c・ストリング全体をパラメーターとして使用することを意味します。シェルは、その文字列内の他のすべてを解釈する役割を担います。これがシェルが呼び出される理由です。シェル注文する。

正規表現とは異なり、[^abc]これは実際にはシェルワイルドカードの標準構文要素ではなく、[!abc]正しい方法で書かれています。一部のシェル(たとえば、Bash)は両方の形式を受け入れますが、/ bin / shはBashまたはBash関連の拡張をサポートしているわけではありません。

そのため、Debian では /bin/sh がより単純なシェル (パフォーマンスに最適化された) であるダッシュにリンクされる可能性が高くなります。可能いくつかのバージョンより前にデフォルトだったBashにまだ接続されています。 1つの違いは、ダッシュは代替^記号をサポートしていません!

(先月のことがどんどん覚えていますが、Bash 5.2でも「POSIXシェル」モードを呼び出すときに同じ動作がありましたか?今は覚えていません。)


ちなみに、これは実際にPerlを介してファイルを一覧表示する良い方法ではありません。すでに独自のglob()機能があります!再帰的に使用するには、標準File::Findモジュールを使用してください(または再帰的なPerl関数を作成してください)。 system() を使用してもfind除外は不要なので、この問題を回避できます..

関連情報