Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

share|improve this question
    
access(2) is not very granular, so the solution typically involves writing something to iterate and test each path element in turn... – thrig Feb 3 at 23:53
up vote 5 down vote accepted

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.

share|improve this answer
    
Clearly: for p in $pathname will be subject to "word splitting" and "Pathname expansion". – BinaryZebra Feb 4 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 Feb 4 at 6:03
    
You are avoiding the "Pathname expansion" on var $pathname by using set -f" which is not returned to its default value. – BinaryZebra Feb 4 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 Feb 4 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 Feb 4 at 18:13

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.

share|improve this answer
    
@StéphaneChazelas I hoped equivalents might be present on other systems. Nothing for the BSDs? – muru Feb 4 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 Feb 6 at 0:36
    
@kuzzooroo which version of namei? – muru Feb 6 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 Feb 7 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 Feb 7 at 17:23

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
share|improve this answer
    
It assumes the arguments are meant to be directories (or symlinks to directories). – Stéphane Chazelas Feb 4 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 Feb 5 at 2:38
(( 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.

share|improve this answer
    
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 Feb 4 at 0:24

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"
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.