Ausführen von `exec` mit einem eingebauten Bash

9

Ich habe eine Shell-Funktion definiert (nennen wir sie clock), die ich als Wrapper für einen anderen Befehl verwenden möchte, ähnlich der timeFunktion, z clock ls -R.

Meine Shell-Funktion führt einige Aufgaben aus und endet dann mit exec "$@".

Ich möchte, dass diese Funktion auch mit integrierten Shell-Funktionen funktioniert, z. B. clock time ls -Rsollte das Ergebnis der timeintegrierten Shell und nicht der /usr/bin/timeausführbaren Datei ausgegeben werden . Am Ende wird jedoch execimmer der Befehl ausgeführt.

Wie kann ich meine Bash-Funktion als Wrapper verwenden, der auch integrierte Shell-Argumente als Argumente akzeptiert?

Bearbeiten : Ich habe gerade erfahren, dass dies timekein eingebauter Bash ist, sondern ein spezielles reserviertes Wort, das sich auf Pipelines bezieht. Ich bin immer noch an einer Lösung für integrierte Funktionen interessiert, auch wenn diese nicht funktioniert time, aber eine allgemeinere Lösung wäre noch besser.

anol
quelle
Sie müssen eine Shell explizit mit aufrufen exec bash -c \' "$@" \'. Sofern Ihr Befehl im ersten Parameter nicht als Shell-Skript erkannt wird, wird er als Binärdatei interpretiert, die direkt ausgeführt wird. Alternativ und einfacher, verpassen Sie einfach das execund rufen Sie "@"von Ihrer ursprünglichen Shell aus an.
AFH

Antworten:

9

Sie haben eine Bash-Funktion definiert. Sie befinden sich also bereits in einer Bash-Shell, wenn Sie diese Funktion aufrufen. Diese Funktion könnte dann einfach so aussehen:

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

Diese Funktion kann mit Bash-Builtins, speziellen reservierten Wörtern, Befehlen und anderen definierten Funktionen aufgerufen werden:

Ein Alias:

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

Ein Bash eingebaut:

$ clock type type
do something
type is a shell builtin

Eine weitere Funktion:

$ clock clock
do something
do something

Eine ausführbare Datei:

$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015
Chaos
quelle
Gibt es in diesem Fall einen Unterschied zwischen dem Ausführen $@und exec $@, wenn ich weiß, dass ich einen tatsächlichen Befehl ausführe?
Anol
3
Bei Verwendung execersetzt der Befehl die Shell. Daher gibt es keine eingebauten, Aliase, speziellen reservierten Wörter, definierten Wörter mehr, da die ausführbare Datei über einen Systemaufruf ausgeführt wird execve(). Dieser Systemaufruf erwartet eine ausführbare Datei.
Chaos
Aber aus der Sicht eines externen Beobachters ist es immer noch möglich, sie zu unterscheiden, z. B. exec $0wenn es einen einzigen Prozess gibt, während es $@noch zwei gibt.
Anol
4
Ja, $@hat die laufende Shell als übergeordneten und den Befehl als untergeordneten Prozess. Wenn Sie jedoch integrierte Funktionen, Aliase usw. verwenden möchten, müssen Sie die Shell beibehalten. Oder starten Sie eine neue.
Chaos
4

Die einzige Möglichkeit, eine integrierte Shell oder ein Shell-Schlüsselwort zu starten, besteht darin, eine neue Shell zu starten, da exec "die Shell durch den angegebenen Befehl ersetzt". Sie sollten Ihre letzte Zeile ersetzen durch:

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

Dies funktioniert sowohl mit eingebauten als auch mit reservierten Wörtern. Das Prinzip ist das gleiche.

Anthony Geoghegan
quelle
3

Wenn der Wrapper vor dem angegebenen Befehl Code einfügen muss, funktioniert ein Alias, da er sehr früh erweitert wird:

alias clock="do this; do that;"

Aliase werden fast wörtlich anstelle des Alias-Wortes eingefügt, daher ;ist das Nachstellen wichtig - es clock time fooerweitert sich auf do this; do that; time foo.

Sie können dies missbrauchen, um magische Aliase zu erstellen, die sogar das Zitieren umgehen.


Zum Einfügen von Code nach einem Befehl können Sie den Hook "DEBUG" verwenden.

shopt -s extdebug
trap "<code>" DEBUG

Speziell:

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

Der Hook wird immer noch vor dem Befehl ausgeführt, aber wenn er zurückgegeben wird false, weist er bash an, die Ausführung abzubrechen (da der Hook ihn bereits über eval ausgeführt hat).

Als weiteres Beispiel können Sie diesen Alias ​​verwenden command pleasefür sudo command:

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

Die einzige Lösung, die ich bisher finden könnte, wäre eine Fallanalyse, um zu unterscheiden, ob das erste Argument ein Befehl, ein integriertes oder ein Schlüsselwort ist, und im letzten Fall fehlzuschlagen:

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

Es funktioniert jedoch nicht time, sodass es möglicherweise eine bessere (und präzisere) Lösung gibt.

anol
quelle