0

I do not understand what happens here.

What I try to achieve:

When I call an alias I would like to have

  • output who and where called this, like the CLI does
  • output the actually commands behind the alias
  • execute the commands behind the alias.

Example:

# The prefix to be re-used.
__PREFIX='$(whoami)@$(hostname):$(pwd)\$'

# The commands to execute in a var to be able to print them too.
FOO="echo 'foo' && echo 1"
BAR="echo 'bar' && echo 2"
BAZ="echo 'baz' && echo 3"

# Define aliases
# echo the prefix and the command that will be executed, 
# and the execute the commands.
alias FOO='echo ${__PREFIX}'" '${FOO}' && ${FOO}"
alias BAR='echo ${__PREFIX}'" '${BAR}' && ${BAR}"
alias BAZ='echo ${__PREFIX}'" '${BAZ}' && ${BAZ}"

I found out that I have to use $(pwd) in single quotes ' to call it and not "print" it into the alias.

The FOO example above prints:

username@notebook:~$ 
$(whoami)@$(hostname):$(pwd)\$ echo foo && echo 1
foo
1

When I change the FOO example to

alias FOO='echo $(whoami)@$(hostname):$(pwd)\$'" '${FOO}' && ${FOO}"

then it works:

username@notebook:~$ 
username@notebook:/home/username$ echo foo && echo 1
foo
1

Question: how can I have the whoami, hostname and pwd in a var, and then use it in an alias?

cottton
  • 1,522
  • 14
  • 29
  • 2
    Why would you use an alias for any of this _at all_ instead of just jumping direct to a function? ("If you have to ask, use a function instead" has been core to the #bash IRC channel's factoid on aliases for decades). – Charles Duffy Aug 24 '23 at 14:41
  • Does this answer your question? [Why does shell ignore quoting characters in arguments passed to it through variables?](https://stackoverflow.com/questions/12136948/why-does-shell-ignore-quoting-characters-in-arguments-passed-to-it-through-varia) – tjm3772 Aug 24 '23 at 14:58
  • 1
    Alias logic aside, your problem is that expansions aren't recursive. Try it on the command line: `__PREFIX='$(whoami)@$(hostname):$(pwd)\$' ; echo ${__PREFIX}`. After __PREFIX is expanded, bash does not go back and check its contents for more expansions. – tjm3772 Aug 24 '23 at 14:59
  • (and it's essential that it doesn't: you wouldn't want something that echos a filename or a log line to let anyone who can control that filename or the log's content to run arbitrary commands) – Charles Duffy Aug 24 '23 at 15:03

2 Answers2

1

Don't. It's strictly possible to run strings from variables as commands, but unless done very cautiously it's very bad practice for security reasons. Instead, store commands as functions (which you can conditionally redefine and update just as you might update a variable).

# note that running hostname and whoami over and over is very inefficient; don't do this.
log_prefix() { printf '%s@%s:%s$ ' "$(whoami)" "$(hostname)" "$PWD"; }
log_cmd() { printf '%q ' "$@"; printf '\n'; }
log_and_run() { log_prefix; log_cmd "$@"; "$@"; }

If you would otherwise want to programatically define aliases, you can do the same thing for functions (though it takes some care not to invoke the previously-referenced security problems):

# note that getting this wrong could cause serious security problems
# consequently, it's very carefully written; don't make changes without understanding them
wrap_cmd() {
  local wrapper_name cmd_content def_cmd
  wrapper_name=$1; shift
  printf -v cmd_content '%q ' "$@"
  printf -v def_cmd '%q() { log_and_run %s "$@"; }' "$wrapper_name" "$cmd_content"
  eval "$def_cmd"
}

With the above defined, you can use it as:

$ wrap_cmd sayhi echo hello # create 'sayhi' to run 'hello world'
$ sayhi world               # and test it! (below is output)
user@host:/home/user$ echo hello world
hello world

...and you can also redefine the log_prefix function even after commands have been wrapped, in the same way you intend to update __PREFIX:

$ log_prefix() { printf %s "NEW PREFIX HERE: "; }
$ sayhi world
NEW PREFIX HERE: echo hello
hello
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
0

You need to evaluate the expansion to get another expansion:

alias FOO='eval "echo ${__PREFIX}"; echo "${FOO}"; eval "${FOO}"'
Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • You're `eval`ing not just `${FOO}` itself but also all the remaining arguments. That's going to lead to a lot of surprises. – Charles Duffy Aug 24 '23 at 15:07
  • ...let's say that `FOO` is something like `ls -l`. `ls -l *.txt` is safe no matter what the filenames are. By contrast, `eval ls -l *.txt` in a directory with a file named `$(rm -rf ~).txt` is going to cause pain and suffering. – Charles Duffy Aug 24 '23 at 15:08
  • @CharlesDuffy then a ending ';' will make the trick. Or am I missing what you meant? – Jean-Baptiste Yunès Aug 24 '23 at 15:08
  • ...at the expense of the alias no longer being able to pass through arguments at all. – Charles Duffy Aug 24 '23 at 15:09
  • @CharlesDuffy ahah right, but that's the fun of expansion isn't it? – Jean-Baptiste Yunès Aug 24 '23 at 15:10
  • I'm too old and jaded to consider leaving pitfalls and traps for the unwary "fun". – Charles Duffy Aug 24 '23 at 15:10
  • (...speaking of which, also think about `eval "${FOO}"` with the quotes -- doesn't fix the problem with user-provided arguments not always being eval-safe, but it does close off some corner cases when your `FOO` doesn't evaluate back to its original contents when word-split, glob-expanded, and then re-joined back to a single string) – Charles Duffy Aug 24 '23 at 15:11
  • @CharlesDuffy I designed it this way first. I have to answer it like this back then. – Jean-Baptiste Yunès Aug 24 '23 at 15:12