今天我注意到 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
没有看到标志,所以这是来自 APT)。--version
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
亲切的问候,卡米尔
改变的不是 Perl,而是系统上的默认 shell。Perl 的
system()
调用使用/bin/sh
. 在最近的 Debian 和 Debian 衍生系统中,这是dash
一个基本 POSIX shell 的符号链接。在较旧的系统和许多非 Debian 系统中,它是bash
.事实上,两个 shell 的行为有所不同
[^.]
:您还可以通过执行以下操作轻松测试:
然后再次运行 Perl 脚本。您会看到它的行为符合您的预期。只需记住返回并撤消更改即可:
perl
的函数文档system()
可以通过 找到perldoc -f system
。使用 perl 5.34,我发现:在这里,对于
system("ls -R /etc/skel/.[^.]*")
,您会遇到以下情况:[
和*
1 (^
是 Bourne shell 中的一个元字符,作为|
与 Thompson shell 向后兼容的别名,但它不再在现代 POSIX 中sh
)。所以这实际上就像你写的:
它要求在子进程中
sh
解释该ls -R /etc/skel/.[^.]*
shell 代码并等待其终止。除非
ls -R /etc/skel/.[^.]*
不是有效的 POSIXsh
代码。如果您查看Pathname Expansion规范,该规范又指POSIX 规范 2018 版中用于文件名扩展的 Patterns,特别是有关Patterns Matching a Single Character的部分,您会发现:
换句话说,要否定您使用
[!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()
该修复与您的问题并不真正相关,但请注意,它反过来又引入了更多错误,例如:
因此,当 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 未指定它。所以,你的代码应该是:
是有效的
sh
语法。现在该代码还有更多问题:.
我想目的是列出除和之外的隐藏文件和目录(及其内容)..
(某些 shell 仍然在其全局中返回,尽管这几乎是不可取的),但请注意,它会丢失..foo
例如命名的文件。/etc/skel/.[^.]*
不存在。perl
是一种比 更强大的语言sh
,而且它也更可移植,因为只有一个实现,因此您不必要求sh
在 中查找隐藏文件/etc
以传递给ls
,而是可以在 中执行此操作perl
:严格来说,空格也是 中的一个元字符
sh
,但在 perl 描述中并不这么认为;如果除了空格之外没有元字符,perl 会自行对空格进行分割,而不是调用sh
.没有什么。这些符号由您的 shell 解释,而不是由 Perl 解释。
所做
system()
的是以/bin/sh -c
整个命令字符串作为参数生成。shell 解释该字符串内的所有其他内容 - 这就是它被称为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
也会避免这个问题,因为它不需要排除..
。