Uso de xargs en combinación con bash -c para crear comandos complejos

Contenidos

xargs son fuegos artificiales para sus comandos de shell. Cualquier salida, generada a partir de cualquier comando de shell, se puede enviar a xargs para que se procese en otra línea de comando. ¡Aprenda a aprovechar este gran poder de los xargs hoy mismo!

Introducción a xargs

Mi colega Dave McKay escribió un post interesante Cómo usar el comando xargs en Linux, que puede que le guste leer primero para obtener una introducción detallada y una exploración de los xargs en general.

Este post se centrará en un obstáculo específico: qué hacer cuando se encuentra con limitaciones del apilamiento tradicional basado en tuberías o normal (; delimitado) de los comandos de Linux, y cuando inclusive el uso de xargs no parece proporcionar una respuesta de inmediato?

Este es el caso frecuente, si no frecuente, cuando se escriben scripts de una sola línea en la línea de comandos y / o cuando se procesan datos o estructuras de datos complejos. La información que se presenta aquí se basa en investigaciones y muchos años de experiencia usando este método y enfoque en particular.

Tubos y xargs

Cualquiera que aprenda Bash crecerá en sus habilidades de scripting de línea de comandos con el tiempo. Lo mejor de Bash es que cualquier habilidad aprendida en la línea de comandos se traduce fácilmente en scripts de Bash (que de forma general están marcados con un .sh sufijo). La sintaxis es casi idéntica.

Y, a medida que mejoren las habilidades, los ingenieros más nuevos de forma general descubrirán primero el idioma Pipe. Utilizar una tubería es fácil y directo: la salida del comando anterior se ‘canaliza’ a la entrada para el siguiente comando, piense en ello como una tubería de agua que lleva agua desde una fuente de salida a una fuente de entrada en otro lugar, a modo de ejemplo, agua de una presa conectado a una turbina de agua.

Veamos un ejemplo sencillo:

echo 'a' | sed 's/a/b/'

Un ejemplo simple de tubería Bash con reemplazo de texto sed regex

Aquí simplemente hicimos eco de ‘a’ y posteriormente cambiamos lo mismo usando el editor de flujo de texto sed. La salida es naturalmente ‘b’: el sed sustituyó (comando s) ‘a’ a ‘b’.

Algún tiempo después, el ingeniero se dará cuenta de que las tuberías aún disponen capacidades limitadas, especialmente cuando se desea preprocesar datos en un formato listo para la próxima herramienta. A modo de ejemplo, considere esta situación:

Enfrentarse a desafíos usando una tubería en combinación con matar

Aquí iniciamos un sueño de fondo. A continuación usamos pidof para recuperar el PID del comando de suspensión que se está ejecutando e intentar eliminarlo con kill -9 (Piense en -9 como un modo destructivo para matar un procedimiento). Fracasa. Después intentamos utilizar el PID proporcionado por el shell cuando empezamos el procedimiento en segundo plano, pero esto falla de manera semejante.

El problema es ese kill no acepta la entrada de forma directa, ya sea que provenga de ps o inclusive de un simple echo. Para arreglar este problema, podemos utilizar xargs para tomar la salida de la ps o la echo comando) y proporcionarlos como entrada para kill, haciéndolos argumentos para el comando kill. Es así como si ejecutáramos kill -9 some_pid de forma directa. Veamos cómo funciona esto:

sleep 300 &
pidof sleep | xargs kill -9

xargs resolviendo el problema del comando pipe kill visto anteriormente

Esto funciona estupendamente y logra lo que nos propusimos: matar el procedimiento de sueño. Un pequeño cambio en el código (dicho de otra forma, simplemente agregue xargs delante del comando), ¡pero un gran cambio en lo útil que puede ser Bash para el ingeniero de desarrollo!

Además podemos usar el -I opción (que establece el argumento reemplazar cadena) a kill para explicar un poco cómo estamos pasando argumentos para matar: i12b Aquí, definimos {} como nuestra cadena de reemplazo. Dicho de otra forma, siempre que xargs vea {}, lo sustituirá {} a cualquier entrada que recibió del último comando.

Aún así, inclusive esto tiene sus limitaciones. ¿Qué tal si quisiéramos proporcionar una buena información de depuración impresa en línea y desde dentro de la declaración? Parece imposible hasta ahora.

Sí, podríamos postprocesar la salida con un sed regex, o inserte una subcapa ($()) en algún lugar, pero todos estos aún parecen tener limitaciones, y especialmente cuando llega el momento de crear flujos de datos complejos, sin utilizar un nuevo comando y sin utilizar archivos temporales intermedios.

¿Y si pudiéramos, de una vez por todas, dejar atrás estas limitaciones y ser 100% libres de crear alguna ¿La línea de comandos de Bash que nos gusta, solo utiliza tuberías, xargs y el shell Bash, sin archivos intermedios temporales y sin iniciar un nuevo comando? Es factible.

Y no es nada complejo si alguien te lo muestra, pero la primera vez tomó algo de tiempo y discusión para resolverlo. Especialmente quiero dar crédito y agradecer a mi mentor de Linux anterior y colega anterior, Andrew Dalgleish: juntos descubrimos la mejor manera de hacer esto hace poco menos de 10 años.

Bienvenido a xargs con bash -c

Como hemos visto, inclusive cuando se usan tuberías en combinación con xargs, aún se encontrarán limitaciones para la creación de scripts de nivel de ingeniero superior. Tomemos nuestro ejemplo anterior e inyectemos información de depuración sin procesar posteriormente el resultado. Regularmente esto sería difícil de lograr, pero no es así con xargs combinado con bash -c:

sleep 300 &
pidof sleep | xargs -I{} echo "echo 'The PID of your sleep process was: {}'; kill -9 {}; echo 'PID {} has now been terminated'" | xargs -I{} bash -c "{}"

Una secuencia de compilación compleja del comando xargs y el uso de bash -c

Aquí usamos dos xargs comandos. El primero construye una línea de comando personalizada, usando como entrada la salida del comando anterior en la tubería (siendo pidof sleep) y el segundo comando xargs ejecuta ese comando generado, personalizado por entrada (¡importante!).

¿Por qué personalizar por entrada? El motivo es que xargs de forma predeterminada procesará línea por línea a través de su entrada (la salida del comando anterior en la tubería) y ejecutará lo que se le haya ordenado que se ejecute para cada línea de entrada.

Aquí hay mucho poder. Esto significa que puede crear y construir cualquier comando personalizado y después ejecutarlo, totalmente libre de cualquier formato en el que se encuentren los datos de entrada, y totalmente libre de tener que preocuparse por cómo ejecutarlo. La única sintaxis que debe recordar es esta:

some_command | xargs -I{} echo "echo '...{}...'; more_commands; more_commands_with_or_without{}" | xargs -I{} bash -c "{}"

Tenga en cuenta que el anidado echo (el segundo eco) solo es verdaderamente necesario si desea volver a generar el texto real. Caso contrario, si el segundo echo no estaba ahí, el primero echo comenzaría a mostrar ‘El PID …’ etc. y el bash -c el subshell no podría analizar esto como un comando (IOW, ‘El PID …’ no es un comando y no se puede ejecutar como tal, de ahí el eco secundario / anidado).

Una vez que recuerdes el bash -c, los -I{} y la manera de hacer eco desde dentro de otro eco (y alternativamente se podrían utilizar secuencias de escape si es necesario), se encontrará usando esta sintaxis una y otra vez.

Digamos que tiene que ejecutar tres cosas por archivo en el directorio: 1) generar el contenido del archivo, 2) moverlo a un subdirectorio, 3) eliminarlo. Regularmente, esto requeriría una serie de pasos con diferentes comandos por etapas, y si se torna más complejo, es factible que inclusive necesite archivos temporales. Pero se hace muy fácilmente usando bash -c y xargs:

echo '1' > a
echo '2' > b
echo '3' > c
mkdir subdir
ls --color=never | grep -v subdir | xargs -I{} echo "cat {}; mv {} subdir; rm subdir/{}" | xargs -I{} bash -c "{}"

Un mini script completamente funcional basado en xargs y bash -c en un comando de una sola línea

En primer lugar, notando rápidamente que siempre es una buena idea usar --color=never por ls, para evitar problemas en el sistema que utiliza codificación de colores para las salidas de la lista de directorios (activada por defecto en Ubuntu), dado que esto a menudo causa fuertes problemas de análisis debido a que los códigos de color se envían verdaderamente al terminal y se prefijan a las entradas de la lista de directorios.

Primero creamos tres archivos, a, byc, y un subdirectorio llamado subdir. Excluimos este subdirectorio de la lista de directorios con grep -v después de que notamos que una ejecución de prueba rápida (sin ejecutar los comandos, o dicho de otra forma, sin los segundos xargs), el subdirectorio aún se muestra.

Siempre es esencial probar sus comandos complejos primero observando la salida antes de pasarlos a una subcapa de bash con bash -c para su ejecución.

Por último, usamos exactamente el mismo método que se vio previamente para construir o ordenar; cat (mostrar) el archivo, mover el archivo (indicado por {}) al subdirectorio y, para terminar, elimine el archivo dentro del subdirectorio. Vemos que el contenido de los 3 archivos (1, 2, 3) se muestra en la pantalla, y si revisamos el directorio actual, nuestros archivos se han ido. Además podemos ver cómo no hay más archivos en el subdirectorio. Todo funcionó bien.

Terminando

Usando xargs siendo un gran poder para un usuario avanzado de Linux (y en esta circunstancia) Bash. Usando xargs en combinación con bash -c trae un poder mucho mayor aún; la capacidad única de crear líneas de comando complejas y personalizadas 100% gratuitas, sin necesitar de archivos o construcciones intermedias, y sin necesitar de tener comandos apilados / secuenciados.

¡Disfrutar!

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.