`...`
and $(...)
are the same thing with different syntax, one if from the Bourne shell, the other from the Korn shell, the former is deprecated but still supported by Bourne-like shells for backward compatibility with the ancient Bourne shell.
Other shells have different syntax for that. For instance, fish
has (...)
and rc
/es
have `cmd
or `{more complex cmd}
, or `(sep){cmd}
to specify different splitting behaviour. ksh93
and mksh
also have ${ ...; }
(no subshell) variant.
In any case, that's syntax in the shell language, so you need a shell to interpret it and perform the command substitution.
In:
sudo cmd1 `cmd2`
In Bourne-like shells, the shell
- runs
cmd2
in a child process with its output redirected to a pipe
- reads the output of that cmd from the other end of the pipe,
- removes the trailing newline characters,
- splits the result according to
$IFS
- perform filename generation on the resulting words (except in
zsh
; some ksh variants also perform brace expansion)
- and then passes the resulting words as separate arguments to a
sudo
command executed in another child process.
sudo
then changes uids and runs the command it is being passed as argument.
If you wanted cmd2
, to be run with the different uid, you'd need sudo
to run a shell to interpret the shell code that performs the command substitution:
sudo sh -c 'cmd1 $(cmd2)'
sudo fish -c 'cmd1 (cmd2)'
sudo rc -c 'cmd1 `cmd2'
and so on.
Note that in Bourne-like shells, command substitution is still performed inside double quotes, so do not do:
sudo sh -c "cmd1 $(cmd2)"
First, that would not run cmd2
as root
, but also the output of cmd2
(this time, not subjected to split+glob as it's within quotes), would be interpreted as sh
code, so would typically constitute a command injection vulnerability. For instance, if cmd2
outputs $(reboot)
, the sh
invoked by sudo
would be asked to interpret cmd1 $(reboot)
and reboot.
Similarly, don't do sudo sh -c 'cmd1 $(cmd2) '"$var"
or sudo sh -c 'cmd1 $(cmd2 '"$var"')'
if you want to pass the contents of variables of your shell (as opposed to the one started by sudo
) to cmd1
or cmd2
. Instead, pass the contents of those variables as extra arguments to sh
(not inside the code argument), or via environment variables (so they become variables of the shell started by sudo
as well):
sudo sh -c 'cmd1 $(cmd2) "$1"' sh "$var"
sudo VAR="$var" sh -c 'cmd1 $(cmd2) "$VAR"'
sudo sh -c 'cmd1 $(cmd2 "$1")' sh "$var"
sudo VAR="$var" sh -c 'cmd1 $(cmd2 "$VAR")'
Here, you can always also do:
sudo cmd1 $(sudo cmd2) "$var"
sudo cmd1 $(sudo cmd2 "$var")
That is have your shell run both commands through two separate invocations of sudo
so both be run with elevated privileges.
As hinted above, in Bourne-like shells (but that also applies, though differently, to csh-like shells), unquoted command substitution is subject to split+glob, so you'd only leave $(cmd2)
unquoted in cmd1 $(cmd2)
if cmd2
is outputting a $IFS
delimited list of wildcard patterns. If you want the output of cmd2
(without the trailing newline characters) to be passed as a whole as one argument to cmd1
, you'd want cmd1 "$(cmd2)"
or more likely cmd1 -- "$(cmd2)"
to make sure that argument is not treated as an option (assuming cmd1
supports that --
end-of-option marker).
So for your particular use case, that would be:
sudo DEVICE="$device" sh -c 'mount -- "$(blkid -u -- "$DEVICE")"'
Or to call mount
only if blkid
succeeds:
sudo DEVICE="$device" sh -c '
output=$(blkid -u -- "$DEVICE") &&
mount -- "$output"
'
$(...)
– alecxs Jul 22 '20 at 07:00IFS=':+ '; printf '<%s>\n' $(echo '/*:a b++{a,b}+c')
in various Bourne/Korn-like shells. You'll find some variation when it comes to globbing and brace expansion, but all do the splitting as it's a common usage pattern. You'll find some variation on how that splitting is done though (even more if you consider non-Bourne-like shells). – Stéphane Chazelas Jul 22 '20 at 07:23var=$()
maybe i misunderstood that completely (can't find that answer anymore) – alecxs Jul 22 '20 at 07:30