78

Here's how one might list all files matching a pattern in bash:

ls *.jar

How to list the complement of a pattern? i.e. all files not matching *.jar?

bishop
  • 37,830
  • 11
  • 104
  • 139
calebds
  • 25,670
  • 9
  • 46
  • 74
  • 1
    this probably shouldn't be labeled regex, * is a shell-globbing wildcard, and is not a regular expression *, which is the "zero or more quantifier". – Scott Weaver Dec 15 '11 at 19:57
  • Good point, although I'm learning that using regex is probably better practice for getting the matches you expect. – calebds Dec 15 '11 at 20:04

11 Answers11

95

Use egrep-style extended pattern matching.

ls !(*.jar)

This is available starting with bash-2.02-alpha1. Must first be enabled with

shopt -s extglob

As of bash-4.1-alpha there is a config option to enable this by default.

j0k
  • 22,600
  • 28
  • 79
  • 90
Christian
  • 959
  • 6
  • 2
  • 1
    Yay for mentioning the shopt -s extglob! – Ogre Psalm33 Dec 13 '12 at 15:23
  • 3
    This is the correct answer. The answer marked as correct doesn't use the shell directly, it uses an ancillary command. – dbn Jul 08 '13 at 23:34
  • 1
    @dbw - Even if I like more this answer, the accepted answer is ok for the question. The OP does not specifically ask for using "the shell directly". – sancho.s ReinstateMonicaCellio Oct 13 '17 at 08:48
  • "egrep-style" is quite misleading, as grep deals with regular expressions, while this here are shell patterns. – Benjamin W. Jan 31 '18 at 06:16
  • It did not work for me. For long lists returning about 2000 items, I got "argument list to long". I guess it happens because the branches expand the outputs of the command inside them. So it becomes an argument list with about 2000 items which is really long. – Christoforos Mar 09 '19 at 08:23
  • For some reason I cannot use it with `watch` if I enclose the command in single or double quotes. I get unexpected token. – Marinos An Sep 29 '21 at 08:56
67
ls | grep -v '\.jar$'

for instance.

Michael Krelin - hacker
  • 138,757
  • 24
  • 193
  • 173
42

Little known bash expansion rule:

ls !(*.jar)
ccarton
  • 3,556
  • 16
  • 17
25

With an appropriate version of find, you could do something like this, but it's a little overkill:

find . -maxdepth 1 ! -name '*.jar'

find finds files. The . argument specifies you want to start searching from ., i.e. the current directory. -maxdepth 1 tells it you only want to search one level deep, i.e. the current directory. ! -name '*.jar' looks for all files that don't match the regex *.jar.

Like I said, it's a little overkill for this application, but if you remove the -maxdepth 1, you can then recursively search for all non-jar files or what have you easily.

Dan Fego
  • 13,644
  • 6
  • 48
  • 59
  • I do like this one because it works in zsh too, where some of the bash-extension ones do not (at least not with my zsh skill level :D ) – Greg Lyon Oct 25 '21 at 16:17
18

POSIX defines non-matching bracket expressions, so we can let the shell expand the file names for us.

ls *[!j][!a][!r]

This has some quirks though, but at least it is compatible with about any unix shell.

stefanct
  • 2,503
  • 1
  • 28
  • 32
6

If your ls supports it (man ls) use the --hide=<PATTERN> option. In your case:

$> ls --hide=*.jar

No need to parse the output of ls (because it's very bad) and it scales to not showing multiple types of files. At some point I needed to see what non-source, non-object, non-libtool generated files were in a (cluttered) directory:

$> ls src --hide=*.{lo,c,h,o}

Worked like a charm.

emvee
  • 4,371
  • 23
  • 23
5

Another approach can be using ls -I flag (Ignore-pattern).

ls -I '*.jar'
Divyanshu Srivastava
  • 1,379
  • 11
  • 24
4

And if you want to exclude more than one file extension, separate them with a pipe |, like ls test/!(*.jar|*.bar). Let's try it:

$ mkdir test
$ touch test/1.jar test/1.bar test/1.foo
$ ls test/!(*.jar|*.bar)
test/1.foo

Looking at the other answers you might need to shopt -s extglob first.

James Brown
  • 36,089
  • 7
  • 43
  • 59
2

One solution would be ls -1|grep -v '\.jar$'

fge
  • 119,121
  • 33
  • 254
  • 329
  • 1
    [Don't parse the output of `ls`](http://mywiki.wooledge.org/ParsingLs), thank you. – gniourf_gniourf May 05 '14 at 20:11
  • 6
    @gniourf_gniourf don't be religious about rules such as parsing `ls` or using `goto`. Sometimes those solutions work best. – Jonathan E. Landrum Jul 23 '14 at 20:39
  • 1
    This isn’t one of those cases; `find . -print0 | grep -z '\.jar$'` works better even if you don’t want to use `--ignore` or Bash’s extended globbing. That said, most people don’t want to read a whole article to understand a rule or what they *should* do, and you can fit a simple explanation and recommendation like “it doesn’t work in edge cases; use `find -print0` and `grep -z` instead” in a comment. – Daniel H Jan 06 '17 at 22:25
1

Some mentioned variants of this form:

ls -d *.[!j][!a][!r]

But this seems to be only working on bash, while this seems to work on both bash and zsh:

ls -d *.[^j][^a][^r]
1
ls -I "*.jar"

-I, --ignore=PATTERN
do not list implied entries matching shell PATTERN

  • It works without having to execute anything before
  • It works also inside watch quotes: watch -d 'ls -I "*.gz"', unlike watch 'ls !(*.jar)' which produces: sh: 1: Syntax error: "(" unexpected

Note: For some reason in Centos requires quoting the pattern after -I while Ubuntu does not

Marinos An
  • 9,481
  • 6
  • 63
  • 96