52

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.

3
  • 2
    Keep in mind that the behavior of ls vs find vs globbing may differ for hidden dotfiles.
    – jw013
    Commented Sep 4, 2012 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
    Commented Sep 4, 2012 at 20:20
  • So no recursive, just list the files in current dir ?
    – daisy
    Commented Sep 5, 2012 at 7:20

8 Answers 8

56

Assuming one has GNU 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
3
  • 9
    GNU ls options aren't portable.
    – bahamat
    Commented Sep 5, 2012 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
    Commented Sep 5, 2012 at 20:53
  • 1
    Why ls isn't portable? What should I use then?
    – Freedo
    Commented Jan 17, 2018 at 4:39
41

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
4
  • 1
    Thanks! Why shouldn't I parse the output of ls?
    – Andrew
    Commented Sep 4, 2012 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
    Commented Sep 4, 2012 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
    Commented Dec 18, 2015 at 20:41
  • 1
    Just to help decode the statement for humans: find . NOT ( *.txt OR *.pdf )
    – wisbucky
    Commented Mar 7, 2018 at 22:08
15

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.

2
  • 1
    +1 but if you've subdirectories then their contents would be listed too; use -d to avoid that: ls -d !(*.txt|*.pdf)
    – legends2k
    Commented Nov 11, 2014 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
    Commented Jan 17, 2018 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' *
5
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.

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

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.

0

If you don't have a subdirectory

ls !(*.pdf|*.txt)

should also work!

But

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

is the common way.

1
  • These variations were already mentioned in answers from 2012.
    – Kusalananda
    Commented Apr 5, 2020 at 6:48

You must log in to answer this question.

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