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

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

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

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

#!/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我在破折号中没有看到--version标志,所以这是来自 APT)。

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,而是系统上的默认 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

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

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

在这里,对于system("ls -R /etc/skel/.[^.]*"),您会遇到以下情况:

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

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

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

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

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

如果你看一下规格路径名扩展这又指的是用于文件名扩展的模式在 2018 版 POSIX 规范中,特别是有关的部分匹配单个字符的模式, 你会找到:

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

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

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

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

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

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

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

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

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

所以,你的代码应该是:

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

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

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

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

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

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

答案3

没有什么。这些符号由您的 shell 解释,而不是由 Perl 解释。

system()spawn 是什么意思/bin/sh -c的是以整个命令字符串作为参数。 shell 负责解释该字符串内的所有其他内容 - 这就是为什么它被称为命令。

与正则表达式 (regex) 不同,[^abc]它实际上并不是 shell 通配符 (glob) 中的标准语法元素,并且按照[!abc]正确的方式编写它。碰巧某些 shell(例如 Bash)接受这两种形式 - 但 /bin/sh 不保证是 Bash 或支持任何特定于 Bash 的扩展;它只需要在 shell 中支持 POSIX 的要求。

因此,在 Debian 上,/bin/sh 现在更有可能链接到 dash,这是一个更简单的 shell(针对性能进行了优化),尽管旧安装可能仍然将它链接到 Bash,因为它曾经是许多版本之前的默认设置。区别之一是破折号不支持替代^符号,仅支持!

(我还依稀记得上个月的一些事情,甚至 Bash 5.2 在调用“POSIX shell”模式时也有同样的行为?我现在不记得了。)


如果我可以补充一点,这确实不是通过 Perl 列出文件的好方法。它已经有自己的glob()功能了!如果您希望它是递归的,请使用标准File::Find模块(或创建递归 Perl 函数)。即使使用 system(),find也会避免这个问题,因为它不需要排除..

相关内容