Hoje notei que algo mudou no Perl, provavelmente recentemente, na forma como ele executa comandos shell. Alguém poderia explicar o que mudou? Não consigo encontrar a resposta sozinho e, infelizmente, aprendemos sobre essa mudança da maneira mais difícil possível. Alguns novos usuários obtiveram conteúdos interessantes de seus novos diretórios pessoais...
Estou executando um comando/script simples:
#!/usr/bin/perl -w
system("ls -R /etc/skel/.[^.]*");
No Debian 11: perl v5.32.1
, a saída é apenas o conteúdo de /etc/skel
(como esperado):
. .. .bash_logout .bashrc .face .face.icon .kshrc .profile
Mas no Debian 12: perl v5.36.0
o globbing ^
é ignorado e o todo /etc
é lido, o que significa ..
que não é ignorado .
Quando mudo ^
para o símbolo alternativo !
: system("ls -R /etc/skel/.[!.]*");
, ele funciona conforme o esperado novamente.
A questão é: o que mudou no Perl no tratamento de símbolos !
e ^
na system()
invocação?
EDITAR: 29.09.2023, 19h50
Fiz alguns testes nos dois servidores e parece que algo mudou dash
?
Debian 11: dash Version: 0.5.11+git20200708+dd9ef66-5
(não vejo uma --version
bandeira no traço, então é do 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
Atenciosamente, Kamil
Não foi o Perl que mudou, é o shell padrão do seu sistema. A chamada do Perl
system()
usa/bin/sh
. Em sistemas Debian recentes e derivados do Debian, esse é um link simbólico paradash
, um shell POSIX básico. Nos sistemas mais antigos, e em muitos sistemas não-debian, é, em vez disso, um link simbólico parabash
.E, de fato, os dois shells se comportam de maneira diferente com
[^.]
:Você também pode testar isso facilmente fazendo:
E então execute seu script Perl novamente. Você verá que ele se comporta conforme o esperado. Apenas lembre-se de voltar e desfazer a alteração:
A documentação da função
perl
desystem()
pode ser encontrada emperldoc -f system
. Com perl 5.34, encontro:Aqui, com
system("ls -R /etc/skel/.[^.]*")
, você está no caso em que:[
e*
¹ (^
era um metacaractere no shell Bourne como um alias para|
compatibilidade com versões anteriores do shell Thompson, mas não está mais no POSIX modernosh
).então será como se você tivesse escrito:
Que pede
sh
para interpretar essels -R /etc/skel/.[^.]*
código shell em um processo filho e aguarda seu encerramento.Exceto que
ls -R /etc/skel/.[^.]*
não é um código POSIX válidosh
.Se você observar a especificação de Pathname Expansion que por sua vez se refere a Patterns Used for Filename Expansion na edição 2018 da especificação POSIX e, em particular, a parte sobre Patterns Matching a Single Character , você encontrará:
Em outras palavras, para negar um conjunto que você usa
[!x]
, not[^x]
, e o que[^x]
does não é especificado, ele pode corresponder ao mesmo que[!x]
ou^
oux
(como seush
) ou qualquer coisa no que diz respeito ao POSIX.Portanto, se o comportamento mudou para você, é provável que você
sh
tenha mudado de alguém que se comportou de uma maneira nesse aspecto para outro que se comportou de outra maneira.No caso de
dash
(o shell usado no Debian, derivado dosh
próprio NetBSD derivado do shell Almquist), há uma série de mudanças que afetam ou podem afetar o comportamento.dash
para usar as libcsfnmatch()
eglob()
realizar globbing em vez de fazê-lo internamente (dash
o glob interno de 'não reconhece^
).^
como um alias para!
, o glibc oferece).fnmatch()
ao usado nacase
instrução, mas o uso deglob()
ainda está desabilitado por padrão.Essa correção não é realmente relevante para o seu problema, mas observe que, por sua vez, ela introduz mais bugs como:
Portanto, com o dash vinculado à GNU libc, houve uma pequena janela entre maio e novembro de 2020 onde
^
teria sido reconhecido como um alias para!
e seu 0.5.11+git20200708+dd9ef66-5 pousou no meio dele.A razão pela qual
^
(de regexp) foi alterado para!
in globs é histórica. Como visto acima^
(inicialmente esse caractere era uma seta para cima em ASCII, não um cursor) era um operador de pipe no shell Thompson e no shell Bourne, entãoecho [^x]
teria sido o mesmo queecho [ | x]
nosh
.Esse
^
alias|
foi removido no shell Korn e o POSIX proíbe^
ser tratado como um canal, mas o shell Korn não mudou[!x]
de volta para[^x]
tentar preservar a compatibilidade com versões anteriores. Alguns outros shells, como bash ou zsh, fizeram (ou shells como csh que nunca tiveram uma bagagem de herança Bourne), então o POSIX deixa isso sem especificação.Então, seu código deveria ter sido:
Para ser
sh
uma sintaxe válida. Agora ainda há mais problemas com esse código:.
e..
(que alguns shells ainda retornam em seus globos, embora isso quase nunca seja desejável), mas observe que faltam arquivos nomeados,..foo
por exemplo./etc/skel/.[^.]*
não existe.perl
é uma linguagem muito mais capaz do quesh
e também é mais portátil, pois há apenas uma implementação; portanto, em vez de pedirsh
para encontrar os arquivos ocultos para/etc
passar parals
, você pode fazê-lo emperl
:¹ A rigor, o espaço também é um metacaractere em
sh
, mas não é considerado como tal nessa descrição do Perl; se não houver metacaracteres além do espaço, o perl faz a divisão do espaço sozinho, em vez de chamarsh
.Nada. Esses símbolos são interpretados pelo seu shell, não pelo Perl.
O que
system()
acontece é gerar/bin/sh -c
toda a string de comando como argumento. O shell é o que interpreta todo o resto dentro dessa string – é por isso que é chamado de comando shell .Ao contrário das expressões regulares (regex),
[^abc]
não é realmente um elemento de sintaxe padrão em curingas de shell (globs) e é escrito da[!abc]
maneira correta. Acontece que alguns shells, como o Bash, aceitam ambas as formas - mas não é garantido que /bin/sh seja Bash ou suporte qualquer extensão específica do Bash; ele só é necessário para suportar o que o POSIX exige de um shell.Portanto, no Debian, hoje em dia é muito mais provável que /bin/sh esteja vinculado ao dash, um shell muito mais minimalista (otimizado para desempenho), embora instalações antigas ainda possam tê-lo vinculado ao Bash, pois costumava ser o padrão muitos lançamentos atrás . Uma das diferenças é que o traço não suporta o
^
símbolo alternativo, apenas!
.(Também me lembro vagamente de algo do mês passado sobre até o Bash 5.2 se comportar da mesma maneira quando invocado o modo "shell POSIX"? Não consigo me lembrar agora.)
Se eu pudesse acrescentar, essa não é realmente uma boa maneira de listar arquivos por meio do Perl.
glob()
Já tem sua própria função! E se você quiser que seja recursivo, use oFile::Find
módulo padrão (ou faça uma função Perl recursiva). Mesmo com system(),find
teria evitado esse problema, pois não precisa de..
exclusão.