8

I want to grep all lines with only one "#" in a line.

Example:

xxx#aaa#iiiii
xxxxxxxxx#aaa
#xxx#bbb#111#yy
xxxxxxxxxxxxxxxx#
xxx#x
#x#v#e#

Should give this output

xxxxxxxxx#aaa
xxxxxxxxxxxxxxxx#
xxx#x
0

6 Answers 6

25

try

grep '^[^#]*#[^#]*$' file

where

^      ; begin of line
[^#]*  ; any number of char ≠ #
#      ; #
[^#]*  ; any number of char ≠ #
$      ; end of line

as sugested, you can grep on the whole line, with

grep -x '[^#]*#[^#]*'

with

  • same pattern without begin of line/end of line anchor.
  • -x to grep whole line, see man grep
-x, --line-regexp
   Select  only  those  matches  that  exactly  match  the  whole line.  For a regular
   expression pattern, this is like parenthesizing the pattern and then surrounding it
   with ^ and $.
0
15

Using awk:

awk -F'#' 'NF==2' infile

based on # field separator, if number of fields in a line was exactly two fields then will print out. note that for example #x or x# or even # are considered two fields so.

1
  • 1
    Comments are not for extended discussion; this conversation has been moved to chat.
    – Jeff Schaller
    Jul 6, 2020 at 16:09
10

With two calls to grep: pick any line that has at least one #, then remove those that have at least two:

grep '#' filename | grep -v '#.*#'
1
  • 1
    The second regexp can be simplified to '#.*#'.
    – Jim L.
    Jul 3, 2020 at 18:34
5

Using GNU grep:

$ grep -P '^(?!(?:.*#){2}).*#' infile
xxxxxxxxx#aaa
xxxxxxxxxxxxxxxx#
xxx#x
$

The -P option means use PCRE (Perl Compatible Regular Expressions) regex. See https://www.pcre.org. PCRE are extensions to ERE (Extended Regular Expressions), originally introduced into Perl, which were later adopted by many commands, utilities, applications and programming languages.

If GNU grep is not available on your platform, you can install pcregrep which is part of the pcre-tools package that is available on many platforms.

The generalized form of this particular PCRE regex is:

^(?!(?:.*PATTERN){2}).*PATTERN

where PATTERN stands for the pattern that you want to occur once and only once in the grepped string. In our case the pattern is #.

  • ^ - start of the string
  • (?!(?:.*PATTERN){2}) - a negative lookahead that fails the match that is immediately to the right of the current location, i.e. the start of string, if there are two ({2}) consecutive occurrences of:
    • .* - 0 or more characters
    • PATTERN - the pattern
  • .* - 0 or more characters
  • PATTERN - the pattern
18
  • Ahhhh... Million times thanks!!! I wasn't aware about the "p" option.
    – Torsten
    Jul 3, 2020 at 6:12
  • 7
    would you care to explain the regex ?
    – Archemar
    Jul 3, 2020 at 6:37
  • and THAT is the main problem with -P - something that's trivial and obvious in a BRE or ERE becomes convoluted runes in a PCRE. The other 2 problems are that it's only available in GNU grep so it's not portable, and its still experimental and doesn''t play well with other grep options (per the GNU grep man page).
    – Ed Morton
    Jul 4, 2020 at 13:02
  • 1
    @EdMorton. Actually, the current grep manpage states that -P is "experimental when combined with the -z (--null-data) option, and grep -P may warn of unimplemented features." Other than -z option, the manpage does not state any other options that -P "doesn''t play well with". What other grep options are you aware of that -P is problematic with?
    – fpmurphy
    Jul 4, 2020 at 15:24
  • 1
    @Isaac, incidentally that's just about the same as the ERE in the accepted answer. This problem isn't much if a showcase for the usefulness of PCRE. :) PCRE might start to be more useful than ERE if the question was about a separator longer than one character. (On the other hand, if we had the the whole Perl language at hand, we could do something other than regex-optimizing too.)
    – ilkkachu
    Jul 5, 2020 at 13:12
1

With awk we can use the gsub function in the condition block to select our lines :

$ awk 'gsub(/#/, "#") == 1' file

$ awk '/#/ && ! /#.*#/' file 

$ sed -ne 's/#/&/2;t' -e '//p' file
  • Lines with atleast 2 # will not be printed due to the t command and -n option given to sed.
  • That leaves us with lines either with exactly one # or none. Print he former with //

With perl we can count the number of # chars in a scalar context to detect our lines :

$ perl -ne 'print if tr/#/#/ == 1'  file
-1

command

awk '{print $0,gsub("#",$1)}' filename |awk '$NF == "1"'

output

xxxxxxxxx#aaa 1
xxxxxxxxxxxxxxxx# 1
xxx#x 1

python

#!/usr/bin/python
import re
k=open('filename','r')
for i in k:
    if i.count("#") == 1:
        print i.strip()

output

xxxxxxxxx#aaa
xxxxxxxxxxxxxxxx#
xxx#x
3

You must log in to answer this question.

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