29

I want to search with grep for a string that looks like this:

something ~* 'bla' 

I tried this, but the shell removes the single quotes. Argh...

grep -i '"something ~* '[:alnum:]'"' /var/log/syslog 

What would be the correct search?

4
  • Shell removes single quotes inside double quotes? first time I see it! :) Commented Aug 31, 2011 at 8:42
  • He has single quotes inside double quotes inside single quotes: the regex begins with '" not just " Commented Aug 31, 2011 at 8:43
  • @Matteo, yes, hard to see at first sight :) Commented Aug 31, 2011 at 8:46
  • The canonical is probably How to escape single quotes within single quoted strings (2009. 26 answers. 1380 votes). See this answer for a relatively simple syntax. Commented Aug 16, 2023 at 18:34

4 Answers 4

44

If you do need to look for quotes in quotes in quotes, there are ugly constructs that will do it.

echo 'And I said, "he said WHAT?"' 

works as expected, but for another level of nesting, the following doesn't work as expected:

echo 'She said, "And I said, \'he said WHAT?\'"' 

Instead, you need to escape the inner single quotes outside the single-quoted string:

echo 'She said, "And I said, '\''he said WHAT?'\''"' 

Or, if you prefer:

echo 'She said, "And I said, '"'"'he said WHAT?'"'"'"' 

It ain't pretty, but it works. :)

Of course, all this is moot if you put things in variables.

[ghoti@pc ~]$ i_said="he said WHAT?" [ghoti@pc ~]$ she_said="And I said, '$i_said'" [ghoti@pc ~]$ printf 'She said: "%s"\n' "$she_said" She said: "And I said, 'he said WHAT?'" [ghoti@pc ~]$ 

:-)

Sign up to request clarification or add additional context in comments.

11 Comments

Thanks for the brief overview, I just +1 to raise your score to 10k :)
@oldergod, \n is not support by many implementations of echo. It's supported by the built-in echo that bash uses, but it doesn't work in Bourne compatibility mode (i.e. when bash is run as /bin/sh) and it's not there in the echo built in to FreeBSD's tcsh. I recommend that you use printf if you want \n to work consistently. As for your question ... my advice is that you experiment ... or more sage advice would be: If the code is unclear, use different code.
@mklement0, re terminology, yes, I've updated my colloquialisms since I wrote that comment. :) Re escape sequences, I know you know how this works, so I think we must be miscommunicating. In POSIX mode, bash's builtin echo does not interpret escape sequences like \n as anything but the two characters \ and n. Try: bash --posix -c 'echo "one\ntwo"'. I note also that the /bin/echo from coreutils behaves as I would expect. dash, on the other hand, which IIRC is the default sh on Ubuntu, DOES interpret escape sequences when launched as sh. Could it be that you tested with dash?
@mklement0, Actually, the interpretation of those sequences is for XSI conformance, which is (mostly) inclusive of but not equivalent to POSIX.1. As the document you linked to states, "The following character sequences shall be recognized on XSI-conformant systems within any of the arguments:" I suppose it would be good to know whether "POSIX comatibility mode" refers to POSIX.1, POSIX:2001 (SUS), which I think would include XSI, or something else.
I appreciate your digging deeper, @ghoti; to complement what you found: specifically, XSI is an optional superset of what's required for POSIX conformance (still applies as of the current POSIX standard, POSIX.1-2008, 2013 edition). getconf _XOPEN_UNIX tells whether a given system supports XSI, getconf _XOPEN_VERSION reports the XSI version. Both OSX 10.11, Linux 3.x-kernel systems report support. Linux is not officially certified, but OSX is (UNIX 03), which explains why they went out of their way to tweak Bash.
|
14
grep -i "something ~\* '[[:alnum:]]*'" /var/log/syslog 

works for me.

  • escape the first * to match a literal * instead of making it the zero-or-more-matches character:
    ~* would match zero or more occurrences of ~ while
    ~\* matches the expression ~* after something
  • use double brackets around :alnum: (see example here)
  • use a * after [[:alnum::]] to match not only one character between your single quotes but several of them
  • the single quotes don't have to be escaped at all because they are contained in an expression that is limited by double quotes.

Comments

1
  • character classes are specified with [[:alnum:]] (two brackets)

  • [[:alnum:]] is matching only one character. To match zero or more characters [[:alnum:]]*

  • you can just use " " to quote the regex:

     grep -i "something ~\* '[[:alnum:]]*'" /var/log/syslog 

2 Comments

@Matteo - sure it does, if it's intended to be taken literally. ~* means "zero or more '~' characters".
@Graham, thank for the correction. I missed that he was looking for a literal '*'.
0

It seems, as per your expression, that you are using first ', then ". If you want to escape the single quotes, you can either use ' and escape them, or use double quotes. Also, as Matteo comments, character classes have double square brackets. Either:

grep -i "something \~\* '[[:alnum:]]+'" /var/log/syslog 

or

grep -i 'something ~* \'[[:alnum:]]+\'' /var/log/syslog 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.