Perl:system() 调用中 shell 符号的解释发生变化?

Kam*_*mil 8 perl

今天我注意到 Perl 中发生了一些变化,可能是最近,它运行 shell 命令的方式发生了变化。有人可以解释一下发生了什么变化吗?我自己找不到答案,遗憾的是我们以最艰难的方式了解到了这一变化。一些新用户在他们的新主目录中获得了有趣的内容......

\n

我正在运行一个简单的命令/脚本:

\n
#!/usr/bin/perl -w\n\nsystem("ls -R /etc/skel/.[^.]*");\n
Run Code Online (Sandbox Code Playgroud)\n

在 Debian 11: 中perl v5.32.1,输出只是以下内容/etc/skel(如预期):

\n
.  ..  .bash_logout  .bashrc  .face  .face.icon  .kshrc  .profile\n
Run Code Online (Sandbox Code Playgroud)\n

但在 Debian 12 中:perl v5.36.0通配符^被忽略,整体/etc被读取,这意味着..不被忽略

\n

当我更改^为替代符号!:时system("ls -R /etc/skel/.[!.]*");,它再次按预期运行。

\n

问题是,Perl 在处理符号!和调用^方面发生了什么变化system()

\n

编辑: 2023年9月29日 19:50

\n

我在两台服务器上做了一些测试,看起来有些东西发生了dash变化?

\n

Debian 11:(我在破折号中dash Version: 0.5.11+git20200708+dd9ef66-5没有看到标志,所以这是来自 APT)。--version

\n
root@s:~# dash -c \'ls -R /etc/skel/.[^.]*\'\n/etc/skel/.bash_logout  /etc/skel/.bashrc  /etc/skel/.forward+spam  /etc/skel/.kshrc  /etc/skel/.profile\nroot@s:~# dash -c \'ls -R /etc/skel/.[!.]*\'\n/etc/skel/.bash_logout  /etc/skel/.bashrc  /etc/skel/.forward+spam  /etc/skel/.kshrc  /etc/skel/.profile\n\n
Run Code Online (Sandbox Code Playgroud)\n

Debian 12:dash Version: 0.5.12-2

\n
[students] ~ \xe2\x9e\xbd $ dash -c \'ls -R /etc/skel/.[^.]*\' | more\n/etc/skel/..:\na2ps.cfg\na2ps-site.cfg\nadduser.conf\nadjtime\naliases\naliases.db\nalsa\nalternatives\n\n[students] ~ \xe2\x9e\xbd $ dash -c \'ls -R /etc/skel/.[!.]*\'\n/etc/skel/.bash_logout  /etc/skel/.bashrc  /etc/skel/.face  /etc/skel/.face.icon  /etc/skel/.kshrc  /etc/skel/.profile\n
Run Code Online (Sandbox Code Playgroud)\n

亲切的问候,卡米尔

\n

ter*_*don 12

改变的不是 Perl,而是系统上的默认 shell。Perl 的system()调用使用/bin/sh. 在最近的 Debian 和 Debian 衍生系统中,这是dash一个基本 POSIX shell 的符号链接。在较旧的系统和许多非 Debian 系统中,它是bash.

事实上,两个 shell 的行为有所不同[^.]

$ 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
Run Code Online (Sandbox Code Playgroud)

您还可以通过执行以下操作轻松测试:

$ cd /bin
$ sudo rm sh
$ sudo ln -s bash sh
Run Code Online (Sandbox Code Playgroud)

然后再次运行 Perl 脚本。您会看到它的行为符合您的预期。只需记住返回并撤消更改即可:

$ cd /bin
$ sudo rm sh
$ sudo ln -s dash sh
Run Code Online (Sandbox Code Playgroud)

  • @Kamil Aha,他们从使用系统的“fnmatch()”实现(可能将“^”视为“[...]”中的“!”)切换到使用例程的单独实现,这可能在它符合 POSIX。无论如何,您的 Perl 代码应该使用 `!` 以获得最大的可移植性。 (6认同)
  • @Kamil 将 `debian:11` docker hub 映像中的 `sh` 与 `docker:12` 映像中的 `sh` 进行比较,我认为这可能是一个正确的评估。两者都是“破折号”,但它们在“[!.]”与“[^.]”模式方面的行为有所不同(新版本不接受“^”作为“[...]”中“!”的别名`)。缺少的链接是查找“dash”的更改日志。 (4认同)
  • :facepalm: shell 很少改变,所以我完全忘记检查默认的 shell 是什么。但我现在确实检查了,在两个安装中,默认值都是“dash”(来自“sh”的符号链接),因此 shell 没有改变。我又做了一些测试,看起来“dash”中的某些内容发生了变化? (3认同)
  • 在 Debian 或衍生版本上,更改“/bin/sh”符号链接可以使用“dpkg-reconfigure dash”来完成。 (3认同)
  • 是的,我现在正在查看 Debian 的变更日志,最近看到了一些与 globbing 相关的内容。https://metadata.ftp-master.debian.org/changelogs//main/d/dash/dash_0.5.12-2_changelog (2认同)
  • @Kamil 你是否尝试过重建 perl 并告诉“Configure”使用“/bin/bash”而不是“/bin/sh”来实现这些目的?如果您现有的代码执行诸如 `sh - 之类的 bash 操作,那么当从 **“sh is bash”** 系统迁移到 **“sh is dash”** 系统时,这可以帮助使从旧系统的迁移显着减少困难。 o pipelinefail`(以及更多)每当perl“系统”通过在字符串而不是列表上调用“system/exec”、管道打开和反引号操作而进入“系统外壳”时)。这样您就不必覆盖已安装的符号链接。 (2认同)

Sté*_*las 8

perl\ 函数的文档system()可以通过 找到perldoc -f system。使用 perl 5.34,我发现:

\n
\n

system LIST
\n system PROGRAM LIST
\n与 执行完全相同的操作exec,只不过首先执行 fork,并且父进程等待子进程\n退出。请注意,参数处理因参数数量而异。\n 如果 LIST 中有多个参数,\n或者如果 LIST 是一个具有多个值的数组,则\n启动由列表的第一个元素给出的程序,\n程序由列表的其余部分给出。如果只有一个标量参数,则检查该参数是否有 shell 元字符,如果有,则将整个参数传递到系统的命令 shell 进行解析(这是“/bin/sh -c " 在 Unix\n平台上,但在其他平台上有所不同)。如果参数中没有 shell 元字符,则将其拆分为单词并直接传递给“execvp”,这样效率更高。

\n
\n

在这里,使用system("ls -R /etc/skel/.[^.]*"),您将面临以下情况:

\n
    \n
  • 一个参数被传递
  • \n
  • 该参数包含 shell 元字符,即[\ *xc2\xb9 (^是 Bourne shell 中的一个元字符,作为|与 Thompson shell 向后兼容的别名,但它不再在现代 POSIX 中sh)。
  • \n
\n

所以这实际上就像你写的:

\n
system({"/bin/sh"} "sh", "-c", "ls -R /etc/skel/.[^.]*");\n
Run Code Online (Sandbox Code Playgroud)\n

它要求在子进程中sh解释该ls -R /etc/skel/.[^.]*shell 代码并等待其终止。

\n

除非ls -R /etc/skel/.[^.]*不是有效的 POSIXsh代码。

\n

如果您查看 POSIX 规范 2018 版中的Pathname Expansion规范,该规范又指用于文件名扩展的 Patterns,特别是有关Patterns Matching a Single Character的部分,您会发现:

\n
\n

[
\n如果开括号引入括号表达式,如 XBD RE 括号表达式中所示,但 <exclamation-mark>字符 ( \'!\' ) 应替换 <circumflex> 字符 ( \'^\' ) 的作用正则表达式表示法中的非匹配列表,应引入模式括号表达式。以不带引号的 <circumflex> 字符开头的方括号表达式会产生未指定的结果。否则,\'[\' 应匹配字符本身。

\n
\n

换句话说,要否定您使用[!x], not 的集合[^x],并且[^x]未指定做什么,它可以匹配相同的[!x]或任一^x(就像您的sh)或任何 POSIX 涉及的内容。

\n

因此,如果你的行为发生了变化,很可能是因为你sh从在这方面的一种行为方式转变为另一种行为方式。

\n

对于dash(Debian 上使用的 shell,源自 NetBSDsh本身,源自 Almquist shell)的情况,有许多影响或可能影响行为的更改。

\n\n

该修复与您的问题并不真正相关,但请注意,它反过来又引入了更多错误,例如:

\n
$ string=\'\\\' pattern=\'[\\^x]\' dash -c \'case $string in ($pattern) echo match; esac\'\nmatch\n
Run Code Online (Sandbox Code Playgroud)\n

因此,当 dash 链接到 GNU libc 时,2020 年 5 月到 11 月之间有一个很短的窗口,该窗口^将被识别为别名,!而您的 0.5.11+git20200708+dd9ef66-5 恰好落在其中。

\n

^(从 regexp) 更改为!in glob 的原因是历史性的。如上所示^(最初该字符是 ASCII 中的向上箭头,而不是插入符号)是 Thompson shell 和 Bourne shell 中的管道运算echo [^x]符,因此与echo [ | x]现代sh.

\n

^别名 to|已在 Korn shell 中删除,并且 POSIX 禁止^将其视为管道,但 Korn shell 并未改[!x]回 to[^x]来尝试保持向后兼容性。一些其他 shell,例如 bash 或 zsh(或者像 csh 这样从来没有 Bourne 传统包袱的 shell),因此 POSIX 未指定它。

\n

所以,你的代码应该是:

\n
ls -R /etc/skel/.[!.]*\n
Run Code Online (Sandbox Code Playgroud)\n

是有效的sh语法。现在该代码还有更多问题:

\n
    \n
  • 我想目的是列出除.和之外的隐藏文件和目录(及其内容) ..(某些 shell 仍然以它们的全局方式返回,尽管这几乎是不可取的),但请注意,它会丢失..foo例如名为 的文件和目录。
  • \n
  • 如果没有匹配的文件,您将收到一条错误消息,指出所调用的文件/etc/skel/.[^.]*不存在。
  • \n
\n

perl是一种比 更强大的语言sh,而且它也更可移植,因为只有一个实现,因此您不必要求sh找到隐藏文件来/etc传递给ls,而是可以在 中执行此操作perl

\n
@hidden_files = grep {!m{/\\.\\.?\\z}} </etc/skel/.*>;\nif (@hidden_files) {\n  system "ls", "-R", @hidden_files;\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

\xc2\xb9 严格来说,空格也是 中的一个元字符sh,但在 Perl 描述中并不这么认为;如果除了空格之外没有元字符,perl 会自行对空格进行分割,而不是调用sh.

\n