I thought this would be simple - but it is proving more complex than I expected.

I want to iterate through all the files of a particular type in a directory, so I write this:


for fname in *.zip ; do
   echo current file is ${fname}

This works as long as there is at least one matching file in the directory. However if there are no matching files, I get this:

current file is *.zip

I then tried:


FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}

While the body of the loop does not execute when there are no files, I get an error from ls:

ls: *.zip: No such file or directory

How do I write a loop which cleanly handles no matching files?

  • 9
    Add shopt -s nullglob before running the for loop.
    – cuonglm
    Oct 30, 2015 at 14:07
  • @cuolnglm: spookily this results in ls returning the name of the executing script rather than an empty list on this RHEL5 box (bash 3.2.25) if I do FILES=ls *.zip; for fname in "${FILES}"... but it does work as expected with for fname in *.zip ; do....
    – symcbean
    Oct 30, 2015 at 14:12
  • 5
    Use for file in *.zip, not `ls ...`. @cuonglm's suggestion is so that *.zip expands to nothing when the pattern doesn't match any file. ls without arguments lists the current directory. Oct 30, 2015 at 14:16
  • 1
    This question discusses why parsing the output of ls is generally to be avoided: Why not parse ls?; also see the link near the top of that page to BashGuide's ParsingLs article.
    – PM 2Ring
    Nov 1, 2015 at 11:03
  • 1

4 Answers 4


In bash, you can set the nullglob option so that a pattern that matches nothing "disappears", rather than treated as a literal string:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"

In POSIX shell script, you just verify that fname exists (and at the same time with [ -f ], check it is a regular file (or symlink to regular file) and not other types like directory/fifo/device...):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"

Replace [ -f "$fname" ] with [ -e "$fname" ] || [ -L "$fname ] if you want to loop over all the (non-hidden) files whose name ends in .zip regardless of their type.

Replace *.zip with .*.zip .zip *.zip if you also want to consider hidden files whose name ends in .zip.

  • 3
    shopt -s nullglob did not work for me on Ubuntu 17.04, but [ -f "$fname" ] || continue worked well.
    – koppor
    Sep 2, 2017 at 12:24
  • 3
    @koppor It sounds like you aren't actually using bash.
    – chepner
    Sep 2, 2017 at 12:55
  • 1
    +1 for a POSIX solution. Dec 19, 2020 at 17:24
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once


${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration

In a comment here you mention invoking a function...

    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg

 file_fn *

Use find

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

You MUST export your shell function with export -f for this to work. Now find executes bash which executes your shell function, and remains at the current dir level only.

  • Which recurses through subdirectories, and I want to invoke a bash function (not script) for the matches.
    – symcbean
    Oct 30, 2015 at 15:15
  • @symcbean I've edited to limit to single dir and handle bash functions
    – Dani_l
    Nov 1, 2015 at 10:34

Instead of:

FILES=`ls *.zip`


FILES=`ls * | grep *.zip`

This way if ls fails (which it does in your case) it will grep the failed output and return as a blank variable.

current file is      <---Blank Here

You can add some logic to this to make it return "No File Found"


FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    echo "No Files Found"

This way if the previous command succeeded (exited with a 0 value) then it will print the current file, otherwise it would print "No Files Found"

  • 1
    I think it is a bad idea to add one more process (grep) rather than trying to fix the issue by using a better tool (find) or changing the relevant setting for the current solution (with shopt -s nullglob) Nov 2, 2015 at 10:05
  • 1
    According to the OP's comment on their original post the shopt -s nullglob does not work. I tried find while verifying my answer and it kept failing. I think because of the export thing Dani said.
    – Kip K
    Nov 2, 2015 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.