Aqui está um script para corrigir nomes de arquivos cirílicos quebrados se os arquivos foram movidos do Windows para o Mac (com base em uma resposta para Reverter nomes de arquivos depois que eles foram corrompidos pelo uso de codificação diferente )
#!/bin/zsh
# Usage: <script> <target directory>
# Requires Perl::Rename
find "$1" -mindepth 1 -print0 |
rename -0 -d -e '
use Unicode::Normalize qw(NFC);
use Encode qw(:all);
if ($_ =~ /[†°Ґ£§•¶І®©™Ђђ≠]/) {
my $check = DIE_ON_ERR | LEAVE_SRC;
my $new = eval {encode("UTF-8",
decode("cp866",
encode("mac-cyrillic",
NFC(decode("UTF-8", $_, $check)), $check), $check))
};
if ($new) {$_ = $new;} else {warn $@;}
}'
Quero que ele renomeie apenas os arquivos no diretório de destino que tenham pelo menos um dos seguintes caracteres em seus nomes: †°Ґ£§•¶І®©™Ђђ≠
. Mas, por algum motivo, o script renomeia todos os arquivos ali: por exemplo, um nome de arquivo correto срочно.txt
é alterado para um ёЁюўэю.txt
. O que estou fazendo errado?
O caminho para minha pasta de teste é simples /Users/john/scripts/test
: sem espaços, sem caracteres cirílicos ou especiais.
O script é usado no macOS e com a versão BSD do find
.
Uma atualização dois dias após a resposta à pergunta: as versões Chazelas e Choroba do Stéphane funcionam bem para mim. A versão do Terdon ainda não funciona.
Por padrão, o Perl não espera código-fonte codificado em UTF-8. Você precisa informar se estiver usando caracteres codificados em UTF-8; caso contrário, o Perl os trata como bytes (no nosso caso, o byte 209 corresponde).
Além disso, você deve usar a
-u
opção pararename
informar ao Perl que os nomes dos arquivos são codificados em UTF-8 (ou especificar qualquer outra codificação, se necessário). Então, crie seu script:Testado com o seguinte Makefile (
fix
é o próprio script):Saídas:
Você está fazendo essa correspondência no nome de arquivo não decodificado, você precisaria fazer a decodificação (das partes
decode("UTF-8", $_, $check)
eNFC()
) antes de fazer a correspondência.Também porque, como já mencionado,
perl
ele interpreta seu código em iso8859-1 por padrão no Unix (ou melhor, em nível de byte, sem precisar fazer nenhuma codificação ou decodificação) e não em UTF-8, ele/[†°Ґ£§•¶І®©™Ђђ≠]/
não funcionará a menos que você informe que eles são expressos em UTF-8.Então isso
/[†°Ґ£§•¶І®©™Ђђ≠]/
é literalmente o mesmo que/[\x{E2}\x{80}\x{A0}\x{C2}\x{B0}\x{D2}\x{90}\x{C2}\x{A3}\x{C2}\x{A7}\x{E2}\x{80}\x{A2}\x{C2}\x{B6}\x{D0}\x{86}\x{C2}\x{AE}\x{C2}\x{A9}\x{E2}\x{84}\x{A2}\x{D0}\x{82}\x{D1}\x{92}\x{E2}\x{89}\x{A0}]/
, aí você reconhecerá\xe2\x80\xa0
a codificação UTF-8 do†
caractere:Essa expressão regular corresponderia a qualquer string que contivesse qualquer um dos caracteres
\xe2
ou\x80
ou\xa0
etc , que, se você não fizer nenhuma codificação/decodificação do nome do arquivo, corresponderá a qualquer string em que a codificação de qualquer caractere contenha os bytes , ... e milhares de caracteres contêm esses bytes quando codificados em UTF-8, incluindo (U+0440), por exemplo, cuja codificação UTF-8 é 0xd1 0x80.\xe2
\x80
р
use utf8
Como outros sugeriram, informa ao Perl que seu código é codificado em UTF-8, mas isso precisa ser feito no início do script. Aqui, operl
código é passado como um argumento regular para orename
script (não como um argumento de código paraperl
) e avaliado por esse script como parte de umaeval
instrução, portanto, umause utf8
adição não se aplicaria. Compare:o mesmo que:
Este é o ponto de código de
≠
, que foi decodificado corretamente de UTF-8. Com:Esse é o valor do primeiro byte da codificação UTF-8 de
≠
(também ponto de código deâ
(U+00E2), que é codificado como 0xe2 em iso8859-1).Usar
-u
não ajuda porque se trata da codificação/decodificação de nomes de arquivos, não do código Perl, e aqui não queremos usar-u
, pois queremos fazer nossa própria codificação/decodificação com a verificação de sucesso.Aqui, você pode usar
PERL_UNICODE=A rename...
o que informa queperl
osA
argumentos do script devem ser codificados em UTF-8 ou você pode usar\x{HHHH}
ou\N{character name}
para esses caracteres e manter o código em ASCII:(Eu costumava
uconv -x name
obter esses nomes de personagens, usadosuconv -x hex/perl
para obter o\x{HHHH}
formulário).Ou
find
faça a correspondência (assumindo uma implementaçãofind
/fnmatch()
que funcione bem com caracteres multibyte)com:
(onde
=\u0338
é a forma decomposta do≠
caractere que o macOS pode usar no nome do arquivo¹).Ou use
zsh
globs em vez defind
:¹ e que
NFC()
no código Perl será convertido para sua formaC
compostaF
, aquela para a qual a cadeia de decodificação/codificação converteráн
.O problema é que você está correspondendo,
$_
mas não está tratando como Unicode. Você precisa primeiro decodificar$_
para Unicode e depois corresponder. Isso deve funcionar:Eu testei usando (onde
foo.sh
está o script acima):E:
Aqui está minha própria versão, com alguns ajustes adicionais e um caso de teste.
Antes:
Depois:
rename --version:
/Users/john/perl5/bin/rename using File::Rename version 2.02, File::Rename::Options version 2.01