views:

552

answers:

6

I have a file named cmd that contains a list of unix commands as follows:

hostname
pwd
ls  /tmp
cat /etc/hostname
ls -la
ps -ef | grep java
cat cmd

I have another script that executes the commands in cmd as:

IFS=$'\n'
clear
for cmds in `cat cmd`
do
        if [  $cmds ] ; then
     $cmds;
     echo "****************************";
        fi
done

The problem is that commands in cmd without spaces run fine, but those with spaces are not correctly interpreted by the script. Following is the output:

patrick-laptop
****************************
/home/patrick/bashFiles
****************************
./prog.sh: line 6: ls  /tmp: No such file or directory
****************************
./prog.sh: line 6: cat /etc/hostname: No such file or directory
****************************
./prog.sh: line 6: ls -la: command not found
****************************
./prog.sh: line 6: ps -ef | grep java: command not found
****************************
./prog.sh: line 6: cat cmd: command not found
****************************

What am I missing here?

A: 

EDIT: The comment by Ben Blank pointed out that my old answer was wrong, thanks.

Looks like you're executing commands as a single string, so bash sees them as the script/executable name.

One way to avoid that would be to invoke bash on the command. Change:

    if [  $cmds ] ; then
    $cmds;
    echo "****************************";
    fi

to

    if [  $cmds ] ; then
    bash -c $cmds
    echo "****************************";
    fi
Andomar
Actually, he's having the opposite problem. Setting $IFS as he's done here causes `for` to break on on newlines, exactly as he wants. However, it's then attempting to interpret the entire string as a command name rather than a command with arguments.
Ben Blank
+1 to offset downvote. :-)
Ben Blank
by invoking bash -c you're starting a new process for each command. That seems excessive and might have surprising results. For example, if one of the commands is a cd command, subsequent commands won't be run in that other directory.
Bryan Oakley
It would also prevent side effects from spilling over, like changes in PATH. So a separate process might be just what you want. I did vote for your solution though.
Andomar
A: 

Edit: Turns out this fails on pipes and redirection. Thanks, Andomar.

You need to change IFS back inside the loop so that bash knows where to split the arguments:

IFS=$'\n'
clear
for cmds in `cat cmd`
do
    if [ $cmds ] ; then
        IFS=$' \t\n' # the default
        $cmds;
        echo "****************************";
        IFS=$'\n'
    fi
done
Ben Blank
-1, tried this out, it fails on the "ps -ef | grep java" command.
Andomar
+2  A: 

Try changing the one line to "eval $cmds" rather than just "$cmds"

Bryan Oakley
-1 for advocating `eval`; don't patch crap up with more crap (you're not even properly quoting $cmds!), tell him how to do it right.
lhunath
Instead of just being negative, can you share with us what you think the proper quoting should be? I'll admit that my bash scripting is a tad rusty, maybe you can jog my memory. I don't understand why you think eval is "crap" though, can you justify your response?
Bryan Oakley
+2  A: 

You can replace your script with the command

sh cmd

The shell’s job is to read commands and run them! If you want output/progress indicators, run the shell in verbose mode

sh -v cmd
andrew
+1  A: 

I personally like this approach better - I don't want to munge the IFS if I don't have to do so. You do need to use an eval if you are going to use pipes in your commands. The pipe needs to be processed by the shell not the command. I believe the shell parses out pipes before the expanding strings.

Note that if your cmd file contains commands that take input there will be an issue. (But you can always create a new fd for the read command to read from.)

clear
while read cmds 
do
        if [ -n "$cmds" ] ; then
        eval $cmds
        echo "****************************";
        fi
done < cmd
jabbie
A: 
sed 'aecho "-----------"' cmd > cmd.sh; bash cmd.sh

sed 'aXX' appends XX to every line. This will not work for multiline-commands like:

for f in * 
do 
    something $f
fi

but for single-line commands in most cases, it should do.

user unknown