13 herramientas de procesamiento de texto basadas en shell

Aquí hay un fragmento de un libro futuro, Herramientas y prácticas básicas para un desarrollador de software novato, de Balthazar Ruberol y Etienne Broad . El libro debería ayudar a educar a la generación más joven de desarrolladores. Se tratarán temas tales como el dominio de la consola, la configuración y el trabajo de manera eficiente en la consola de comandos, control de versiones de código usando gitconceptos básicos de SQL, como herramientas Make, jqy expresiones regulares, fundamentos de la creación de redes, así como las mejores prácticas para el desarrollo de software y la colaboración. Los autores están trabajando duro en este proyecto y están invitando a todos a participar en la lista de correo .

Contenido



Procesamiento de texto de shell


Una de las razones que hacen que el shell de comandos sea una herramienta invaluable es la gran cantidad de comandos de procesamiento de texto y la capacidad de combinarlos fácilmente en la tubería, creando plantillas de procesamiento complejas. Estos comandos hacen que muchas tareas sean triviales para analizar texto y datos, convertir datos entre diferentes formatos, filtrar cadenas, etc.

Al trabajar con datos de texto, el principio principal es dividir cualquier problema complejo en muchos más pequeños, y resolver cada uno de ellos utilizando una herramienta especializada.

Haga que cada programa haga una cosa bien: los fundamentos de la filosofía de Unix

Los ejemplos en este capítulo pueden parecer un poco exagerados a primera vista, pero esto se hace a propósito. Cada una de las herramientas está diseñada para resolver un pequeño problema. Sin embargo, cuando se combinan, se vuelven extremadamente poderosos.

Analizaremos algunos de los comandos de procesamiento de texto más comunes y útiles en el shell de comandos y demostraremos los flujos de trabajo reales que los conectan entre sí. Sugiero mirar el maná de estos equipos para ver todas las posibilidades a tu disposición.

Un archivo CSV de ejemplo está disponible en línea . Puede descargarlo para consultar el material.

gato


El comando se catutiliza para compilar una lista de uno o más archivos y mostrar su contenido en la pantalla.

$ 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.

cabeza


headimprime las primeras n líneas en el archivo. Esto puede ser muy útil para buscar un archivo de estructura y formato desconocidos sin llenar toda la consola con un montón 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

Si -nno se especifica, headimprime las primeras diez líneas del archivo o flujo de entrada especificado.

cola


tail- un análogo head, solo muestra las últimas n líneas en el archivo.

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

Si desea imprimir todas las líneas ubicadas después de la enésima línea (incluida), puede usar el 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

Hay 43 líneas en nuestro archivo, por lo tanto, tail -n +42solo genera las líneas 42 y 43.

Si -nno se especifica el parámetro , tailgenerará las últimas diez líneas en el archivo o flujo de entrada especificado.

tail -fo tail --followmostrar las últimas líneas en el archivo y cada nueva línea a medida que se escriben en el archivo. Esto es muy útil para ver la actividad en tiempo real, por ejemplo, lo que se registra en los registros del servidor web, etc.

baño


wc(recuento de palabras) muestra el número de caracteres ( -c), palabras ( -w) o líneas ( -l) en el archivo o flujo especificado.

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

Por defecto, se muestra todo lo anterior.

$ wc metadata.csv
43     405    5094 metadata.csv

Si los datos de texto se canalizan o se redirigen a stdin, solo se muestra el contador.

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

grep


grep- Este es un cuchillo suizo que filtra las cuerdas de acuerdo con un patrón dado.

Por ejemplo, podemos encontrar todas las apariciones de la palabra mutex en un archivo.

$ 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

greppuede procesar archivos especificados como argumentos o una secuencia de texto que se le pasa stdin. Por lo tanto, podemos concatenar varios comandos greppara filtrar aún más el texto. En el siguiente ejemplo, filtramos las líneas en nuestro archivo metadata.csvpara encontrar líneas que contengan tanto mutex como 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

Consideremos algunas opciones grepy su comportamiento.

grep -vRealiza la coincidencia inversa: filtra las cadenas que no coinciden con el patrón de argumento.

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

grep -iRealiza coincidencias entre mayúsculas y minúsculas. El siguiente ejemplo se grep -i osencuentra tanto OS y 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 Enumera archivos que contienen una coincidencia.

$ grep -l mysql metadata.csv
metadata.csv

El equipo grep -ccuenta cuántas veces se encontró una muestra.

$ grep -c select metadata.csv
3

grep -r busca archivos de forma recursiva en el directorio de trabajo actual y todos sus subdirectorios.

$ 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 muestra solo palabras completas coincidentes.

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

cortar


cutextrae parte del archivo (o, como de costumbre, la secuencia de entrada). El comando define el separador de campo (que separa las columnas) usando la opción -d, y los números de columna para recuperar usando la opción -f.

Por ejemplo, el siguiente comando recupera la primera columna de las últimas cinco líneas de nuestro archivo 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 tratando con CSV, las columnas están separadas por una coma, y ​​la opción es responsable de recuperar la primera columna -f 1.

Se puede seleccionar la primera y segunda columnas utilizando la opción -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

pegar


paste combina dos archivos diferentes en un archivo de varias columnas.

$ 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 defecto, se pasteutiliza un delimitador de pestaña, pero se puede cambiar mediante el parámetro -d.

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

Otro caso de uso común paste es combinar todas las líneas en una secuencia o archivo usando el delimitador especificado, usando una combinación de -sy -d.

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

Si un parámetro se especifica como un archivo de entrada -, se leerá en su lugar stdin.

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

ordenar


El comando sortrealmente clasifica los datos (en el archivo o flujo de entrada especificado).

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

sort -r realiza la clasificación inversa.

$ sort -r ingredients
tomatoes
salt
milk
eggs
butter

sort -n Ordena los campos por su 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 y filtra líneas idénticas adyacentes en el archivo o flujo 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 solo uniqfiltra las líneas adyacentes , es posible que aún queden duplicados en nuestros datos. Para filtrar las mismas líneas de un archivo, primero debe ordenar su contenido.

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

uniq -c al comienzo de cada línea inserta el número de sus ocurrencias.

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

uniq -u Muestra solo cadenas únicas.

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

Nota. uniqEs especialmente útil en combinación con la clasificación, ya que la tubería le | sort | uniqpermite eliminar todas las líneas duplicadas en un archivo o secuencia.

awk


awk- Esto es un poco más que una simple herramienta de procesamiento de texto: de hecho, tiene un lenguaje de programación completo . Lo que es awk realmente bueno es dividir archivos en columnas, y lo hace con un brillo especial cuando los espacios y las pestañas se mezclan en los archivos.

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

Nota. cat -tmuestra pestañas como ^I.

Como puede ver, las columnas están separadas por espacios o pestañas, y no siempre por el mismo número de espacios. cutes inútil aquí porque funciona con un solo carácter separador. Pero awkes fácil lidiar con un archivo así.

awk '{ print $n }'muestra la enésima columna en el 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

Aunque es awkcapaz de mucho más, la salida de los altavoces es probablemente el 99% de los casos de uso en mi caso personal.

Nota. { print $NF }muestra la última columna en una fila.

tr


trsignifica traducir . Este comando reemplaza un personaje con otro. Funciona con caracteres o clases de caracteres como minúsculas, mecanografiadas, espacios, alfanuméricos, etc.

En la entrada estándar, tr <char1> <char2>reemplaza todas las apariciones de <char1> con <char2>.

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

trpuede traducir clases de caracteres usando notación [:class:]. En la página del manual se describe una lista completa de las clases disponibles tr, pero aquí se muestran algunas.

[:space:]representa todo tipo de espacios, desde espacios simples hasta pestañas o líneas nuevas.

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

Todos los caracteres, como los espacios, están separados por comas. Tenga en cuenta que el carácter %al final de la salida indica la ausencia de una nueva línea de terminación. De hecho, este personaje también se convierte en una coma.

[:lower:]representa todos los caracteres en minúscula y [:upper:] todos los caracteres en mayúscula. Por lo tanto, la transformación entre ellos se vuelve trivial.

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

tr -c SET1 SET2convierte cualquier carácter no incluido en SET1 en caracteres en SET2. En el siguiente ejemplo, todos los caracteres, excepto las vocales indicadas, se reemplazan con espacios.

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

tr -dElimina los caracteres especificados, pero no los reemplaza. Este es el equivalente tr <char> ''.

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

trTambién puede reemplazar rangos de caracteres, por ejemplo, todas las letras entre una y e o todos los números entre 1 y 8, usando la notación s-e, en donde s es el carácter de partida, y e es el final.

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

El comando tr -s string1comprime todas las apariciones múltiples de caracteres string1en una sola. Uno de los usos más útiles tr -ses reemplazar múltiples espacios consecutivos con uno.

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

doblez


El comando foldcontrae todas las líneas de entrada al ancho especificado. Por ejemplo, puede ser útil asegurarse de que el texto se ajuste a pantallas pequeñas. Entonces, fold -w napila cadenas de ancho n caracteres.

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

El comando fold -sromperá líneas solo en caracteres de espacio. Se puede combinar con el anterior para limitar la cadena al número especificado de caracteres.

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

sed


sedEs un editor de flujo no interactivo que se utiliza para convertir texto en el flujo de entrada línea por línea. Como entrada, ya sea un archivo o, o stdin, en la salida, ya sea un archivo o stdout.

Los comandos del editor pueden incluir una o más direcciones , una función y parámetros . Por lo tanto, los comandos son los siguientes:

[address[,address]]function[arguments]

Aunque sedrealiza muchas funciones, solo consideraremos reemplazar el texto como uno de los casos de uso más comunes.

Reemplazo de texto


El comando de reemplazo es el sedsiguiente:

s/PATTERN/REPLACEMENT/[options]

Ejemplo : reemplazar la primera instancia de una palabra en cada línea de un archivo:

$ 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 en la primera línea solo se reemplaza la primera instancia hello. Para reemplazar todas las ocurrencias helloen todas las líneas, puede usar la opción 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

sedle permite usar cualquier delimitador excepto /, lo que mejora especialmente la legibilidad si hay barras en los argumentos del 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

La dirección le dice al editor en qué línea o rango de líneas realizar la sustitución.

$ 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

La dirección 1indica reemplazar helloen Hey I just met youla primera línea. Podemos especificar el rango de direcciones en la notación <start>,<end>, donde <end>puede ser el número de línea o $, es decir, la última línea del archivo.

$ 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 defecto, sedproduce el resultado en sí mismo stdout, pero también puede editar el archivo original con la opción -i.

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

Nota. En Linux, lo suficiente -i. Pero en macOS, el comportamiento del comando es ligeramente diferente, por lo -ique debe agregarlo inmediatamente después ''.

Ejemplos reales


Filtrado CSV con grep y 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

En este ejemplo grep, el archivo metadata.csvprimero filtra las líneas que contienen la palabra gauge, luego aquellas con la querycuarta columna, y muestra el nombre de la métrica (primera columna) con el valor correspondiente per_unit_name(quinta columna).

Muestra la dirección IPv4 asociada con la interfaz de red.


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

El comando ifconfig <interface name>muestra información sobre la interfaz de red especificada. Por ejemplo:

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

Luego corre grephacia inet, eso producirá dos líneas de correspondencia.

$ 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

Luego, usando grep -vexcluir la línea con ipv6.

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

Finalmente, con la ayuda, awksolicitamos la segunda columna en esta fila: esta es la dirección IPv4 asociada con nuestra interfaz de red en0.

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

Nota. Me ofrecieron reemplazar con grep inet | grep -v inet6un equipo tan confiable awk:

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

Es más corto y está específicamente dirigido a IPv4 con la condición $1 == "inet".

Recuperando un valor de un archivo de configuración


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

En el archivo de configuración de git del usuario actual, busque el valor editor =, recorte el carácter =, extraiga la segunda columna y elimine todos los espacios.

$ 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

Extraer IP de un archivo de registro


El siguiente código real busca un mensaje en el registro de la base de datos Too many connections from(seguido de una dirección IP) y muestra los diez intrusos principales.

$ 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

Veamos qué hace esta tubería. Primero, cómo se ve la línea en el registro.

$ 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

Luego awk '{ print $12 }'extrae la dirección IP de la cadena.

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

El comando sed 's@/@@'elimina la barra diagonal inicial.

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

Nota. Como vimos anteriormente, sedpuede usar cualquier separador en. Aunque generalmente se usa como separador /, aquí estamos reemplazando este carácter en particular, lo que perjudicará ligeramente la legibilidad de la expresión de sustitución.

sed 's/\///'

sort | uniq -c clasifica las direcciones IP en orden lexicográfico y luego elimina los duplicados, agregando el número de entradas antes de cada dirección 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 10ordena las líneas por el número de ocurrencias, numéricamente y en orden inverso, de modo que los infractores principales se muestren primero, de los cuales se muestran 10 líneas. El último comando awk { print $2 }recupera las direcciones 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

Renombrar una función en el archivo fuente


Imagine que estamos trabajando en un proyecto y nos gustaría cambiar el nombre de la función (o clase, variable, etc.) en el archivo fuente. Puede hacer esto con un comando sed -ique reemplaza directamente en el archivo 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. En macOS, sed -iuse en su lugar sed -i ''.

Sin embargo, cambiamos el nombre de la función solo en el archivo original. Esto interrumpirá la importación bool_from_stren cualquier otro archivo ya que esta función ya no está definida. Necesitamos encontrar una manera de cambiar el nombre de bool_from_strnuestro proyecto. Esto se puede lograr usando comandos grep, sedasí como bucles foro usando xargs.

Profundización: ciclos foryxargs


Para reemplazar todas las ocurrencias en nuestro proyecto bool_from_str, primero debe encontrarlas recursivamente con 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 solo estamos interesados ​​en archivos con coincidencias, también debe usar la opción -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

Entonces podemos usar el comando xargspara llevar a cabo acciones desde cada línea de la salida (es decir, todos los archivos que contienen la línea bool_from_str).

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

La opción -n 1indica que cada línea en la salida debe ejecutar un comando separado sed.

Luego se ejecutan los siguientes comandos:

$ 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

Si el comando con el que llamas xargs(en nuestro caso sed) admite múltiples argumentos, entonces debes descartar el argumento -n 1por rendimiento.

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

Este comando se ejecutará

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

Nota. De la sinopsis seden la página del manual, se puede ver que el equipo puede tomar varios argumentos.

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

De hecho, como vimos en el capítulo anterior, file ...significa que se aceptan varios argumentos, que son nombres de archivos.

Vemos que se hacen reemplazos para todas las ocurrencias 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 suele suceder, hay varias formas de lograr el mismo resultado. En cambio, xargspodríamos usar bucles forpara iterar sobre las líneas de la lista y tomar medidas sobre cada elemento. Estos bucles tienen la siguiente sintaxis:

for item in list; do
    command $item
done

Si envolvemos nuestro mando grepen $(), a continuación, la cáscara se ejecutará en un subnivel , el resultado de que luego se repiten en un bucle 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 se ejecutará

$ 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

La sintaxis del bucle forme parece más clara que eso xargs, pero este último puede ejecutar comandos en paralelo utilizando parámetros -P n, donde nes el número máximo de comandos paralelos ejecutados al mismo tiempo, lo que puede proporcionar una ganancia de rendimiento.

Resumen


Todas estas herramientas abren todo un mundo de posibilidades, ya que le permiten extraer y transformar datos, creando canalizaciones enteras de equipos que nunca tuvieron la intención de trabajar juntos. Cada uno de ellos realiza una función relativamente pequeña (clasificación sort, combinación cat, filtros grep, edición sed, corte cut, etc.).

Cualquier tarea que incluya texto puede reducirse a una tubería de tareas más pequeñas, cada una de las cuales realiza una acción simple y transfiere su salida a la siguiente tarea.

Por ejemplo, si queremos saber cuántas direcciones IP únicas hay en el archivo de registro, y para que estas direcciones IP siempre aparezcan en la misma columna, entonces podemos ejecutar la siguiente secuencia de comandos:

  • grep cadenas que coinciden con el patrón de cadenas con direcciones IP
  • encuentre la columna con la dirección IP, extráigala con awk
  • ordenar la lista de direcciones IP usando sort
  • eliminar duplicados adyacentes con uniq
  • cuente el número de líneas (es decir, direcciones IP únicas) usando wc -l

Dado que hay muchas herramientas de procesamiento de texto nativas y de terceros, también hay muchas formas de resolver cualquier problema.

Los ejemplos en este artículo fueron exagerados, pero le sugiero que lea el increíble artículo "Las herramientas de línea de comandos pueden ser 235 veces más rápidas que su clúster Hadoop" para tener una idea de cuán útiles y poderosos son realmente estos comandos y qué problemas reales ellos pueden decidir.

Que sigue


  1. Cuente la cantidad de archivos y directorios ubicados en su directorio de inicio.
  2. .
  3. , .
  4. . .



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

, !

All Articles