Couting todos os caracteres pela classe de caracteres BASH

2

Eu preciso contar eficientemente cada caractere de um arquivo arbitrário por seu caracter CLASS (como definido pela man page BASH); ou seja,

[[:alnum:]], [[:alpha:]], [[:ascii:]], [[:blank:]], [[:cntrl:]], [[:digit:]], [[:graph:]], [[:lower:]], [[:print:]], [[:punct:]], [[:space:]], [[:upper:]], [[:word:]] and [[:xdigit:]]

Quando o arquivo é processado, exiba em uma única linha as contagens resultantes para cada um, mesmo quando zero.

Pesquisas na Web não foram proveitosas em encontrar algo nesse sentido.

O arquivo arbitrário ( /tmp/f1.txt ) conterá uma variedade de textos / dados diversos.

Eu não estou procurando processar binários ELF nem o conteúdo UniCode (ou qualquer forma de multi-byte).

Não estou preocupado com a contagem de linhas ( CR e / ou LF ), apenas fixadas em acumular uma contagem de cada 'caractere' no arquivo de destino pelas classes acima.

Eu pretendo que isso acabe como um padrão function() em um script bash maior. Bash / sed / awk e similares são desejados; enquanto perl / python / ruby nem tanto.

Arquivos de dados de amostra podem ser:

  • Zero bytes, ou seja, nenhum conteúdo.

  • Um único caractere

  • Uma única palavra

  • Várias palavras separadas por espaço em branco

  • Várias linhas intercaladas com espaço em branco e / ou retornos de carro e / ou alimentações de linha.

  • Para arquivos de várias linhas, pode não haver um CR ou LF para significar o final da última linha (no entanto, todos os caracteres ainda devem ser contados).

por user43609 23.07.2013 / 01:34

3 respostas

2

file=myfile
for class in alnum alpha blank cntrl digit graph lower print punct space upper xdigit
do
  printf '%7s: %d\n' "$class" "$(tr -Cd "[:${class}:]" < "$file" | wc -m)"
done

ascii e word não são classes de caracteres padrão e são bash específicas. word é alnum mais sublinhado e ascii é caracteres de 0 a 127, então você pode fazer:

printf '%7s: %d\n' word "$(tr -Cd "_[:alnum:]" < "$file" | wc -m)"
printf '%7s: %d\n' ascii "$(LC_ALL=C tr -cd '
file=myfile
for class in alnum alpha blank cntrl digit graph lower print punct space upper xdigit
do
  printf '%7s: %d\n' "$class" "$(tr -Cd "[:${class}:]" < "$file" | wc -m)"
done
-7' < "$file" | wc -c)"

(note que a implementação GNU de tr , a partir do coreutils-8.22, não funcionará com caracteres multi-byte).

    
por 23.07.2013 / 09:10
1

Parece uma aula divertida! O que é isso?

Isso vai te levar a maior parte do caminho; sed parece não suportar: ascii: ou: word :, mas:

for f in alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit
do
  echo "$f: $(sed s/[^[:$f:]]//g b.txt | tr -d '\n' | wc -c)"
done

Onde usamos sed para remover tudo exceto os caracteres de que gostamos, excluímos todas as linhas em branco e obtemos apenas a contagem de caracteres restantes.

Deve ser relativamente preciso, com a possível exceção de erros de +/- 1 ou de fator-de-dez.

    
por 23.07.2013 / 03:03
0

Eu não vou procurar quais personagens se encaixam em qual classe - provavelmente você pode descobrir isso e / ou apenas consultar as outras respostas. Mas isso lhe dará uma representação inequívoca do seu caractere de arquivo por caractere sem perder nenhum:

 _c2o() { od -A n -t o1 -w1 -v | tr -dc '0-9\n' ; } 
 _c2o <file
 163
 150
 072
 040
 167
 141
 162
 156
 151
 156
 147

Essa é uma função que eu uso de várias maneiras diferentes. Cada linha é uma única mordida expressa em formato octal - embora od seja muito configurável. Mas desta forma você pode facilmente apenas grep ou sed para seus valores alvo e implementar um contador de linha. É realmente um pedaço de bolo. E é muito rápido.

Ok, então fui em frente e fiz as aulas de qualquer maneira:

_classes() { set -- ${classes=alnum alpha blank cntrl digit graph lower print punct space upper xdigit}
        while ${1+:} false ; do
                printf %b $(printf '\%04o\n' $(seq 0 127)) |
                tr -dc "[:${1}:]" | {
                        printf "$1='"
                        _c2o
                        printf "'\n"
                } ; shift
        done
}

Execute o exemplo acima e você terá uma saída como:

xdigit='060
061
062
063
064
065
066
067
070
...
'

De lá, eu imagino algo como:

eval "$(_classes)"
for class in $classes ; do
    eval "$class=\$(_c2o <file | grep -c -F "$class")"
done

Eu preciso entender isso um pouco melhor - mas isso faz todo o trabalho.

    
por 07.05.2014 / 09:03