Is there any way in bash to glob files that does not end with a certain suffix ?

e.g. I'm doing this:


But I don't want to move the HDR_10_* that ends with .gz

3 Answers 3


This would work for the file move:

mv $( echo $INCDIR/HDR_10_* | grep -v '\.gz$') $BACKUPDIR

You also asked 'to glob files that does not end with a certain suffix ?' Bash has the shell option (shopt) extglob, which allows extended globbing syntax. The !(...glob...) matches everything but the glob pattern.

shopt -s extglob
mv !(*.gz) $BACKUPDIR

would work only if $INCDIR contained only files named HDR_10_*, specifically it would match any file or directory in $INCDIR that doesn't match *.gz. Technically you're asking for a glob that both matches one pattern but not another, which i don't think exists as a simple single entity.

  • 1
    Your first command simply won't work, as the echo command puts all names on one line. Nov 12, 2010 at 16:18
  • P.S. And if you try to fix that, you'll run into problems if a file name contains a special character such as whitespace or globbing characters. Nov 12, 2010 at 16:24
  • 3
    Since the patterns (the one to include and the one to exclude) don't overlap, it's actually easy to combine them: mv $INCDIR/HDR_10_!(*.gz) $BACKUPDIR. Nov 12, 2010 at 16:35

Under ksh, !(*.gz) matches all files in the current directory except the files that match *.gz. The same pattern works in bash after shopt -s extglob, and in zsh after setopt ksh_glob. Neither ksh nor bash has a way to take the intersection of a positive match and a negative match. In zsh, after setopt extended_glob , you can write $INCDIR/HDR_10_*~$INCDIR/*.gz.

One solution, in any Bourne-style shell (ash, bash, ksh, zsh, ...), is to iterate over the files and check each match.

for x in "$INCDIR"/HDR_10_*; do
  case "$x" in
    *.gz) :;;
    *) mv -- "$x" "$BACKUPDIR";;

Another solution is to use find. The following command will move files in subdirectories as well.

find "$INCDIR" -type f -name 'HDR_10_*' \! -name '*.gz' -exec mv {} "$BACKUPDIR" \;

If you have GNU find (e.g. under Linux), You can add -maxdepth 1 after "$INCDIR" to move files only directly in $INCDIR.

Note that You should always use double quotes around variable substitutions (e.g. "$INCDIR", not $INCDIR) unless you have a good reason to omit them. Otherwise you will run into trouble if you have files whose name contains special characters such as whitespace or \[?*.


If you use find, grep, and xargs, you can do this fairly easily.

I am sure there are better constructions, but this is one that comes to mind right away:

# find <here>| <pattern>    | <notpattern>| <what to do>
find $INCDIR | grep HDR_10_ | grep -v .gz | xargs mv $BACKUPDIR

A correction was pointed-out in the comments that the above quick-stab won't work because of mv not getting its arguments in the proper order from xargs:

find $INCDIR -name 'HDR_10_*' ! -name '*.gz' -exec mv {} $BACKUPDIR \
  • 1
    You can't use xargs here - xargs adds parameters to the end, you need after mv. Remember that find allows both positive and negative patterns as well. Maybe something like: find $INCDIR -name 'HDR_10_*' ! -name '*.gz' -exec mv {} $BACKUPDIR \; Nov 12, 2010 at 15:59
  • Your command has many bugs. Beyond what Rich already said about xargs, the grep patterns are matching anywhere on the line, so they will include or exclude things that should not be (e.g. HDR_10_agz.txt will be excluded while NOT_HDR_10_blah will be included). It's possible to use grep here, but it's a lot simpler to use -name with find as per Rich's comment. Also your command will include files in subdirectories, which is not in the original specification. Then there is the generic problem with xargs requiring quoted output in a way that find can't produce. Nov 12, 2010 at 16:21
  • @Gilles - yes, the behavior as currently written will search anywhere on the line: that's not a "bug" in my mind, but a starting place that could take refinement based on the specific use. Didn't know about the xargs issue till @Rich Homolka said something though :)
    – warren
    Nov 12, 2010 at 20:59
  • It's because of the issue with using xargs that (at least GNU) mv has --target-directory=DIRECTORY and find and xargs can use a null delimiter. So you can do xargs ... -print0 | xargs -0 --target-directory="$BACKUPDIR" and only get one copy of mv spawned (by not using -I with xargs). Nov 13, 2010 at 4:21

You must log in to answer this question.

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