9

I'll start by saying that I think this problem is a little less innocent than it sounds.

What I need to do: check for a folder within the PATH environment variable. It could be at the start or somewhere after. I just need to verify that that folder is there.

Example of my problem - let's use /opt/gnome.


SCENARIO 1: folder is not at the beginning of PATH

# echo "$PATH"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

# echo "$PATH" | grep ":/opt/gnome"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

Note that the grep needs to be specific enough so that it doesn't catch /var/opt/gnome. Hence the colon.


SCENARIO 2: folder is at beginning of PATH.

# echo "$PATH"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

# echo "$PATH" | grep "^/opt/gnome"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

This is my problem - I need to search for either a colon or a start-of-line with this folder. What I would like to do is one of these two bracket expressions:

# echo $PATH | grep "[^:]/opt/gnome"
# echo $PATH | grep "[:^]/opt/gnome"

BUT [^ and [: have their own meanings. Therefore, the two commands above do not work.

Is there a way I can grep for these two scenarios in one command?

  • Note that Gilles’s comment on Costas’s answer applies to the question, too: since you’re not grepping for /opt/gnome: or /opt/gnome$, you will find /opt/gnome-foo or /opt/gnome/bar. – Scott Nov 12 '14 at 2:06
  • @Scott - As long as you include in your match the intervening space, you can always anchor any string to the line's head and tail without such complications. Just like grep '^\(any number of other matches:*:\)*my match\(:.*\)*$' – mikeserv Nov 12 '14 at 6:15
10

If you're checking the content of the PATH environment variable, as opposed to looking for something in a file, then grep is the wrong tool. It's easier (and faster and arguably more readable) to do it in the shell.

In bash, ksh and zsh:

if [[ :$PATH: = *:/opt/gnome:* ]]; then
 : # already there
else
  PATH=$PATH:/opt/gnome
fi

Portably:

case :$PATH: in
  *:/opt/gnome:*) :;; # already there
  *) PATH=$PATH:/opt/gnome;;
esac

Note the use of :$PATH: rather than $PATH; this way, the component is always surrounded by colons in the search string even if it was at the beginning or end of $PATH.

If you're searching through a line of a file, then you can use the extended regexp (i.e. requiring grep -E) (^|:)/opt/gnome($|:) to match /opt/gnome but only if it's either at the beginning of a line or following a colon, and only if it's either at the end of the line or followed by a colon.

|improve this answer|||||
8

You can use extended regular expressions by just using grep -E

You have to match the beginning and the end of the path you are trying to find if you want to avoid false positives.

Matches the instance at the beginning:

$ TEST=/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

Also matches the instance at the middle:

$ TEST=/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

Avoiding false positives:

$ TEST="/home/bob/opt/gnome:/opt/gnome/somethingelse:/opt/gnome-beta"
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"

No matches there.

Compact and elegant. Tested on Debian 7.

|improve this answer|||||
  • 1
    egrep is deprecated use grep -E (source: man grep) – Anthon Nov 11 '14 at 21:15
  • Thanks, works like a charm! I didn't choose it as the answer though, because I think the -w option is a little bit more simple. Even more simple than I had originally imagined! – JamesL Nov 11 '14 at 21:43
  • 3
    Warning. The -w option has some problems. Only digits, letters and the underscore are considered "words". So some unusual but possible chars will make it fail. Example echo '/sbin:/usr/sbin:/var-/opt/gnome' | grep -w "/opt/gnome" and echo '/sbin:/usr/sbin:/var./opt/gnome' | grep -w "/opt/gnome". Those deliver wrong results. – Luis Antolín Cano Nov 11 '14 at 21:49
  • 1
    You're on the right track, but there are still false positives: /opt/gnome/somethingelse. – Gilles 'SO- stop being evil' Nov 12 '14 at 0:32
  • 1
    Totally right. We should care about the end explicitly, not only the beginning. I think that this fixes the problems echo "/home/bob/opt/gnome:/opt/gnome/somethingelse:/opt/gnome-beta" | grep -E "(:|^)/opt/gnome(:|$)". Editing answer. – Luis Antolín Cano Nov 12 '14 at 10:58
7

If you are not wed to grep, you can use awk and separate the records on :

awk 'BEGIN {RS=":"} /^\/opt\/gnome$/'
|improve this answer|||||
5

You could also use

echo "$PATH" | tr ':' '\n' | grep -x "/opt/gnome"

which splits the path-variable into separate lines (one per path), so grep -x can look for exact results. This has of course the disadvantage that it needs an additional process for tr. And it will not work when a folder name in PATH contains newline characters.

|improve this answer|||||
2

I don't know is it enough for answer but

grep -w "/opt/gnome"

will satisfy your need.

echo '/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome' | grep -w "/opt/gnome" -o
/opt/gnome
echo '/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome' | grep -w "/opt/gnome" -o
/opt/gnome

but

echo '/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome' | grep "/opt/gnome" -o
/opt/gnome
/opt/gnome
|improve this answer|||||
  • This works perfectly because colons are non-word characters. Thanks! – JamesL Nov 11 '14 at 21:44
  • @Sman865 There is other reason: because / is not a part of word but r is. – Costas Nov 11 '14 at 21:48
  • 2
    Warning. As I said at the comment on my answer. There are legal characters for a directory name that are non-word chars. That lead to wrong results. It is not usual to end a directory name in - but it could happen. – Luis Antolín Cano Nov 11 '14 at 22:05
  • 4
    @Sman865 False positives: /opt/gnome-beta, /home/bob/opt/gnome, … – Gilles 'SO- stop being evil' Nov 12 '14 at 0:25
  • Not working case: grep -w /usr/local -o <<< /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games ------ /usr/local /usr/local /usr/local – pabouk Nov 12 '14 at 1:30
0

To select /opt/gnome surrounded by non-word characters (new lines, :, /, etc) try this one:

grep '\B/opt/gnome'
|improve this answer|||||
0

You can do this reliably and with little effort in grep. You can take advantage of extensions which are widely available and from among which many solutions have already been offered, but even with basic regex it is easily done, though it may not be intuitively so at first glace.

With basic regex - and so with grep - you always have two reliable anchors - the head and tail of the line. You can anchor a match to both of these regardless of its location on the line like:

grep '^\(ignore case, delimiter\)*match\(delimiter, ignore case\)*$'

grep will match from the head of the line as many occurrences of the \(grouped\) subexpressions as it must to encounter next your delimiter then your explicit match, and from the tail of your match to the tail of the line in the same way. If your explicit match is not matched explicitly it will fail and print nothing.

And so you might do, for example:

grep '^\(.*:\)*/opt/gnome\(:.*\)*$'

See for yourself:

grep '^\(.*:\)*/opt/gnome\(:.*\)*$
' <<\INPUT
/opt/gnome-beta
/opt/gnome
/home/bob/opt/gnome
:/opt/gnome:
/home/bob/opt/gnome:/opt/gnome:/opt/gnome-beta
/opt-gnome-beta
/opt/gnomenot::::/opt/gnome
INPUT

OUTPUT

/opt/gnome
:/opt/gnome:
/home/bob/opt/gnome:/opt/gnome:/opt/gnome-beta
/opt/gnomenot::::/opt/gnome
|improve this answer|||||
0

you have noticed an edge case... you could avoid it by forcing the apparition of a : at the beginning of the line :

 echo ":$PATH" | grep ":/opt/gnome"

or if the path is exact add also one at the end to ensure it is bounded :

 echo ":${PATH}:" | grep ":/opt/gnome:"
|improve this answer|||||

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.