25

Suppose that I have a folder containing .txt, .pdf, and other files. I would like to list the "other" files (i.e., files not having the extensions .txt or .pdf). Do you have any advice on how to do this?

I know how to list files not having a given extension. For example, if I want to list all files except the .txt files, then either

find -not -iname "*.txt"

or

ls | grep -v '\.txt$' | column

seem to work. But, how can I list everything except .txt files or .pdf files? It seems that I need to use some sort of logical "or" in find or grep.

  • 2
    Keep in mind that the behavior of ls vs find vs globbing may differ for hidden dotfiles. – jw013 Sep 4 '12 at 19:32
  • 1
    Another thing to keep in mind: find will traverse subdirectories, like a recursive ls. Use -maxdepth 1 with find to get it to behave more like ls. – jw013 Sep 4 '12 at 20:20
  • So no recursive, just list the files in current dir ? – daisy Sep 5 '12 at 7:20
26

Assuming one has an appropriate version of ls, this is possibly the simplest way:

ls -I "*.txt" -I "*.pdf"

If you want to iterate across all the subdirectories:

ls -I "*.txt" -I "*.pdf" -R
  • 8
    GNU ls options aren't portable. – bahamat Sep 5 '12 at 20:45
  • 4
    ls doesn't really belong in portable scripts anyways, so I've been assuming the OP is asking about interactive use only. – jw013 Sep 5 '12 at 20:53
  • 1
    Why ls isn't portable? What should I use then? – Freedo Jan 17 '18 at 4:39
26

Find supports -o

find . ! '(' -name '*.txt' -o -name '*.pdf' ')'

You need the parenthesis to make the precedence right. Find does a lot of stuff; I suggest reading through its manpage.

You can also do an or in grep (but really, you should not parse the output of ls)

ls | grep -Ev '\.(txt|pdf)$' | column
  • 1
    Thanks! Why shouldn't I parse the output of ls? – Andrew Sep 4 '12 at 19:36
  • 6
    @Andrew first, because its fragile (consider a file name with a newline in it—yes, that's a valid file name—find's -print0/-exec/-delete/etc. avoid that problem); second, because there is usually an easier way. – derobert Sep 4 '12 at 19:38
  • In addition to the find manpage, I wholeheartedly recommend the Unix Power Tools articles about find, such as docstore.mik.ua/orelly/unix3/upt/ch09_06.htm and docstore.mik.ua/orelly/unix3/upt/ch09_12.htm – Wildcard Dec 18 '15 at 20:41
  • Just to help decode the statement for humans: find . NOT ( *.txt OR *.pdf ) – wisbucky Mar 7 '18 at 22:08
12

With bash extended globbing (turn on with shopt -s extglob), the glob !(*.txt|*.pdf) should work. You can pass this glob directly to any command that takes file arguments, including but not limited to ls.

  • 1
    +1 but if you've subdirectories then their contents would be listed too; use -d to avoid that: ls -d !(*.txt|*.pdf) – legends2k Nov 11 '14 at 14:43
  • Thank you, I was looking exactly for the syntax to accept multiple files, this would fit well here stackoverflow.com/questions/216995/… – Freedo Jan 17 '18 at 4:40
6

In zsh with extendedglob:

print -rl -- *~*.(txt|pdf)

or

print -rl -- ^*.(txt|pdf)

Or with kshglob (yes, that is ksh globbing not "bash extended globbing"):

print -rl -- !(*.txt|*.pdf)

Remember though that those also exclude dot files.

ksh93 has the FIGNORE (mis)feature:

FIGNORE='@(.|..|*.txt|*.pdf)'
printf '%s\n' *
4
find /path/to/directory '!' -name '*.pdf' '!' -name '*.txt'

This is equivalent to the command with the OR operator, because of the De Morgan's laws.

3

As suggested by derobert, your best bet is to use find. However, you can in fact use the result in a pipeline with other commands.

GNU (and some BSD's) find support the -print0 predicate which tells it to print the filename terminated by a NUL character, which character isn't allowed within a file name and guarantees there won't be a collision. Other commands can be instructed to use the NUL as their input delimiter.

The most important of which is GNU xargs, which runs the command you specify and passes to it the list of files as command line arguments. You want to run xargs -r0 in conjunction with find's -print0 For example:

find . -type f ! \( -name \*.pdf -o -name \*.txt \) -print0 | xargs -r0 ls -ld

This safely prints a long directory listing of all the pdf and txt files, including those with spaces or unprintable characters in the name.

You can also use it with GNU tar as follows:

tar -zcf myarchive.tar.gz --null --files-from <(
  find . -type f ! -name \*.tar.gz -print0)

This builds a tar.gz file of all the files whose names don't end in .tar.gz

rsync also accepts null-delimited files with the -0 parameter, as do several others. But xargs is the glue you'll usually use for this type of purpose. Either that or find's -exec feature.

  • The tar command example is a good one. For most purposes, though, -exec is a better fit. – Wildcard Dec 18 '15 at 20:43
1

If you don't have a subdirectory

ls !(*.pdf|*.txt)

should also work!

But

ls -I "*.pdf" -I "*.txt"

is the common way.

0

As a complement, if you use a bash-compatible shell, you can use the GLOBIGNORE variable to exclure resuts from pattern matching. From the man:

   GLOBIGNORE
          A colon-separated list of patterns defining the set of filenames
          to be ignored by pathname expansion.  If a filename matched by a
          pathname  expansion  pattern also matches one of the patterns in
          GLOBIGNORE, it is removed from the list of matches.

In your particular case:

sh$ (GLOBIGNORE='*.pdf:*.txt'; ls -d *)

Please note I run that command as a sub-shell (using parenthesis) in order to not alter the GLOBIGNORE environment variable of my interactive shell.

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.