Executando `exec` com um Bash embutido

9

Eu defini uma função shell (vamos chamá-lo clock), que eu quero usar como invólucro para outro comando, semelhante à timefunção, por exemplo clock ls -R.

Minha função shell executa algumas tarefas e termina com exec "$@".

Eu gostaria que essa função funcionasse mesmo com os shell embutidos, por exemplo, clock time ls -Rdeve gerar o resultado do timeembutido, e não do /usr/bin/timeexecutável. Mas execsempre acaba executando o comando.

Como posso fazer com que minha função Bash funcione como um invólucro que também aceite argumentos internos do shell?

Edit : Acabei de saber que timenão é um Bash embutido, mas uma palavra reservada especial relacionada a pipelines. Ainda estou interessado em uma solução para embutidos, mesmo que não funcione time, mas uma solução mais geral seria ainda melhor.

anol
fonte
Você precisa invocar um shell explicitamente usando exec bash -c \' "$@" \'. A menos que seu comando no primeiro parâmetro seja reconhecido como um script de shell, ele será interpretado como um binário para ser executado diretamente. Alternativamente, e de forma mais simples, apenas perca a execchamada e "@"do seu shell original.
AFH

Respostas:

9

Você definiu uma função bash. Então você já está em um shell bash ao invocar essa função. Portanto, essa função poderia simplesmente se parecer com:

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

Essa função pode ser chamada com bash builtins, palavras reservadas especiais, comandos e outras funções definidas:

Um apelido:

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

Um bash embutido:

$ clock type type
do something
type is a shell builtin

Outra função:

$ clock clock
do something
do something

Um executável:

$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015
caos
fonte
Nesse caso, existe alguma diferença entre executar $@e exec $@, se eu sei que estou executando um comando real?
Anol 21/04
3
Quando você usa exec, o comando substitui o shell. Portanto, não há mais builtins, aliases, palavras reservadas especiais, palavras definidas, porque o executável é executado via chamada do sistema execve(). Esse syscall espera um arquivo executável.
caos
Mas, do ponto de vista de um observador externo, ainda é possível distingui-los, por exemplo, com exec $0um único processo, enquanto $@ainda há dois.
anol 21/04/2015
4
Sim, $@possui o shell em execução como pai e o comando como processo filho. Mas quando você quiser usar builtins, aliases e assim por diante, precisará preservar o shell. Ou comece um novo.
caos
4

A única maneira de iniciar um shell embutido ou uma palavra-chave do shell é iniciar um novo shell, porque o exec "substitui o shell pelo comando especificado". Você deve substituir sua última linha por:

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

Isso funciona com palavras internas e reservadas; O princípio é o mesmo.

Anthony Geoghegan
fonte
3

Se o wrapper precisar inserir código antes do comando fornecido, um alias funcionará à medida que forem expandidos em um estágio muito inicial:

alias clock="do this; do that;"

Os aliases são quase literalmente inseridos no lugar da palavra com alias, portanto, o final ;é importante - ele faz a clock time fooexpansão para do this; do that; time foo.

Você pode abusar disso para criar aliases mágicos que ignoram as aspas.


Para inserir código após um comando, você pode usar o gancho "DEBUG".

shopt -s extdebug
trap "<code>" DEBUG

Especificamente:

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

O gancho ainda é executado antes do comando, mas, ao retornar, falseele diz ao bash para cancelar a execução (porque o gancho já o executou via eval).

Como outro exemplo, você pode usar isso como alias command pleasepara sudo command:

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

A única solução que eu poderia encontrar até agora seria realizar uma análise de caso para distinguir se o primeiro argumento é um comando, interno ou palavra-chave e falhar no ú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

No timeentanto, ele não funciona , portanto pode haver uma solução melhor (e mais concisa).

anol
fonte