1

What is wrong with this indirect command when run with eval ?

#!/bin/bash
OS=AIX
host=myhost

CMD_AIX="(o=\`host \"$host\" \`)"
CMD=\$CMD_$OS

echo $CMD
eval echo $CMD

eval "$CMD"

Ouput:

$ myscript.sh
$CMD_AIX
(o=`host "myhost" `)
myscript.sh: line 12: (o=`host: command not found

EDIT - NOTES: It's in a very heterogeneous structure with about 40 servers, mixing SCO, AIX and Linux, several versions of each, including variations of 32 and 64 bits. SCO and AIX cannot be updated. Bash and Korn shell are common in all servers but I'm limited to version 3 of bash (in SCO), Ksh has several differences between them, so we're preferring bash, which haven't support to associative arrays in v3 (it cannot be updated and it's outside of my decisions, and these servers will begin to be replaced by AIX 7 and Linux in a few months).

There are a big system to be maintained which includes some scripts already based in sh and bsh, we won't and cannot reconstruct it now, just tittle maintenance to resist some months until the end of replacement.

The approach used in these scripts is largely spread in a big solution which generates some scripts, which we cannot change it from scratch.

The sample code above is just an adopted snippet, it isn't the real code. Please don't consider the logic, just the problem of indirection (eval).

SOLUTION: I got a solution that worked very fine just chaining the eval, like:

torun=`eval $CMD`
output=`eval torun`

This answer and that another one, which worked very fine and answered the question, were oddly down voted a lot.

  • 2
    What problem are you trying to solve? – lcd047 May 25 '15 at 16:21
  • Like the question snippet of code, I need to capture the ouput of a command which need to be mounted dynamically, so I guess that eval is the best approach to do that. – Luciano May 25 '15 at 16:25
  • It might be better to describe the problem, rather than the solution. Why does that command need to be "mounted dynamically"? What does that even mean? – lcd047 May 25 '15 at 16:28
  • 3
    I'm willing to bet it doesn't need to be built. Have you considered using arrays instead? Could you explain why you are building your command this way? – terdon May 25 '15 at 16:32
  • 1
    @Luciano - while I'm not at all opposed to eval - and am rather fond of it personally - I can say that it is the eval code that would give me pause - more likely than any other kind - before I went snipping and sticking. – mikeserv Jun 12 '15 at 18:33
2

So you have a variable CMD whose value is the string $CMD_AIX, and the value of the variable CMD_AIX is the string (o=`host "myhost" `). You want to interpret the value of CMD_AIX as a shell command and execute it.

To execute the value of CMD_AIX as a shell command, you need to construct a shell command that does that. That's what eval is for:

eval "$CMD_AIX"

This command, in turn, is constructed dynamically. So you need to run it with eval:

dispatcher=…
eval "$dispatcher"

Given the way the inner command is constructed, we get

dispatcher="eval \"\$CMD_$OS\""
eval "$dispatcher"

Or cutting one variable out:

eval "eval \"\$CMD_$OS\""

Note that the command (o=`host "myhost" `) is pointless: it assigns the output of host to the variable o in a subshell; the value is not made available outside the subshell. Remove the parentheses. Also, host \"$host\" is weird: you're putting the value of the variable host between double quotes; it doesn't matter since host names don't contain shell special characters, but to use the value of the variable when evaluating the shell snippet, you should leave host unexpanded here.

#!/bin/sh
OS=AIX # should probably be OS=`uname -r`
host=myhost
CMD_AIX="o=\`host \"\$host\"\`"
eval "eval \"\$CMD_$OS\""

If you have ksh93 or bash ≥ 4.0, you can simplify this script by using associative arrays. Instead of stuffing the code for each operating system in a different variable, put them in an associative array.

#!/usr/bin/env bash
typeset -A commands
commands[AIX]="o=\`host \"\$host\"\`"
OS=AIX
host=myhost
if [ -n "${commands[$OS]}" ]; then
  eval "${commands[$OS]}"
else
  echo >&2 "Operating system $OS not supported"
  exit 2
fi
  • Thanks, good explanation. Although it was already fixed, I added notes to the question. I guess this can explain better my situation. – Luciano Jun 12 '15 at 13:07
2

I think the problem with what you're doing there is that it doesn't make any sense. No offense, but I mean that I think you just have the wrong idea about what eval does. It looks like you're trying to use it just to run a command - that's basic shell functionality and doesn't require that the shell attempt to interpret its last expansions as new commands - which is what eval does.

The way you have all of your quotes written in you're protecting anything from being secondly evaluated anyway. Or, rather, you're protecting the first pass completely - and so nothing at all happens until the second pass. The problem with all of that is that you might as well just not do a second evaluation (and a second layer of quotes) at all.

It looks to me like you're attempting to run a certain command based on the value of $OS - maybe...? Well, this is among the most accessible API features in shell.

For example:

OS=AIX
host=myhost
_cmd_AIX() { host "$host"; }
_cmd_WIN() { exit "$((LOSE=1))"; }

You see - you can run those defined functions quoted. Their command name doesn't need any special second interpreter to have significance - not even when it occurs as the result of an expansion. It just needs to be in command position. They will still even accept parameters that way - so you could pass $host as $1 if you wished. You just call it like this:

"_cmd_$OS"

The $OS var will expand to AIX and the resulting command word will be _cmd_AIX - which is a predefined shell function the name of which occurs as a whole and properly delimited shell word in command position - and so it is executed. Just like that. No contortions necessary - and it even comes with its own array.

Redefine $OS at any time to the name of some other valid suffix for other behaviors.

1

With zsh:

$ echo $CMD
$CMD_AIX
$ echo ${(e)CMD}
(o=`host "myhost" `)
$ echo ${(e)${(e)CMD}}
(o=myhost has address 1.2.3.4)

The (e) expansion flag is to evaluate the expansions in the value of the variable being expanded.

With ksh93:

$ function CMD.get { eval ".sh.value=\"$_\""; }
$ function CMD_AIX.get { eval ".sh.value=\"$_\""; }
$ echo "$CMD_AIX"
(o=myhost has address 1.2.3.4)
$ echo "$CMD"
(o=myhost has address 1.2.3.4)
$ CMD=\$USER
$ echo "$CMD"
stephane

CMD.get is a discipline function that is invoked any time $CMD is expanded typically used to define what that expansion should be to allow variables with dynamic content. Here we define it as returning the evaluation of its content within double quotes (that assumes the value doesn't contain double quotes).

You could also use types:

typeset -T evaluating_variable=(
  function get { eval ".sh.value=\"$_\""; }
)

OS=AIX host=myhost
evaluating_variable CMD_AIX='(o=`host "'$host'"`)'
evaluating_variable CMD=\$CMD_$OS

Now why you'd want to do that in a shell script is another matter.

-2

eval eval $CMD instead on the last line fixes the problem

-3

This fixed the problem:

eval `eval echo $CMD`

instead of:

eval "$CMD"

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.