Macacos me mordam Batman. Mas eu já tenho let, expr, bc, dc, $(( )), além do Usando o sed para contar, para que complicar mais ?!
Hei garoto-prodígio, para se ter conhecimento é preciso praticar. Lembre-se sempre do Sr. Miyagi:
Daniel-San, pinte a cerca! Lixe o assoalho!! Lixe com a mão esquerda, lixe com a mão direita....
Utilizando a técnica de lookup tables, podemos diminuir aquele sed para incrementar um número. Primeiro uma explicação sobre esta técnica.
Lookup tables é uma técnica ninja, onde utilizamos uma espada (s///
) afiada
para cortar nossa entrada e deixar somente o pedaço que nos interessa.
Vamos introduzir (ops) o assunto aos poucos. Imagine que tenhamos a seguinte entrada 12345678901, e queremos pegar o caractere após o 3, no caso o 4. Então nós usaríamos este sed:
prompt> echo 12345678901 | sed 's/.*3\(.\).*/\1/' 4 prompt>
Qualquer coisa até encontrarmos o 3 (.*3
), depois que encontrarmos,
criamos um grupo com o próximo caractere (\(.\)
), no caso o 4,
mais um .*
para pegar o resto da linha e, trocamos tudo isto, pelo
grupo que criamos, ou seja, o próximo caractere depois do 3.
Com o sed acima, podemos colocar no lugar do 3 qualquer número que irá retornar sempre o próximo, como se fosse uma lista circular (round-robin). Agora imagine que nós não sabemos qual vai ser o valor que estará no lugar do 3 ou que nós queremos passá-lo junto no echo. Para isto, criamos um caractere especial para separá-lo da nossa lista e, usamos backreference para achá-lo na lista. Exemplo:
prompt> echo 3=12345678901 | sed 's/^\(.\)=.*\1\(.\).*/\2/' 4 prompt> echo 9=12345678901 | sed 's/^\(.\)=.*\1\(.\).*/\2/' 0 prompt>
Criamos um grupo com o primeiro caractere, e dizemos ao sed para procurar
qualquer caractere repetido 0 ou mais vezes .*
até encontrar esse
caractere que está no primeiro grupo (\1
). Após encontrá-lo, cria um novo
grupo com o próximo caractere e substitua tudo isto por este segundo grupo.
No primeiro exemplo, seria a mesma coisa que:
prompt> echo 3=12345678901 | sed 's/^3=.*3\(.\).*/\1/'
Agora imagine que este sed esteje em um script, é muito mais fácil usar uma solução genérica do que, para cada número, ter que editar o script para colocar o número desejado. Na solução genérica, você passa o número quando chama o script.
Vamos a mais um exemplo para fixar na mente:
prompt> cat lookup_table.sed #!/bin/sed -f G s/$/12345678901/ s/^\(.*\)\(.\)\n.*\2\(.\).*/\1\3/ prompt>
Calma que é a mesma coisa.
A grande diferença deste script para o anterior é que aqui o
caractere que separa o caractere desejado da lista é o newline (\n
) e,
nos colocamos a lista dentro do script sed. sedsed
vai nos ajudar a entender. sedsed é um programa em python para sed escrito
pelo Aurélio. A saída dele é para console, assim vou utilizar um sed para
deixar sua saída mais legível para o nosso exemplo:
prompt> cat limpa.sed #!/bin/sed -f s/^[^:]*:\(.*\)\$$/\1/ s/^COMM:/ / prompt>
Testando:
prompt> echo 0 | sedsed -d --hide=HOLD -f lookup_table.sed | limpa.sed 0 G 0\n s/$/12345678901/ 0\n12345678901 s/^\(.*\)\(.\)\n.*\2\(.\).*/\1\3/ 1 1 prompt>
Entendendo a saída:
0\n
).
Com a próxima instrução (s/$/12345678901/
), colocamos após o (\n
) a
ordem de como incrementamos um número, em outras palavras, depois
do 1 vem o 2, depois 3 até 0, que vira 1 e recomeça o incremento.
O pattern space após a segunda instrução está assim:
0\n12345678901 que é igual a: 0 12345678901
É como no exemplo anterior, só que neste caso, nós passamos para o script só a entrada, ele te devolve o próximo caractere após o que você passou. Agora, com muita calma, vamos analisar o último comando, que é a chave da porta do tesouro. Como Jack, vamos por partes nesta expressão regular.
Primeiro, repare que o segundo grupo é de apenas 1 caractere e logo após
este caractere vem o newline (\n
). No exemplo, o primeiro grupo será
vazio. Até agora o que temos é o último caractere antes do (\n
) no grupo 2.
Com este grupo nós vamos indexar o que vem após o (\n
) para pegar o
caractere depois do que estiver no grupo 2. Usamos o grupo 2 para marcar um
ponto da nossa cadeia (12345678901), e pegamos o próximo caractere. Para
pegar o próximo caractere após o (\2
) criamos um terceiro grupo com ele.
Vamos executar mais uma vez este script com uma entrada diferente:
prompt> echo 123 | sedsed -d --hide=HOLD -f lookup_table.sed | limpa.sed 123 G 123\n s/$/12345678901/ 123\n12345678901 s/^\(.*\)\(.\)\n.*\2\(.\).*/\1\3/ 124 124 prompt>
Agora, no grupo 1 vai ter os caracteres 1 e 2. No grupo 2 vai estar o
caractere antes do (\n
), no caso o 3. Como criamos um grupo com o
caractere 3, que está em (\2
), vamos utilizá-lo para indexar a nossa lista de
caracteres, ou seja, após o (\n
) qualquer caractere repetido 0 ou
mais vezes até encontrar o que está no grupo 2, no caso o caractere 3.
Criamos um grupo com este próximo caractere, e trocamos tudo isto, pelo
grupo 1, que contém os caracteres 1e 2, e o grupo 3, que vai ter o
caractere 4. Calma, respire fundo e releia a última execução e este parágrafo.
Sacaram o poder das lookup tables =8)
Você pode fazer mapeamento n:1, 1:n, n:m. Vamos a um exemplo de 1:n, onde passamos um número e o sed nos devolve a fruta relativa a este número:
prompt> cat exemplo2.sed #!/bin/sed -f G s/$/1banana2maca3pera4abacaxi5uva/ s/^\(.\)\n.*\1\([a-z]*\).*/\2/ /[0-9]/s/.*/numero invalido/ prompt>
Testando:
prompt> echo 1 | ./exemplo2.sed banana prompt> echo 3 | ./exemplo2.sed pera prompt> echo 8 | ./exemplo2.sed numero invalido prompt>
Criamos um grupo com o primeiro caractere (antes do \n
), depois usamos
este caractere para indexar a nossa lista, e criamos um segundo grupo com
as letras após este número. A última instrução é para garatir que,
encontramos alguma fruta com o número que recebemos como entrada.
Último exemplo, vamos criar um mapeamento n:m
Vamos supor que você tenha uma lista de palavras, e que você tenha que sempre pegar a próxima da lista. Exemplo:
prompt> cat exemplo3.sed #!/bin/sed -f G s/$/arroz feijao massa batata arroz/ s/^\(.*\)\n.*\1 \([^ ]*\).*/\2/ prompt>
prompt> echo arroz | exemplo3.sed feijao prompt> echo feijao | exemplo3.sed massa prompt> echo batata | exemplo3.sed arroz prompt>
Usando o sedsed para ajudar:
prompt> echo arroz | sedsed -d --hide=HOLD -f exemplo3.sed | limpa.sed arroz G arroz\n s/$/arroz feijao massa batata arroz/ arroz\narroz feijao massa batata arroz s/^\(.*\)\n.*\1 \([^ ]*\).*/\2/ feijao feijao prompt>
Recomendo que, antes de seguir em frente, você leia Usando o sed para contar, para entender como o algoritmo abaixo funciona.
O que o algoritmo de somar faz basicamente é, trocar todos os 9s no final da linha por _, testar se precisa aumentar o número de casa decimais e depois incrementar o último dígito da linha. Para fazer esta última parte podemos usar uma lookup table. Vamos a mais uma tentativa:
prompt> cat incr_tentativa_6.sed #!/bin/sed -f s/^\(9*\)$/0\1/ :nove s/\(.*\)9\(_*\)$/\1_\2/ tnove G s/$/12345678901/ s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/ s/_/0/g prompt>
Repare que temos o loop inicial para trocar os 9s por _ e o teste (primeira
instrução) para testar se devemos ou não aumentar o número de casas
decimais. Na última instrução, trocamos todos os _ por 0. A diferença está
em como vamos incrementar um número entre 0 e 9. Ao invés de fazer uma
instrução para cada substituição, ou seja, s/0/1/
, s1/2/
, s/2/3/
...
utilizamos esta técnica. Vamos testar:
prompt> echo 0 | incr_tentativa_6.sed 1 prompt> echo 9 | incr_tentativa_6.sed 10 prompt> echo 99 | incr_tentativa_6.sed 100 prompt> echo 1499 | incr_tentativa_6.sed 1500 prompt> echo 1459 | incr_tentativa_6.sed 1460 prompt>
Para nos ajudar de novo, chamamos o Mr. sedsed.
prompt> echo 0 | sedsed -d --hide=HOLD -f incr_tentativa_6.sed | limpa.sed 0 s/^\(9*\)$/0\1/ 0 : nove s/\(.*\)9\(_*\)$/\1_\2/ 0 t nove G <<< --- adiciona o \n 0\n s/$/12345678901/ <<< --- adiciona a lista após o \n 0\n12345678901 s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/ <<< --- pegamos o que vem após o 0 1 s/_/0/g 1 1 prompt>
Como no nosso exemplo não tem 9, as primeiras instruções não têm efeito, ou seja, não mudam a entrada.
A instrução G
, anexa uma linha em branco no patter space, repare que
após sua execução nossa entrada vira (0\n
). Com a próxima instrução
(s/$/12345678901/
), colocamos após o \n
a ordem de como incrementamos
um número.
O grande esquema está na próxima instrução.
(s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/
), passando um número à ela, o
que retorna é o mesmo número, mmmmmaaassssss o último dígito será o
próximo na cadeia.
Mais 2 exemplos:
prompt> echo 1499 | sedsed -d --hide=HOLD -f incr_tentativa_6.sed | limpa.sed 1499 s/^\(9*\)$/0\1/ 1499 : nove s/\(.*\)9\(_*\)$/\1_\2/ <<< --- troca 9 por _ 149_ t nove s/\(.*\)9\(_*\)$/\1_\2/ <<< --- troca 9 por _ 14__ t nove s/\(.*\)9\(_*\)$/\1_\2/ 14__ t nove G 14__\n s/$/12345678901/ <<< --- adicionamos a lista 14__\n12345678901 s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/ <<< --- retorna o mesmo número mas com o **último** dígito "mais" 1 15__ s/_/0/g 1500 1500 prompt>
prompt> echo 1449 | sedsed -d --hide=HOLD -f incr_tentativa_6.sed | limpa.sed 1449 s/^\(9*\)$/0\1/ 1449 : nove s/\(.*\)9\(_*\)$/\1_\2/ 144_ t nove s/\(.*\)9\(_*\)$/\1_\2/ 144_ t nove G 144_\n s/$/12345678901/ 144_\n12345678901 s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/ <<< --- último dígito mais 1 145_ s/_/0/g 1450 1450 prompt>
O princípio de fazer a some é o mesmo de antes, a diferença está em como "somar" 1 ao último "dígito".
Seu filho está crescendo e já sabe contar. Hora de ensiná-lo que na vida nem sempre se soma, também temos que diminuir e, em certas ocisiões, ficamos devendo alguma coisa. =8)
Depois de aprender a somar utilizando lookup table, vamos a um exemplo onde passamos o valor e o tipo de operação que desejamos fazer sobre este número.
prompt> cat count.sed #!/bin/sed -f bmain :add :nine s/\(.*\)9\(_*\)$/\1_\2/ tnine s/^\(-\)\?\(_*\)$/\10\2/ G s/$/12345678901/ s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/ s/_/0/g s/^[+-]0$/0/ q :sub :zero s/\(.*\)0\(_*\)$/\1_\2/ tzero s/^1\(_\+\)$/\1/;tfim /^_$/{s//-1/;q;} G s/$/98765432109/ s/^\(.*\)\(.\)\(_*\)\n.*\2\(.\).*/\1\4\3/ :fim s/_/9/g s/^[+-]0$/0/ q :main s/-\([0-9]*\) *+$/-\1/;tsub s/-\([0-9]*\) *-$/-\1/;tadd s/ *+$//;tadd s/ *-$//;tsub prompt>
Exemplos de uso:
prompt> echo 0 + | count.sed 1 prompt> echo 0 - | count.sed -1 prompt> echo -10 + | count.sed -09 prompt> echo -10 - | count.sed -11 prompt> echo 100 - | count.sed 99 prompt> echo 100 + | count.sed 101 prompt> echo 99 + | count.sed 100 prompt> echo -99 + | count.sed -98 prompt> echo -99 - | count.sed -100 prompt>
Para subtrair temos que cuidar se o número é 0 e inverter a nossa tabela, ou seja, 98765432109.
Com um pouco de análise você verá que os princípos são os mesmo.
E assim, terminamos mais um tour pelo maravilhoso mundo do sed! =8)
This HTML page is (see source)