Os comandos tr podem ser encadeados para evitar vários processos tr em um pipeline?

11

Eu tenho um monte de arquivos txt, gostaria de apresentá-los em letras minúsculas, apenas alfabéticos e uma palavra por linha, posso fazê-lo com vários trcomandos em um pipeline como este:

tr -d '[:punct:]' <doyle_sherlock_holmes.txt | tr '[:upper:]' '[:lower:]' | tr ' ' '\n'

É possível fazer isso em uma varredura? Eu poderia escrever um programa em C para fazer isso, mas eu sinto que há uma maneira de fazê-lo usando tr, sed, awkou perl.

tlehman
fonte
Qual sistema operacional você está usando? Você tem acesso às ferramentas GNU?
terdon

Respostas:

9

Você pode combinar várias traduções (exceto casos complexos que envolvem conjuntos dependentes de localidade sobrepostos), mas não é possível combinar exclusão com tradução.

<doyle_sherlock_holmes.txt tr -d '[:punct:]' | tr '[:upper:] ' '[:lower:]\n'

trÉ provável que duas chamadas sejam mais rápidas que uma única chamada para ferramentas mais complexas, mas isso depende muito do tamanho da entrada, das proporções de caracteres diferentes, da implementação tre das ferramentas concorrentes, do sistema operacional, do número de núcleos, etc.

Gilles 'SO- parar de ser mau'
fonte
Não estou certo de re combinandotr -s '[:upper:] [:punct:]' '[:lower:]\n' <doyle_sherlock_holmes.txt
Costas
1
@Costas Isso transformaria a pontuação em novas linhas. Pode ser bom para este aplicativo específico, mas a saída não é a mesma que a original.
Gilles 'SO- stop be evil'
@ Costas - embora a coisa da nova linha possa ser acessada aqui, eu não acho que apertar os caracteres maiúsculos seria. Por exemplo: printf 'A.AAAA,A' | tr -s '[:upper:] [:punct:]' '[:lower:][\n*]'gets a\na\na', e a transformação para ... '[:lower:]\n'pode não necessariamente fazer absolutamente nada '[:punct:]'- alguns trs truncarão set1 para corresponder a 2 e outros farão um implícito [\n*]. É melhor apenas usar o alcance lá.
mikeserv
4

Aqui estão algumas abordagens:

  • GNU grepe tr: encontre todas as palavras e as minúsculas

    grep -Po '\w+' file | tr '[A-Z]' '[a-z]'
  • GNU grep e perl: como acima, mas o perl manipula a conversão para minúsculas

    grep -Po '\w+' file | perl -lne 'print lc()'
  • perl: encontre todos os caracteres alfabéticos e imprima-os em letras minúsculas (obrigado @steeldriver):

    perl -lne 'print lc for /[a-z]+/ig' file
  • sed: remova todos os caracteres que não são alfabéticos ou espaços, substitua todos os caracteres alfabéticos por suas versões em minúsculas e substitua todos os espaços por novas linhas. Observe que isso pressupõe que todo espaço em branco seja espaços, sem guias.

    sed 's/[^a-zA-Z ]\+//g;s/[a-zA-Z]\+/\L&/g; s/ \+/\n/g' file
terdon
fonte
2
Algo como perl -lne 'print lc for /[[:alpha:]]+/g'também funcionaria? ou é estilo pobre? (Eu sou novo para Perl e tentando aprender!)
steeldriver
@steeldriver sim, seria bom! Se você está aprendendo Perl, tenho certeza de que encontrou o seu lema: TMTOWTDI :) Obrigado, acrescentarei esse.
terdon
3
Com nova versão (> 4.2.1)sed -z 's/\W*\(\w\+\)\W*/\L\1\n/g'
Costas
@ Ah ah, sedpode fazer \wagora? Legal!
terdon
@terdon - faz isso por um tempo, mas, como Costas não mencionou, acho que a coisa mais interessante sobre o comentário acima é seda -zopção de delimitação de ero do GNU - ele passa por \0NULs em vez de novas linhas. Muito legal quando você faz algo como tar -c . | tr -s \\0 | sed -z ...- mas meio lento.
mikeserv
4

Sim. Você pode fazer isso com trum código de idioma ASCII (que é, para um GNU de trqualquer maneira, uma espécie de sua única competência) . Você pode usar as classes POSIX ou pode referenciar os valores de bytes de cada caractere pelo número octal. Você também pode dividir as transformações entre os intervalos.

LC_ALL=C tr '[:upper:]\0-\101\133-140\173-\377' '[:lower:][\n*]' <input

O comando acima transforma todos os caracteres em maiúsculas em minúsculas, ignora completamente os caracteres em minúsculas e transforma todos os outros caracteres em novas linhas. Claro, então você acaba com uma tonelada de linhas em branco. A tr -sopção de repetição de queeze pode ser útil nesse caso, mas se você a usar ao lado da transformação [:upper:]para, também acabará [:lower:]apertando caracteres maiúsculos. Dessa forma, ainda requer um segundo filtro como ...

LC... tr ... | tr -s \\n

...ou...

LC... tr ... | grep .

... e acaba sendo muito menos conveniente do que fazer ...

LC_ALL=C tr -sc '[:alpha:]' \\n <input | tr '[:upper:]' '[:lower:]'

... que comprime o -complement de caracteres alfabéticos por sequência em uma única nova linha de uma peça e depois faz a transformação de cima para baixo no outro lado do pipe.

Isso não quer dizer que faixas dessa natureza não sejam úteis. Coisas como:

tr '\0-\377' '[1*25][2*25][3*25][4*25][5*25][6*25][7*25][8*25][9*25][0*]' </dev/random

... pode ser bastante útil, pois converte os bytes de entrada em todos os dígitos em um espectro de dispersão de seus valores. Não desperdice, não queira, você sabe.

Outra maneira de fazer a transformação pode envolver dd.

tr '\0-\377' '[A*64][B*64][C*64][D*64]' </dev/urandom |
dd bs=32 cbs=8 conv=unblock,lcase count=1

dadbbdbd
ddaaddab
ddbadbaa
bdbdcadd

Como ddpode fazer as duas coisas unblocke as lcaseconversões ao mesmo tempo, pode até ser possível passar muito do trabalho para ele. Mas isso só pode ser realmente útil se você puder prever com precisão o número de bytes por palavra - ou pelo menos conseguir preencher cada palavra com espaços de antemão para uma contagem previsível de bytes, porque unblockcome espaços à direita no final de cada bloco.

mikeserv
fonte
+2 pontos de bônus para obter ddenvolvido :)
tlehman
@TobiLehman - Estou muito satisfeito por você aprovar.
mikeserv