Ejecutando `exec` con un Bash incorporado

9

Definí una función de shell (llamémosla clock), que quiero usar como un contenedor para otro comando, similar a la timefunción, por ejemplo clock ls -R.

Mi función de shell realiza algunas tareas y luego termina con exec "$@".

Me gustaría que esta función funcione incluso con las funciones integradas de shell, por ejemplo, clock time ls -Rdebería generar el resultado de la función timeincorporada, y no el /usr/bin/timeejecutable. Pero execsiempre termina ejecutando el comando en su lugar.

¿Cómo puedo hacer que mi función Bash funcione como una envoltura que también acepta incorporaciones de shell como argumentos?

Editar : Acabo de enterarme que timeno es un Bash incorporado, sino una palabra reservada especial relacionada con las tuberías. Todavía estoy interesado en una solución para dispositivos integrados, incluso si no funciona time, pero una solución más general sería aún mejor.

anol
fuente
Necesitas invocar un shell explícitamente, usando exec bash -c \' "$@" \'. A menos que su comando en el primer parámetro se reconozca como un script de shell, se interpretará como un binario que se ejecutará directamente. Alternativamente, y de manera más simple, simplemente omita execy llame "@"desde su shell original.
AFH

Respuestas:

9

Definiste una función bash. Entonces ya estás en un shell bash cuando invocas esa función. Entonces esa función podría simplemente verse así:

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

Esa función se puede invocar con bash builtins, palabras especiales reservadas, comandos, otras funciones definidas:

Un alias:

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

Un bash incorporado:

$ clock type type
do something
type is a shell builtin

Otra función:

$ clock clock
do something
do something

Un ejecutable:

$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015
caos
fuente
En este caso, ¿hay alguna diferencia entre ejecutar $@y exec $@, si sé que estoy ejecutando un comando real?
anol
3
Cuando lo usa exec, el comando reemplaza el shell. Por lo tanto, ya no hay palabras incorporadas, alias, palabras especiales reservadas, palabras definidas, porque el ejecutable se ejecuta a través de una llamada al sistema execve(). Esa llamada al sistema espera un archivo ejecutable.
caos
Pero desde el punto de vista de un observador externo, ¿es posible distinguirlos, por ejemplo, con exec $0un solo proceso, mientras que $@todavía tiene dos?
anol
44
Sí, $@tiene el shell en ejecución como padre y el comando como proceso hijo. Pero cuando desea usar builtins, alias, etc., debe preservar el caparazón. O comenzar uno nuevo.
caos
4

La única forma de iniciar un shell incorporado o una palabra clave de shell es iniciar un nuevo shell porque exec "reemplaza el shell con el comando dado". Debe reemplazar su última línea con:

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

Esto funciona con palabras integradas y palabras reservadas; El principio es el mismo.

Anthony Geoghegan
fuente
3

Si el contenedor necesita insertar código antes del comando dado, un alias funcionaría ya que se expanden en una etapa muy temprana:

alias clock="do this; do that;"

Los alias se insertan casi literalmente en lugar de la palabra con alias, por lo que el seguimiento ;es importante, hace que se clock time fooexpanda a do this; do that; time foo.

Puede abusar de esto para crear alias mágicos que incluso eviten las citas.


Para insertar código después de un comando, puede usar el gancho "DEBUG".

shopt -s extdebug
trap "<code>" DEBUG

Específicamente:

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

El gancho todavía se ejecuta antes del comando, pero cuando regresa falsele dice a bash que cancele la ejecución (porque el gancho ya lo ejecutó a través de eval).

Como otro ejemplo, puede usar esto para alias command pleasepara sudo command:

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

La única solución que podría encontrar hasta ahora sería realizar un análisis de caso para distinguir si el primer argumento es un comando, incorporado o una palabra clave, y fallar en el último 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

Sin embargo, no maneja time, por lo que podría haber una solución mejor (y más concisa).

anol
fuente