1

Is there a way to pass in a variable as an argument to a bash script and have it evaluated scoped by the bash script?

Given:

# cat /path/to/file/of/host/names
bob
tom
joe
etc...


# dofor
FILE=$1
shift
CMD=$*

while read host; do
    # recursively turns "ssh \$host hostname" from $@ into:
    # ssh bob hostname
    # ssh tom hostname
    # ssh joe hostname
    # etc...
    eval $CMD
done < $FILE

When:

# dofor /path/to/file/of/host/names "ssh \$host hostname"

Then: I'd receive the output from running ssh host hostname for each hostname listed in /path/to/file/of/host/names. e.g.:

bob.example.com
tom.example.com
joe.example.com
etc...
3
  • I cannot figure out what's the question here: the dofor code as posted already does what you're asking... Oct 7, 2011 at 10:15
  • @RiccardoMurri It does as long as, in invocation, the second parameter passed to the script (one that contains $host is enclosed in single quotes and the command does not try to steal access to stdin. Oct 7, 2011 at 12:45
  • Indeed, I suppose I was just unnecessarily escaping the $host. Sorry for that, I was certain I'd checked that that wasn't the reason it wasn't working. Oct 7, 2011 at 12:59

3 Answers 3

2

Try this:

#!/bin/bash

FILE=$1
shift
CMD=$*
cmds=""

source <({
cat $FILE | while read host; do
    echo $CMD | sed -e "s|\$host|$host|g"
done
})
3
  • My initial description wasn't very clear Oct 6, 2011 at 15:45
  • Does the this version get you what you are looking for?
    – k.parnell
    Oct 6, 2011 at 16:25
  • yes, that would work, although it's pretty much what I came up w/ also. I was hoping to do something other than replacing values. I was hoping to take advantage of some sort of "scoping" feature of bash Oct 6, 2011 at 18:56
1

You can use

#!/bin/bash
FILE=$1
exec 3<$FILE
while read -u 3 host; do
    $2 $host $3
done

Then if you have:

$ cat /tmp/hostnames
some.host1.com
another.host2.net
some.host3.org    
some.host4.com

and run dofor.sh /tmp/hostnames ssh ls, it will run in sequence:

ssh some.host1.com ls
ssh another.host2.net ls
ssh some.host3.org ls
ssh some.host4.com ls

EDIT1:

If you'd like to change ssh or ls into some longer commands (or parts of such), just use quotes:

dofor.sh /tmp/hostnames "ssh -p 23" "ls -lh /"

EDIT2:

With the following script, you'll be able to use $host variable in as many places in your command as you want:

#!/bin/bash
exec 3<$1
while read -u 3 host; do
    eval $2
done

(I made this one shorter - no useless introducing of $FILE variable.) The important part here is that you need to use single quotes around the command containing the $host variable:

 dofor.sh /tmp/hostnames 'echo "trying $host :"; ssh -p 23 myuser@$host "ls -lh /"'

But beware that it is dangerous to use eval (see l0b0's wall) because if the file /tmp/hostnames contained a command on some line, it would be executed. Better not use this as root!

7
  • that's an alternate method sure, granted there's no way to ensure that the $2'th and $3'th arguments will always surround the argument that I've passed in, and want replaced by values from the file Oct 6, 2011 at 18:52
  • So you mean to say you want to be able to use $host in different places in the command? But just once in each? Oct 6, 2011 at 19:05
  • not necessarily. I want the $argument, possibly multiple times, that I pass to the script to be evaluated in the context of the for loop Oct 6, 2011 at 20:02
  • I see. If that's te main point, I'll think about it... Meanwhile, I corrected the script to allow commands like ssh to steal your input without breaking the while-read-loop. (The previous version would exit after executing ssh if it prompted for password.) Oct 6, 2011 at 20:19
  • See the last edit - no need to use any substitution if you use single quotes. Oct 6, 2011 at 20:33
1

This should work, but it's a really bad idea unless you completely control the contents of $FILE and it only contains very simple statements.

while read -r
do
    $REPLY
done < $FILE

For the SSH example, you could do something much simpler by just looping over the host names:

while read -r host
do
    echo -n "${host}:"
    ssh "$host" hostname
done 9< "$FILE"

That should return a list of my hostname:remote hostname entries.

If you have a file with actual commands, you have to make sure whitespace is handled properly. The only way to be able to handle any parameters (including for example echo commands where the parameters are multiple lines) would be to use a custom format. One relatively simple way would be to separate each command and argument with a ␀ character, and put an extra ␀ character between the last argument of a command and the next command.

3
  • I do control contents of that file and it is very basic Oct 6, 2011 at 14:05
  • From the example given in the question, I understand that the commands would come from parameters passed to the script, not the file. But I admit the goal is quite unclear. Oct 6, 2011 at 14:58
  • @rozcietrzewiacz correct. I've updated the example in the description Oct 6, 2011 at 15:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .