18

Imagine I have a path that doesn't exist:

$ ls /foo/bar/baz/hello/world
ls: cannot access /foo/bar/baz/hello/world: No such file or directory

But let's say /foo/bar does exist. Is there a quick way for me to determine that baz is the breaking point in the path?

I'm using Bash.

1
  • access(2) is not very granular, so the solution typically involves writing something to iterate and test each path element in turn...
    – thrig
    Commented Feb 3, 2016 at 23:53

5 Answers 5

15

One of my favourite utilties is namei, part of util-linux and hence generally present only on Linux:

$ namei /usr/share/foo/bar
f: /usr/share/foo/bar
 d /
 d usr
 d share
   foo - No such file or directory

But its output is not very parseable. So, if you just wish to point out something is missing, namei might be useful.

It's useful for troubleshooting general problems in accessing a path, since you can get it to state whether a component is a link or a mount point, as well as its permissions:

$ ln -sf /usr/foo/bar /tmp/
$ namei -lx /tmp/bar
f: /tmp/bar
Drwxr-xr-x root    root    /
Drwxrwxrwt root    root    tmp
lrwxrwxrwx muru    muru    bar -> /usr/foo/bar
Drwxr-xr-x root    root      /
drwxr-xr-x root    root      usr
                             foo - No such file or directory

The capital D indicates a mount point.

7
  • @StéphaneChazelas I hoped equivalents might be present on other systems. Nothing for the BSDs?
    – muru
    Commented Feb 4, 2016 at 5:47
  • namei works for me as long as a feed it a path that exists, but when I give it one that doesn't I get namei: failed to stat: /usr/share/foo/bar: No such file or directory.
    – kuzzooroo
    Commented Feb 6, 2016 at 0:36
  • @kuzzooroo which version of namei?
    – muru
    Commented Feb 6, 2016 at 3:07
  • my version of namei does not give a version in its help nor its man page and does not support a flag giving version info. I am able to determine that it comes from the package linux-utils 2.16-1ubuntu5 but can't figure out what that implies for the version of namei.
    – kuzzooroo
    Commented Feb 7, 2016 at 17:21
  • @kuzzooroo Since even Ubuntu 12.04 has 2.20.1, you must be on a very old version of Ubuntu indeed. 10.04?
    – muru
    Commented Feb 7, 2016 at 17:23
5

Given a canonical pathname, such as yours, this will work:

set -f --; IFS=/
for p in $pathname
do    [ -e "$*/$p" ] || break
      set -- "$@" "$p"
done; printf %s\\n "$*"

That prints through the last fully existing/accessible component of $pathname, and puts each of those separately into the arg array. The first nonexistent component is not printed, but it is saved in $p.

You might approach it oppositely:

until cd -- "$path" && cd -
do    case   $path  in
      (*[!/]/*)
              path="${path%/*}"
;;    (*)   ! break
      esac
done  2>/dev/null   && cd -

That will either return appropriately or will pare down $path as needed. It declines to attempt a change to /, but if successful will print both your current working directory and the directory to which it changes to stdout. Your current $PWD will be put in $OLDPWD as well.

10
  • Clearly: for p in $pathname will be subject to "word splitting" and "Pathname expansion".
    – user79743
    Commented Feb 4, 2016 at 5:53
  • 1
    @BinaryZebra - yes, it is split on $IFS. that's exactly how it works. it is not subject to pathname expansion, except that the variable here called $pathname is expanded to an array of path components as split on $IFS.
    – mikeserv
    Commented Feb 4, 2016 at 6:03
  • You are avoiding the "Pathname expansion" on var $pathname by using set -f" which is not returned to its default value.
    – user79743
    Commented Feb 4, 2016 at 17:45
  • @BinaryZebra - im not avoiding anything. im specifying the environment i need in order to do this robustly. thats all. no - its not returned to its default value and neither will it install itself on the asker's computer or make me breakfast.
    – mikeserv
    Commented Feb 4, 2016 at 17:49
  • 1
    @BinaryZebra - by the by, if, as any good programmer should, you often find yourself worrying about unnecessarily affecting/recovering your execution environment, you might take an interest in ns, which, if used here, would render this discussion moot.
    – mikeserv
    Commented Feb 4, 2016 at 18:13
1

Something like this (accounting for pathnames with embedded blanks):

#!/bin/sh
explain() {
    if [ -d "$1" ]
    then
        printf "\t%s: is a directory\n" "$1"
    elif [ -e "$1" ]
    then
        printf "\t%s: is not a directory\n" "$1"
    else
        printf "\t%s: does not exist\n" "$1"
    fi
}

for item in "$@"
do
    last=
    test="$item"
    printf "testing: '%s'\n" "$item"
    while [ ! -d "$test" ]
    do
        last="$test"
        test=$(dirname "$test")
        [ -z "$test" ] && break
    done
    if [ -n "$last" ]
    then
        explain "$test"
        explain "$last"
    else
        printf "\t%s: ok\n" "$item"
    fi
done
2
  • It assumes the arguments are meant to be directories (or symlinks to directories). Commented Feb 4, 2016 at 5:52
  • 1
    if you just cd not_a_directory your shell will just write to stderr something like cd:cd:6: no such file or directory: not_a_directory. Actually, the user's shell will do so in a format with which the user is probably already very familiar. Its almost always both easier and better in the end anyway to just do stuff and let the shell handle the reporting as necessary. That kind of philosophy though does require a very strict attention to return values and the promotion/preservation of same.
    – mikeserv
    Commented Feb 5, 2016 at 2:38
1

Just an alternative solution for bash, assuming the path is an absolute path (starts with /) :

#!/bin/bash

pathname="$1"

IFS='/' read -r -a p <<<"${pathname#/}"

pa=""    max="${#p[@]}"    i=0
while (( i<"$max" )); do
      pa="$pa/${p[i++]}"
      if     [[ ! -e $pa ]]; then
             printf 'failed at: \t"%s"\t"%s"\n' "${pa##*/}" "${pa}"
             break
      fi
done

$ ./script "/foo/ba r/baz/hello/world"
failed at:      "hello"        "/foo/ba r/baz/hello"
2
  • This worked for me. I needed the last valid path in the path string, so I saved pa as pa_prev as the first line of the while loop (before it gets incremented). When the test for "pa" then fails, pa_prev has the last existing directory in the given path.
    – Ville
    Commented Feb 19, 2017 at 22:16
  • This approach will fail if one of the existing folders in our path was "chmod 000 dir". But if you use "sudo namei" then it will correctly indicate that folder and will continue digging deeper. The error of this approach is an assumption that "test -e" will be able to look inside "not allowed to look" folders. This can be fixed if instead of "[[ ! -e $pa ]]" one would first: "sudo test -e ${pa}" and then check "if [ $? -ne 0 ];", then it will look deeper. To be fair: if you run "namei" w/o "sudo", then it will give you "permissin denied" on "chmod 000 dir", which is more clear/expected output. Commented Nov 4, 2022 at 21:24
0
(( dirct=$(echo ${dir}|tr "/" " "|wc -w)+1 ))
i=2
while [ ${i} -le ${dirct} ]
do
  sdir=$(echo ${dir}|cut -d/ -f1,${i})
  if [ ! -d ${sdir} ]
  then
    echo "Path is broken at ${sdir}"
  fi
  (( i++ ))
done

it is not simple but you can put it in a script, make it executable and stick it somewhere in your path, if you are going to use it frequently.

Caveat emptor: If your directory name at any level contains a space character, this will NOT work.

1
  • Is this bash code? I had to swap in ${dir} was coming up blank so I swapped in $1, but now sdir is coming up blank for me.
    – kuzzooroo
    Commented Feb 4, 2016 at 0:24

You must log in to answer this question.

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