Uruchamianie `exec` z wbudowanym Bash

9

Zdefiniowałem funkcję powłoki (nazwijmy ją clock), której chcę użyć jako opakowania innego polecenia, podobnego do timefunkcji, np clock ls -R.

Moja funkcja powłoki wykonuje niektóre zadania, a następnie kończy się na exec "$@".

Chciałbym, aby ta funkcja działała nawet z wbudowanymi powłokami, np. clock time ls -RPowinna wypisywać wynik timewbudowanego, a nie /usr/bin/timepliku wykonywalnego. Ale execzawsze kończy się uruchomienie polecenia.

Jak mogę sprawić, by moja funkcja Bash działała jako opakowanie, które przyjmuje również argumenty wbudowane powłoki?

Edycja : Właśnie dowiedziałem się, że timenie jest to wbudowane Bash, ale specjalne słowo zastrzeżone związane z potokami. Nadal jestem zainteresowany rozwiązaniem dla wbudowanych, nawet jeśli nie działa time, ale bardziej ogólne rozwiązanie byłoby jeszcze lepsze.

anol
źródło
Musisz jawnie wywołać powłokę, używając exec bash -c \' "$@" \'. O ile polecenie w pierwszym parametrze nie jest rozpoznawane jako skrypt powłoki, to będzie interpretowane jako plik binarny do bezpośredniego uruchomienia. Alternatywnie i prościej, po prostu przegap execi zadzwoń "@"z oryginalnej powłoki.
AFH,

Odpowiedzi:

9

Zdefiniowałeś funkcję bash. Więc już jesteś w powłoce bash podczas wywoływania tej funkcji. Ta funkcja mogłaby więc wyglądać następująco:

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

Tę funkcję można wywołać za pomocą wbudowanych poleceń bash, specjalnych słów zastrzeżonych, poleceń i innych zdefiniowanych funkcji:

Alias:

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

Wbudowane bash:

$ clock type type
do something
type is a shell builtin

Inna funkcja:

$ clock clock
do something
do something

Plik wykonywalny:

$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015
chaos
źródło
Czy w tym przypadku jest jakaś różnica między uruchomieniem $@a exec $@, jeśli wiem, że uruchamiam właściwe polecenie?
anol
3
Podczas użycia execpolecenie zastępuje powłokę. Dlatego nie ma już wbudowanych, aliasów, specjalnych słów zastrzeżonych, zdefiniowanych słów, ponieważ plik wykonywalny jest wykonywany przez wywołanie systemowe execve(). To syscall oczekuje pliku wykonywalnego.
chaos
Ale z punktu widzenia zewnętrznego obserwatora nadal można je rozróżnić, np. exec $0W przypadku jednego procesu, a $@nadal dwóch.
anol
4
Tak, $@ma działającą powłokę jako proces nadrzędny, a polecenie jako proces podrzędny. Ale jeśli chcesz używać wbudowanych, aliasów i tak dalej, musisz zachować powłokę. Lub rozpocznij nowy.
chaos
4

Jedynym sposobem na uruchomienie wbudowanej powłoki lub słowa kluczowego powłoki jest uruchomienie nowej powłoki, ponieważ exec „zamienia powłokę na podane polecenie”. Powinieneś zamienić swój ostatni wiersz na:

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

Działa to zarówno z wbudowanymi, jak i zastrzeżonymi słowami; zasada jest taka sama.

Anthony Geoghegan
źródło
3

Jeśli opakowanie musi wstawić kod przed danym poleceniem, alias działałby, ponieważ są rozwijane na bardzo wczesnym etapie:

alias clock="do this; do that;"

Aliasy są prawie dosłownie wstawiane w miejsce słowa aliasu, więc końcowe ;jest ważne - powoduje clock time foorozwinięcie do do this; do that; time foo.

Możesz użyć tego do tworzenia magicznych aliasów, które nawet omijają cytowanie.


Do wstawiania kodu po poleceniu można użyć haka „DEBUG”.

shopt -s extdebug
trap "<code>" DEBUG

Konkretnie:

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

Hak wciąż działa przed poleceniem, ale gdy zwraca false, mówi bashowi, aby anulował wykonanie (ponieważ hak już go uruchomił przez eval).

Jako kolejny przykład możesz użyć tego do aliasu, command pleaseaby sudo command:

trap 'case $BASH_COMMAND in *\ please) eval sudo ${BASH_COMMAND% *}; false; esac' DEBUG
użytkownik1686
źródło
1

Jedynym rozwiązaniem, jakie do tej pory mogłem wymyślić, byłoby przeprowadzenie analizy wielkości liter w celu rozróżnienia, czy pierwszy argument jest poleceniem, wbudowanym słowem kluczowym, czy też błąd w ostatnim przypadku:

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

Nie radzi sobie timejednak z tym, więc może istnieć lepsze (i bardziej zwięzłe) rozwiązanie.

anol
źródło