2

I seem to be able to create environment variables that execute commands; like this:

$ cat ./src
FOO="echo"
$ . ./src
$ echo $FOO
echo
$ $FOO hello
hello
$

Is there a way I can modify that environment variable so that it prefixes the setting of another environment variable before the command? I.e. is there a way to work around the following problem?

$ cat ./src
FOO="MY_DIR=/tmp echo"
$ . ./src
$ echo $FOO
MY_DIR=/tmp echo
$ $FOO hello
-bash: MY_DIR=/tmp: No such file or directory
$

I.e. what I'd like to happen is to have an environment variable that does the equivalent of the following manually typed in the shell:

$ MY_DIR=/tmp echo hello
hello
$

...similar to how sans envvar-prefix, $FOO effectively had the same effect as typing echo at the shell.


/tmp/ exists of course, btw:

$ ls -ld /tmp/
drwxrwxrwt. 25 root root 500 May 19 11:35 /tmp/
$

Update:

I have a constraint that "FOO" must be invoked like $FOO hello and not FOO hello. So unfortunately a function like in @John Kugelman's (current) answer can't be a solution, even if it's more proper.

StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • Re: "I have a constraint that '`FOO`' must be invoked like `$FOO hello` and not `FOO hello`": If that is literally the only problem, then don't worry, that constraint is trivial to accommodate: you can just write `FOO=FOO`, and thereafter `$FOO hello` will expand to `FOO hello` and call whatever `FOO` function you might have defined. – ruakh May 19 '20 at 21:40
  • Does the setup have to be the assignment to `FOO`, can you have setup code before the call to `FOO="MY_DIR=/tmp echo"`? like a `.bashrc` file or equivalent? – root May 19 '20 at 22:19
  • @root - as it turns out, I think your suggestion is okay; sorry if these are details I should have mentioned up-front, but all the many semantics and rules of expansion, setup, etc. of bash programming are still often out of my immediate grasp, so I often just can't rationalize what's relevant to mention. – StoneThrow May 20 '20 at 16:59

1 Answers1

5

It's best to put data into variables, code into functions. Functions are more natural, expressive, and flexible than variables holding code. They look just like any other command but can take arbitrary actions, including but not limited to prepending commands and variable assignments.

foo() {
    MY_DIR=/tmp echo "$@"
}
foo hello

Here "$@" is a placeholder for the arguments passed to foo().

I have a constraint that "FOO" must be invoked like $FOO hello and not FOO hello.

That constraint is impossible, I'm afraid.

I am curious about the mechanics of what's going on here: i.e. why can you make an environment variable that's sort of "aliased" to a command (I know true aliasing is something else), but that mechanism doesn't accommodate the seemingly small change to prefix "stuff" to the command?

Bash expands commands in several passes in a fixed, prescribed order. Very early on it splits the command into words and then marks the variable assignments with invisible flags. It expands $variable references in a later pass. It doesn't look at the results to see if they look like additional variable expansions. The equal signs are effectively ignored.

If you want to know the nitty gritty details, open up the Bash man page. It's incredibly long and the details are scattered throughout. Let me pull out the key sections and some choice quotes to help you digest it:

  1. Shell Grammar, Simple Commands

    A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator.

  2. Simple Command Expansion

    When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.

    1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.

    2. The words that are not variable assignments or redirections are expanded. 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.

    ...

    If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.

  3. Expansion

    Expansion is performed on the command line after it has been split into words. There are seven kinds of expansion performed: brace expansion, tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, word splitting, and pathname expansion.

    The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.

  4. Expansion, Parameter Expansion

    The $ character introduces parameter expansion, command substitution, or arithmetic expansion.

Assignments are marked in step 1 and variables (AKA parameters) are expanded in step 4.

The only things that happen after variable expansion are:

  1. Word splitting. A variable can expand to multiple words if it contains whitespace. (Or to be more precise, if it contains any of the characters in the inter-field separator variable $IFS.)

  2. Pathname expansion. Also known as globbing, or wildcards. If a variable contains *, ?, or [ they'll be expanded to the names of matching files, if there are any.

  3. Quote removal. This pass happens after variable expansion, but it specifically does not apply to the results of any previous expansion step. So quotes the user typed are removed, but quotes that were the results of a substitution are retained.

Neither word splitting nor pathname expansion are what you need, so that's why it's not possible to store an assignment in a variable.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578