116

How would you go about telling whether files of a specific extension are present in a directory, with bash?

Something like

if [ -e *.flac ]; then 
echo true; 
fi 
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
Wurlitzer
  • 2,819
  • 5
  • 21
  • 13

15 Answers15

140
#!/bin/bash

count=`ls -1 *.flac 2>/dev/null | wc -l`
if [ $count != 0 ]
then 
echo true
fi 
JeremyWeir
  • 24,118
  • 10
  • 92
  • 107
  • 12
    Hey Thomas, how about adding a better solution to the question instead of just a downvote. – JeremyWeir May 19 '15 at 22:47
  • 10
    Also Thomas, your example of why not to use it is using `ls -l`, my answer uses `ls -1` – JeremyWeir May 19 '15 at 22:49
  • 12
    And, this answer isn't parsing the output, it is just counting the lines output, and I'm almost certain even if there are strange characters in a filename, `ls -1` is going to print one line per file – JeremyWeir May 19 '15 at 22:59
  • 3
    Counting lines _is_ parsing. And it will return an incorrect number if any files contain `\n` (and yes, that is valid in file names). – marcelm May 09 '19 at 18:21
  • 3
    Pray tell us then, what magical character would have to be in a file's name in order to make the line count naught? – yeoman May 11 '19 at 07:34
  • 1
    Maybe a bit late, but for completeness, `find . -maxdepth 1 -type f -name *.flac | wc -l`. This is in accordance to ShellCheck SC2012. – Stefan Jun 09 '20 at 18:02
  • 1
    @Stefan, don't forget to quote `"*.flac"` otherwise you get errors like "find: possible unquoted pattern after predicate `-name'?" – Mint Jan 27 '21 at 02:03
  • In case directory contains very large number of files it will throw an error `Argument list too long` – Saurabh Gangamwar Nov 13 '21 at 09:36
  • You should probably add `-q` or `-b` (or `--hide-control-chars` and `--escape` respectively) to substitute control characters with `?` or escape them as octals – then you will be safe with line breaking filenames. – Cromax Jul 27 '23 at 13:43
  • Also you could pass `ls` output through `head` to skip unnecessary counting of all lines, if the output is large, so you would get: `any_exists="$(ls -q1 2>/dev/null | head -n1 -q | wc -l)"` – the funny part is, `any_exists` would contain `0` or `1` depending on results, the value is like binary digit for false or true. :-) – Cromax Jul 27 '23 at 13:51
38
#/bin/bash

myarray=(`find ./ -maxdepth 1 -name "*.py"`)
if [ ${#myarray[@]} -gt 0 ]; then 
    echo true 
else 
    echo false
fi
wkl
  • 77,184
  • 16
  • 165
  • 176
20

This uses ls(1), if no flac files exist, ls reports error and the script exits; othewise the script continues and the files may be be processed

#! /bin/sh
ls *.flac  >/dev/null || exit
## Do something with flac files here
frayser
  • 1,754
  • 10
  • 17
12
shopt -s nullglob
if [[ -n $(echo *.flac) ]]    # or [ -n "$(echo *.flac)" ]
then 
    echo true
fi
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
4

The top solution (if [ -e *.flac ];) did not work for me, giving: [: too many arguments

if ls *.flac >/dev/null 2>&1; then it will work.

Rodrigue
  • 3,617
  • 2
  • 37
  • 49
4

You need to be carful which flag you throw into your if statement, and how it relates to the outcome you want.

If you want to check for only regular files and not other types of file system entries then you'll want to change your code skeleton to:

if [ -f file ]; then
echo true;
fi

The use of the -f restricts the if to regular files, whereas -e is more expansive and will match all types of filesystem entries. There are of course other options like -d for directories, etc. See http://tldp.org/LDP/abs/html/fto.html for a good listing.

As pointed out by @msw, test (i.e. [) will choke if you try and feed it more than one argument. This might happen in your case if the glob for *.flac returned more than one file. In that case try wrapping your if test in a loop like:

for file in ./*.pdf
do
    if [ -f "${file}" ]; then
    echo 'true';
    break
    fi
done

This way you break on the first instance of the file extension you want and can keep on going with the rest of the script.

dtlussier
  • 3,018
  • 2
  • 26
  • 22
  • 1
    except `test` (aka `[`) complains if the pattern expands to more than one file: "binary operator expected" or "too many arguments" – msw Oct 04 '10 at 15:49
  • 1
    @msw - thanks, I didn't even think of that issue. I've tried to modify my answer to take it into account. – dtlussier Oct 04 '10 at 18:03
  • You should quote the variable, i.e. use `"$file"`, because the filename may contain some bad characters, like spaces or asterisks. – Roman Cheplyaka Oct 04 '10 at 21:55
4
#!/bin/bash
files=$(ls /home/somedir/*.flac 2> /dev/null | wc -l)
if [ "$files" != "0" ]
then
echo "Some files exists."
else
echo "No files with that extension."
fi
Ruel
  • 15,438
  • 7
  • 38
  • 49
3

You can use -f to check whether files of a specific type exist:

#!/bin/bash

if [ -f *.flac ] ; then
   echo true
fi 
Uzi
  • 31
  • 2
  • This works but you're burying the lede - the _reason_ it works is because the single-brace `[` (as opposed to the more common `[[`) doesn't prevent globbing. – Adrian Petrescu Mar 06 '19 at 02:35
3

For completion, with zsh:

if [[ -n *.flac(#qN) ]]; then
  echo true
fi

This is listed at the end of the Conditional Expressions section in the zsh manual. Since [[ disables filename globbing, we need to force filename generation using (#q) at the end of the globbing string, then the N flag (NULL_GLOB option) to force the generated string to be empty in case there’s no match.

Iso
  • 3,148
  • 23
  • 31
2
   shopt -s nullglob
   set -- $(echo *.ext)
    if [ "${#}" -gt 0 ];then
      echo "got file"
    fi
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • This is wrong. First, you probably meant `-ge` instead of `-gt`, because otherwise your code won't detect the single file. Second, if there are no files matching the pattern, any conforming shell will left the string `*.ext` untouched, so you still will get one "result". – Roman Cheplyaka Oct 04 '10 at 21:46
  • $# is 1 for a singe file and 0 for no files. – frayser Jan 24 '15 at 01:14
2

bash only:

any_with_ext () ( 
    ext="$1"
    any=false
    shopt -s nullglob
    for f in *."$ext"; do
        any=true
        break
    done
    echo $any 
)

if $( any_with_ext flac ); then
    echo "have some flac"
else 
    echo "dir is flac-free"
fi

I use parentheses instead of braces to ensure a subshell is used (don't want to clobber your current nullglob setting).

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
1

Here is a solution using no external commands (i.e. no ls), but a shell function instead. Tested in bash:

shopt -s nullglob
function have_any() {
    [ $# -gt 0 ]
}

if have_any ./*.flac; then
    echo true
fi

The function have_any uses $# to count its arguments, and [ $# -gt 0 ] then tests whether there is at least one argument. The use of ./*.flac instead of just *.flac in the call to have_any is to avoid problems caused by files with names like --help.

jochen
  • 3,728
  • 2
  • 39
  • 49
1

Here's a fairly simple solution:

if [ "$(ls -A | grep -i \\.flac\$)" ]; then echo true; fi

As you can see, this is only one line of code, but it works well enough. It should work with both bash, and a posix-compliant shell like dash. It's also case-insensitive, and doesn't care what type of files (regular, symlink, directory, etc.) are present, which could be useful if you have some symlinks, or something.

TSJNachos117
  • 121
  • 4
1

I tried this:

if [ -f *.html ]; then
    echo "html files exist"
else
    echo "html files dont exist"
fi

I used this piece of code without any problem for other files, but for html files I received an error:

[: too many arguments

I then tried @JeremyWeir's count solution, which worked for me:

count=`ls -1 *.flac 2>/dev/null | wc -l`
if [ $count != 0 ]
then 
echo true
fi 

Keep in mind you'll have to reset the count if you're doing this in a loop:

count=$((0))
GChuf
  • 1,135
  • 1
  • 17
  • 28
  • 1
    If there are more then one file with the given extension in the directory, *.html is expanding to too many arguments. Like "if [ -f file1.html file2.html ]; then"... – Nic Stray May 25 '21 at 07:13
1

This should work in any borne-like shell out there:

if [ "$(find . -maxdepth 1 -type f | grep -i '.*\.flac$')" ]; then
    echo true
fi

This also works with the GNU find, but IDK if this is compatible with other implementations of find:

if [ "$(find . -maxdepth 1 -type f -iname \*.flac)" ]; then
    echo true
fi
TSJNachos117
  • 121
  • 4