Esecuzione di `exec` con un Bash integrato


9

Ho definito una funzione di shell (chiamiamola clock), che voglio usare come wrapper per un altro comando, simile alla timefunzione, ad es clock ls -R.

La mia funzione shell esegue alcune attività e quindi termina exec "$@".

Vorrei che questa funzione funzionasse anche con gli incorporati della shell, ad esempio clock time ls -Rdovrebbe produrre il risultato dell'integrato timee non l' /usr/bin/timeeseguibile. Ma execfinisce sempre per eseguire invece il comando.

Come posso far funzionare la mia funzione Bash come wrapper che accetta anche gli elementi incorporati della shell come argomenti?

Modifica : ho appena appreso che timenon è un Bash integrato, ma una parola speciale riservata relativa alle pipeline. Sono ancora interessato a una soluzione per built-in anche se non funziona time, ma una soluzione più generale sarebbe ancora migliore.


È necessario richiamare esplicitamente una shell, usando exec bash -c \' "$@" \'. A meno che il tuo comando nel primo parametro non sia riconosciuto come uno script di shell, verrà interpretato come un file binario per essere eseguito direttamente. In alternativa, e più semplicemente, perdi exece chiama "@"dalla tua shell originale.
AFH,

Risposte:


9

Hai definito una funzione bash. Quindi sei già in una shell bash quando invochi quella funzione. Quindi quella funzione potrebbe semplicemente apparire come:

clock(){
  echo "do something"
  $@
}

Quella funzione può essere invocata con builtin bash, parole riservate speciali, comandi, altre funzioni definite:

Un alias:

$ clock type ls
do something
ls is aliased to `ls --color=auto'

Un bash incorporato:

$ clock type type
do something
type is a shell builtin

Un'altra funzione:

$ clock clock
do something
do something

Un eseguibile:

$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015

In questo caso, c'è qualche differenza tra l'esecuzione $@e exec $@, se so che sto eseguendo un comando effettivo?
anol

3
Quando si utilizza exec, il comando sostituisce la shell. Quindi non ci sono più builtin, alias, parole speciali riservate, parole definite, perché l'eseguibile viene eseguito tramite chiamata di sistema execve(). Quel syscall si aspetta un file eseguibile.
caos,

Ma dal punto di vista di un osservatore esterno, è ancora possibile distinguerli, ad esempio con exec $0un singolo processo, mentre $@ne ha ancora due.
anol

4
Sì, $@ha la shell in esecuzione come genitore e il comando come processo figlio. Ma quando vuoi usare builtin, alias e così via, devi preservare la shell. O creane uno nuovo.
caos,

4

L'unico modo per avviare un builtin della shell o una parola chiave della shell è lanciare una nuova shell perché exec "sostituisce la shell con il comando dato". Dovresti sostituire l'ultima riga con:

IFS=' '; exec bash -c "$*"

Funziona sia con builtin che con parole riservate; Il principio è lo stesso.


3

Se il wrapper deve inserire il codice prima del comando dato, un alias funzionerebbe in quanto espanso in una fase molto precoce:

alias clock="do this; do that;"

Gli alias sono quasi letteralmente inseriti al posto della parola con alias, quindi il trailing ;è importante - lo fa clock time fooespandere do this; do that; time foo.

Puoi abusare di questo per creare alias magici che aggirano persino la quotazione.


Per inserire il codice dopo un comando, è possibile utilizzare l'hook "DEBUG".

shopt -s extdebug
trap "<code>" DEBUG

In particolare:

shopt -s extdebug
trap 'if [[ $BASH_COMMAND == "clock "* ]]; then
          eval "${BASH_COMMAND#clock }"; echo "Whee!"; false
      fi' DEBUG

L'hook viene ancora eseguito prima del comando, ma quando ritorna falsedice a bash di annullare l'esecuzione (perché l'hook lo ha già eseguito tramite eval).

Come altro esempio, puoi usarlo come alias command pleaseper sudo command:

trap 'case $BASH_COMMAND in *\ please) eval sudo ${BASH_COMMAND% *}; false; esac' DEBUG

1

L'unica soluzione che potrei trovare finora sarebbe quella di eseguire un'analisi del caso per distinguere se il primo argomento è un comando, incorporato o una parola chiave e fallire nell'ultimo caso:

#!/bin/bash

case $(type -t "$1") in
  file)
    # do stuff
    exec "$@"
    ;;
  builtin)
    # do stuff
    builtin "$@"
    ;;
  keyword)
    >&2 echo "error: cannot handle keywords: $1"
    exit 1
    ;;
  *)
    >&2 echo "error: unknown type: $1"
    exit 1
    ;;
esac

Non gestisce time, tuttavia, quindi potrebbe esserci una soluzione migliore (e più concisa).

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.