bash - redirigir - Llamando múltiples comandos a través de xargs
xargs grep (10)
cat a.txt | xargs -I % echo %
En el ejemplo anterior, xargs toma echo %
como argumento de comando. Pero en algunos casos, necesito varios comandos para procesar en lugar de uno, por ejemplo:
cat a.txt | xargs -I % {command1; command2; ... }
Pero xargs no acepta esta forma. Una solución que conozco es que puedo definir una función para envolver los comandos, pero no es canalización, no la prefiero. ¿Hay otra solución?
Con GNU Parallel puedes hacer:
cat a.txt | parallel ''command1 {}; command2 {}; ...; ''
Mire los videos de introducción para obtener más información: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Esta parece ser la versión más segura.
tr ''[/n]'' ''[/0]'' < a.txt | xargs -r0 /bin/bash -c ''command1 "$@"; command2 "$@";''
( -0
puede eliminarse y tr
reemplaza con una redirección (o el archivo se puede reemplazar con un archivo separado nulo). Está principalmente allí, ya que principalmente uso xargs
con find
con salida -print0
) (Esto también podría ser relevante en las versiones xargs
sin la extensión -0
)
Es seguro, ya que args pasará los parámetros al shell como una matriz que lo ejecuta. La shell (al menos bash
) luego los pasaría como una matriz inalterada a los otros procesos cuando todos se obtengan usando ["$@"][1]
Si usas ...| xargs -r0 -I{} bash -c ''f="{}"; command "$f";''
...| xargs -r0 -I{} bash -c ''f="{}"; command "$f";''
, la asignación fallará si la cadena contiene comillas dobles. Esto es cierto para cada variante utilizando -i
o -I
. (Debido a que se reemplaza en una cadena, siempre puede inyectar comandos insertando caracteres inesperados (como comillas, comillas invertidas o signos de dólar) en los datos de entrada)
Si los comandos solo pueden tomar un parámetro a la vez:
tr ''[/n]'' ''[/0]'' < a.txt | xargs -r0 -n1 /bin/bash -c ''command1 "$@"; command2 "$@";''
O con algo menos de procesos:
tr ''[/n]'' ''[/0]'' < a.txt | xargs -r0 /bin/bash -c ''for f in "$@"; do command1 "$f"; command2 "$f"; done;''
Si tiene GNU xargs
u otro con la extensión -P
y desea ejecutar 32 procesos en paralelo, cada uno con no más de 10 parámetros para cada comando:
tr ''[/n]'' ''[/0]'' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c ''command1 "$@"; command2 "$@";''
Esto debería ser robusto contra cualquier carácter especial en la entrada. (Si la entrada está separada por nulo). La versión tr
obtendrá alguna entrada no válida si algunas de las líneas contienen nuevas líneas, pero eso es inevitable con un archivo separado por nuevas líneas.
Este es solo otro enfoque sin xargs ni cat:
while read stuff; do
command1 "$stuff"
command2 "$stuff"
...
done < a.txt
Mi actual BKM para esto es
... | xargs -n1 -I % perl -e ''system("echo 1 %"); system("echo 2 %");''
Es desafortunado que esto use perl, que es menos probable que se instale que bash; pero se maneja más entrada que la respuesta aceptada. (Doy la bienvenida a una versión ubicua que no se basa en perl.)
Sugerencia de KeithThompson de
... | xargs -I % sh -c ''command1; command2; ...''
es genial, a menos que tenga el carácter de comentario de shell # en su entrada, en cuyo caso se truncarán parte del primer comando y todo el segundo comando.
Los # de hash pueden ser bastante comunes, si la entrada se deriva de una lista del sistema de archivos, como ls o find, y su editor crea archivos temporales con # en su nombre.
Ejemplo del problema:
$ bash 1366 $> /bin/ls | cat
#Makefile#
#README#
Makefile
README
Vaya, aquí está el problema:
$ bash 1367 $> ls | xargs -n1 -I % sh -i -c ''echo 1 %; echo 2 %''
1
1
1
1 Makefile
2 Makefile
1 README
2 README
Ahh, eso es mejor:
$ bash 1368 $> ls | xargs -n1 -I % perl -e ''system("echo 1 %"); system("echo 2 %");''
1 #Makefile#
2 #Makefile#
1 #README#
2 #README#
1 Makefile
2 Makefile
1 README
2 README
$ bash 1369 $>
Otra posible solución que funciona para mí es algo como:
cat a.txt | xargs bash -c ''command1 $@; command2 $@'' bash
Tenga en cuenta el ''bash'' al final: supongo que se pasa como argv [0] a bash. Sin él, en esta sintaxis, se pierde el primer parámetro de cada comando. Puede ser cualquier palabra.
Ejemplo:
cat a.txt | xargs -n 5 bash -c ''echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $@; echo "data again: " $@'' bash
Prefiero el estilo que permite el modo de ejecución en seco (sin | sh
):
cat a.txt | xargs -I % echo "command1; command2; ... " | sh
Funciona con tuberías también:
cat a.txt | xargs -I % echo "echo % | cat " | sh
Puedes usar
cat file.txt | xargs -i sh -c ''command {} | command2 {} && command3 {}''
{} = variable para cada línea en el archivo de texto
Un poco tarde para la fiesta.
Uso el formato a continuación para comprimir mis directorios con miles de archivos pequeños antes de migrar. Si no necesita comillas simples dentro de los comandos, debería funcionar.
Con algunas modificaciones, estoy seguro de que será útil para alguien. Probado en Cygwin
(babun)
find . -maxdepth 1 ! -path . -type d -print0 | xargs -0 -I @@ bash -c ''{ tar caf "@@.tar.lzop" "@@" && echo Completed compressing directory "@@" ; }''
find .
Encontrar aqui
-maxdepth 1
No entrar en directorios secundarios
! -path .
Excluir / Ruta del directorio actual
-type d
solo directorios
-print0
Salida separada por bytes nulos / 0
| xargs
Tubería xargs a xargs
-0
entrada es nula bytes separados.
-I @@
Placeholder es @@. Reemplace @@ con la entrada.
bash -c ''...''
Ejecutar comando Bash
{...}
comando de agrupación
&&
Ejecutar el siguiente comando solo si el comando anterior se cerró correctamente (salida 0)
Final Es importante, de lo contrario fallará.
Salida:
Completed compressing directory ./Directory1 with meta characters in it
Completed compressing directory ./Directory2 with meta characters in it
Completed compressing directory ./Directory3 with meta characters in it
Actualización de julio de 2018:
Si te encantan los hacks y jugar, aquí hay algo interesante:
echo "a b c" > a.txt
echo "123" >> a.txt
echo "###this is a comment" >> a.txt
cat a.txt
myCommandWithDifferentQuotes=$(cat <<''EOF''
echo "command 1: $@"; echo ''will you do the fandango?''; echo "command 2: $@"; echo
EOF
)
< a.txt xargs -I @@ bash -c "$myCommandWithDifferentQuotes" -- @@
Salida:
command 1: a b c
will you do the fandango?
command 2: a b c
command 1: 123
will you do the fandango?
command 2: 123
command 1: ###this is a comment
will you do the fandango?
command 2: ###this is a comment
Explicación:
- Crea un único guión de línea y guárdalo en una variable.
- a.txt
lee un a.txt
y lo ejecuta como un script bash
- @@
se asegura cada vez que se pasa una línea completa
- Poner @@
después --
asegura que @@
se toma como entrada de parámetro posicional para el comando bash
, no como OPTION
inicio bash
, es decir, como el mismo -c
que significa run command
--
es mágico, funciona con muchas otras cosas, es decir, ssh
, incluso kubectl
Una cosa que hago es agregar a .bashrc / .profile esta función:
function each() {
while read line; do
for f in "$@"; do
$f $line
done
done
}
entonces puedes hacer cosas como
... | each command1 command2 "command3 has spaces"
que es menos detallado que xargs o -exec. También puede modificar la función para insertar el valor de la lectura en una ubicación arbitraria en los comandos para cada uno, si también necesita ese comportamiento.
cat a.txt | xargs -I % sh -c ''command1; command2; ...''
Tenga en cuenta que este es un uso inútil de gato . Lo escribiría como:
< a.txt xargs -I % sh -c ''command1; command2; ...''
(Sí, la redirección puede ser al principio del comando).
Presumiblemente, el command1
y / o el command2
contendrán uno o más %
caracteres; de lo contrario, no tendría mucho sentido la opción -I %
para xargs
.