Is there any way in bash to glob files that does not end with a certain suffix ?
e.g. I'm doing this:
mv $INCDIR/HDR_10_* $BACKUPDIR
But I don't want to move the HDR_10_* that ends with .gz
Super User is a question and answer site for computer enthusiasts and power users. It only takes a minute to sign up.
Sign up to join this communityThis 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
cd $INCDIR
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.
echo
command puts all names on one line.
Nov 12, 2010 at 16:18
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";;
esac
done
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 \
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
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
xargs
issue till @Rich Homolka said something though :)
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