Bash: move files of specific pattern

I write a script that iterates over a set of zip files, extracts them and moves only files that match a specific filename pattern to another folder.

The script:

#!/bin/bash

ARCHIVE_FILEMASK="????-??-??_E_MDT_0_0.7z"
FILEMASK="?????????_?????_A_?_????????????????-?????????"
extractDir=/path/to/dir
dest=/path/to/dest

for f in ${ARCHIVE_FILEMASK}
do
    7z e -aoa -o"${extractDir}" "$f"
    if [ $? != 0 ]
    then
        mv "${extractDir}/${FILEMASK}".xml "$dest"
    fi
done

When the script is executed an error occurs:

mv: cannot stat `/path/to/dir/?????????_?????_A_?_????????????????-?????????.xml': No such file or directory

I’ve already tried turning on nullglob and extglob but it didn’t help. If I execute the command directly from the shell, it runs just fine. The answers from other posts with similar problems also didn’t help.

edit: After Paweł Rumian mentioned possible problems with spaces in directory names, I tried AVJ’s tip and added set +x before the mv command.

The result is:

+ mv '/path/to/dir/?????????_?????_A_?_????????????????-?????????.xml' /path/to/dest

So obviously I have to get rid of the single quotes now. Any idea how?

Answer

The usual advice to put double quotes around variable expansions is because $foo outside quotes means “take the value of foo, split it at whitespace¹, and expand each resulting word as a wildcard pattern if it matches at least one file”, rather than “take the value of foo”. In particular, when the variable foo contains a file name, "$foo" is what you need. But here the variables ARCHIVE_FILEMASK and FILEMASK contain wildcard patterns. So you do in fact want to expand them when they’re used, therefore you should use them unquoted. They will thus be interpreted as a whitespace-separated list of patterns; that doesn’t make a difference here since you have no whitespace in these variables’ value.

The variables f and extractDir contain file names, so they must remain double-quoted.

for f in ${ARCHIVE_FILEMASK}
do
    if 7z e -aoa -o"${extractDir}" "$f"; then
        mv "${extractDir}/"${FILEMASK}.xml "$dest"
    fi
done

Since you’re using bash, you can call shopt -s nullglob to make the wildcard pattern "${extractDir}/"${FILEMASK}.xml expand to an empty list if it doesn’t match any file. The mv command will complain that it’s missing a file if you don’t pass it any source file; you can solve that problem by invoking it only if there is at least one match.

shopt -s nullglob
for f in ${ARCHIVE_FILEMASK}
do
    if 7z e -aoa -o"${extractDir}" "$f"; then
        matches=("${extractDir}/"${FILEMASK}.xml)
        if ((${#matches[@]})); then
            mv "${matches[@]}" "$dest"
        fi
    fi
done

¹ Or more generally as indicated by IFS.

Leave a Reply

Your email address will not be published. Required fields are marked *