Exécuter `exec` avec un Bash intégré

9

J'ai défini une fonction shell (appelons-la clock), que je veux utiliser comme wrapper pour une autre commande, similaire à la timefonction, par exemple clock ls -R.

Ma fonction shell effectue certaines tâches, puis se termine par exec "$@".

Je voudrais que cette fonction fonctionne même avec les commandes internes du shell, par exemple clock time ls -Rdevrait produire le résultat de la commande timeintégrée, et non l' /usr/bin/timeexécutable. Mais execfinit toujours par exécuter la commande à la place.

Comment puis-je faire en sorte que ma fonction Bash fonctionne comme un wrapper qui accepte également les arguments intégrés au shell comme arguments?

Edit : je viens d'apprendre que ce timen'est pas un Bash intégré, mais un mot réservé spécial lié aux pipelines. Je suis toujours intéressé par une solution pour les intégrés même si cela ne fonctionne pas time, mais une solution plus générale serait encore meilleure.

anol
la source
Vous devez invoquer un shell explicitement, en utilisant exec bash -c \' "$@" \'. À moins que votre commande dans le premier paramètre ne soit reconnue comme un script shell, elle sera interprétée comme un binaire à exécuter directement. Alternativement, et plus simplement, il vous suffit de rater le execet d'appeler "@"depuis votre shell d'origine.
AFH

Réponses:

9

Vous avez défini une fonction bash. Vous êtes donc déjà dans un shell bash lorsque vous appelez cette fonction. Donc, cette fonction pourrait alors simplement ressembler à:

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

Cette fonction peut être invoquée avec des commandes bash, des mots réservés spéciaux, des commandes, d'autres fonctions définies:

Un alias:

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

Un bash intégré:

$ clock type type
do something
type is a shell builtin

Une autre fonction:

$ clock clock
do something
do something

Un exécutable:

$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015
le chaos
la source
Dans ce cas, y a-t-il une différence entre l'exécution $@et exec $@, si je sais que j'exécute une commande réelle?
anol
3
Lorsque vous utilisez exec, la commande remplace le shell. Il n'y a donc plus de commandes intégrées, d'alias, de mots réservés spéciaux, de mots définis, car l'exécutable est exécuté via un appel système execve(). Ce syscall attend un fichier exécutable.
chaos
Mais du point de vue d'un observateur externe, est-il encore possible de les distinguer, par exemple avec exec $0un seul processus, alors $@qu'il en existe encore deux.
anol
4
Oui, $@le shell en cours d'exécution est parent et la commande est un processus enfant. Mais lorsque vous souhaitez utiliser des commandes internes, des alias, etc., vous devez conserver le shell. Ou commencez-en un nouveau.
chaos
4

La seule façon de lancer un shell intégré ou un mot clé shell est de lancer un nouveau shell car exec "remplace le shell par la commande donnée". Vous devez remplacer votre dernière ligne par:

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

Cela fonctionne à la fois avec les mots intégrés et les mots réservés; Le principe est le même.

Anthony Geoghegan
la source
3

Si l'encapsuleur doit insérer du code avant la commande donnée, un alias fonctionnera car ils sont développés à un stade très précoce:

alias clock="do this; do that;"

Les alias sont presque littéralement insérés à la place du mot aliasé, donc la fin ;est importante - elle se clock time foodéveloppe jusqu'à do this; do that; time foo.

Vous pouvez en abuser pour créer des alias magiques qui contournent même les guillemets.


Pour insérer du code après une commande, vous pouvez utiliser le hook "DEBUG".

shopt -s extdebug
trap "<code>" DEBUG

Plus précisément:

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

Le hook s'exécute toujours avant la commande, mais au retour, falseil indique à bash d'annuler l'exécution (car le hook l'a déjà exécuté via eval).

Comme autre exemple, vous pouvez l'utiliser comme alias command pleasepour sudo command:

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

La seule solution que j'ai pu trouver jusqu'à présent serait d'effectuer une analyse de cas pour distinguer si le premier argument est une commande, intégrée ou un mot clé, et échouer dans le dernier cas:

#!/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

Il ne gère pas time, cependant, donc il pourrait y avoir une meilleure solution (et plus concise).

anol
la source