今天我注意到 Perl 中发生了一些变化,可能是最近,它运行 shell 命令的方式发生了变化。有人可以解释一下发生了什么变化吗?我自己找不到答案,遗憾的是我们以最艰难的方式了解到了这一变化。一些新用户在他们的新主目录中获得了有趣的内容......
\n我正在运行一个简单的命令/脚本:
\n#!/usr/bin/perl -w\n\nsystem("ls -R /etc/skel/.[^.]*");\nRun Code Online (Sandbox Code Playgroud)\n在 Debian 11: 中perl v5.32.1,输出只是以下内容/etc/skel(如预期):
. .. .bash_logout .bashrc .face .face.icon .kshrc .profile\nRun 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\nRun 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\nRun 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/.[^.]*");\nRun 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\nRun 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/.[!.]*\nRun 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}\nRun Code Online (Sandbox Code Playgroud)\n\xc2\xb9 严格来说,空格也是 中的一个元字符sh,但在 Perl 描述中并不这么认为;如果除了空格之外没有元字符,perl 会自行对空格进行分割,而不是调用sh.