## 33.8. nom: List Files That Don't Match a Wildcard

Go to http://examples.oreilly.com/upt3 for more information on: nom

The nom (no match) script takes filenames (usually expanded by the shell) from its command line. It outputs all filenames in the current directory that don't match. As Section 33.2 shows, some shells have an operator -- `!` or `^` -- that works like nom, but other shells don't. Here are some examples of nom:

- To get the names of all files that don't end with .ms:

  >     % nom *.ms

- To edit all files whose names don't have any lowercase letters, use command substitution (Section 27.14):

  >     % vi `nom *[a-z]*`

- To copy all files to a subdirectory named Backup (except Backup itself):

  >     % cp `nom Backup` Backup

Here's the script:

`trap` Section 35.17, `case` Section 35.11, `$*` Section 35.20, `comm` Section 11.8

>     #! /bin/sh
>     temp=/tmp/NOM$$
>     stat=1     # Error exit status (set to 0 before normal exit)
>     trap 'rm -f $temp; exit $stat' 0 1 2 15

>     # Must have at least one argument, and all have to be in current directory:
>     case "$*" in
>     "") echo Usage: `basename $0` pattern 1>&2; exit ;;
>     */*)    echo "`basename $0` quitting: I can't handle '/'s." 1>&2; exit ;;
>     esac

>     # ls gives sorted file list. -d=don't enter directories, -1=one name/line.
>     ls -d ${1+"$@"} > $temp   # Get filenames we don't want to match
>     ls -1 | comm -23 - $temp  # Compare to current dir; output names we want
>     stat=0

The -d option (Section 8.5) tells ls to list the names of any directories, not their contents. The ${1+"$@"} (Section 36.7) works around a problem in some Bourne shells. You can remove the -1 option on the script's `ls` command line if your version of ls lists one filename per line by default; almost all versions of ls do that when they're writing into a pipe. Note that nom doesn't know about files whose names begin with a dot (.); you can change that if you'd like by adding the `ls` `-A` option (uppercase letter "A", which isn't on all versions of ls).

Finally, if you've got a shell with process substitution, such as bash, which is what we use below, you can rewrite nom without the temporary file and the trap:

>     #!/bin/bash
>     # Must have at least one argument, and all have to be in current directory:
>     case "$*" in
>     "")  echo Usage: `basename $0` pattern 1>&2; exit ;;
>     */*) echo "`basename $0` quitting: I can't handle '/'s." 1>&2; exit ;;
>     esac

>     # ls gives sorted file list. -d=don't enter directories, -1=one name/line.
>     # Compare current directory with names we don't want; output names we want:
>     comm -23 <(ls -1) <(ls -d "$@")

-- JP

Copyright © 2003 O'Reilly & Associates. All rights reserved.