3

When looping on a folder through a

for f in $path 

I am experiencing an unexpected behaviour, i.e. the loops is entered anyway even if no file match the wildcard. I am using cygwin and this is my code

#!/bin/sh
here=$(pwd)
release(){
    release="$1"
    targetdisk="$2"
    package="$3"
    searchPath="../$package/${package}Setup/*/*/*.msi"
    echo $searchPath
    ls $searchPath
    for f in $searchPath; do
        mode=$(echo $file | sed -e 's:.*/.*/\(.*\)/.*\.msi:\1:')
        version=$(echo $file | sed -e 's:.*/\(.*\)/.*/*.\.msi:\1:')
        PREVIOUSIFS=$IFS
        IFS=#
        targetPath="$targetdisk:\\Software and tools\\Install\\$release"
        newFile="$targetPath\\$package-$mode-$release.exe"
        if [ ! -d "$targetPath" ]; then
            mkdir -p "$targetPath"
        fi
        echo $newFile
        echo $f


        #cp $file $newFile
        IFS=$PREVIOUSIFS
    done;
}
release $1 $2 MyStuff

I put on purpose MyStuff, which does not exist:

$ ./release.sh 1.0-dev G
../MyStuff/MyStuffSetup/*/*/*.msi
ls: ../MyStuff/MyStuffSetup/*/*/*.msi: No such file or directory
G:\Software and tools\Install\1.0-dev\MyStuff--1.0-dev.exe
../MyStuff/MyStuffSetup/*/*/*.msi

As you can see however, the two echos are executed. Why this happens even if there is no match?

0

2 Answers 2

7

A wildcard pattern that doesn't match any file is left unexpanded. The intent is to allow typing an argument to a command that contains wildcard characters but isn't intended to be a pattern. This makes some modicum of sense on the command line, where it allows saving two quote characters occasionally. In a script, it's very annoying. Unfortunately, there's no way to change this behavior in traditional sh. You can check if the pattern has been left unexpanded.

for f in "../$package/${package}Setup"/*/*/*.msi; do
  if [ "$f" = "../$package/${package}Setup"/*/*/*.msi" ]; then
    # No match
    break
  fi
  …
done

Bash can be instructed to expand a non-matching wildcard pattern to the empty list. Change your shebang line to #!/bin/bash, and you can do this:

shopt -s nullglob
for f in "../$package/${package}Setup"/*/*/*.msi; do
  …

There are other issues in your script. The way you use the searchPath variable will break if there are spaces or wildcard characters (including backslashes) in $package. Don't leave the $searchPath variable substitutions unquoted. Always use double quotes around variable substitutions unless you want them to be treated as a whitespace-separated list of patterns, which is not the case here — $package is a string (requiring double quotes) while the apparent * make the lot a wildcard pattern. Put the pattern directly in the for loop, as above.

For logging, instead of these echo commands (which are lacking double quotes around the variable substitutions, so the arguments will be mangled), use set -x to display each command on stderr as it is executed.

At some point in the script, you're setting IFS=#, but you're never doing any splitting on #. Don't set IFS if you aren't going to make use of it.

There are several problems with mode=$(echo $file | sed -e 's:.*/.*/\(.*\)/.*\.msi:\1:'). As explained above, it's lacking double quotes around "$file". Furthermore calling an extra sed for each file to process is slow (executing external processes is slow on Cygwin) and overly complex. The shell has string processing constructs; while they are primitive, they are sufficient here. Your

dir="${file%/*}"
mode="${dir##*/}"
dir="${dir%/*}"
version="${dir##*/}"
2
  • the reason why I was using IFS is that targetPath contains a space, and passing at the end to cp $file $newFile resulted in error because $newFile was considered a space separated list of files instead that a single file. Also can you please show me how the $package should be quoted in the searchPath assignment?
    – Edmondo
    Jul 30, 2013 at 8:24
  • btw I got your suggestion. everything works right. Besides mkdir -p under cygwin apparently
    – Edmondo
    Jul 30, 2013 at 9:31
5

This script has a bunch of issues, but the behavior you're asking about, why the loop executes, is this:

In the shell construct

for f in <expr>; do <commands>; done

a file pattern is often used for "expr", which is then replaced by shell filename globbing, and that is what you have done. But your expression matches nothing, so globbing does not expand it into a list of file paths. But it also isn't a zero-length (null) list like in many other languages, but rather is the path expression with the asterisks that you echo out just above the loop. So the loop is executed once, with $f set equal to the unexpanded string ending in *.msi. You can see this where you echo it out before the loop. We know that this matches nothing because you also use it with ls, and that is why you get the "No such file or directory" error.

(Note also that you use $file in the loop, and I think you mean to use $f there.

You must log in to answer this question.

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