Parametrizzare le chiamate concatenate a un programma di utilità in Bash


12

Ho un programma UNIX black box usato in una shell Bash che legge colonne di dati da stdin, le elabora (applicando un effetto smoothing) e poi le emette su stdout. Lo uso da pipe UNIX, come

generate | smooth | plot  

Per un maggiore smoothing, posso ripetere il smooth, quindi sarebbe invocato dalla riga di comando di Bash come

generate | smooth | smooth | plot   

o anche

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

Questo sta diventando unwewey. Vorrei creare un wrapper Bash per essere in grado di reindirizzare smoothe alimentare il suo output direttamente in una nuova istanza di smoothun numero arbitrario di volte, qualcosa come

generate | newsmooth 5 | plot

invece di

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

Il mio primo tentativo è stato uno script Bash che ha generato file temporanei nella directory corrente e li ha eliminati, ma che è diventato brutto quando non ero in una directory con accesso in scrittura e ha anche lasciato i file spazzatura quando interrotto.

Non ci sono argomenti per il smoothprogramma.

Esiste un modo più elegante di "avvolgere" un tale programma per parametrizzare il numero di chiamate?


1
Spero che il tuo esempio sia un caso forzato per il bene della domanda e non un reale bisogno
arielnmz,

Risposte:


18

Potresti avvolgerlo in una funzione ricorsiva:

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

Lo useresti come

generate | smooth 5 | plot

quale sarebbe equivalente a

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

Questo è perfetto, si comporta esattamente come necessario. E ora ho imparato a conoscere la parola chiave "comando" bash.
Diane Wilbor,


5

Se puoi permetterti di digitare tutte le virgole della quantità di smoothcomandi che desideri, puoi trarre vantaggio dall'espansione di parentesi separata dalla virgola della shell.

TL; DR

L'intera riga di comando per il caso di esempio sarebbe:

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

Nota:

  • aggiungere o rimuovere le virgole se si desidera più o meno ripetizioni di smooth |
  • non c'è |prima plotperché è incluso nell'ultima smooth |stringa prodotta da Brace Expansion
  • è inoltre possibile fornire argomenti a smooth, purché sia ​​possibile includerli correttamente nella parte fissa quotata che precede la parentesi graffa aperta; in ogni caso ricorda che li forniresti a tutte le ripetizioni del comando

Come funziona

L'espansione di parentesi graffe separate da virgola consente di produrre in modo dinamico stringhe, ciascuna composta da una parte fissa specificata più le parti variabili specificate. Produce tante stringhe quante sono le parti variabili indicate, come a{b,c,d}produce ab ac ad.

Il piccolo trucco qui è che se invece si crea un elenco di parti variabili vuote , ovvero con solo virgole all'interno delle parentesi graffe, l'espansione Brace produrrà solo copie della parte fissa. Per esempio:

smooth{,,,,}

produrrà:

smooth smooth smooth smooth smooth

Si noti che 4 virgole producono 5 smoothstringhe. Ecco come funziona questa espansione Brace: produce stringhe quante più virgole più una.

Ovviamente nel tuo caso devi anche |separare ciascuno smooth, quindi aggiungilo nella parte fissa ma fai attenzione a citarlo correttamente per fare in modo che la shell non lo interpreti immediatamente. Questo è:

'smooth|'{,,,,}

produrrà:

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

Fare attenzione a posizionare sempre la parte fissa immediatamente adiacente alla staffa aperta, cioè senza spazi tra la ' e la {.

(Si noti inoltre che per formare la parte fissa è possibile utilizzare anche virgolette doppie anziché virgolette singole, se è necessario espandere le variabili della shell nella parte fissa. Basta occuparsi dell'escaping aggiuntivo necessario quando si verificano i caratteri speciali di alcune shell all'interno di una stringa tra virgolette doppie).

A questo punto hai bisogno di eval applicarlo a quella stringa per far sì che la shell finalmente la interpreti come il comando pipeline che dovrebbe essere.

Quindi, per riassumere tutto, l'intera riga di comando per il tuo caso di esempio sarebbe:

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

1
Esistono importanti problemi di sicurezza se questo viene utilizzato in luoghi in cui la chiamata è parametrizzata. Vedi la mia risposta sulla funzione bash ricorsiva vs la costruzione di stringhe "eval" iterative: quale si comporta meglio? sopra Stack Overflow.
Charles Duffy,

1
@CharlesDuffy Concordo pienamente con le tue preoccupazioni circa i rischi impliciti nell'uso evalquando si forniscono stringhe non attendibili, non igienizzate, che possono essere valutate, cioè quando vengono utilizzate con variabili che possono contenere contenuti "sconosciuti" come nel caso collegato. D'altra parte, evalpuò anche essere molto utile per una rapida "idraulica" dei comandi, specialmente se utilizzato al prompt, come sembra il caso in esame, in cui evall'input sarebbe solo una stringa letterale digitata manualmente dall'utente in persona
LL3

Come già visto altrove, puoi sempre sostituire eval strqualcosa di pretenzioso e stupido . /dev/stdin <<<str. Questo non solo farà colpo sugli sciocchi, ma terrà anche @CharlesDuffy lontano dalla tua schiena ;-)
pizdelect

1
@pizdelect, potresti leggere attentamente il precedente commento di LL3: è equilibrato, sfumato e saggio. (In effetti, il mio commento iniziale aveva delle sfumature che sembra ignorare: "se usato nei casi in cui la chiamata è parametrizzata" è una distinzione fondamentale: l'istanza di LL3 non è parametrizzata, rendendola sicura).
Charles Duffy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.