One Two Three
One Two Three

Reputation: 23547

pass a command as an argument to bash script

How do I pass a command as an argument to a bash script? In the following script, I attempted to do that, but it's not working!

#! /bin/sh

if [ $# -ne 2 ]
then
    echo "Usage: $0 <dir> <command to execute>"
    exit 1;
fi;

while read line
do
    $($2) $line
done < $(ls $1);

echo "All Done"

A sample usage of this script would be

./myscript thisDir echo

Executing the call above ought to echo the name of all files in the thisDir directory.

Upvotes: 12

Views: 27483

Answers (3)

Gordon Davisson
Gordon Davisson

Reputation: 126048

First big problem: $($2) $line executes $2 by itself as a command, then tries to run its output (if any) as another command with $line as an argument to it. You just want $2 $line.

Second big problem: while read ... done < $(ls $1) doesn't read from the list of filenames, it tries to the contents of a file specified by the output of ls -- this will fail in any number of ways depending on the exact circumstances. Process substitution (while read ... done < <(ls $1)) would do more-or-less what you want, but it's a bash-only feature (i.e. you must start the script with #!/bin/bash, not #!/bin/sh). And anyway it's a bad idea to parse ls, you should almost always just use a shell glob (*) instead.

The script also has some other potential issues with spaces in filenames (using $line without double-quotes around it, etc), and weird stylistic oddities (you don't need ; at the end of a line in shell). Here's my stab at a rewrite:

#! /bin/sh

if [ $# -ne 2 ]; then
    echo "Usage: $0 <dir> <command to execute>"
    exit 1
fi

for file in "$1"/*; do
    $2 "$file"
done

echo "All done"

Note that I didn't put double-quotes around $2. This allows you to specify multiword commands (e.g. ./myscript thisDir "cat -v" would be interpreted as running the cat command with the -v option, rather than trying to run a command named "cat -v"). It would actually be a bit more flexible to take all arguments after the first one as the command and its argument, allowing you to do e.g. ./myscript thisDir cat -v, ./myscript thisDir grep -m1 "pattern with spaces", etc:

#! /bin/sh

if [ $# -lt 2 ]; then
    echo "Usage: $0 <dir> <command to execute> [command options]"
    exit 1
fi

dir="$1"
shift

for file in "$dir"/*; do
    "$@" "$file"
done

echo "All done"

Upvotes: 6

shellter
shellter

Reputation: 37318

your command "echo" command is "hidden" inside a sub-shell from its argments in $line.

I think I understand what your attempting in with $($2), but its probably overkill, unless this isn't the whole story, so

 while read line ; do
    $2 $line
 done < $(ls $1)

should work for your example with thisDir echo. If you really need the cmd-substitution and the subshell, then put you arguments so they can see each other:

   $($2 $line)

And as D.S. mentions, you might need eval before either of these.

IHTH

Upvotes: 2

Kent
Kent

Reputation: 195239

you could try: (in your codes)

echo "$2 $line"|sh

or the eval:

eval "$2 $line"

Upvotes: 2

Related Questions