Parametrizar chamadas encadeadas para um programa utilitário no Bash

12

Eu tenho um programa UNIX de caixa preta usado em um shell Bash que lê colunas de dados de stdin, processa-as (aplicando um efeito de suavização) e depois produz para stdout. Eu o uso por pipes UNIX, como

generate | smooth | plot  

Para mais suavização, posso repetir a suavização, para que seja invocada na linha de comando do Bash como

generate | smooth | smooth | plot   

ou mesmo

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

Isso está ficando desagradável. Gostaria de criar um wrapper Bash para poder canalizar smoothe alimentar sua saída de volta para uma nova instância de smoothum número arbitrário de vezes, algo como

generate | newsmooth 5 | plot

ao invés de

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

Minha primeira tentativa foi um script Bash que gerou arquivos temporários no diretório atual e os excluiu, mas ficou feio quando eu não estava em um diretório com acesso de gravação e também deixou arquivos de lixo quando interrompidos.

Não há argumentos para o smoothprograma.

Existe uma maneira mais elegante de "encapsular" esse programa para parametrizar o número de chamadas?

Diane Wilbor
fonte
1
Espero que o seu exemplo é um caso forçado por causa da questão e não uma necessidade real
arielnmz

Respostas:

18

Você pode envolvê-lo em uma função recursiva:

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

Você usaria isso como

generate | smooth 5 | plot

o que seria equivalente a

generate | smooth | smooth | smooth | smooth | smooth | plot
muru
fonte
Isso é perfeito, comporta-se exatamente conforme necessário. E agora eu aprendi sobre a palavra-chave "comando" do bash.
Diane Wilbor
2
Aliás, essa é a mesma abordagem que uso em Como codifico uma cadeia de tubos arbitrariamente longa? - e, muito antes disso, no tratamento de longas listas de edição no xmlstarlet .
Charles Duffy
5

Se você puder digitar tantas vírgulas quanto a quantidade de smoothcomandos que desejar, poderá aproveitar a expansão Brace separada por vírgula do shell.

TL; DR

A linha de comando inteira para o seu caso de amostra seria:

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

Nota:

  • adicione ou remova vírgulas se desejar mais ou menos repetições de smooth |
  • não há |antes, plotporque isso está incluído na última smooth |string produzida pela Brace Expansion
  • você também pode fornecer argumentos smooth, desde que possa incluí-los corretamente na parte fixa citada que precede a chave aberta; de qualquer forma, lembre-se de que você as forneceria a todas as repetições do comando

Como funciona

A expansão de cinta separada por vírgula permite produzir dinamicamente seqüências de caracteres, cada uma feita de uma parte fixa especificada mais as partes variáveis ​​especificadas. Produz tantas seqüências quanto as partes variáveis ​​indicadas, como a{b,c,d}produz ab ac ad.

O pequeno truque aqui é que, se você preferir fazer uma lista de peças variáveis vazias , ou seja, com apenas vírgulas dentro dos aparelhos, a expansão de aparelho produzirá apenas cópias da peça fixa. Por exemplo:

smooth{,,,,}

vai produzir:

smooth smooth smooth smooth smooth

Observe que 4 vírgulas produzem 5 smoothstrings. É assim que essa expansão de cinta funciona: produz seqüências de caracteres tantas vírgulas quanto uma.

É claro que, no seu caso, você também precisa |separar cada um deles smooth, portanto, basta adicioná-lo na parte fixa, mas tenha o cuidado de citá-lo adequadamente para que o shell não o interprete de uma só vez. Isso é:

'smooth|'{,,,,}

vai produzir:

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

Sempre coloque a peça fixa imediatamente adjacente ao suporte aberto, ou seja, não há espaços entre o ' e o {.

(Observe também que para formar a parte fixa, você também pode usar aspas duplas em vez de aspas simples, se precisar expandir variáveis ​​de shell na parte fixa. Apenas cuide da fuga extra que é necessária quando ocorrem alguns caracteres especiais do shell dentro de uma sequência de aspas duplas).

Neste ponto, você precisa de uma eval aplicação aplicada a essa string para fazer com que o shell finalmente a interprete como o comando em pipeline que deveria ser.

Portanto, para resumir tudo, toda a linha de comando do seu caso de amostra seria:

generate | eval 'smooth |'{,,,,} plot
LL3
fonte
1
Existem preocupações de segurança significativas se isso for usado em locais onde a chamada é parametrizada. Veja minha resposta sobre a função bash recursiva versus a criação iterativa de "eval": Qual é o melhor desempenho? sobre Stack Overflow.
Charles Duffy
1
@CharlesDuffy Concordo plenamente com suas preocupações sobre os riscos implícitos no uso evalquando se fornece seqüências de caracteres não confiáveis ​​e não higienizadas para avaliação, ou seja, quando usado com variáveis ​​que podem conter conteúdo "desconhecido", como o caso que você vinculou. Por outro lado, evaltambém pode ser muito útil para o rápido "encanamento" de comandos, especialmente quando usado no prompt, como o caso em questão, onde evala entrada de apenas seria uma string literal digitada manualmente pelo usuário em pessoa
LL3 19/06/19
Como já visto em outro lugar, você sempre pode substituir eval strpor algo pretensioso e estúpido . /dev/stdin <<<str. Não só este vai fazer uma impressão em tolos, mas também irá manter @CharlesDuffy fora de sua volta ;-)
pizdelect
1
@pizdelect, você pode ler o comentário anterior do LL3 com atenção - é equilibrado, matizado e sábio. (De fato, meu próprio comentário inicial tinha nuances que você parece estar ignorando; "se usado nos casos em que a chamada é parametrizada" é uma distinção crítica: a instância do LL3 não é parametrizada, tornando-a segura).
Charles Duffy