47

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 9

60

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; })"/*) …
0
39

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.

5
  • 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
  • FWIW, this may be also useful: ${PWD/#$HOME/\~} Nov 1, 2015 at 8:24
  • 1
    Alright, but how about PWD="/home/../etc/" Mar 22, 2016 at 14:21
  • 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.
    – Jander
    Mar 22, 2016 at 20:34
  • 1
    Use "${PWD%%/subdir*}" to detect whether the user is currently in subdir or in a subdirectory of subdir, 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
11

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" ]
3
  • 4
    ${#var} is the length of $var, so you can generalize it as [ "${PWD:0:${#1}}" = "$1" ] Jan 25, 2011 at 15:58
  • Heads up: see here if you're getting the Bad substitution error Dec 12, 2019 at 13:31
  • Also, if you need this in sh, not bash, see here Dec 12, 2019 at 13:45
6

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/
2

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...

1
  • 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
0

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.

1
  • [[ does regexp so yo get startswith [[ $PWD =~ ^\/home\/ ]]
    – teknopaul
    Dec 2, 2015 at 23:32
0

If the searched part of path is found I "empty" the variable :

[[ -z "${PWD//*\/home\/*/}" ]] && echo "toto"
0

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
-2

Using awk:

echo $PWD | awk -v h="/home" '$0 ~ h {print "MATCH"}'
2
  • +1 for working, but a bit slower than a native bash test Jan 26, 2011 at 11:13
  • 3
    That'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

You must log in to answer this question.

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