4

Say I have a long path such as:

/a/b/c/d/e/f

I would like to run a command only on each of the subpaths of the path:

e.g. If my command is cmd, I am looking for a one-liner that can do (perhaps using a one-line loop)

cmd /a
cmd /a/b
cmd /a/b/c
cmd /a/b/c/d
cmd /a/b/c/d/e
cmd /a/b/c/d/e/f

One of the reasons why I need this (I need it for other things as well), is because I would like to give somebody from my unix group the ability to cd incrementally her way to /a/b/c/d/e/f. To do this, I think I need to run chmod g+x on each of these subpaths (not sure if there is an alternative). Note that I want to avoid chmod -R g+x /a.

I work primarily on zsh so I wonder if there is anything that takes advantage of specific zsh features.

4 Answers4

4

Something like

path=/a/b/c/d/e/f
while test "$path" != "/"; do
    chmod g+x "$path"
    path=$(dirname "$path")
done
Mikel
  • 57,299
  • 15
  • 134
  • 153
  • Would by nice if dirname could be used recursively from the commandline, but I don't see that happen. +1 for cleanest script. – Bernhard Apr 20 '12 at 21:14
  • You could incorporate this idea into a function like Stéphane's if you so desire. – augurar Aug 14 '13 at 19:13
4

Quick to type and works in all shells:

(while [ "$PWD" != / ]; do cmd .; cd ..; done)

The parentheses keep the directory change local. Like your example, the root directory is omitted. If you want to stop if running cmd fails, add set -e; after the opening parenthesis.

3

Add this to your zsh config file:

function pathargs {
  local P=$1; local L=${#P}; shift
  local N=1; while [[ $N -le $L ]]; do
    [[ ($N == 1 && ${P[1]} = '/') || \
       ${P[$N]} != '/' && ($N == $L || ${P[$N+1]} == '/') ]] \
    && $@ ${P:0:$N}
    N=$(($N+1))
  done
}

Example:

% pathargs /abc/def/ghi/jkl echo
/
/abc
/abc/def
/abc/def/ghi
/abc/def/ghi/jkl

You can use it with chmod -R g+x instead of echo. The command will be called on the top-most directories first.

  • This looks great Stéphane. I'm not sure I understand the difference between using -n 1 and not using it. How are they exactly different? – Amelio Vazquez-Reina Apr 20 '12 at 20:35
  • I removed the feature, it was the same difference as xargs and xargs -n 1. The new (and cleaner) function will always do to the latter: Call one instance of the command on every argument. – Stéphane Gimenez Apr 20 '12 at 20:47
0

How about find start_directory -type d -exec my_cmd {} \;

  • start_directory is /a in your example
  • type d indicates only directories. type f for files. Leave this argument for both files and directories.
  • my_cmd is cmd in your example
  • {} is replaced with the directory name found
  • \; don't forget it, the command won't work without it.

Example1: This will print out all full path directory names under /home:

find /home -type d -exec echo {} \;

Example2: This is your use case:

find /a -type d -exec echo chmod g+x {} \;

It prints the commands, doesn't execute them. You can check if it is really what you want and if so, you can pipe it to bash (or zsh if you wish):

find /a -type d -exec echo chmod g+x {} \; | bash
jippie
  • 14,086
  • Thanks @jippie, but each of my subpaths contain many, many other folders on which I don't want to run my command. I would also like to avoid typing the name of the subpaths one by one (i.e. I am looking for something that can take /a/b/c/d/e/f/ directly) – Amelio Vazquez-Reina Apr 20 '12 at 20:11
  • So use the variation with bash, but instead of piping it to bash redirect it to a file. Remove the unwanted lines, then execute the file. find /a -type d -exec echo chmod g+x {} \; > /tmp/my_commands.sh followed by vi /tmp/my_commands or a smart grep ... . then . /tmp/my_commands to execute the lines. Just don't forget the echo or all commands will be executed right away. – jippie Apr 20 '12 at 20:19
  • This is exactly what roseck said he didn't want. – Gilles 'SO- stop being evil' Apr 20 '12 at 23:47