28

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?

animuson
  • 53,861
  • 28
  • 137
  • 147
JohnnyFromBF
  • 9,873
  • 10
  • 45
  • 59
  • Shell removes single quotes inside double quotes? first time I see it! :) – Diego Sevilla Aug 31 '11 at 08:42
  • He has single quotes inside double quotes inside single quotes: the regex begins with `'"` not just `"` – Matteo Aug 31 '11 at 08:43
  • @Matteo, yes, hard to see at first sight :) – Diego Sevilla Aug 31 '11 at 08:46
  • The canonical is probably *[How to escape single quotes within single quoted strings](https://stackoverflow.com/questions/1250079/)* (2009. 26 answers. 1380 votes). See [this answer](https://stackoverflow.com/questions/1250079/how-to-escape-single-quotes-within-single-quoted-strings/16605140#16605140) for a relatively simple syntax. – Peter Mortensen Aug 16 '23 at 18:34

4 Answers4

43

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 ~]$ 

:-)

ghoti
  • 45,319
  • 8
  • 65
  • 104
  • 1
    Thanks for the brief overview, I just +1 to raise your score to 10k :) – Dag Nov 28 '12 at 11:28
  • What about `\n` is such case ? `sudo sh -c 'echo '\''A\nB'\'''`. Does `\n` need to be escaped out of the single quotes ? – oldergod Jan 22 '14 at 03:11
  • 2
    @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.** – ghoti Jan 27 '14 at 05:57
  • @ghoti: Good advice re inconsistencies of `echo` and using `printf` instead, but you have the behavior of `bash` backward: when running in POSIX compatibility mode (not "Bourne compatibility mode"), `"\n"` and other escape sequences _are_ recognized by the `echo` builtin (so as to conform with POSIX), whereas by default `echo` performs _no_ backslash interpretation, unless you use the (`bash`-specific) `-e` option (conversely, `-E` turns backlash-sequence interpretation explicitly off). In POSIX compatibility mode the echo `builtin` supports no options at all, and thus _invariably_ interprets. – mklement0 Oct 21 '15 at 22:50
  • 1
    @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? – ghoti Oct 22 '15 at 13:01
  • @ghoti: Oops! Thanks for that. I forgot that the makers of _OS X_ must have felt so strongly about POSIX compliance that they created `/bin/sh` as _a separate Bash binary_ in which option `xpg_echo` is implicitly ON, causing the interpretation of sequences such as `\n`. While using a mere _symlink_ named `sh` to `/bin/bash` puts Bash in POSIX compatibility mode, `xpg_echo` remains off. In other words: _what I described only applies to OS X_; on other platforms you'd have to run `shopt -s xpg_echo` to get that behavior. The BSD `/bin/echo` implementation does _not_ interpret escape sequences. – mklement0 Oct 22 '15 at 13:18
  • @ghoti: Yes, `dash` is being a good citizen here by interpreting `\n`, ..., as [POSIX mandates](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html). It is curious that `bash` doesn't also behave that way by default when running in POSIX compatibility mode. You have to admire OS X's pluckiness in trying to fix that, but ultimately I think it was misguided. – mklement0 Oct 22 '15 at 13:23
  • 1
    @mklement0, Actually, the interpretation of those sequences is for [XSI conformance](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap02.html), 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. – ghoti Oct 22 '15 at 15:18
  • 1
    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](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html)). `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. – mklement0 Oct 22 '15 at 18:27
  • 1
    @ghoti: Final thoughts re `bash` behavior: You can get the `echo` behavior of an _XSI-conformant_ POSIX system by _manually_ turning on `shopt -s xpg_echo` (irrespective of POSIX compatibility mode; this is what OSX bakes into `/bin/sh`). By contrast, you cannot get the `echo` behavior of a _non-XSI_ POSIX system at all, because Bash supports `-e` and `-E` even in POSIX compatibility mode. As an aside: on an XSI-conformant system it is apparently OK for the _utility_ form of `echo` _not_ to support XSI features, as long as the _shell's builtin_ does - both OSX and Linux exhibit this. – mklement0 Oct 22 '15 at 19:26
  • 1
    Very interesting. For reference, FreeBSD's `/bin/sh`, which is based on the Almquist shell, *does* include `-e` option, and *does not* by default interpret any of the XSI sequences. The documentation states "*The current version of sh is close to the IEEE Std 1003.1 (POSIX.1) specification for the shell. It only supports features designated by POSIX, plus a few Berkeley extensions.*" It's amazing that we all get along as well as we do. :-) – ghoti Oct 22 '15 at 20:58
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.
eckes
  • 64,417
  • 29
  • 168
  • 201
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
    

Matteo

Matteo
  • 14,696
  • 9
  • 68
  • 106
  • @Matteo - sure it does, if it's intended to be taken literally. `~*` means "*zero or more '~' characters*". – Graham Mar 15 '12 at 04:26
  • @Graham, thank for the correction. I missed that he was looking for a literal '*'. – Matteo Mar 15 '12 at 06:04
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
Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87