E.g. check if $PWD
is a subdirectory of /home. In other words I'm searching for a bash string operation to check if one string starts with another.
9 Answers
To test if a string is a prefix of another, in any Bourne-style shell:
case $PWD/ in
/home/*) echo "home sweet home";;
*) echo "away from home";;
esac
The same principle works for a suffix or substring test. Note that in case
constructs, unlike in file names, *
matches any character, including a /
or an initial .
.
In shells that implement the [[ … ]]
syntax (i.e. bash, ksh and zsh), it can be used to match a string against a pattern. (Note that the [
command can only test strings for equality.)
if [[ $PWD/ = /home/* ]]; then …
If you're specifically testing whether the current directory is underneath /home
, a simple substring test is not enough, because of symbolic links.
If /home
is a filesystem of its own, test whether the current directory (.
) is on that filesystem.
if [ "$(df -P . | awk 'NR==2 {print $6}')" = "/home" ]; then
echo 'The current directory is on the /home filesystem'
fi
If you have the NetBSD, OpenBSD or GNU or busybox or toybox readlink
(the last 3 commonly found on Linux-based systems such as Debian, OpenWrt or Android), you can use readlink -f
to strip symbolic links from a path.
case $(readlink -f .)/ in "$(readlink -f /home)"/*) …
Some systems also have a realpath
command for that (also in the GNU tool chest where it's preferred over readlink
these days):
case $(realpath .)/ in "$(realpath /home)"/*) …
The zsh
shell also has its own realpath()
operator with the :P
modifier:
if () [[ $1:P/ = $2:P/* ]] . /home; then …
Otherwise, you can use pwd
to show the current directory. But you must take care not to use a shell built-in if your shell tracks cd
commands and keeps the name you used to reach the directory rather than its “actual” location.
case $(pwd -P 2>/dev/null || env PWD= pwd)/ in
"$(cd /home && { pwd -P 2>/dev/null || env PWD= pwd; })"/*) …
If you want to reliably test whether a directory is a subdirectory of another, you'll need more than just a string prefix check. Gilles' answer describes in detail how to do this test properly.
But if you do want a simple string prefix check (maybe you've already normalized your paths?), this is a good one:
test "${PWD##/home/}" != "${PWD}"
If $PWD
starts with "/home/", it gets stripped off in the left side, which means it won't match the right side, so "!=" returns true.
-
3+1 perfect, and for a more general case:
[ "${PWD##$VAR}" != "$PWD" ]
And if this were a [code-golf ](stackoverflow.com/questions/tagged/code-golf)question you'd have beaten me by two chars ;-) Also it looks more readable... Jan 26, 2011 at 11:16 -
-
1
-
1@AlexisPeters: You're right: I was focusing on the "string prefix" part of the question, and I have edited to make that more clear. Gilles has nicely covered the proper way to check for subdirectories.– JanderMar 22, 2016 at 20:34
-
1Use
"${PWD%%/subdir*}"
to detect whether the user is currently insubdir
or in a subdirectory ofsubdir
, since %% captures from the end of the string instead of the beginning. This should be useful to anyone looking for more info on parameter substitution: tldp.org/LDP/abs/html/parameter-substitution.html Jan 23, 2018 at 19:21
Crude version:
[ "${PWD:0:6}" = /home/ ]
Has the disadvantage that one has to count characters first and one can't replace /home/
by something general like $1
.
edit (thanks @Michael) for the generalization to compare with $VAR
one can use
[ "${PWD:0:${#VAR}}" = "$VAR" ]
-
4
${#var}
is the length of$var
, so you can generalize it as[ "${PWD:0:${#1}}" = "$1" ]
Jan 25, 2011 at 15:58 -
-
You can use gnu realpath
with the --relative-to or --relative-base parameters, in combination with a string check
$ mkdir -p /tmp/a/b/c/d /tmp/a/e/f
$ isSubPath() if [[ $(realpath --relative-base="$1" -- "$2") =~ ^/ ]]
then printf '%s\n' "$2 NOT subpath of $1"
else printf '%s\n' "$2 subpath of $1"
fi
$ isSubPath /tmp/a/ /tmp/a/e/
/tmp/a/e/ subpath of /tmp/a/
$ isSubPath /tmp/a/ /tmp/a/e/f/
/tmp/a/e/f/ subpath of /tmp/a/
$ isSubPath /tmp/a/b/ /tmp/a/e/f/
/tmp/a/e/f/ NOT subpath of /tmp/a/b/
$ cd /tmp/a/b/c/d/
$ isSubPath . /tmp/a/
/tmp/a/ NOT subpath of .
$ isSubPath /tmp/a/ .
. subpath of /tmp/a/
$ isSubPath /tmp/a/ ../../../
../../../ subpath of /tmp/a
$ isSubPath /tmp/a/ ../../../../../../
../../../../../../ NOT subpath of /tmp/a/
$ isSubPath /tmp/a/ ../../../../../../tmp/a/e/
../../../../../../tmp/a/e/ subpath of /tmp/a/
I don't understand the question too well, but to find the parent of $PWD, do dirname -- "$PWD"
. To find the parent of the parent, run dirname -- "$(dirname -- "$PWD")"
, and so on...
-
That would only work if I knew the depth, otherwise I end up with
/
. Ok, I could check recursively, but isn't there something easier? Jan 25, 2011 at 15:41
Hm, it's pity that [
doesn't have an option of testing STRING1 starts with STRING2
condition.
You may try echo $PWD | grep '^$VAR'
, but it can fail in interesting ways when VAR
contains special symbols.
awk
's index
function should be able to do the trick. But all this seems just too heavy for such an easy thing to test.
If the searched part of path is found I "empty" the variable :
[[ -z "${PWD//*\/home\/*/}" ]] && echo "toto"
Combining some of the answers above:
$isSubDir () { local sd=$(realpath $1); local d=$(realpath $2); [ "${sd:0:${#d}}" = "$d" ]; echo $?; };
$isSubDir .. .
1
$isSubDir . $HOME
0
Using awk
:
echo $PWD | awk -v h="/home" '$0 ~ h {print "MATCH"}'
-
-
3That's just plain wrong.
echo
can't be used for arbitrary data, parameter expansions must be quoted,~
is for ERE matching not to check whether a string starts with another.awk
works on one line at a time an a path can be made of several,-v
mangles backslashes. Feb 3, 2022 at 20:56