0

I can't seem to "access" the value of my loop variable when executing a command line argument in a Bash script. I'd like to be able to write something like

#!/bin/bash
for myvar in 1 2 3
do
  $@
done

and run the script as ./myscript echo "${myvar}".

When I do this, the lines are echoed as empty. I probably don't have a firm grasp one exactly what's being evaluated where.

Is what I want even possible?

1
  • You can use bash -x myscript to see how myvar is being set (along with a lot of other detail about the execution of your script). Commented Aug 2, 2012 at 16:22

1 Answer 1

6

$myvar is evaluated before the child script is even run, so it can't be evaluated within.

That is, when you invoke your script as:

./myscript echo "${myvar}"

what is actually being called is:

./myscript echo ''

presuming that $myvar is empty in the enclosing environment.


If you wanted to be evil (and this is evil, and will create bugs, and you should not do it), you could use eval:

#!/bin/bash
for myvar in 1 2 3; do
  eval "$1"
done

...and then call as:

./myscript 'echo "${myvar}"'

Something less awful would be to export a variable:

#!/bin/bash
export myvar
for myvar in 1 2 3; do
  "$@"
done

...but even then, the way you call the script will need to avoid preevaluation. For instance, this will work in conjunction with the above:

./myscript bash -c 'echo "$myvar"'

...but that basically has you back to eval. On the other hand:

./myscript ./otherscript

...will do what you want, presuming that otherscript refers to $myvar somewhere within. (This need not be a shell script, or even a script; it can be an executable or other command, and it will still be able to find myvar in the environment).


What's the real goal (ie. business purpose) you're trying to achieve? There's probably a way to accomplish it better aligned with best practices.

Sign up to request clarification or add additional context in comments.

7 Comments

Maybe you could use something like for myvar in 1 2 3; do "$@" "$myvar"; done
I started trying roll my own solution to run a command on several remote machines and encountered this issue. Long term, I'd probably look at one of the tools from that linked question. For a simple one-off, I thought I could make a simple for remote in host1 host2 host3; do "$@"; done sort of thing, plugging ${remote} in as needed. I guess the only way to do that (without writing a second script!) is to eval? I might go strait to the heavier-weight tools...
@Coderer Just pass the hostname as an argument: `"$@" "$remote"
That doesn't really solve situations like scp thisFile ${remote}:/foo/bar -- I mean, I could probably do something with xargs I guess, but for personal-use one-offs like this eval sounds easier...
@Coderer If hosts=(host1 host2 host3), printf '%s\0' "${hosts[@]}" | xargs -0 -i "$@" would be better. xargs without -print0 is a recipe for suffering; so is echo without quoting its arguments. See mywiki.wooledge.org/BashPitfalls/#echo_.24foo for the latter.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.