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?

2
  • 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. Commented Nov 12, 2014 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
    Commented Nov 12, 2014 at 6:15

8 Answers 8

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.

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.

10
  • 1
    egrep is deprecated use grep -E (source: man grep)
    – Anthon
    Commented Nov 11, 2014 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
    Commented Nov 11, 2014 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. Commented Nov 11, 2014 at 21:49
  • 1
    You're on the right track, but there are still false positives: /opt/gnome/somethingelse. Commented Nov 12, 2014 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. Commented Nov 12, 2014 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$/'
0
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.

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
9
  • This works perfectly because colons are non-word characters. Thanks!
    – JamesL
    Commented Nov 11, 2014 at 21:44
  • @Sman865 There is other reason: because / is not a part of word but r is.
    – Costas
    Commented Nov 11, 2014 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. Commented Nov 11, 2014 at 22:05
  • 4
    @Sman865 False positives: /opt/gnome-beta, /home/bob/opt/gnome, … Commented Nov 12, 2014 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 Commented Nov 12, 2014 at 1:30
0

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

grep '\B/opt/gnome'
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
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:"

You must log in to answer this question.

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