Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am writing a script that applies a command to loads of files. The way I automate this boils down to

#!/bin bash
for in_file in ${PWD}/*/*/*.txt; do
    out_file="${in_file//in/out}"
    CM="/usr/local/bin/command -in \"${in_file}\" -out \"${out_file}\""
    echo ${CM}
    ${CM}
done

The typical output I get is

/usr/local/bin/command -in "/home/user/infile.txt" -out "/home/user/outfile.txt"
error:
/usr/local/bin/command: unable to find file "/home/user/infile.txt"
...

But then I cut & paste the same command, verbatim, including all the quotes etc., on the command prompt, and it runs without a problem!

Because many file names (that I did not make!) have spaces, I need the quotes in the script. But can someone tell me why they do not work in the script and do work on the command prompt? And is there a way to do it properly?

share|improve this question
3  
mywiki.wooledge.org/BashFAQ/050: "I'm trying to put a command in a variable, but the complex cases always fail!" – glenn jackman Feb 25 '14 at 14:55
    
+1 Using functions, that looks like scripting as Brian Fox intended. I know I should, but it feels like overkill for small scripts. – alle_meije Feb 26 '14 at 12:44
up vote 4 down vote accepted

Use an array: that's the best way to preserve a multi-word command with arguments containing whitespace.

#!/bin bash
for in_file in ${PWD}/*/*/*.txt; do
    out_file="${in_file//in/out}"
    cmd=( /usr/local/bin/command -in "$in_file" -out "$out_file" )
    echo "${cmd[@]}"
    "${cmd[@]}"
done
share|improve this answer
    
that is an excellent (and very clean) way to do it -- thanks! – alle_meije Feb 25 '14 at 16:32

"${CM}" will not use blank characters as separators. So bash thinks the whole string is the name of your program.

With ${CM} bash think the " are part of the argument.

You could use << eval "${CM}" >> but you could have side effects with unescaped special chars in file names.

So better is the following :

Does this work ?

#!/bin bash
for in_file in "$PWD"/*/*/*.txt; do
    out_file="$in_file/in/out"
    CM="/usr/local/bin/command -in \"$in_file\" -out \"$out_file\""
    echo "${CM}"
    /usr/local/bin/command -in "$in_file" -out "$out_file"
done

Instead of echoing your commands, use "set -x" to debug your script :

#!/bin bash
set -x
for in_file in "$PWD"/*/*/*.txt; do
    out_file="$in_file/in/out" 
    /usr/local/bin/command -in "$in_file" -out "$out_file"
done

or this :

#!/bin bash
for in_file in "$PWD"/*/*/*.txt; do
    out_file="$in_file/in/out" 
    set -x
    /usr/local/bin/command -in "$in_file" -out "$out_file"
    set +x
done
share|improve this answer
    
Thanks! This solution works -- but the idea was not to have to repeat typing (or having in the script) the same command. I did not know the set -x and set -e lines, nice! (the +x is required!) – alle_meije Feb 26 '14 at 12:38

You must log in to answer this question.

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