今天我注意到 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
(如预期):
. .. .bash_logout .bashrc .face .face.icon .kshrc .profile\n
Run Code Online (Sandbox Code Playgroud)\n但在 Debian 12 中:perl v5.36.0
通配符^
被忽略,整体/etc
被读取,这意味着..
不被忽略。
当我更改^
为替代符号!
:时system("ls -R /etc/skel/.[!.]*");
,它再次按预期运行。
问题是,Perl 在处理符号!
和调用^
方面发生了什么变化system()
?
我在两台服务器上做了一些测试,看起来有些东西发生了dash
变化?
Debian 11:(我在破折号中dash Version: 0.5.11+git20200708+dd9ef66-5
没有看到标志,所以这是来自 APT)。--version
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)\nDebian 12:dash Version: 0.5.12-2
[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亲切的问候,卡米尔
\nter*_*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)
perl
\ 函数的文档system()
可以通过 找到perldoc -f system
。使用 perl 5.34,我发现:
\n\n\n
system LIST
\nsystem PROGRAM LIST
\n与 执行完全相同的操作exec
,只不过首先执行 fork,并且父进程等待子进程\n退出。请注意,参数处理因参数数量而异。\n 如果 LIST 中有多个参数,\n或者如果 LIST 是一个具有多个值的数组,则\n启动由列表的第一个元素给出的程序,\n程序由列表的其余部分给出。如果只有一个标量参数,则检查该参数是否有 shell 元字符,如果有,则将整个参数传递到系统的命令 shell 进行解析(这是“/bin/sh -c " 在 Unix\n平台上,但在其他平台上有所不同)。如果参数中没有 shell 元字符,则将其拆分为单词并直接传递给“execvp”,这样效率更高。
在这里,使用system("ls -R /etc/skel/.[^.]*")
,您将面临以下情况:
[
\ *
xc2\xb9 (^
是 Bourne shell 中的一个元字符,作为|
与 Thompson shell 向后兼容的别名,但它不再在现代 POSIX 中sh
)。所以这实际上就像你写的:
\nsystem({"/bin/sh"} "sh", "-c", "ls -R /etc/skel/.[^.]*");\n
Run Code Online (Sandbox Code Playgroud)\n它要求在子进程中sh
解释该ls -R /etc/skel/.[^.]*
shell 代码并等待其终止。
除非ls -R /etc/skel/.[^.]*
不是有效的 POSIXsh
代码。
如果您查看 POSIX 规范 2018 版中的Pathname Expansion规范,该规范又指用于文件名扩展的 Patterns,特别是有关Patterns Matching a Single Character的部分,您会发现:
\n\n\n\n
[
\n如果开括号引入括号表达式,如 XBD RE 括号表达式中所示,但 <exclamation-mark>字符 ( \'!\' ) 应替换 <circumflex> 字符 ( \'^\' ) 的作用正则表达式表示法中的非匹配列表,应引入模式括号表达式。以不带引号的 <circumflex> 字符开头的方括号表达式会产生未指定的结果。否则,\'[\' 应匹配字符本身。
换句话说,要否定您使用[!x]
, not 的集合[^x]
,并且[^x]
未指定做什么,它可以匹配相同的[!x]
或任一^
或x
(就像您的sh
)或任何 POSIX 涉及的内容。
因此,如果你的行为发生了变化,很可能是因为你sh
从在这方面的一种行为方式转变为另一种行为方式。
对于dash
(Debian 上使用的 shell,源自 NetBSDsh
本身,源自 Almquist shell)的情况,有许多影响或可能影响行为的更改。
dash
因此它使用libcfnmatch()
并glob()
执行globbing而不是在内部执行(dash
\的内部glob)不认识^
)。^
作为 的别名!
,glibc 则支持)。fnmatch()
于语句中使用的,但默认情况下仍禁用 的case
使用。glob()
该修复与您的问题并不真正相关,但请注意,它反过来又引入了更多错误,例如:
\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 恰好落在其中。
^
(从 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 未指定它。
所以,你的代码应该是:
\nls -R /etc/skel/.[!.]*\n
Run Code Online (Sandbox Code Playgroud)\n是有效的sh
语法。现在该代码还有更多问题:
.
和之外的隐藏文件和目录(及其内容) ..
(某些 shell 仍然以它们的全局方式返回,尽管这几乎是不可取的),但请注意,它会丢失..foo
例如名为 的文件和目录。/etc/skel/.[^.]*
不存在。perl
是一种比 更强大的语言sh
,而且它也更可移植,因为只有一个实现,因此您不必要求sh
找到隐藏文件来/etc
传递给ls
,而是可以在 中执行此操作perl
:
@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\xc2\xb9 严格来说,空格也是 中的一个元字符sh
,但在 Perl 描述中并不这么认为;如果除了空格之外没有元字符,perl 会自行对空格进行分割,而不是调用sh
.