1

How can I use a "POSIX BRE" or a "POSIX ERE" regex to match a string (line or word) that does not have bak at the end?

I want to do a ls | egrep '<regex>' to find all files which DO NOT have bak at the end of the filename.

For example, if there are three files file1, file2_bak, and bak_file3, the regex should match only file1 and bak_file3 (but not file2_bak).

I know that this can be done with ls | grep -v 'bak$', but I want to do this without using -v option for grep or egrep. I don’t want to use -v because this is just a theoretical/academic question on POSIX regexes.

This is how I match the filenames which DO HAVE bak at the end:

$ ls | egrep 'bak$'
file2_bak
$ 

The above regex, bak$, matches all strings that DO HAVE bak at the end. But how can I write a regex which matches all strings that do not have bak at the end?

4
  • 1
    I suggest to make explicit clear in your question that you are asking an academical question (as opposed to a practical one), and formulate it accordingly, i.e. without reference to the ls or grep tools, but only formulated in terms of Regular Expressions. (I seem to have been not the only one misleaded by your question.)
    – Janis
    Jun 13, 2015 at 4:22
  • 1
    You should maybe explain why just using -v isn't ok (because that's the "normal" way to do what you ask with grep).
    – hyde
    Jun 13, 2015 at 6:46
  • @Janis Yes looks like i confused many people.. I edited the question.. maybe someone who reads it in future wont get confused ..
    – sps
    Jun 14, 2015 at 13:56
  • @hyde Actually my question was on the regex part that is why I did not want to use -v. It was just a theoretical/academical question on regex. As grep is the only utility I have used so far to use regex my question somehow had a grep command in it. For practical puprpose even I will do -v and keep it simple and clear. I am practising posix regex so i was just trying different things to test myself ...
    – sps
    Jun 14, 2015 at 14:17

5 Answers 5

5

If you use ksh (or bash with extended globbing activated, or zsh with ksh globs enabled) you can achieve the desired function using only file globbing patterns:

ls -d -- !(*bak)

With grep, to get a simple solution, just use the negation -v:

ls | grep -v 'bak$'
1
  • Works also for bash- I don't know how I couldn't think of it.
    – kos
    Jun 13, 2015 at 5:44
4

Regular languages (i.e. "this can be matched with a RE") are closed under complement, so it's possible, but it's not very useful for practical purposes: what you start out with is the condition

last letter is k AND letter before that is a AND letter before that is b

(let me write s[-1]=='k' and s[-2]=='a' and s[-3]=='b' in a pythonesque fashion) so a string that fails that has

not(s[-1]=='k' and s[-2]=='a' and s[-3]=='b')

i.e.

not(s[-1]=='k') or not (s[-2]=='a' and s[-3]=='b'))

i.e.

not(s[-1]=='k') or not(s[-2]=='a') or not(s[-3]=='b')

applying deMorgan's rule twice, and of course, this would in particular be the case if your string has length 2 or less, so you would roughly end up with

grep '^$\|^.$\|^..$\|..[^k]$\|.[^a].$\|[^b]..$'

which I'd consider typeable but unmaintainable.

(Theoretical sidenote: in general, you'd convert your regular expression into a deterministic finite automaton (DFA), invert the terminal states there, and then convert the new DFA back into a regular expression, which is all well-defined, but a somewhat tedious and error-prone process.)

4
  • This is what I trying to come up with. Although I do not about deMorgan rule or few other things you said above, but I understood the command grep '^$\|^.$\|^..$\|..[^k]$\|.[^a].$\|[^b]..$'. With egrep no need to escape |. ls | egrep '^$|^.$|^..$|..[^k]$|.[^a].$|[^b]..$' . Answer by @.kos has a pcre answer which does similar to what i was asking for. I was thinking i could do that way without using any options for grep.
    – sps
    Jun 13, 2015 at 5:11
  • POSIX BREs have no \|. Jun 14, 2015 at 14:29
  • @StéphaneChazelas Yes, there is no | alternating character in BRE. I am just learning it and found out that, in the gnu version of grep, if we escape a ERE metacahracter (which is not a metacharacter of BRE) with a baskslash then they get the meaning which they have in ERE. So if we put a '\' before any of these | ( ) ? + { } then they get their meaning which they have in usual ERE. I dont know if that is a feature only of the gnu grep or it is also a standard for BRE. Just learning now and i maybe wrong though..
    – sps
    Jun 14, 2015 at 16:19
  • 1
    @sps, yes for GNU tools BRE and ERE are the same thing with different syntax. GNU grep is found on quite a few systems. For instance, many BSDs had GNU grep for a while (until they rewrote their own (backward compatible) one. The POSIX spec is there. Note that things like \{were added to BREs before EREs ({). Jun 14, 2015 at 17:31
3

Using find:

find . -maxdepth 1 -type f ! -name "*bak"
  • .: asserts to search in the current working directory
  • -maxdepth 1: asserts to search only one level below the specified directory (i.e. only the current working directory)
  • -type f: asserts to search only for files
  • ! -name "*bak": asserts to search only for filenames not ending in bak

However, if you want to grep ls's output:

ls | grep -v 'bak$'
  • -v: prints only the lines not matching the given regex

Regex breakdown:

  • bak: matches a bak string
  • $: matches the end of the line

The same using negative look-behind (for PCREs-compatible grep versions):

ls | grep -P '(?<!bak)$'
  • -P: matches lines using PCREs

Regex breakdown:

  • (?<!bak): only matches the following pattern if not preceded by a bak string
  • $: matches the end of the line
20
  • 1
    @kos Thanks +1 for the help. But I want to do it using ls | grep or ls | egrep , and not by using find. My question is about the regex and not the utility (find or grep etc)...
    – sps
    Jun 13, 2015 at 3:39
  • 2
    @sps However, I don't understand the need of not using -v.
    – kos
    Jun 13, 2015 at 3:57
  • 1
    @kos I am trying to not use -v because I am learning POSIX regex. So I want to learn how to do the above thing just by using regex.
    – sps
    Jun 13, 2015 at 4:00
  • 1
    @sps Nevermind, just saw your comment. Going to update
    – kos
    Jun 13, 2015 at 4:06
  • 1
    @sps Updated, however I couldn't make it using negative look-ahead, I had to use a negative look-behind. Someone more proficient in PCREs than me perhaps might be able to explain why. In any case I could drop the use of -v.
    – kos
    Jun 13, 2015 at 4:36
1

With POSIX BREs that have no alternation operator, you can use that \{0,1\} instead:

LC_ALL=C grep '^\.\{0,2\}\(.*[^k]\)\{0,1\}\(.*[^a].\)\{0,1\}\(.*[^b]..\)\{0,1\}$'
0

If you have perl extensions, you can use negative look ahead.

grep -P '^.{0,2}$|^.*(?!bak)...$'

For matching a line not ending with bak

You must log in to answer this question.

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