Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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...
share|improve this question
    
I cannot figure out what's the question here: the dofor code as posted already does what you're asking... –  Riccardo Murri Oct 7 '11 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. –  rozcietrzewiacz Oct 7 '11 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. –  frogstarr78 Oct 7 '11 at 12:59

3 Answers 3

up vote 1 down vote accepted

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!

share|improve this answer
    
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 –  frogstarr78 Oct 6 '11 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? –  rozcietrzewiacz Oct 6 '11 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 –  frogstarr78 Oct 6 '11 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.) –  rozcietrzewiacz Oct 6 '11 at 20:19
    
See the last edit - no need to use any substitution if you use single quotes. –  rozcietrzewiacz Oct 6 '11 at 20:33

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.

share|improve this answer
    
I do control contents of that file and it is very basic –  frogstarr78 Oct 6 '11 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. –  rozcietrzewiacz Oct 6 '11 at 14:58
    
@rozcietrzewiacz correct. I've updated the example in the description –  frogstarr78 Oct 6 '11 at 15:46

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
})
share|improve this answer
    
My initial description wasn't very clear –  frogstarr78 Oct 6 '11 at 15:45
    
Does the this version get you what you are looking for? –  k.parnell Oct 6 '11 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 –  frogstarr78 Oct 6 '11 at 18:56

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.