¿Qué hace "xargs grep"?

25

Conozco el grepcomando y estoy aprendiendo acerca de las funcionalidades de xargs, así que leí esta página que ofrece algunos ejemplos sobre cómo usar el xargscomando.

Estoy confundido con el último ejemplo, el ejemplo 10. Dice "El comando xargs ejecuta el comando grep para encontrar todos los archivos (entre los archivos proporcionados por el comando find) que contenían una cadena 'stdlib.h'"

$ find . -name '*.c' | xargs grep 'stdlib.h'
./tgsthreads.c:#include
./valgrind.c:#include
./direntry.c:#include
./xvirus.c:#include
./temp.c:#include
...
...
...

Sin embargo, ¿cuál es la diferencia de simplemente usar

$ find . -name '*.c' | grep 'stdlib.h'

?

Obviamente, todavía estoy luchando con lo que hace exactamente xargs, por lo que cualquier ayuda es apreciada.

Alpha Omega
fuente
2
Esta pregunta puede ser útil: ¿ Cuándo se necesita XArgs?
TheOdd

Respuestas:

30
$ find . -name '*.c' | grep 'stdlib.h'

Esto canaliza la salida (stdout) * de finda (stdin of) * grep 'stdlib.h' como texto (es decir, los nombres de archivo se tratan como texto). grephace lo habitual y encuentra las líneas coincidentes en este texto (cualquier nombre de archivo que contenga el patrón). El contenido de los archivos nunca se lee.

$ find . -name '*.c' | xargs grep 'stdlib.h'

Esto construye un comando grep 'stdlib.h' para el que cada resultado findes un argumento, por lo que buscará coincidencias dentro de cada archivo encontrado por find( xargsse puede considerar que convierte su stdin en argumentos para los comandos dados) *

Úselo -type fen su comando de búsqueda, o obtendrá errores de grepdirectorios coincidentes. Además, si los nombres de archivo tienen espacios, xargsse estropearán mal, así que use el separador nulo agregando -print0y xargs -0para obtener resultados más confiables:

find . -type f -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

* agregó estos puntos explicativos adicionales como se sugiere en el comentario de @cat

Zanna
fuente
2
puede considerar señalar (porque parece que ha omitido) el punto clave que |canaliza stdout al stdin de grep, que no es lo mismo que los argumentos de grep y da resultados confusos.
gato
1
O use GNU find's find -name '*.c' -exec grep stdlib.h {} +. Casi nunca uso xargs. También me sorprendió que nadie mencionara que xargs tiene un propósito similar para la grep $(find)sustitución de comandos, así que escribí una respuesta propia. Explicar los xargs como sustitución de comandos con menos limitaciones y problemas parece natural.
Peter Cordes
Una situación en la que uso xargs es si estoy borrando muchos archivos como resultado de find. Si solo hace -exec rm, se ejecutará rm en cada archivo uno a la vez, lo cual es muy ineficiente. La tubería a xargs los hará todos a la vez con una habitación. Limitar con say -n50 (hacer 50 a la vez) podría evitar el desbordamiento de la línea de comando (problema con muchos archivos).
lsd
1
@lsd: ¿Por qué no find -deletepara ese caso especial? O para los comandos que no sean rm, si tiene GNU find, se -exec some_command {} +agrupa en lotes como xargs, en lugar del \;comportamiento de ejecutar el comando por separado para cada uno.
Peter Cordes
@lsd findejecuta el comando en cada archivo si y solo si usa -exec command \;Both xargsy -exec command \+llamará al comando con el número máximo de argumentos permitidos por el sistema. En otras palabras, son equivalentes
Sergiy Kolodyazhnyy
6

xargs toma su entrada estándar y la convierte en argumentos de línea de comando.

find . -name '*.c' | xargs grep 'stdlib.h' es muy similar a

grep 'stdlib.h' $(find . -name '*.c')  # UNSAFE, DON'T USE

Y dará los mismos resultados siempre que la lista de nombres de archivo no sea demasiado larga para una sola línea de comando. (Linux admite megabytes de texto en una sola línea de comando, por lo que generalmente no necesita xargs).


Pero ambos apestan, porque se rompen si sus nombres de archivo contienen espacios . En cambio, find -print0 | xargs -0funciona, pero también lo hace

find . -name '*.c' -exec grep 'stdlib.h' {} +

Eso nunca canaliza los nombres de archivo en ningún lugar: los findagrupa en una gran línea de comando y se ejecuta grepdirectamente.

\;en lugar de +ejecuta grep por separado para cada archivo, que es mucho más lento. No hagas eso. Pero +es una extensión GNU, por lo que debe xargshacerlo de manera eficiente si no puede asumir que GNU encuentra.


Si lo omite xargs, find | grepsu patrón coincide con la lista de nombres de archivo que se findimprime.

Entonces, en ese punto, bien podrías hacerlo find -name stdlib.h. Por supuesto, con -name '*.c' -name stdlib.h, no obtendrá ningún resultado porque esos patrones no pueden coincidir, y el comportamiento predeterminado de find es Y las reglas juntas.

Sustituya lessen cualquier punto del proceso para ver qué salida produce cualquier parte de la tubería.


Lectura adicional: http://mywiki.wooledge.org/BashFAQ tiene algunas cosas geniales.

Peter Cordes
fuente
1
GNU xargs también tiene -dque establecer el separador, por lo que puede usar -d'\n'para manejar una lista separada por una nueva línea, lo que puede ser útil si maneja una lista de nombres de archivo en un archivo, etc. (siempre que los nombres de archivo no tengan nuevas líneas en ellos, eso es.)
ilkkachu
@ilkkachu: sí, las nuevas líneas en los nombres de archivos son mucho más raras que los espacios, ya que rompen la mayoría de los scripts. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }también funciona con el mismo efecto. O como una línea, una (IFS=...; cmd...)subshell también funciona para contener el cambio a IFS sin tener que guardarlo / restaurarlo.
Peter Cordes
@ PeterCordes Por favor, no hagas ningún command $( find )tipo de cosas. Los nombres de archivo problemáticos con espacios y caracteres especiales pueden romper este tipo de cosas. Como mínimo, comillas dobles la sustitución del comando.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy: Gracias por señalar que parece que realmente recomiendo hacerlo. Las personas que descreman pueden haber copiado / pegado eso en lugar de leer la siguiente sección. Actualizado para abordar eso.
Peter Cordes
@SergiyKolodyazhnyy: ¿O estabas respondiendo a mi comentario? Tenga en cuenta que configuro, IFSpor lo que es equivalente a usar xargs '-d\n'. La expansión global y el procesamiento de metacaracteres de shell ocurren antes de los efectos de sustitución de comandos, por lo que creo que es seguro incluso con nombres de archivo que contienen $()o >. Convino en que el uso de la división de palabras en la sustitución de comandos no es una buena práctica, excepto para el uso interactivo único en el que se sabe algo sobre los nombres de archivo. Pero command "$(find)"solo es útil si espera que produzca exactamente 1 nombre de archivo ...
Peter Cordes
5

En general, xargsse usa para casos en los que canalizaría (con el símbolo |) algo de un comando a otro ( Command1 | Command2), pero la salida del primer comando no se recibe correctamente como entrada para el segundo comando.

Esto suele suceder cuando el segundo comando no maneja la entrada de datos a través de la entrada estándar (stdin) correctamente (por ejemplo: varias líneas como entrada, la forma en que se configuran las líneas, los caracteres utilizados como entrada, múltiples parámetros como entrada, el tipo de datos recibido como entrada, etc.). Para darle un ejemplo rápido, pruebe lo siguiente:

Ejemplo 1:

ls | echo- Esto no hará nada ya echoque no sabe cómo manejar la entrada que está recibiendo. Ahora, en este caso, si lo usamos xargs, procesará la entrada de una manera que pueda ser manejada correctamente echo( por ejemplo: como una sola línea de información)

ls | xargs echo- Esto generará toda la información de lsuna sola línea

Ejemplo 2

Digamos que tengo varios archivos goLang dentro de una carpeta llamada go. Los buscaría con algo como esto:

find go -name *.go -type f | echo- Pero si el símbolo de la tubería allí y echoal final, no funcionaría.

find go -name *.go -type f | xargs echo- Aquí funcionaría gracias a, xargspero si quisiera cada respuesta del findcomando en una sola línea, haría lo siguiente:

find go -name *.go -type f | xargs -0 echo- En este caso, findse mostrará la misma salida de echo.

Los comandos como cp, echo, rm, lessy otros que necesitan una mejor manera de manejar la entrada obtienen un beneficio cuando se usan con xargs.

Luis Alvarado
fuente
4

xargs se usa para generar automáticamente argumentos de línea de comandos basados ​​(generalmente) en una lista de archivos.

Entonces, considerando algunas alternativas al uso del xargscomando followoing :

find . -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

Hay varias razones para usarlo en lugar de otras opciones que no se mencionaron originalmente en otras respuestas:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;generará un grepproceso para cada archivo; esto generalmente se considera una mala práctica y puede poner una gran carga en el sistema si se encuentran muchos archivos.
  2. Si hay muchos archivos, grep 'stdlib.h' $(find . -name '*.c')es probable que falle un comando, porque la salida de la $(...)operación excederá la longitud máxima de la línea de comando del shell

Como se mencionó en otras respuestas, la razón para usar el -print0argumento to finden este escenario y el -0argumento para xargs es que los nombres de archivo con ciertos caracteres (por ejemplo, comillas, espacios o incluso líneas nuevas) aún se manejan correctamente.

Michael Firth
fuente