Analisando e transformando textos no Bash

Curso de Bash

Seção do curso de Bash sobre processamento de textos com wc, tr, diff, patch e paste.

Comandos para transformar, analisar e extrair dados de arquivos de texto.

Conteúdo

  1. Contando linhas, palavras e caracteres com wc
  2. Substituindo caracteres com tr
  3. Comparando linhas com diff
  4. Incluindo linhas diferentes com patch
  5. Dividindo linhas entre arquivos com split
  6. Mesclando linhas com paste
  7. Exercícios

Contando linhas, palavras e caracteres com wc

Utilizaremos o arquivo criado abaixo nesta seção.

printf "X Y\nN\n" > contagem
cat contagem
X Y
N

wc conta a quantidade de linhas, caracteres e palavras de arquivos de texto.

A opção -l exibe a quantidade de novalinha (que é o caractere inserido quando pressionamos Enter para encerrar cada linha e iniciar a próxima) seguida do nome do arquivo.

wc -l contagem
2 contagem

A opção -w exibe a quantidade de palavras seguida do nome do arquivo.

wc -w contagem
3 contagem

A opção -m exibe a quantidade de caracteres seguida do nome do arquivo.

wc -m contagem

espaço e novalinha são contados também no arquivo contagem, por isso o resultado é 6.

6 contagem

Sem opções, wc exibe quantidade de linhas, palavras, bytes e nome do arquivo.

wc contagem
2 3 6 contagem

Sem argumentos ou com - como argumento, wc lê a entrada padrão. Pressione Ctrl + D uma vez para terminar quando em uma linha vazia e duas quando conteúdo foi digitado.

wc

Aqui Bash foi digitado e Ctrl + D pressionado duas vezes. A saída de wc é exibida na mesma linha.

Bash      0       1       4

Abaixo Bash foi digitado, Enter foi pressionado e então Ctrl + D pressionado uma vez.

Bash
      1       1       5

Note que o primeiro número passou de 0 para 1 entre as duas saídas pois Enter inseriu o caractere "invisível" novalinha. E a quantidade de bytes aumentou de 4 para 5 pelo mesmo motivo.

⚠️ bytes e caracteres não são intercambiáveis.

P. ex. á equivale a um caractere e dois bytes.

wc -m

Na saída abaixo, á foi digitado e Ctrl + d pressionado duas vezes. 1 é a saída efetiva de wc.

á1

A opção -c de wc conta a quantidade de bytes.

wc -c
á2

Com mais de um argumento, wc exibe a soma.

wc -w contagem contagem
 3 contagem
 3 contagem
 6 total

Substituindo caracteres com tr

tr remove ou substitui um ou mais caracteres em textos.

tr não recebe arquivos como argumento, apenas por meio de redirecionamento de entrada. O operador < realiza o redirecionamento do arquivo à direita.

O arquivo frase abaixo será utilizado ao longo desta seção.

printf "Bash, \n1 shell para Linux.\n" > frase
cat frase
Bash,
1 shell para Linux.

Removendo caracteres com -d

A opção -d de tr remove os caracteres enviados como argumento. O primeiro argumento é chamado de conjunto1.

Abaixo, os dois l de shell são removidos.

tr -d l < frase
Bash,
1 she para Linux.

Todos caracteres a serem excluídos devem ser definidos no primeiro argumento, sem espaço entre eles. A ordem em que aparecem é indiferente: hl, como abaixo, é equivalente à lh.

tr -d hl < frase
Bas,
1 se para Linux.

Sequências de caracteres podem ser definidas utilizando o formato inicio-fim, novamente sem espaço entre os caracteres.

No argumento as-x0-2 abaixo é especificado que a letra a será removida bem como as letras entre s e x e os números entre 0 e 2. Efetivamente as-x0-2 é uma forma resumida de astuvwx012.

tr -d as-x0-2 < frase
Bh,
 hell pr Lin.

Classes de caractere são sequências pré-definidas como [:punct:] que representa todas as pontuações. Consulte a página de manual de tr para visualizar a lista completa ou leia no próximo capítulo.

tr -d [:punct:] < frase
Bash
1 shell para Linux

Alguns caracteres apresentam certa dificuldade em serem representados diretamente. Caso de novalinha que, por esse motivo, é representado pela sequência \n envolvida em um par de '.

Abaixo os dois novalinha são removidos (por isso o prompt aparece na mesma linha da saída).

tr -d '\n' < frase
Bash, 1 shell para Linux.

Substituindo caracteres

Com dois argumentos, tr substitui os caracteres no primeiro pelos do segundo.

tr 1 o < frase
Bash,
o shell para Linux.

O primeiro caractere do conjunto1 será substituído pelo primeiro caractere do conjunto2 e assim por diante.

Abaixo, l é substituído por L e espaço por .. espaço é envolto por '' para que não seja interpretado como separador de palavras.

tr l' ' L. < frase
Bash,.
1.sheLL.para.Linux.

⚠️ Contudo quando o conjunto1 contém mais caracteres que o conjunto 2, tr repete a última letra do conjunto2 para que ambas tenham a mesma quantidade de caracteres.

O conjunto2 HS portanto é efetivamente interpretado como HSSS já que o conjunto1 contém 4 caracteres.

tr hsal HS  < frase
BSSH,
1 SHeSS pSrS Linux.

Classes de caracteres também são suportadas.

Abaixo, letras minúsculas são substituídas por maiúsculas.

tr [:lower:] [:upper:] < frase
BASH,
1 SHELL PARA LINUX.

A opção -c transforma o conjunto1 em todos os caracteres não especificados no conjunto1. Função conhecida como complemento de conjunto.

Abaixo todos os caracteres diferentes de a-zA-Z são substituídos por ..

tr -c a-zA-Z . < frase
Bash.....shell.para.Linux..

Deduplicação de caracteres com -s

Sequências repetidas de caracteres são substituídas por apenas uma ocorrência daquele.

Abaixo, ll de shell torna-se l.

tr -s l < frase
Bash,
1 shel para Linux.

Comparando linhas com diff

diff recebe dois arquivos, os compara linha a linha e exibe na saída as linhas que diferem entre eles e como elas diferem. Caso não haja diferenças nenhuma saída é produzida.

diff não altera nenhum dos arquivos, apenas exibe o delta entre eles, ou seja, o que deveria ser mudado no arquivo da esquerda (primeiro argumento) para que ele fosse igual ao arquivo da direita (segundo argumento).

Arquivos original e atualizacao1, criados abaixo, serão comparados ao longo desta seção.

printf "%s\n" Omega Zeta Alfa Betha Gama  > original
printf "%s\n" Alfa Beta Gama Delta  > atualizacao1
head original atualizacao1
==> original <==
Omega
Zeta
Alfa
Betha
Gama

==> atualizacao1 <==
Alfa
Beta
Gama
Delta

A opção -q apenas atesta se os arquivos diferem. Caso sejam iguais nenhuma saída é exibida.

diff -q esquerda direita
Files esquerda and direita differ

Formato normal de saída

O formato normal de saída é o padrão de diff. Apenas as diferenças são exibidas.

Nesse formato cada diferença é exibida em trechos. Cada trecho é iniciado com o comando de mudança — por ex. 4c2,3, que especifica: o número das linhas alteradas no arquivo da esquerda, uma letra que indica o tipo da mudança e as linhas alteradas no arquivo da direita — seguido pelas linhas que diferem entre os arquivos e é terminado antes do próximo comando de mudança ou o final da saída.

diff original atualizacao1
1,2d0
< Omega
< Zeta
4c2
< Betha
---
> Beta
5a4
> Delta

O primeiro trecho de diferença começa em 1,2d0 e termina em < Zeta, o segundo em 4c2 e termina em > Beta e o último começa em 5a4 e termina em > Delta.

O comando de mudança 1,2d0 é dividido em três partes:

  • 1,2 que representa os números das linhas no arquivo da esquerda. A , denota um intervalo de números com 1 sendo o menor e 2 o maior. 2,7 representaria da linha 2 até a 7. Um número, ao invés de um intervalo, também seria válido;
  • d significa que as linhas foram excluídas ou seja já não aparecem no arquivo da direita, outras possibilidades contam com a para linhas adicionadas e c para linhas substituídas;
  • 2 o número da linha do arquivo da direita. Um intervalo também seria possível aqui.

< Omega e < Zeta são as linhas 1 e 2 do primeiro arquivo. < no começo da linha indica que a linha pertence ao arquivo da esquerda e > ao da direita.

Resumidamente, na saída acima:

  1. 1,2d0 significa que as linhas 1 até 2 do arquivo da esquerda foram excluídas (d) no da direita. Caso não tivessem sido apareceriam após a linha 0 do arquivo da direita. O conteúdo removido foi Omegae Zeta(linhas 1 e 2 do arquivo original).
  2. 4c2 significa que a linha 4 do arquivo da esquerda foi substituída (c) pela linha 2 do arquivo da direita. Betha (linha 4 do arquivo original) é substituído por Beta (linha 2 do arquivo atualizacao1).
  3. 5a4 significa que após a linha 5 do arquivo da esquerda apareceria a linha 4, incluída (a) no arquivo da direita. Delta (linha 4 do arquivo atualizacao1) é adicionado.

Formato unificado de saída

A opção -u exibe o formato unificado. Diferentemente do formato normal, esse formato exibe linhas comuns aos dois arquivos, chamadas de contexto. Geralmente o contexto conta com 3 linhas antes e 3 linhas depois de alguma diferença.

Os arquivos abaixo contêm todo o alfabeto, com exceção de E em esquerda e N em direita.

printf "%s\n" {A..D} {F..Z} > esquerda
printf "%s\n" {A..M} {O..Z} > direita

A opção --color com o argumento always coloriza as diferenças.

diff -u --color=always esquerda direita
--- esquerda    2020-11-22 17:18:55.296029454 -0300
+++ direita 2020-11-22 17:19:31.228421642 -0300
@@ -2,6 +2,7 @@
 B
 C
 D
+E
 F
 G
 H
@@ -10,7 +11,6 @@
 K
 L
 M
-N
 O
 P
 Q

A primeira linha inicia com ---, que indica qual o arquivo da esquerda e então o nome do arquivo, data e horário seguido de fuso da última alteração do arquivo. +++, da segunda linha, indica o arquivo da direita.

O primeiro trecho começa no cabeçalho @@ -2,6 +2,7 @@ e termina antes do segundo trecho, que começa com o cabeçalho @@ -10,7 +11,6 @@.

Sobre o cabeçalho @@ -2,6 +2,7 @@:

  • O par de @@ delimitam o início e fim do cabeçalho.
  • - indica que os caracteres subsequentes são relativos ao arquivo da esquerda e + aos da direita.
  • 2,6 significa que o trecho abaixo do cabeçalho inicia na linha 2 e se estende por 6 linhas, ou seja, termina na linha 7 (linha 2 + 6 linhas - 1 = linha 7).

Quanto às linhas abaixo do cabeçalho, as que começam com espaço são comuns aos dois arquivos, com + são exclusivas do arquivo da direita e com - do arquivo da esquerda.

No primeiro cabeçalho temos @@ -2,6 +2,7 @@. O trecho aparece nas linhas 2 à 7 no arquivo da esquerda e nas linhas 2 à 8 no da direita. + E aparece apenas no arquivo da direita enquanto as demais linhas são comuns aos dois arquivos.

No segundo cabeçalho temos @@ -10,7 +11,6 @@ indicando que o trecho aparece nas linhas 10-16 no arquivo da esquerda e nas linhas 11-16 no da direita. - N aparece apenas no arquivo da esquerda.

Comparando diretórios por arquivos em comum

diff pode receber diretórios como argumento e então exibir o nome dos arquivos que não são comuns.

O diretório um_dir conta com os arquivos esquerda e direita, já outro_dir apenas com direita.

mkdir um_dir outro_dir
cp esquerda direita um_dir
cp direita outro_dir

diff então exibe o nome do arquivo esquerda que está exclusivamente sob um_dir. Caso ambos os diretórios tivessem os mesmos arquivos, diff não produziria saída.

diff um_dir outro_dir
Only in um_dir: esquerda

Incluindo linhas diferentes com patch

patch modifica um arquivo utilizando a saída de diff -u salva em um arquivo.

Abaixo, a saída unificada de diff é salva no arquivo delta.

diff -u esquerda direita > delta

O primeiro argumento de patch é o arquivo a ser modificado e o segundo as modificações. A opção -b cria uma cópia do arquivo a ser modificado.

patch cria a cópia esquerda.orig e então atualiza esquerda com as modificações descritas em delta.

patch -b esquerda delta
patching file esquerda

Agora, esquerda e direita estão idênticos.

diff esquerda direita

Para reverter as modificações, utilize a opção -R.

patch -R esquerda delta
patching file esquerda

Após a reversão, a diferença entre esquerda e direita volta a ser a mesma.

diff esquerda direita
4a5
> E
13d13
< N

E esquerda é idêntico à esquerda.orig mais uma vez.

diff esquerda esquerda.orig

Dividindo linhas entre arquivos com split

split divide um arquivo em múltiplos outros, cada um com 1000 linhas ou menos.

O arquivo tres_mil_e_um é criado com 3.001 linhas.

printf "%s\n" {1..3001} > tres_mil_e_um
wc -l tres_mil_e_um
3001 tres_mil_e_um

--verbose exibe a lista de arquivos criados.

Abaixo split divide tres_mil_e_um em 4 arquivos: 3 com 1000 linhas e 1 com 1 linha.

split --verbose tres_mil_e_um
creating file 'xaa'
creating file 'xab'
creating file 'xac'
creating file 'xad'
wc -l xaa xab xac xad
 1000 xaa
 1000 xab
 1000 xac
    1 xad
 3001 total

O segundo argumento de split define o prefixo no nome dos arquivos a serem criados. Sem o segundo argumento, x é o prefixo padrão.

split --verbose tres_mil_e_um arquivo_
creating file 'arquivo_aa'
creating file 'arquivo_ab'
creating file 'arquivo_ac'
creating file 'arquivo_ad'

A opção -l altera a quantidade máxima de linhas dos arquivos que serão criados.

split -l 700 tres_mil_e_um _
wc -l _aa _ab _ac _ad _ae
  700 _aa
  700 _ab
  700 _ac
  700 _ad
  201 _ae
 3001 total

Mesclando linhas com paste

paste mescla linhas de dois ou mais arquivos e exibe o resultado na saída.

Arquivos números e letras criados abaixo.

printf "%s\n" {1..5} > numeros
printf "%s\n" {A..E} > letras
cat numeros letras
1
2
3
4
5
A
B
C
D
E

paste dispõe as colunas na mesma ordem dos argumentos.

paste numeros letras
1   A
2   B
3   C
4   D
5   E

tabulação é o delimitador padrão. -d permite alterá-lo.

paste -d . numeros letras
1.A
2.B
3.C
4.D
5.E

A opção -s muda a exibição para a disposição horizontal.

paste -s numeros letras
1   2   3   4   5
A   B   C   D   E

Exercícios

Selecione as afirmações verdadeiras

O cabeçalho @@ -1,3 +1,9 @@
O cabeçalho @@ -9,3 +8,6 @@
O cabeçalho 4c2,3
Escrito por Caio Santesso.

Comentários

  • Conteúdo dos posts, exceto onde indicado contrário, licenciado sob a licença CC BY-SA 4.0 .