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
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 com1
sendo o menor e2
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 coma
para linhas adicionadas ec
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,2d0
significa que as linhas1
até2
do arquivo da esquerda foram excluídas (d
) no da direita. Caso não tivessem sido apareceriam após a linha0
do arquivo da direita. O conteúdo removido foiOmega
eZeta
(linhas 1 e 2 do arquivooriginal
).4c2
significa que a linha4
do arquivo da esquerda foi substituída (c
) pela linha2
do arquivo da direita.Betha
(linha 4 do arquivooriginal
) é substituído porBeta
(linha 2 do arquivoatualizacao1
).5a4
significa que após a linha5
do arquivo da esquerda apareceria a linha4
, incluída (a
) no arquivo da direita.Delta
(linha 4 do arquivoatualizacao1
) é 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