Take the 2-minute tour ×
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.

Before I start writing a (probably easy) script, is there a command that does the equivalent of the following one:

[:~/software/scripts] % ls -ld / /usr /usr/bin /usr/bin/tee 
drwxr-xr-x 21 root root  4096 Mar 31 08:48 /
drwxr-xr-x 11 root root  4096 Jan 30 09:48 /usr
drwxr-xr-x  2 root root 73728 Apr 14 07:54 /usr/bin
-rwxr-xr-x  1 root root 26308 Jan 16  2013 /usr/bin/tee

without having to manually type all the partial paths? The idea would be to be able to say

ls --this-new-flag /usr/bin/tee  

or

command -l /usr/bin/tee 

and having the output above --- showing the detailed listing of all partial path leading to the final one. A shell expansion trick that can output / /usr /usr/bin /usr/bin/tee given /usr/bin/tee will do, too.

share|improve this question
1  
Could you clarify? Which paths do you want? I assume you would like to give the deepest path and have the script/command also ls the parents is that correct? –  terdon Apr 14 at 15:46
1  
find probably does this, take a look at the manpage –  RSFalcon7 Apr 14 at 15:50
    
also, bash string function might be useful –  RSFalcon7 Apr 14 at 15:52
    
You can use awk's split. E.g awk '{split ($0,array,"/"); printf ("%s\n%s\n%s\n", array[1],array[2],array[3])}' or similar. –  val0x00ff Apr 14 at 16:10

5 Answers 5

up vote 4 down vote accepted
ls -ld `echo 'path/to/file' | sed ':0 p;s!/[^/]*$!!;t0' | sort -u`

sed part:

  • :0 label 0;
  • p print;
  • s!p!r! replace pattern p with replacement r;
  • /[^/]*$ search for /, then any sequence of not-/ till the end of line;
  • replacement is empty, so just delete the match;
  • t0 if s!!! performs a replacement, then go to label 0.

Edit by OP after comments

I did the following (thanks to the comments, especially by Jander and Andrey): adding

explode() {echo "$1" | sed -n ':0 p;s![^/]\+/*$!!;t0' | sort -u}

to my .zshrc and then I can use

ls -ld $(explode /path/to/file) 

and having the desired output.

share|improve this answer
    
Nice. Could you explain the sed magic (the sort -u one is really a great idea!)? –  Rmano Apr 14 at 16:22
    
@Rmano I added sed explanation. You might want to play with it to handle particular cases, e.g. for paths starting with '/'. –  Andrey Apr 14 at 16:30
    
it seems it works for paths starting with / too... if the path ends with '/' I have a repeated listing, but well, GIGO... –  Rmano Apr 14 at 16:32
    
@Rmano It doesn't return the root itself. If you don't need it, then it's OK. –  Andrey Apr 14 at 16:34
2  
sed -n ':0 p;s![^/]\+/*$!!;t0' cleans up some corner cases: prints the root; removes multiple/trailing slashes; no more need for sort -u. –  Jander Apr 14 at 16:46

How about using brace expansions?

$ ls -ld /{,usr/{,bin/{,tee}}}
drwxr-xr-x 23 root root  4096 Mar  7 06:57 /
drwxr-xr-x 10 root root  4096 Jan  9  2013 /usr/
drwxr-xr-x  2 root root 40960 Apr  9 23:57 /usr/bin/
-rwxr-xr-x  1 root root 26176 Nov 19  2012 /usr/bin/tee
share|improve this answer
    
Not bad, but you still have to type the weird thing in --- with the shell function I can use tab expansion too. –  Rmano Apr 15 at 0:44

I can't think of any expansion trick or utility to do it all in one go. So a loop is the way to go. Here's some code that works under both bash and zsh, and accommodates directories with arbitrary names.

## Usage: set_directory_chain VAR FILENAME
## Set VAR to the chain of directories leading to FILENAME
## e.g. set_directory_chain a /usr/bin/env is equivalent to
## a=(/ /usr /usr/bin /usr/bin/env)
set_directory_chain () {
  local __set_directory_chain_a __set_directory_chain_path
  __set_directory_chain_a=()
  __set_directory_chain_path=$2
  while [[ __set_directory_chain_path = *//* ]]; do
    __set_directory_chain_path=${__set_directory_chain_path//\/\///}
  done
  if [[ $__set_directory_chain_path != /* ]]; then
    __set_directory_chain_path=$PWD/$__set_directory_chain_path
  fi
  while [[ -n $__set_directory_chain_path ]]; do
    __set_directory_chain_a=("$__set_directory_chain_path" "${__set_directory_chain_a[@]}")
    __set_directory_chain_path=${__set_directory_chain_path%/*}
  done
  eval "$1=(/ \"\${__set_directory_chain_a[@]}\")"
}

## Apply a command to all the directories in a chain
## e.g. apply_on_directory_chain /usr/bin/env ls -ld is equivalent to
## ls -ld / /usr /usr/bin /usr/bin/env
apply_on_directory_chain () {
  local __apply_on_directory_chain_a
   set_directory_chain __apply_on_directory_chain_a "$1"
   shift
   "$@" "${__apply_on_directory_chain_a[@]}"
}

lschain () {
  for x; do apply_on_directory_chain "$x" ls -ld; done
}

Note that if this treats the directory chain as a string. If there are .. components or symbolic links, then this may not be what you need. For example, if you want to check the permissions on the directories, you need to resolve the directory to an absolute path first. In zsh, you can use /path/to/foo(:A). On Linux, you can use readlink -f /path/to/foo.

share|improve this answer

Linux (and no acl support):

namei -l /foo/bar/baz
share|improve this answer

In zsh:

als() {
  until [[ $1 = [/.] ]] {argv[1,0]=$1:h;}; ls -ld -- "$@"
}

POSIXly:

als() (
  while :; do
    case $1 in
      [./]) exec ls -ld -- "$@"
    esac
    set -- "$(dirname -- "$1")" "$@"
  done
)
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.