Questão Como faço para remover caracteres não ascii de nomes de arquivos?


Eu tenho vários arquivos com nomes contendo vários caracteres Unicode. Eu gostaria de renomeá-los para conter apenas os caracteres ASCII "imprimíveis" (32-126).

Por exemplo,

Läsmig.txt         //Before
L_smig.txt         //After
Mike’s Project.zip 
Mike_s Project.zip 

Ou para pontos de bônus, transcreva para o personagem mais próximo

Läsmig.txt
Lasmig.txt
Mike’s Project.zip
Mike's Project.zip

O ideal é procurar uma resposta que não exija ferramentas de terceiros. (Edit: Scripts encorajados; estou apenas tentando evitar aplicativos shareware de nicho que precisam ser instalados para funcionar)


Power snippet que encontra os arquivos que eu estou interessado em renomear:

gci -recurse | onde {$ _. Name -match "[^ \ u0020- \ u007E]"}

Pergunta python semelhante não respondida - https://stackoverflow.com/questions/17870055/how-to-rename-a-file-with-non-ascii-character-encoding-to-ascii


5


origem


Não existe algo como “ASCII estendido”. - kinokijuf
@kinokijuf, diga isso para todo mundo. - Synetech
@kinokijuf e, claro, nada existia antes do Windows NT. - Synetech
@kinokijuf, Synetech está correto. O conjunto de códigos ASCII estendido já existia por mais de uma década quando o Windows NT foi lançado. Cada programa DOS conhecido pelo homem usava o conjunto ASCII estendido. - Roger
@ Kinokijuf, existem páginas de código agora. Isso não está em disputa. O DOS adicionou suporte à página de códigos apenas no DOS 3.3. No entanto, o conjunto de caracteres ASCII estendido foi construído na ROM dos adaptadores de vídeo IBM PC originais. Vejo esse site para mais informações. - Roger


Respostas:


Eu encontrei um tópico semelhante Aqui no estouro de pilha.

Com o seguinte código, a maioria dos caracteres será traduzida para o "personagem mais próximo". Embora eu não conseguisse  traduzido. (Talvez isso aconteça, eu não posso fazer um nome de arquivo no prompt com ele;) ß também não é traduzido.

function Remove-Diacritics {
param ([String]$src = [String]::Empty)
  $normalized = $src.Normalize( [Text.NormalizationForm]::FormD )
  $sb = new-object Text.StringBuilder
  $normalized.ToCharArray() | % {
    if( [Globalization.CharUnicodeInfo]::GetUnicodeCategory($_) -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
      [void]$sb.Append($_)
    }
  }
  $sb.ToString()
}

$files = gci -recurse | where {$_.Name -match "[^\u0020-\u007F]"}
$files | ForEach-Object {
  $newname = Remove-Diacritics $_.Name
  if ($_.Name -ne $newname) {
    $num=1
    $nextname = $_.Fullname.replace($_.Name,$newname)
    while(Test-Path -Path $nextname)
    {
      $next = ([io.fileinfo]$newname).basename + " ($num)" + ([io.fileinfo]$newname).Extension
      $nextname = $_.Fullname.replace($_.Name,$next)
      $num+=1
    }
    echo $nextname
    ren $_.Fullname $nextname
  }
}

Editar:

Eu adicionei algum código para verificar se um nome de arquivo já existe e adicionar (1), (2) etc ... se isso acontecer. (Não é inteligente o suficiente para detectar um já existente (1) no nome do arquivo para ser renomeado para que nesse caso você iria ficar (1) (1). Mas como sempre ... tudo é programável;)

Editar 2:

Aqui é o último para esta noite ...

Este tem uma função diferente para substituir os caracteres. Também adicionou uma linha para alterar caracteres desconhecidos como ß e  por exemplo para _.

function Convert-ToLatinCharacters {
param([string]$inputString)
  [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($inputString))
}

$files = gci -recurse | where {$_.Name -match "[^\u0020-\u007F]"}
$files | ForEach-Object {
  $newname = Convert-ToLatinCharacters $_.Name
  $newname = $newname.replace('?','_')
  if ($_.Name -ne $newname) {
    $num=1
    $nextname = $_.Fullname.replace($_.Name,$newname)
    while(Test-Path -Path $nextname)
    {
      $next = ([io.fileinfo]$newname).basename + " ($num)" + ([io.fileinfo]$newname).Extension
      $nextname = $_.Fullname.replace($_.Name,$next)
      $num+=1
    }
    echo $nextname
    ren $_.Fullname $nextname
  }
}

1



The ß also does not get translated. Provavelmente porque o eszett deve ser mapeado para ss qual é dois personagens. (Bem ou isso ou B o que seria burro se você não estivesse tentando usar o 1337-speak.) Obviamente, não há mapeamento integrado, então você teria que lidar com isso separadamente. - Synetech
Obrigado por todo o tempo que você coloca nisso. Funciona um deleite. - RJFalconer


Eu acredito que isso vai funcionar ...

$Files = gci | where {$_.Name -match "[^\u0020-\u007F]"}

$Files | ForEach-Object {
$OldName = $_.Name
$NewName = $OldName -replace "[^\u0020-\u007F]", "_"
ren $_ $NewName
}

Eu não tenho esse intervalo de nomes de arquivos ASCII para testar embora.


2



Você pode criar facilmente alguns arquivos de teste com clique com o botão direito → Novo documento de texto Em seguida, digite alguns caracteres ASCII misturados com alguns caracteres ANSI / Unicode estendidos. - Synetech
Eu apenas corri um teste com a maioria das permutações. Não surpreendentemente, funcionou na maior parte, mas você pode encontrar erros se os nomes de arquivos somente em ASCII entrarem em conflito com nomes de arquivos existentes (o que também pode acontecer se outros arquivos forem renomeados, por exemplo, resumé1.doc, resumé2.doc, resumé.docetc.) - Synetech