Usando uniq em texto unicode

3

Eu quero remover linhas duplicadas de um arquivo com palavras do script Syriac . O arquivo de origem tem 3 linhas, 1 e 3 são idênticas.

$ cat file.txt 
ܐܒܘܢ
ܢܗܘܐ
ܐܒܘܢ

Quando uso sort e uniq , o resultado presume que todas as 3 linhas são idênticas, o que está errado:

$ cat file.txt | sort | uniq -c
      3 ܐܒܘܢ

Definir localmente como siríaco também não ajuda.

$ LC_COLLATE=syr_SY.utf8 cat file.txt | sort | uniq -c      
     3 ܐܒܘܢ

Por que isso aconteceria? Estou usando o Kubuntu 18 e o bash, se isso importa.

    
por evb 16.09.2018 / 10:47

3 respostas

5

A implementação GNU de uniq como encontrada no Ubuntu, com -c , não reporta contagens de linhas idênticas contíguas, mas conta com linhas contíguas que ordenam o mesmo.

A maioria das localidades internacionais nos sistemas GNU tem esse bug que muitos caracteres completamente não relacionados foram definidos com a mesma ordem de classificação, porque sua ordem de classificação não é definida. A maioria dos outros sistemas operacionais garante que todos os caracteres tenham uma ordem de classificação diferente.

$ expr ܐ = ܒ
1

( expr ' = operator, para argumentos que não são numéricos, retorna 1 se operandos ordenarem o mesmo, 0 caso contrário).

É o mesmo com ar_SY.UTF-8 ou en_GB.UTF-8 .

O que você precisa é de uma localidade em que esses caracteres recebam uma ordem de classificação diferente. Se o Ubuntu tivesse localidades para o idioma siríaco, você poderia esperar que esses caracteres tivessem uma ordem de classificação diferente, mas o Ubuntu não tem essas localidades.

Você pode examinar a saída de locale -a para obter uma lista de localidades suportadas. Você pode ativar mais locales executando dpkg-reconfigure locales as root . Você também pode definir mais localidades manualmente usando localedef com base nos arquivos de definição em /usr/share/i18n/locales , mas não encontrará dados para a linguagem Siríaco.

Observe que em:

LC_COLLATE=syr_SY.utf8 cat file.txt | sort | uniq -c

Você só está configurando a variável LC_COLLATE para o comando cat (o que não afeta a maneira como ela gera o conteúdo do arquivo, cat não se importa com o agrupamento nem mesmo com a codificação de caracteres, pois não é utilitário de texto). Você deseja configurá-lo para sort e uniq . Você também deseja definir LC_CTYPE para uma localidade que tenha um conjunto de caracteres UTF-8.

Como seu sistema não tem syr_SY.utf8 locale, é o mesmo que usar o C locale (o local padrão).

Na verdade, aqui a localidade C ou C.UTF-8 é provavelmente a localidade que você gostaria de usar.

Nesses locais, a ordem de intercalação é baseada no ponto de código, ponto de código Unicode para C.UTF-8, valor de byte para C, mas isso acaba sendo o mesmo que a codificação de caracteres UTF-8 tem essa propriedade. / p>

$ LC_ALL=C expr ܐ = ܒ
0
$ LC_ALL=C.UTF-8 expr ܐ = ܒ
0

Então, com:

(export LANG=ar_SY.UTF-8 LC_COLLATE=C.UTF-8 LANGUAGE=syr:ar:en
 unset LC_ALL
 sort <file | uniq -c)

Você teria um LC_CTYPE com UTF-8 como o conjunto de caracteres, uma ordem de agrupamento com base no ponto de código e outras configurações relevantes para sua região, portanto, por exemplo, mensagens de erro em siríaco ou árabe se GNU coreutils sort ou uniq mensagens foram traduzidas nesses idiomas (ainda não foram).

Se você não se importa com essas outras configurações, é tão fácil (e também mais portátil) usar:

<file LC_ALL=C sort | LC_ALL=C uniq -c

Ou

(export LC_ALL=C; <file sort | uniq -c)

como @isaac já mostrou.

    
por 16.09.2018 / 21:17
4

Uma solução portátil (simplista):

$ ( LC_ALL=C sort syriac.txt | LC_ALL=C uniq -c )
      2 ܐܒܘܢ
      1 ܢܗܘܐ

Para aqueles que não têm uma fonte que possa renderizar o script siríaco:

$ ( LC_ALL=C sort syriac.txt | LC_ALL=C uniq -c ) | xxd
00000000: 2020 2020 2020 3220 dc90 dc92 dc98 dca2        2 ........
00000010: 0a20 2020 2020 2031 20dc a2dc 97dc 98dc  .      1 .......
00000020: 900a                                     ..

EDITAR Isso está mais perto de um hack do que de uma solução real. Isso faz com que sort e uniq processem cada linha com os valores de bytes individuais, em vez da ordem de agrupamento dada por uma tabela de localidade. Uma localidade equivalente a ser usada (como "ordem de classificação de ponto de código" UTF-8) é a mesma ordem que a "ordem de classificação de valor de byte") é C.UTF-8 .

Este trabalho na maioria dos sistemas AFAICT.

Uma solução equivalente é:

$ ( export LC_COLLATE=C.UTF-8; <syriac.txt sort | uniq -c )

O problema básico é que os caracteres da língua siríaca (pontos de código Unicode U + 0700-U + 074F Syriac e U + 0860-U + 086F Suplemento Siríaco ) ainda não tem nenhuma ordem de ordenação de agrupamento.

Esse é um problema com os arquivos de definição de localidade dentro de /usr/share/i18n/locales (debian / ubuntu) e nem sequer listado como um possível idioma em less /usr/share/i18n/SUPPORTED . Isso significa que as informações para esse idioma precisam ser relatadas para o Debian i18n e incorporadas em arquivos locais válidos.

Normalmente, um nome de local geralmente tem o formato 'll_CC'. Aqui, "ll" é um código de idioma de duas letras ISO 639 e "CC" é um código de país de duas letras da ISO 3166. E Syriac (variante ocidental) Syrj .

Mas Syriac tem um código de três letras já atribuído na ISO 639-2 e Lista oficial dos códigos 639-2

O código de país (ISO 3166) é geralmente um código de duas letras e provavelmente deve ser SY. Lista de códigos de países ISO 3166 .

Apenas definir uma ou todas as variáveis de ambiente relacionadas à localidade não é suficiente e pode falhar (como acontece no seu caso), pois todas as tabelas estão faltando. Essas tabelas definem nomes de meses, dias da semana, fórmulas de ano, formato de hora, formato de moeda, idioma para erros relatados (se houver uma tradução disponível), etc. Por favor, leia: Para que devo definir minha localidade e quais são as implicações de fazê-lo?

Quando os pontos de código Unicode não têm uma ordem de agrupamento explicitamente definida, eles podem se tornar todos iguais: undefined. É isso que acontece aqui.

Podemos listar os pontos de código do seu arquivo (apenas para usar um ponto de exemplo) com:

$ echo $(cat syriac.txt | grep -oP '\X' | sort)
ܐ ܒ ܘ ܢ ܢ ܗ ܘ ܐ ܐ ܒ ܘ ܢ 

mas se tentarmos obter apenas valores únicos, todos serão apagados:

$ echo $(cat syriac.txt | grep -oP '\X' | sort -u )
ܐ

porque todos os caracteres têm o mesmo valor de agrupamento (peso):

$ a=ܐ
$ b=ܒ
$ [[ $a == [=$b=] ]] && echo yes
yes

isso significa que o valor var a está na mesma posição de agrupamento [=…=] de var b value.

Em vez disso, isso lista os caracteres não repetidos:

$ echo $(cat syriac.txt | grep -oP '\X' | LC_COLLATE=C.UTF-8 sort -u )
ܐ ܒ ܗ ܘ ܢ
    
por 16.09.2018 / 16:36
3

Primeiro conjunto LC_CTYPE :

$ export LC_CTYPE=syr_SY.utf8
$ <infile sort |uniq -c
      2 ܐܒܘܢ
      1 ܢܗܘܐ
    
por 16.09.2018 / 11:09