3

I just accidentally found out that in bash, we can assign a command into a variable without using alias.

g=date
$g
Mon Jun 27 13:00:40 MYT 2016

That works. Here's another example:

jj="ping yahoo.com"
$jj
PING yahoo.com (98.138.253.109) 56(84) bytes of data.
64 bytes from ir1.fp.vip.ne1.yahoo.com (98.138.253.109): icmp_seq=1 ttl=41 time=347 ms
64 bytes from ir1.fp.vip.ne1.yahoo.com (98.138.253.109): icmp_seq=2 ttl=41 time=345 ms
64 bytes from ir1.fp.vip.ne1.yahoo.com (98.138.253.109): icmp_seq=3 ttl=41 time=345 ms

I am using this version of bash bash --version GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)

I have looked into stackexchange and tldp abs, but didn't find we can do it like this though. So is this a new features of bash or something being overlooked ? Is this considered command substitution ?

  • 1
    Relevant mywiki.wooledge.org/BashFAQ/050 – 123 Jun 27 '16 at 10:01
  • 2
    I want to up vote that link 123 posted again. The reason you don't find many examples of expansions as commands is not because you can't. It's because you should use a function instead. – kojiro Jun 27 '16 at 10:33
2

This is expected behaviour and has been working for ages. It is useful for building up commands in scripts.

Say, for example, that I sometimes should apply decompression on some input before sending it into a pipeline (and compression on the output afterwards), but sometimes not. Using bash:

if (( use_compression == 1 )); then
   infilter='gzip -d -c'
   outfilter='gzip -c'
else
   infilter='cat'
   outfilter='cat'
fi

$infilter "$indatafile" | somepipeline | $outfilter >"$outdatafile"

Is it command substitution? No. "Command substitution" refers specifically to substituting $(...) or the back-tick-variant with the contents of its output.

This is instead parameter expansion, i.e. simply replacing the parameters with their values. This happens before the command is actually executed which is why it works.

This means that stuff like the following works on the command line too:

$ decompress='gzip -d -c'
$ ${decompress/g/gun/} filename >unzippedfilename

(that would execute gunzip rather than gzip)

EDIT: About aliases.

The bash manual says:

For almost every purpose, aliases are superseded by shell functions.

... and I agree.

Aliases are good for short things like

alias ls="ls -F"

(that's the only alias I have in my own shell sessions)

You probably don't want to use parameter expansions for "aliasing" commands on the command line. If not for any other reason than it's a pain in the neck to type. Parameter expansion also does not allow for doing slightly more complex tasks, such as pipelining.

If you have moderately complex operations that you are performing regularly, use shell functions for them instead.

Taking the example from the accepted answer to the U&L question (that you link to in the comments below):

alias my_File='ls -f | grep -v /'

This alias obviously works, but using

my_File='ls -f | grep -v /'

won't work if you then try to use $my_File as a command on the command line (as explained why in that U&L thread).

With a shell function, you will also be able to do things like pass arguments:

function my_File {
    ls -l "$@" | fgrep -v '/'
}

$ my_File -iF symlink
84416642 lrwxr-xr-x  1 kk  staff  4 Jun 27 09:03 symlink@ -> file
  • Thanks. So this actually does what alias did, is it ? Any special reason this method is not being mentioned when same question being asked a few times in Stackexchange, for example this one, [unix.stackexchange.com/questions/121418/… ? – sylye Jun 27 '16 at 6:34
  • @sylye I have updated my answer. – Kusalananda Jun 27 '16 at 7:08
  • @sylye Also note that the shell treats most type of "substitution" differently, and at different stages of processing the command line. "Parameter expansion" and "alias expansion" is done differently. – Kusalananda Jun 27 '16 at 7:14
  • I see, now I got it :) Btw, what do you mean by "shell treats most type of "substitution" differently, and at different stages of processing the command line" ? – sylye Jun 27 '16 at 8:18
  • @sylye Well, parameter expansion needs to happen before command substitutions, otherwise the somecommand in a=$( somecommand $var ) would get the wrong argument. I just wanted to point out that there are stages that the command line goes through before it is actually executed, and different things happen in different stages. See the bash manual and look for the "EXPANSION" heading. – Kusalananda Jun 27 '16 at 8:30
3

In interactive use, the shell reads lines of input from the terminal device. After a line has been entered, it is parsed by splitting into tokens (words and operators). The tokens or words are then expanded or resolved in a specific order.

Note: in general, it’s usually best to refer to the canonical source of information such as project documentation or industry specification over second-hand sources such as the Advanced Bash-Scripting Guide, online tutorials, books – or even Stack Exchange :). Such second-hand sources are useful for introducing and explaining concepts in simpler language but they’re not usually intended to be complete or to replace the official documentation. The information in second-hand source can also be out of date.

In this case, the POSIX specification for parsing Simple Commands states that

  1. The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.

The Bash manual section on Simple Command Expansion also states that

  1. The words that are not variable assignments or redirections are expanded (see Shell Expansions). If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments.
  • Good point on highlighting out the POSIX and bash manual itself. But is bash always compliance to the standard of POSIX ? – sylye Jun 29 '16 at 4:22
  • @sylye It's mostly compliant. It extends the POSIX shell with features such as arrays, process substitution, etc. It also has a POSIX-compatible mode which is very close to but not fully compliant with the specification. – Anthony Geoghegan Jun 29 '16 at 8:07
3

This is not command substitution, it's variable substitution. You aren't assigning a command into a variable, you're assigning a string. The command runs when you use the variable, not at the time of the assignment.

g=date stores the string date in the variable g. If you then run echo "$g", this prints the value of g, i.e. date (followed by a newline), because the value of g is passed to the echo command as the first argument. If you run "$g", this puts the string date in the first position of the command, so it's the command name.

With $jj, a second factor comes into play. Because the variable expansion is outside quotes, the “split+glob operator” is applied to the value. In this case, this means that the value of jj is split into two space-separated words. Thus the word ping ends up in command position and the word yahoo.com is the first argument.

  • Your explanation is very clear and good in understanding the scenario! Too bad I can't accept two answers in same time, as yours are pretty worth accepted as well. Thanks ! – sylye Jun 29 '16 at 4:24
2

It is simply using shell variables, a concept that has existed and worked like that for decades (without exaggeration).

When you put a variable in a command line, the shell substitutes it and then runs the command, so if your variable is at the beginning, it will obviously be considered as the command.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.