Parametrizar llamadas encadenadas a un programa de utilidad en Bash

12

Tengo un programa UNIX de recuadro negro utilizado en un shell Bash que lee columnas de datos de stdin, los procesa (aplicando un efecto de suavizado) y luego los envía a stdout. Lo uso por tuberías UNIX, como

generate | smooth | plot  

Para más suavizado, puedo repetir el suavizado, por lo que se invocará desde la línea de comando Bash como

generate | smooth | smooth | plot   

o incluso

generate | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | plot

Esto se está volviendo extraño. Me gustaría hacer un contenedor Bash para poder canalizar smoothy alimentar su salida de nuevo a una nueva instancia de smoothun número arbitrario de veces, algo así como

generate | newsmooth 5 | plot

en lugar de

generate | smooth | smooth | smooth | smooth | smooth | plot

Mi primer intento fue un script Bash que generó archivos temporales en el directorio actual y los eliminó, pero eso se volvió feo cuando no estaba en un directorio con acceso de escritura, y también dejaba archivos basura cuando se interrumpía.

No hay argumentos para el smoothprograma.

¿Hay una manera más elegante de "envolver" un programa para parametrizar el número de llamadas?

Diane Wilbor
fuente
1
Espero que su ejemplo es un caso forzada por el bien de la pregunta y no una necesidad real
arielnmz

Respuestas:

18

Podría envolverlo en una función recursiva:

smooth() {
  if [[ $1 -gt 1 ]]; then # add another call to function
    command smooth | smooth $(($1 - 1)) 
  else
    command smooth # no further 
  fi
}

Usarías esto como

generate | smooth 5 | plot

que sería equivalente a

generate | smooth | smooth | smooth | smooth | smooth | plot
muru
fuente
Esto es perfecto, se comporta exactamente como sea necesario. Y ahora aprendí acerca de la palabra clave bash "comando".
Diane Wilbor
2
Por cierto, este es el mismo enfoque que utilizo en ¿Cómo codifico una cadena de tuberías arbitrariamente larga? - y, mucho antes de eso, en el manejo de largas listas de edición en xmlstarlet .
Charles Duffy
5

Si puede permitirse escribir tantas comas como la cantidad de smoothcomandos que desee, puede aprovechar la Expansión de llaves separada por comas del shell.

TL; DR

La línea de comandos completa para su caso de muestra sería:

generate | eval 'smooth |'{,,,,} plot

Nota:

  • agregue o elimine comas si desea más o menos repeticiones de smooth |
  • no hay |antes plotporque está incluido en la última smooth |cadena producida por la expansión Brace
  • también puede proporcionar argumentos smooth, siempre y cuando pueda incluirlos correctamente dentro de la parte fija citada que precede a la llave abierta; en cualquier caso, recuerde que los proporcionará a todas las repeticiones del comando

Cómo funciona

La expansión de llaves separadas por comas le permite producir cadenas dinámicamente, cada una hecha de una parte fija específica más las partes variables especificadas. Produce tantas cadenas como hay partes variables indicadas, como a{b,c,d}produce ab ac ad.

El pequeño truco aquí es que si prefiere hacer una lista de partes variables vacías , es decir, con solo comas dentro de las llaves, la expansión de llaves solo producirá copias de la parte fija solamente. Por ejemplo:

smooth{,,,,}

Producirá:

smooth smooth smooth smooth smooth

Tenga en cuenta que 4 comas produce 5 smoothcadenas. Así es como funciona esta expansión Brace: produce cadenas de comas más una.

Por supuesto, en su caso, también necesita |separar cada uno smooth, así que solo agréguelo en la parte fija pero tenga cuidado de citarlo correctamente para que el shell no lo interprete de inmediato. Es decir:

'smooth|'{,,,,}

Producirá:

'smooth|' 'smooth|' 'smooth|' 'smooth|' 'smooth|'

Tenga cuidado de colocar siempre la parte fija inmediatamente adyacente a la llave abierta, es decir, sin espacios entre el ' y el {.

(Tenga en cuenta también que para formar la parte fija también puede usar comillas dobles en lugar de comillas simples, si necesita expandir las variables de shell en la parte fija. Solo tenga cuidado con el escape adicional que se requiere cuando aparecen caracteres especiales de shell dentro de una cadena con comillas dobles).

En este punto, necesita una eval aplicación a esa cadena para que el shell finalmente la interprete como el comando canalizado que se supone que es.

Por lo tanto, para resumirlo todo, la línea de comandos completa para su caso de muestra sería:

generate | eval 'smooth |'{,,,,} plot
LL3
fuente
1
Existen importantes preocupaciones de seguridad si se usa en lugares donde la llamada está parametrizada. Vea mi respuesta sobre la función bash recursiva frente a la construcción iterativa de cadenas "eval": ¿Cuál funciona mejor? sobre el desbordamiento de pila.
Charles Duffy
1
@CharlesDuffy Estoy totalmente de acuerdo con sus inquietudes sobre los riesgos implícitos en el uso evalcuando uno proporciona cadenas no confiables y no desinfectadas para que evalúe, es decir, cuando se usa con variables que pueden llevar contenido "desconocido" como el caso que vinculó. Por otro lado, evaltambién puede ser muy útil para la "plomería" rápida de comandos, especialmente cuando se usa en el indicador, como parece ser el caso en cuestión, donde evalla entrada solo sería una cadena literal escrita manualmente por el usuario en persona
LL3
Como ya se vio en otra parte, siempre puedes reemplazarlo eval strpor algo pretencioso y estúpido . /dev/stdin <<<str. Esto no solo causará impresión en los tontos, sino que también mantendrá a @CharlesDuffy lejos de tu espalda ;-)
pizdelect
1
@pizdelect, puede leer cuidadosamente el comentario anterior de LL3: es equilibrado, matizado y sabio. (De hecho, mi propio comentario inicial tenía matices que parece ignorar: "si se usa en casos donde la llamada está parametrizada" es una distinción crítica: la instancia de LL3 no está parametrizada, por lo que es segura).
Charles Duffy