Test if all three filetypes are present in a directory

I want to detect that there is one of each of the following filetypes in a directory .png , .txt, and .tar.gz.

The following works correctly when testing for two of them

shopt -s nullglob
MYDIR=/some_dir
if [ $MYDIR/*.png] && [ $MYDIR/*.txt]; then
  echo "All files present"
else
  echo "At least one type is missing"
fi

But if I try to test all three using

if [ $MYDIR/*.png] && [ $MYDIR/*.txt] && [ $MYDIR/*.tar.gz]; then
...

or using

if [[ $MYDIR/*.png && $MYDIR/*.txt && $MYDIR/*.tar.gz ]]; then
...

then I don’t get the correct behaviour (it still returns true even if one of the files is missing).

What is going on here?

Asked By: teeeeee

||

You absolutely need to use:

shopt -s nullglob

before the test to avoid having false positives, like $MYDIR/*.png literally.

But you cannot test a wildcard like this in a bash test, you need to test array instead:

txt=( *.txt ) tgz=( *.tar.gz ) png=( *.png )
if [[ ${txt[@]} && ${png[@]} && ${tgz[@]} ]]; then
    echo SUCCESS
fi

Another way to test wilcard existence with compgen hack:

cd "$workdir"
if (compgen -W *.txt && compgen -W *.png && compgen -W *.tar.gz) &>/dev/null; then
    echo 'SUCCESS'
else
    echo >&2 'At least one filetype missed'
fi

Don’t forget to add bash’s shebang:

#!/bin/bash

or

#!/usr/bin/env bash

[[ is a bash keyword similar to (but more powerful than) the [ command. See http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals. Unless you’re writing for POSIX sh, I recommend [[

Example of use:

 [[ ${names[@]} ]] && echo 'SUCCESS'

no need to count number of occurrences like you tried.

Answered By: Gilles Quénot

You can’t test whether a filename globbing pattern matches using [ ... ] (and you lack space before the final ] in all [ ... ] tests, so your code would not work anyway).

The way to do it is to perform the match and count the number of matching names.

shopt -s nullglob    # make globs expand to nothing if they don't match
shopt -s dotglob     # also match hidden names

error=false
for ext in png txt tar.gz; do
    set -- "$MYDIR"/*."$ext"
    if [ "$#" -eq 0 ]; then
        printf 'Nothing matches "%s"n' "$MYDIR/*.$ext" >&2
        error=true
        break  # possibly, unless you want the user to see all patterns that fail
    fi
done

if "$error"; then
    echo 'Can not continue' >&2
    exit 1
fi

If you feel you want to type more (or just need to preserve the existing list of positional parameters), you could uso a named array. Change the set -- line into names=( "$MYDIR"/*."$ext" ) and the "$#" into "${#names[@]}". Note that all quotes are significant.

Answered By: Kusalananda
Categories: Answers Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.