13 ferramentas de processamento de texto baseadas em shell

Aqui está um fragmento de um livro futuro, Ferramentas e práticas básicas para um desenvolvedor de software iniciante, de Balthazar Ruberol e Etienne Broad . O livro deve ajudar a educar a geração mais jovem de desenvolvedores. Ele cobrirá temas como dominar o console, configurar e trabalhar de forma eficiente no shell de comando, controle de versão de código usando gitnoções básicas de SQL, ferramentas como Make, jqe expressões regulares, noções básicas de rede, bem como as melhores práticas para o desenvolvimento de software e colaboração. Os autores estão atualmente trabalhando neste projeto e estão convidando todos a participar da lista de discussão .

Conteúdo



Processamento de texto do shell


Um dos motivos que tornam o shell de comando uma ferramenta inestimável é o grande número de comandos de processamento de texto e a capacidade de combiná-los facilmente no pipeline, criando modelos de processamento complexos. Esses comandos tornam muitas tarefas triviais para analisar texto e dados, converter dados entre diferentes formatos, filtrar seqüências de caracteres, etc.

Ao trabalhar com dados de texto, o princípio principal é dividir qualquer problema complexo em muitos menores - e resolver cada deles usando uma ferramenta especializada.

Faça com que cada programa faça uma coisa bem - The Fundamentals of Unix Philosophy

Os exemplos deste capítulo podem parecer um pouco exagerados à primeira vista, mas isso é feito de propósito. Cada uma das ferramentas foi projetada para resolver um pequeno problema. No entanto, quando combinados, eles se tornam extremamente poderosos.

Examinaremos alguns dos comandos de processamento de texto mais comuns e úteis no shell de comando e demonstraremos os fluxos de trabalho reais que os conectam. Sugiro olhar para a mana dessas equipes para ver toda a amplitude de possibilidades à sua disposição.

Um arquivo CSV de exemplo está disponível online . Você pode baixá-lo para verificar o material.

gato


O comando é catusado para compilar uma lista de um ou mais arquivos e exibir seu conteúdo na tela.

$ cat Documents/readme
Thanks again for reading this book!
I hope you're following so far!

$ cat Documents/computers
Computers are not intelligent
They're just fast at making dumb things.

$ cat Documents/readme Documents/computers
Thanks again for reading this book!
I hope you are following so far!

Computers are not intelligent
They're just fast at making dumb things.

cabeça


headimprime as primeiras n linhas no arquivo. Isso pode ser muito útil para procurar um arquivo de estrutura e formato desconhecido, sem encher o console inteiro com um monte de texto.

$ head -n 2 metadata.csv
metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name
mysql.galera.wsrep_cluster_size,gauge,,node,,The current number of nodes in the Galera cluster.,0,mysql,galera cluster size

Se -nnão especificado, headimprime as dez primeiras linhas do arquivo ou fluxo de entrada especificado.

rabo


tail- um analógico head, apenas exibe as últimas n linhas do arquivo.

$ tail -n 1 metadata.csv
mysql.performance.queries,gauge,,query,second,The rate of queries.,0,mysql,queries

Se você quiser imprimir todas as linhas localizadas após a linha n (inclusive), você pode usar o argumento -n +n.

$ tail -n +42 metadata.csv
mysql.replication.slaves_connected,gauge,,,,Number of slaves connected to a replication master.,0,mysql,slaves connected
mysql.performance.queries,gauge,,query,second,The rate of queries.,0,mysql,queries

Existem 43 linhas em nosso arquivo, portanto, ele tail -n +42apenas gera as 42ª e 43ª linhas.

Se o parâmetro -nnão for especificado, ele tailproduzirá as últimas dez linhas no arquivo especificado ou no fluxo de entrada.

tail -fou tail --followexiba as últimas linhas do arquivo e cada nova linha conforme elas são gravadas no arquivo. Isso é muito útil para visualizar atividades em tempo real, por exemplo, o que é registrado nos logs do servidor da web etc.

banheiro


wc(contagem de palavras) exibe o número de caracteres ( -c), palavras ( -w) ou linhas ( -l) no arquivo ou fluxo especificado.

$ wc -l metadata.csv
43  metadata.csv
$ wc -w metadata.csv
405 metadata.csv
$ wc -c metadata.csv
5094 metadata.csv

Por padrão, todas as opções acima são exibidas.

$ wc metadata.csv
43     405    5094 metadata.csv

Se os dados de texto forem canalizados ou redirecionados stdin, apenas o contador será exibido.

$ cat metadata.csv | wc
43     405    5094
$ cat metadata.csv | wc -l
43
$ wc -w < metadata.csv
405

grep


grep- Trata-se de um filtro de faca suíço, de acordo com um determinado padrão.

Por exemplo, podemos encontrar todas as ocorrências da palavra mutex em um arquivo.

$ grep mutex metadata.csv
mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits
mysql.innodb.mutex_spin_rounds,gauge,,event,second,The rate of mutex spin rounds.,0,mysql,mutex spin rounds
mysql.innodb.mutex_spin_waits,gauge,,event,second,The rate of mutex spin waits.,0,mysql,mutex spin waits

greppode processar arquivos especificados como argumentos ou um fluxo de texto passado para ele stdin. Assim, podemos concatenar vários comandos greppara filtrar ainda mais o texto. No exemplo a seguir, filtramos as linhas em nosso arquivo metadata.csvpara encontrar linhas que contenham mutex e OS .

$ grep mutex metadata.csv | grep OS
mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits

Vamos considerar algumas opções grepe seu comportamento.

grep -vExecuta a correspondência inversa: filtra as strings que não correspondem ao padrão de argumento.

$ grep -v gauge metadata.csv
metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name

grep -iExecuta correspondência sem distinção entre maiúsculas e minúsculas. O exemplo a seguir grep -i oslocaliza OS e OS .

$ grep -i os metadata.csv
mysql.innodb.mutex_os_waits,gauge,,event,second,The rate of mutex OS waits.,0,mysql,mutex os waits
mysql.innodb.os_log_fsyncs,gauge,,write,second,The rate of fsync writes to the log file.,0,mysql,log fsyncs

grep -l Lista arquivos que contêm uma correspondência.

$ grep -l mysql metadata.csv
metadata.csv

A equipe grep -cconta quantas vezes uma amostra foi encontrada.

$ grep -c select metadata.csv
3

grep -r procura recursivamente por arquivos no diretório de trabalho atual e todos os seus subdiretórios.

$ grep -r are ~/Documents
/home/br/Documents/computers:Computers are not intelligent
/home/br/Documents/readme:I hope you are following so far!

grep -w mostra apenas palavras inteiras correspondentes.

$ grep follow ~/Documents/readme
I hope you are following so far!
$ grep -w follow ~/Documents/readme
$

cortar


cutextrai parte do arquivo (ou, como sempre, o fluxo de entrada). O comando define o separador de campos (que separa as colunas) usando a opção -de os números das colunas a serem recuperados usando a opção -f.

Por exemplo, o comando a seguir recupera a primeira coluna das últimas cinco linhas do nosso arquivo CSV.

$ tail -n 5 metadata.csv | cut -d , -f 1
mysql.performance.user_time
mysql.replication.seconds_behind_master
mysql.replication.slave_running
mysql.replication.slaves_connected
mysql.performance.queries

Como estamos lidando com CSV, as colunas são separadas por vírgula e a opção é responsável por recuperar a primeira coluna -f 1.

Você pode selecionar ambas as primeira e segunda colunas usando a opção -f 1,2.

$ tail -n 5 metadata.csv | cut -d , -f 1,2
mysql.performance.user_time,gauge
mysql.replication.seconds_behind_master,gauge
mysql.replication.slave_running,gauge
mysql.replication.slaves_connected,gauge
mysql.performance.queries,gauge

colar


paste mescla dois arquivos diferentes em um arquivo de várias colunas.

$ cat ingredients
eggs
milk
butter
tomatoes
$ cat prices
1$
1.99$
1.50$
2$/kg
$ paste ingredients prices
eggs    1$
milk    1.99$
butter  1.50$
tomatoes    2$/kg

Por padrão, ele pasteusa um delimitador de guia, mas ele pode ser alterado usando o parâmetro -d.

$ paste ingredients prices -d:
eggs:1$
milk:1.99$
butter:1.50$
tomatoes:2$/kg

Outro caso de uso comum paste é combinar todas as linhas em um fluxo ou arquivo usando o delimitador especificado, usando uma combinação de -se -d.

$ paste -s -d, ingredients
eggs,milk,butter,tomatoes

Se um parâmetro for especificado como um arquivo de entrada -, ele será lido stdin.

$ cat ingredients | paste -s -d, -
eggs,milk,butter,tomatoes

ordenar


O comando sortrealmente classifica os dados (no arquivo especificado ou no fluxo de entrada).

$ cat ingredients
eggs
milk
butter
tomatoes
salt
$ sort ingredients
butter
eggs
milk
salt
tomatoes

sort -r executa a classificação reversa.

$ sort -r ingredients
tomatoes
salt
milk
eggs
butter

sort -n Classifica os campos pelo seu valor aritmético.

$ cat numbers
0
2
1
10
3
$ sort numbers
0
1
10
2
3
$ sort -n numbers
0
1
2
3
10

uniq


uniq Detecta e filtra linhas idênticas adjacentes no arquivo ou fluxo de entrada especificado.

$ cat duplicates
and one
and one
and two
and one
and two
and one, two, three
$ uniq duplicates
and one
and two
and one
and two
and one, two, three

Como uniqfiltra apenas as linhas adjacentes , duplicatas ainda podem permanecer em nossos dados. Para filtrar todas as mesmas linhas de um arquivo, você deve primeiro classificar seu conteúdo.

$ sort duplicates | uniq
and one
and one, two, three
and two

uniq -c no início de cada linha insere o número de suas ocorrências.

$ sort duplicates | uniq -c
   3 and one
   1 and one, two, three
   2 and two

uniq -u Exibe apenas cadeias únicas.

$ sort duplicates | uniq -u
and one, two, three

Nota. uniqÉ especialmente útil em combinação com a classificação, pois o pipeline | sort | uniqpermite excluir todas as linhas duplicadas em um arquivo ou fluxo.

awk


awk- Isso é um pouco mais do que apenas uma ferramenta de processamento de texto: na verdade, ele tem uma linguagem de programação inteira . O que é awk realmente bom é dividir arquivos em colunas e o faz com um brilho especial quando espaços e guias são misturados nos arquivos.

$ cat -t multi-columns
John Smith    Doctor^ITardis
Sarah-James Smith^I    Companion^ILondon
Rose Tyler   Companion^ILondon

Nota. cat -texibe guias como ^I.

Como você pode ver, as colunas são separadas por espaços ou tabulações e nem sempre pelo mesmo número de espaços. cutaqui é inútil porque funciona com apenas um caractere separador. Mas awké fácil lidar com esse arquivo.

awk '{ print $n }'exibe a enésima coluna no texto.

$ cat multi-columns | awk '{ print $1 }'
John
Sarah-James
Rose
$ cat multi-columns | awk '{ print $3 }'
Doctor
Companion
Companion
$ cat multi-columns | awk '{ print $1,$2 }'
John Smith
Sarah-James Smith
Rose Tyler

Embora seja awkcapaz de muito mais, a saída dos alto-falantes é provavelmente 99% dos casos de uso no meu caso pessoal.

Nota. { print $NF }exibe a última coluna em uma linha.

tr


trsignifica traduzir . Este comando substitui um caractere por outro. Funciona com caracteres ou classes de caracteres como minúsculas, digitadas, espaços alfanuméricos etc.

Na entrada padrão, tr <char1> <char2>substitui todas as ocorrências de <char1> por <char2>.

$ echo "Computers are fast" | tr a A
computers Are fAst

trpode traduzir classes de caracteres usando notação [:class:]. Uma lista completa de classes disponíveis é descrita na página de manual tr, mas algumas são demonstradas aqui.

[:space:]representa todos os tipos de espaços, de espaços simples a guias ou novas linhas.

$ echo "computers are fast" | tr '[:space:]' ','
computers,are,fast,%

Todos os caracteres, como espaços, são separados por vírgula. Observe que o caractere %no final da saída indica a ausência de uma nova linha final. Na verdade, esse personagem também é convertido em vírgula.

[:lower:]representa todos os caracteres em minúsculas e [:upper:] todos os caracteres em maiúsculas. Assim, a transformação entre eles se torna trivial.

$ echo "computers are fast" | tr '[:lower:]' '[:upper:]'
COMPUTERS ARE FAST
$ echo "COMPUTERS ARE FAST" | tr '[:upper:]' '[:lower:]'
computers are fast

tr -c SET1 SET2converte qualquer caractere não incluído no SET1 em caracteres no SET2. No exemplo a seguir, todos os caracteres, exceto as vogais indicadas, são substituídos por espaços.

$ echo "computers are fast" | tr -c '[aeiouy]' ' '
 o  u e   a e  a

tr -dExclui os caracteres especificados, mas não os substitui. Isto é o equivalente tr <char> ''.

$ echo "Computers Are Fast" | tr -d '[:lower:]'
C A F

trtambém pode substituir os intervalos de caracteres, por exemplo, todas as letras entre a e e ou todos os números entre 1 e 8, usando a notação s-e, onde s é o caractere inicial e e o final.

$ echo "computers are fast" | tr 'a-e' 'x'
xomputxrs xrx fxst
$ echo "5uch l337 5p34k" | tr '1-4' 'x'
5uch lxx7 5pxxk

O comando tr -s string1compacta todas as múltiplas ocorrências de caracteres string1em um único. Um dos usos mais úteis tr -sé substituir vários espaços consecutivos por um.

$ echo "Computers         are       fast" | tr -s ' '
Computers are fast

dobra


O comando foldrecolhe todas as linhas de entrada para a largura especificada. Por exemplo, pode ser útil garantir que o texto caiba em telas pequenas. Então, fold -w nempilha strings de largura n caracteres.

$ cat ~/Documents/readme | fold -w 16
Thanks again for
 reading this bo
ok!
I hope you're fo
llowing so far!

O comando fold -squebrará linhas apenas em caracteres de espaço. Pode ser combinado com o anterior para limitar a sequência ao número especificado de caracteres.

Thanks again
for reading
this book!
I hope you're
following so
far!

sed


sedÉ um editor de fluxo não interativo usado para converter texto no fluxo de entrada linha por linha. Como entrada, um arquivo ou, ou stdin, na saída, um arquivo ou stdout.

Os comandos do editor podem incluir um ou mais endereços , uma função e parâmetros . Assim, os comandos são os seguintes:

[address[,address]]function[arguments]

Embora ele sedexecute muitas funções, consideraremos apenas substituir o texto como um dos casos de uso mais comuns.

Substituição de texto


O comando de substituição é sedo seguinte:

s/PATTERN/REPLACEMENT/[options]

Exemplo : substituindo a primeira instância de uma palavra em cada linha de um arquivo:

$ cat hello
hello hello
hello world!
hi
$ cat hello | sed 's/hello/Hey I just met you/'
Hey I just met you hello
Hey I just met you world
hi

Vemos que na primeira linha apenas a primeira instância é substituída hello. Para substituir todas as ocorrências helloem todas as linhas, você pode usar a opção g(significa global ).

$ cat hello | sed 's/hello/Hey I just met you/g'
Hey I just met you Hey I just met you
Hey I just met you world
hi

sedpermite usar qualquer delimitador /, exceto , o que melhora especialmente a legibilidade se houver barras nos próprios argumentos do comando.

$ cat hello | sed 's@hello@Hey I just met you@g'
Hey I just met you Hey I just met you
Hey I just met you world
hi

O endereço informa ao editor em qual linha ou intervalo de linhas executar a substituição.

$ cat hello | sed '1s/hello/Hey I just met you/g'
Hey I just met you hello
hello world
hi
$ cat hello | sed '2s/hello/Hey I just met you/g'
hello hello
Hey I just met you  world
hi

O endereço 1indica substituir hellono Hey I just met youna primeira linha. Podemos especificar o intervalo de endereços na notação <start>,<end>, onde <end>pode ser o número da linha ou $, ou seja, a última linha do arquivo.

$ cat hello | sed '1,2s/hello/Hey I just met you/g'
Hey I just met you Hey I just met you
Hey I just met you world
hi
$ cat hello | sed '2,3s/hello/Hey I just met you/g'
hello hello
Hey I just met you world
hi
$ cat hello | sed '2,$s/hello/Hey I just met you/g'
hello hello
Hey I just met you world
hi

Por padrão, ele sedproduz o resultado em sua própria stdout, mas também pode editar o arquivo original com a opção -i.

$ sed -i '' 's/hello/Bonjour/' sed-data
$ cat sed-data
Bonjour hello
Bonjour world
hi

Nota. No Linux, apenas o suficiente -i. Mas no macOS, o comportamento do comando é um pouco diferente, então -ivocê precisa adicionar logo em seguida ''.

Exemplos reais


Filtragem CSV com grep e awk


$ grep -w gauge metadata.csv | awk -F, '{ if ($4 == "query") { print $1, "per", $5 } }'
mysql.performance.com_delete per second
mysql.performance.com_delete_multi per second
mysql.performance.com_insert per second
mysql.performance.com_insert_select per second
mysql.performance.com_replace_select per second
mysql.performance.com_select per second
mysql.performance.com_update per second
mysql.performance.com_update_multi per second
mysql.performance.questions per second
mysql.performance.slow_queries per second
mysql.performance.queries per second

Neste exemplo grep, o arquivo metadata.csvfiltra primeiro as linhas que contêm a palavra gauge, depois as da queryquarta coluna e exibe o nome da métrica (1ª coluna) com o valor correspondente per_unit_name(5ª coluna).

Exibe o endereço IPv4 associado à interface de rede


$ ifconfig en0 | grep inet | grep -v inet6 | awk '{ print $2 }'
192.168.0.38

O comando ifconfig <interface name>exibe informações na interface de rede especificada. Por exemplo:

en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    ether 19:64:92:de:20:ba
    inet6 fe80::8a3:a1cb:56ae:7c7c%en0 prefixlen 64 secured scopeid 0x7
    inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: active

Então corra greppara inet, isso produzirá duas linhas de correspondência.

$ ifconfig en0 | grep inet
    inet6 fe80::8a3:a1cb:56ae:7c7c%en0 prefixlen 64 secured scopeid 0x7
    inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255

Em seguida, usando grep -vexcluir a linha com ipv6.

$ ifconfig en0 | grep inet | grep -v inet6
inet 192.168.0.38 netmask 0xffffff00 broadcast 192.168.0.255

Finalmente, com a ajuda, awksolicitamos a segunda coluna nesta linha: este é o endereço IPv4 associado à nossa interface de rede en0.

$ ifconfig en0 | grep inet | grep -v inet6 | awk '{ print $2 }'
192.168.0.38

Nota. Me ofereceram a substituição por grep inet | grep -v inet6uma equipe tão confiável awk:

$ ifconfig en0 | awk ' $1 == "inet" { print $2 }'
192.168.0.38

É mais curto e é direcionado especificamente para IPv4 com a condição $1 == "inet".

Recuperando um valor de um arquivo de configuração


$ grep 'editor =' ~/.gitconfig  | cut -d = -f2 | sed 's/ //g'
/usr/bin/vim

No arquivo de configuração git do usuário atual, procure o valor editor =, corte o caractere =, extraia a segunda coluna e exclua todos os espaços ao redor.

$ grep 'editor =' ~/.gitconfig
     editor = /usr/bin/vim
$ grep 'editor =' ~/.gitconfig  | cut -d'=' -f2
 /usr/bin/vim
$ grep 'editor =' ~/.gitconfig  | cut -d'=' -f2 | sed 's/ //'
/usr/bin/vim

Extrair IPs de um arquivo de log


O código real a seguir procura uma mensagem no log do banco de dados Too many connections from(seguido por um endereço IP) e exibe os dez principais invasores.

$ grep 'Too many connections from' db.log | \
  awk '{ print $12 }' | \
  sed 's@/@@' | \
  sort | \
  uniq -c | \
  sort -rn | \
  head -n 10 | \
  awk '{ print $2 }'
   10.11.112.108
   10.11.111.70
   10.11.97.57
   10.11.109.72
   10.11.116.156
   10.11.100.221
   10.11.96.242
   10.11.81.68
   10.11.99.112
   10.11.107.120

Vamos ver o que esse pipeline faz. Primeiro, como é a linha no log.

$ grep "Too many connections from" db.log | head -n 1
2020-01-01 08:02:37,617 [myid:1] - WARN  [NIOServerCxn.Factory:1.2.3.4/1.2.3.4:2181:NIOServerCnxnFactory@193] - Too many connections from /10.11.112.108 - max is 60

Em seguida, awk '{ print $12 }'extrai o endereço IP da string.

$ grep "Too many connections from" db.log | awk '{ print $12 }'
/10.11.112.108
...

O comando sed 's@/@@'exclui a barra inicial.

$ grep "Too many connections from" db.log | awk '{ print $12 }' | sed 's@/@@'
10.11.112.108
...

Nota. Como vimos anteriormente, sedvocê pode usar qualquer separador no. Embora seja geralmente usado como separador /, aqui estamos substituindo esse caractere em particular, o que prejudicará levemente a legibilidade da expressão de substituição.

sed 's/\///'

sort | uniq -c classifica os endereços IP em ordem lexicográfica e remove duplicatas, adicionando o número de entradas antes de cada endereço IP.

$ grep 'Too many connections from' db.log | \
  awk '{ print $12 }' | \
  sed 's@/@@' | \
  sort | \
  uniq -c
   1379 10.11.100.221
   1213 10.11.103.168
   1138 10.11.105.177
    946 10.11.106.213
   1211 10.11.106.4
   1326 10.11.107.120
   ...

sort -rn | head -n 10classifica as linhas pelo número de ocorrências, numericamente e na ordem inversa, para que os principais violadores sejam exibidos primeiro, dos quais 10 linhas são exibidas. O último comando awk { print $2 }recupera os próprios endereços IP.

$ grep 'Too many connections from' db.log | \
  awk '{ print $12 }' | \
  sed 's@/@@' | \
  sort | \
  uniq -c | \
  sort -rn | \
  head -n 10 | \
  awk '{ print $2 }'
  10.11.112.108
  10.11.111.70
  10.11.97.57
  10.11.109.72
  10.11.116.156
  10.11.100.221
  10.11.96.242
  10.11.81.68
  10.11.99.112
  10.11.107.120

Renomeando uma função no arquivo de origem


Imagine que estamos trabalhando em um projeto e gostaríamos de renomear a função com o sobrenome (ou classe, variável etc.) no arquivo de origem. Você pode fazer isso com um comando sed -ique substitui diretamente no arquivo original.

$ cat izk/utils.py
def bool_from_str(s):
    if s.isdigit():
        return int(s) == 1
    return s.lower() in ['yes', 'true', 'y']

$ sed -i 's/def bool_from_str/def is_affirmative/' izk/utils.py
$ cat izk/utils.py
def is_affirmative(s):
    if s.isdigit():
        return int(s) == 1
    return s.lower() in ['yes', 'true', 'y']

Nota. No macOS, sed -iuse em seu lugar sed -i ''.

No entanto, renomeamos a função apenas no arquivo original. Isso interromperá a importação bool_from_strem qualquer outro arquivo, pois essa função não está mais definida. Precisamos encontrar uma maneira de renomear em bool_from_strtodo o projeto. Isso pode ser conseguido usando comandos grep, sedbem como loops forou usando xargs.

Aprofundamento: ciclos forexargs


Para substituir todas as ocorrências em nosso projeto bool_from_str, você deve primeiro encontrá-las recursivamente com grep -r.

$ grep -r bool_from_str .
./tests/test_utils.py:from izk.utils import bool_from_str
./tests/test_utils.py:def test_bool_from_str(s, expected):
./tests/test_utils.py:    assert bool_from_str(s) == expected
./izk/utils.py:def bool_from_str(s):
./izk/prompt.py:from .utils import bool_from_str
./izk/prompt.py:                    default = bool_from_str(os.environ[envvar])

Como estamos interessados ​​apenas em arquivos com correspondências, você também deve usar a opção -l/--files-with-matches:

-l, --files-with-matches
        Only the names of files containing selected lines are written to standard out-
        put.  grep will only search a file until a match has been found, making
        searches potentially less expensive.  Pathnames are listed once per file
        searched.  If the standard input is searched, the string ``(standard input)''
        is written.

$ grep -r --files-with-matches bool_from_str .
./tests/test_utils.py
./izk/utils.py
./izk/prompt.py

Em seguida, podemos usar o comando xargspara executar ações de cada linha da saída (ou seja, todos os arquivos que contêm a linha bool_from_str).

$ grep -r --files-with-matches bool_from_str . | \
  xargs -n 1 sed -i 's/bool_from_str/is_affirmative/'

A opção -n 1indica que cada linha na saída deve executar um comando separado sed.

Em seguida, os seguintes comandos são executados:

$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/prompt.py

Se o comando com o qual você chama xargs(no nosso caso sed) suporta vários argumentos, você deve descartá-lo -n 1para desempenho.

grep -r --files-with-matches bool_from_str . | xargs sed -i 's/bool_from_str/is_affirmative/'

Este comando será executado

$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py ./izk/utils.py ./izk/prompt.py

Nota. A partir da sinopse sedna página do manual, pode-se ver que a equipe pode aceitar vários argumentos.

SYNOPSIS
     sed [-Ealn] command [file ...]
     sed [-Ealn] [-e command] [-f command_file] [-i extension] [file ...]

De fato, como vimos no capítulo anterior, isso file ...significa que vários argumentos são aceitos, que são nomes de arquivos.

Vemos que são feitas substituições para todas as ocorrências bool_from_str.

$ grep -r is_affirmative .
./tests/test_utils.py:from izk.utils import is_affirmative
./tests/test_utils.py:def test_is_affirmative(s, expected):
./tests/test_utils.py:    assert is_affirmative(s) == expected
./izk/utils.py:def is_affirmative(s):
./izk/prompt.py:from .utils import is_affirmative
./izk/prompt.py:                    default = is_affirmative(os.environ[envvar])

Como costuma acontecer, existem várias maneiras de obter o mesmo resultado. Em vez disso, xargspoderíamos usar loops forpara percorrer as linhas da lista e executar ações em cada item. Esses loops têm a seguinte sintaxe:

for item in list; do
    command $item
done

Se nós envolvemos o nosso comando grepem $(), em seguida, o shell irá executá-lo em um subnível , cujo resultado será então repetido em um loop for.

$ for file in $(grep -r --files-with-matches bool_from_str .); do
  sed -i 's/bool_from_str/is_affirmative/' $file
done

Este comando será executado

$ sed -i 's/bool_from_str/is_affirmative/' ./tests/test_utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/utils.py
$ sed -i 's/bool_from_str/is_affirmative/' ./izk/prompt.py

A sintaxe do loop forme parece mais clara do que isso xargs, mas o último pode executar comandos em paralelo usando parâmetros -P n, onde né o número máximo de comandos paralelos executados ao mesmo tempo, o que pode proporcionar um ganho de desempenho.

Sumário


Todas essas ferramentas abrem um mundo inteiro de possibilidades, pois permitem extrair e transformar dados, criando pipelines inteiros de equipes que talvez nunca foram projetadas para trabalhar juntas. Cada um deles desempenha uma função relativamente pequena (classificação sort, combinação cat, filtros grep, edição sed, corte cut, etc.).

Qualquer tarefa que inclua texto pode ser reduzida a um pipeline de tarefas menores, cada uma executando uma ação simples e transferindo sua saída para a próxima tarefa.

Por exemplo, se quisermos saber quantos endereços IP exclusivos estão no arquivo de log e para que esses endereços IP sempre apareçam na mesma coluna, podemos executar a seguinte sequência de comandos:

  • grep strings que correspondem ao padrão de strings com endereços IP
  • encontre a coluna com o endereço IP, extraia-a com awk
  • classifique a lista de endereços IP usando sort
  • eliminar duplicatas adjacentes com uniq
  • conte o número de linhas (ou seja, endereços IP exclusivos) usando wc -l

Como existem muitas ferramentas de processamento de texto nativas e de terceiros, também existem várias maneiras de resolver qualquer problema.

Os exemplos deste artigo foram exagerados, mas sugiro que você leia o incrível artigo “As ferramentas de linha de comando podem ser 235 vezes mais rápidas que o cluster do Hadoop” para ter uma idéia de quão úteis e poderosos esses comandos realmente são e quais são os problemas reais eles podem decidir.

Qual é o próximo


  1. Conte o número de arquivos e diretórios localizados no seu diretório pessoal.
  2. .
  3. , .
  4. . .



« » (Essential Tools and Practices for the Aspiring Software Developer) , . , , , , git, SQL, Make, jq , , .

, !

All Articles