Para obter a saída de um comando, você precisa lê-lo de alguma forma. type
escreve em sua stdout. E precisamos de alguma forma obter isso e passá-lo para o comando [
.
$(...)
usa um pipe para isso. Mas, para um pipe, você precisa de um escritor e um processo de leitura, então você terá que desembolsar um processo mesmo para executar um comando que esteja embutido. Você pode tentar ler e gravar em um pipe no mesmo processo, mas isso geralmente é propenso a deadlocks, pois a gravação pode ser bloqueada se ninguém estiver lendo (embora seja necessário que o buffer do pipe fique cheio para que isso aconteça, o que é improvável para type
) .
Você poderia fazer isso com o shell yash
que tem uma interface bruta para pipe()
:
{
type echo >&3
echo 3>&- # close the writing end so the reader can see an eof
IFS= read -r answer <&4
} 3>>|4
Acima, você teria um deadlock se a saída de type
fosse maior que o tamanho do buffer de pipe (64KiB por padrão nas versões modernas do Linux).
Com bash
, você sempre pode fazer:
type -t echo > file
IFS= read -rd '' type < file
if [ "$type" = builtin ]...
Mas enquanto isso evita o subshell, isso significa jogar lixo no sistema de arquivos com esse file
.
Aqui type
é um incorporado. Sua saída é gerada pelo shell e é verdade que parece um pouco bobo ter que separar um processo para poder usar essa saída no shell.
Alguns shells ( ksh93
e fish
) implementam algumas otimizações lá. Em seu $(type echo)
( (type echo)
in fish
), eles na verdade falsificam a escrita da saída e a leitura dela. Quando o stdout de um servidor interno é uma substituição de comando, em vez de gravar a saída, o shell apenas anexa o texto de saída a ser enviado ao resultado da substituição do comando e não há necessidade de um fork.
Na verdade, fish
(type echo)
é mais parecido com ksh93
${ type echo;}
, pois nem cria um ambiente subshell. Com $(...)
, ksh93
emula um ambiente subshell para que pareça que um processo filho foi bifurcado para interpretar o código nele e não faz isso para a variante ${ ...;}
ksh93$ a=1
ksh93$ echo "$(a=2; type echo) $a"
echo is a shell builtin 1
ksh93$ echo "${ a=3; type echo;} $a"
echo is a shell builtin 3
fish> set a 1
fish> echo (set a 2; type echo) $a
echo is a builtin 2
Você verá que em alguns shells que não fazem essa otimização, muitos dos builtins podem ser chamados de tal forma que, ao invés de escrever seus resultados, eles armazenam em uma variável.
Os mais óbvios são o padrão read
e getopts
que fazem isso por padrão (você faz IFS= read -r var
em vez de var=$(line)
). bash
e zsh
também têm printf -v variable format args
. zsh
pode fazer o mesmo com seus stat
, strftime
... builtins.
Algumas conchas também disponibilizam algumas informações automaticamente em algumas variáveis especiais, como ksh
' $SECONDS
e $RANDOM
encontradas em algumas outras conchas (e as comuns como $-
, $#
(equivalente a fish
' (count $argv)
por exemplo)).
Em zsh
, isso é generalizado para a maioria das informações internas do shell, portanto, você quase nunca precisa usar a substituição de comandos na saída de um arquivo interno. Por exemplo, ele tem matrizes associativas para a lista de builtins, palavras-chave, comandos ...
if (($+builtins[echo])); then
echo echo is a builtin
fi