
Estoy intentando hacer lo siguiente:
cat file1.txt | xargs -I{} "cat file2.txt | grep {}"
Espero que cada línea del archivo 1 sea el valor de grep al final del tercer tubo. No funciona como se esperaba.
¿Esto se debe a que -I{}
deja de buscar cosas para reemplazar una vez que llega a la tubería? ¿Hay alguna forma de evitar esto?
Respuesta1
Es porque necesita un shell para crear una tubería o realizar una redirección. Tenga en cuenta que este cat
es el comando para concatenar, no tiene mucho sentido usarlo solo para un archivo.
cat file1.txt | xargs -I{} sh -c 'cat file2.txt | grep -e "$1"' sh {}
Hacernohacer:
archivo gato1.txt | xargs -I{} sh -c 'cat archivo2.txt | grep -e {}'
ya que eso equivaldría a una vulnerabilidad de inyección de comandos. Se {}
ampliaría en el argumento del código para sh
interpretarlo como código de shell. Por ejemplo, si la línea de uno file1.txt
fuera $(reboot)
esa, llamaría reboot
.
El -e
(o también podrías usar --
) también es importante. Sin él, tendrías problemas con las expresiones regulares que comienzan con -
.
Puedes simplificar lo anterior usando redirecciones en lugar de cat
:
< file1.txt xargs -I{} sh -c '< file2.txt grep -e "$1"' sh {}
O simplemente pase los nombres de los archivos como argumento grep
en lugar de usar redirecciones, en cuyo caso puede incluso eliminar sh
:
< file1.txt xargs -I{} grep -e {} file2.txt
También podría indicar grep
buscar todas las expresiones regulares a la vez en una sola invocación:
grep -f file1.txt file2.txt
Sin embargo, tenga en cuenta que en ese caso, es solo una expresión regular para cada línea de file1.txt
, no hay ningún procesamiento de cotización especial realizado por xargs
.
xargs
de forma predeterminada, considera su entrada como una lista de palabras en blanco (en algunas implementaciones solo espacio y tabulación, en otras, cualquiera en la [:blank:]
clase de caracteres de la configuración regional actual) o palabras separadas por nueva línea para las cuales se pueden usar barras invertidas y comillas simples y dobles para escapar de los separadores. (Sin embargo, solo se puede escapar de la nueva línea mediante una barra invertida) o entre sí.
Por ejemplo, en una entrada como:
'a "b'\" "bar baz" x\
y
xargs
sin -I{}
pasaría a "b"
, bar baz
y x<newline>y
al mando.
Con -I{}
, xargs
obtiene una palabra por línea pero aún realiza un procesamiento adicional. Ignora los espacios en blanco iniciales (pero no finales). Los espacios en blanco ya no se consideran separadores, pero aún se está procesando la cotización.
En la entrada anterior xargs -I{}
se pasaría un a "b" foo bar x<newline>y
argumento al comando. También tenga en cuenta que muchos sistemas, como lo exige POSIX, no funcionarán si las palabras tienen más de 255 caracteres. Considerándolo todo, xargs -I{}
es bastante inútil.
Si desea que cada línea se pase palabra por palabra como argumento del comando, puede usar la xargs
-d '\n'
extensión GNU:
< file1.txt xargs -d '\n' -n 1 grep file2.txt -e
(aquí confiando en otra extensión de GNU grep
que permite pasar opciones después de los argumentos (siempre que POSIXly correcto no esté en el entorno) o de forma portátil:
sed "s/'/'\\\\\\''/g;s/.*/'&'/" file1.txt | xargs -n1 sh -c '
for line do
grep -e "$line" file2.txt
done' sh
Si quisieras cada unopalabraen file1.txt
(comillas aún reconocidas) en contraposición a cada unalíneapara ser buscado (lo que también solucionaría el problema del espacio final si de todos modos tiene una palabra por línea), puede usarlo xargs -n1
solo en lugar de usar -I
:
< file1.txt xargs -n1 sh -c '
for word do
grep -e "$word" file2.txt
done' sh
Para eliminar los espacios en blanco iniciales y finales (pero sin el procesamiento de cotizaciones que xargs
lo hace), también puede hacer:
unset IFS # restore word splitting to its default
while read -r regexp; do
grep -e "$regexp" file2.txt
done < file1.txt
Respuesta2
Dependiendo de lo que intente hacer, es mejor que lo omita xargs
por completo y opte por esta solución:
grep -f file1.txt file2.txt
Esto difiere de su comando original.(una vez que lo arreglemos como en la respuesta de Stéphane Chazelas) de la siguiente manera:
- Las líneas se imprimen en el orden en que aparecen,
file2.txt
independientemente de los patrones con los que coincidan. En su comando, se imprimen todas las líneas que coinciden con el primer patrón, luego todas las líneas que coinciden con el segundo, y así sucesivamente. - Las líneas que coinciden con más de un patrón se imprimen exactamente una vez. A tu disposición, se imprimen una vez por cada patrón que coincida.
- Se pueden usar varias banderas más fácilmente, incluidas ambas
-v
y-c
.
la -f
bandera esespecificado por POSIXy por lo tanto razonablemente portátil.