今天我注意到 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)的情况,有许多影响或可能影响行为的更改。
- [扩展]添加了configure --enable-glob 和--enable-fnmatch 选项(2007),它增加了编译的可能性
dash
,因此它使用 libcfnmatch()
并glob()
执行 globbing,而不是在内部执行(dash
内部 glob 无法识别^
)。 - shell:默认启用 fnmatch/glob(2020 年 5 月):这成为默认值(并且可能支持也可能不支持
^
作为 glibc 的别名!
)。 - shell:再次禁用 glob,因为它会删除尾部斜杠(2020年11月)
- 扩展:使用 fnmatch 时始终引用插入符号(2022) 之后这个错误报告。适用于语句
fnmatch()
中使用的,但默认情况下仍禁用case
的使用。glob()
该修复与您的问题并不真正相关,但请注意,它反过来又引入了更多错误,例如:
$ 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
也会避免这个问题,因为它不需要排除..
。