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?

  • 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 '15 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 '15 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 '15 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 '15 at 14:17
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.)

  • 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 '15 at 5:11
  • POSIX BREs have no \|. – Stéphane Chazelas Jun 14 '15 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 '15 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 ({). – Stéphane Chazelas Jun 14 '15 at 17:31
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$'
  • Works also for bash- I don't know how I couldn't think of it. – kos Jun 13 '15 at 5:44
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
  • 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 '15 at 3:39
  • 2
    @sps However, I don't understand the need of not using -v. – kos Jun 13 '15 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 '15 at 4:00
  • 1
    @sps Nevermind, just saw your comment. Going to update – kos Jun 13 '15 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 '15 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

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.