2

According to https://www.gnu.org/software/bash/manual/bashref.html#Command-Grouping

Placing a list of commands between curly braces causes the list to be executed in the current shell context.

But when I try this: if [[ { type -t echo; } = "builtin" ]]; then echo 1; else echo 0; fiI get the following error:

-bash: conditional binary operator expected

-bash: syntax error near `type'

That's OK, I get it's not meant to be used like that. Now I know that if I use if [[ $( type -t echo ) = "builtin" ]]; then echo 1; else echo 0; fi I will theoretically achieve the desired functionality.

I found other questions about preventing the unnecessary use of subshells answered but none of them explained comparing the output of a command without a subshell. All I really want is to avoid using a subshell where it's not really needed and I would really like it, if possible, to run this check in the current shell context - it really feels like this is how it's supposed to be done. P.S. I don't mind using variables and manipulation if there's no other way.

1 Answer 1

6

To get the output of a command, you need to read it somehow. type writes it on its stdout. And we need somehow to get that and pass it to the [ command.

$(...) uses a pipe for that. But for a pipe, you need a writer and a reader process, so you'll have to fork a process even for running a command that is builtin. You can try reading and writing to a pipe in the same process but that's usually prone to deadlocks as the writing can block if nobody is reading (though you need the pipe buffer to get full for that to happen which is unlikely for type).

You could do it with the yash shell that has a raw interface to pipe():

{
  type echo >&3
  echo 3>&- # close the writing end so the reader can see an eof
  IFS= read -r answer <&4
} 3>>|4

Above, you'd get a deadlock if type's output was larger than the pipe buffer size (64KiB by default on modern versions of Linux).

With bash, you could always do:

type -t echo > file
IFS= read -rd '' type < file
if [ "$type" = builtin ]...

But while that avoids the subshell, that means littering the file system with that file.

Here type is a builtin. Its output is generated by the shell and it's true it feels a bit silly to have to fork a process to be able to use that output in the shell.

Some shells (ksh93 and fish) do implement some optimisations there. In their $(type echo) ((type echo) in fish), they actually fake the writing of the output and the reading of it (FreeBSD sh also does it for single builtin invocations (like type here)). When a builtin's stdout is a command substitution, instead of writing the output, the shell just appends the would-be-output text to the command substitution result and there's no need for a fork.

Actually, fish's (type echo) is more like ksh93's ${ type echo;} in that it doesn't even create a subshell environment. With $(...), ksh93 emulates a subshell environment so that it appears as though a child process was forked to interpret the code in it and doesn't do that for its ${ ...;} variant.

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

You'll find that in some shells that don't do that optimisation, a lot of the builtins can be called in such a way where instead of writing their result, they store it in a variable.

The most obvious ones are the standard read and getopts that do that by default (you do IFS= read -r var instead of var=$(line)). bash and zsh also have printf -v variable format args. zsh can do the same for its stat, strftime... builtins.

Some shells also make some information already available automatically in some special variables, like ksh's $SECONDS and $RANDOM found in a few other shells (and standard ones like $-, $# (equivalent to fish's (count $argv) for instance)).

In zsh, that's generalised to most of the shell's internal information so you almost never need to use command substitution on the output of a builtin. For instance, it's got associative arrays for the list of builtins, keywords, commands...

if (($+builtins[echo])); then
  echo echo is a builtin
fi

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .