Manipulação de strings em Bash

stringbash_abertura

Não só é possível utilizar expressões regulares diretamente dentro do Bash e assim acelerar a execução de um script como também pode-se aproveitar algumas das facilidades no tratamento de strings — ou cadeias de caracteres se preferir — para não só ganhar alguma velocidade como também dar um pouco mais de clareza no código e, ao mesmo tempo, resolver algumas limitações conhecidas de algumas ferramentas.

Maiúsculas e minúsculas

Talvez a forma mais conhecida para alterar a caixa de uma string em um script seja utilizando o comando tr para trocar as letras¹:

$ MENSAGEM="AaBbCcDdEeFf"
$ echo ${MENSAGEM} | tr [:upper:] [:lower:]
aabbccddeeff
$ echo ${MENSAGEM} | tr [:lower:] [:upper:]
AABBCCDDEEFF

Claro que esta abordagem apresenta um pequeno problema quando o tr se encontra com caracteres acentuados em UNICODE, como no caso deste exemplo²:

$ MENSAGEM="Você deve tomar o ônibus elétrico número três. Não tome o ônibus grená!"
$ echo ${MENSAGEM} | tr [:lower:] [:upper:]
VOCê DEVE TOMAR O ôNIBUS ELéTRICO NúMERO TRêS. NãO TOME O ôNIBUS GRENá!

Mas é um problema que pode ser contornado utilizando awk ao invés de tr.

$ echo ${MENSAGEM} | awk '{ print toupper($0); }'
VOCÊ DEVE TOMAR O ÔNIBUS ELÉTRICO NÚMERO TRÊS. NÃO TOME O ÔNIBUS GRENÁ!

Porém é possível utilizar uma solução inteiramente em Bash com ajuda do comando declare para definir (leia: forçar) que determinada string contenha apenas caracteres em caixa baixa com o parâmetro -l ou em caixa alta usando o -u.

$ declare -l BAIXA=${MENSAGEM}
$ declare -u ALTA=${MENSAGEM} 
$ echo ${BAIXA}
você deve tomar o ônibus elétrico número três. não tome o ônibus grená!
$ echo ${ALTA}
VOCÊ DEVE TOMAR O ÔNIBUS ELÉTRICO NÚMERO TRÊS. NÃO TOME O ÔNIBUS GRENÁ!

Basta colocar o valor desejado na string e deixar o Bash automaticamente cuidar da conversão e sem se atrapalhar com os acentos.

Aliás é possível criar funções específicas para a alteração de caixa, algo assim:

(¹) Pois é,  [:lower:] e [:upper:] são o jeito POSIX de dizer [a-z] e [A-Z], respectivamente.

(²) A frase realmente não faz muito sentido sua função é apenas ter acentos em certas posições.

Substituindo conteúdo

O comando sed é geralmente o escolhido para a substituição de caracteres em strings.

$ echo ${MENSAGEM} | sed "s/ônibus/bonde/"
Você deve tomar o bonde elétrico número três. Não tome o ônibus grená!

ou então,

$ echo ${MENSAGEM} | sed "s/ônibus/bonde/g"
Você deve tomar o bonde elétrico número três. Não tome o bonde grená!

Mas é possível fazer a mesma coisa usando apenas a substituição de strings do próprio Bash.

Para substituir somente a primeira ocorrência.

$ echo ${MENSAGEM/ônibus/bonde}
Você deve tomar o bonde elétrico número três. Não tome o ônibus grená!

Ou para trocar todas as ocorrências.

$ echo ${MENSAGEM//ônibus/bonde}
Você deve tomar o bonde elétrico número três. Não tome o bonde grená!

E apenas lembrando que o conteúdo da string não será alterado — aliás, o mesmo também acontece nos exemplos utilizando o sed.

Removendo

Assim como se substitui também é possível remover uma parte da string, para excluir uma sequência no começo.

$ echo ${MENSAGEM#Você*três.}
Não tome o ônibus grená!

Para fazer o mesmo no final.

$ echo ${MENSAGEM%Não*grená\!}
Você deve tomar o ônibus elétrico número três.

E, sim, o “*” entre as palavras serve para indicar qualquer coisa entre elas e poupar de ter que digitar tudo. 🙂

Cortando strings

É possível extrair partes de uma string utilizando o comando cut³ como, por exemplo, separar os primeiros 35 caracteres:

$ echo $MENSAGEM | cut -c 1-35 
Você deve tomar o ônibus elétric

Só que aqui há um problema pois somente 32 caracteres na sequência foram retornados. Isto se deve ao fato de que o cut tratou cada um dos três caracteres em UNICODE, os “ê”, “ô” e “é”, como se fossem dois caracteres.

$ echo -n $MENSAGEM | cut -c 1-35 | hexdump -C
00000000  56 6f 63 c3 aa 20 64 65  76 65 20 74 6f 6d 61 72  |Voc.. deve tomar|
00000010  20 6f 20 c3 b4 6e 69 62  75 73 20 65 6c c3 a9 74  | o ..nibus el..t|
00000020  72 69 63 0a                                       |ric.|
00000024

A existência de um 0x0a no final é efeito colateral da utilização do cut e não do echo.

Neste caso acaba sendo bem mais seguro utilizar diretamente o Bash para a tarefa:

$ echo -n ${MENSAGEM:0:35}
Você deve tomar o ônibus elétrico n

Só para confirmar a saída de exatos 35 caracteres e em 38 bytes.

$ echo -n "${MENSAGEM:0:34}" | hexdump -C
00000000  56 6f 63 c3 aa 20 64 65  76 65 20 74 6f 6d 61 72  |Voc.. deve tomar|
00000010  20 6f 20 c3 b4 6e 69 62  75 73 20 65 6c c3 a9 74  | o ..nibus el..t|
00000020  72 69 63 6f 20 6e                                 |rico n|
00000026

Esta abordagem permite facilmente pegar tanto os 35 primeiros como também os 35 últimos caracteres — o espaço após o “:” é importante.

$ echo ${MENSAGEM: -35}
mero três. Não tome o ônibus grená!

Aqui há ainda duas sintaxes possíveis:

  • ${«variável»:«posição»} — Retornará o conteúdo da string de «posição» até o final. Utilizando um número negativo este será subtraído do comprimento da string para o cálculo da posição inicial e
  • ${«variável»:«posição»:«nº de caracteres»} — As mesmas considerações da sintaxe acima porém retornará apenas a quantidade definida em «nº de caracteres».

E só para lembrar que o cut que começa a contagem 1 enquanto que o Bash em 0 — como a maioria das linguagens de programação.

(³) Apenas para constar que também é possível usar head e tail para fazer operações similares.

Tamanho da string

A variável MENSAGEM tem 71 caracteres; o tamanho de uma string pode ser verificado tanto com a ajuda do comando wc mas algo importante deve ser considerado:

$ echo -n ${MENSAGEM} | wc -c
79
$ echo -n ${MENSAGEM} | wc --chars
71

Deve-se utilizar o parâmetro “- -chars” (ou ” -m”) ao invés do (óbvio)  “-c”  (que é de “- -bytes”) para que os caracteres UNICODE sejam tratados adequadamente.

Claro, um outra opção para saber com exatidão o comprimento da string é com o próprio Bash:

$ echo ${#MENSAGEM}
71

Que, aliás, foi citado rapidamente quando tratei os arrays em Bash.

Concluindo…

Cale realmente a pena utilizar o Bash ao invés das “abordagens clássicas”? Apenas para ilustrar com um os exemplos acima de substituição de strings, executados 100 mil vezes a versão com o sed levou 2’33,5″ enquanto que a mesma operação usando apenas o Bash demorou apenas 3,596″; um considerável ganho de velocidade.

E para terminar, uma brincadeira misturando quase todos os exemplos acima! 🙂

Até!

Anúncios

6 comentários sobre “Manipulação de strings em Bash

  1. Que artigo fantástico. Parabens pelo trabalho. Eu escrevo poucos scripts, mas neles eu uso quantidades absurdas de cut e sed e vendo seu artigo vejo que posso melhorar muito meu código. Valeu!

    Curtir

      • Pode melhorar, mas deve aumentar o custo de execução, seja de memória, tempo de carga e tempo de execução. Repare que o CUT têm menos de 50 Kbytes, enquanto que o GAWK, têm pouco menos de 660 Kbytes, só para começar pelo custo de memória.

        O GAWK é muito poderoso, reconheço, gosto dele, mas uso ele quando acho que deve ser usado, se o trabalho pode ser feito pelo CUT ou o SED, não têm porque usar o GAWK.

        O GAWK possui uma linguagem de programação, ele é um interpretador! Imagine carregar isso na memória do seu servidor, para fazer apenas uma separação de campos!

        echo -e “12345\tJoão\tEngenheiro” | cut -f2
        echo -e “12345\tJoão\tEngenheiro” | gawk ‘{ print $2 }’

        Repete os comandos acima umas mil vezes e verá que o GAWK leva quase o dobro do tempo (se fizer 100 mil vezes tb dá algo semelhante).

        Mas vê só, o artigo está ensinando que é melhor usar o próprio BASH, que já está na memória, daí se economiza nas chamadas de CUT, SED e, em alguns casos, o GAWK, só não sei se vai ficar tão legível como vc quer.

        Curtir

  2. Pingback: Troca de caixa em Bash (método alternativo) | giovannireisnunes

  3. Pingback: Declaração de variáveis no Bash | giovannireisnunes

  4. Pingback: Mais substituições em Bash | giovannireisnunes

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s