1

Say I have the path gui/site/junior/profile.py

How do I get this?:

gui
gui/site
gui/site/junior

Bonus if you tell me how to loop through each path :D

Pithikos
  • 18,827
  • 15
  • 113
  • 136

4 Answers4

2

You can loop with awk:

awk 'BEGIN{FS=OFS="/"}
     {  for (i=1; i<=NF; i++) {
           for (j=1; j<i; j++)
              printf "%s/", $j
           printf "%s\n", $i
        }
     }' <<< "gui/site/junior/profile.py"

See as one liner:

$ awk 'BEGIN{FS=OFS="/"}{for (i=1; i<=NF; i++) { for (j=1; j<i; j++) printf "%s%s", $j, OFS; printf "%s\n", $i}}' <<< "gui/site/junior/profile.py"
gui
gui/site
gui/site/junior
gui/site/junior/profile.py

This takes advantage of NF, which counts how many fields the current record has. Based on that, it loops from the first up to the last one, printing every time first up to that value.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
2

You can use the shell's built-in splitting facilities. IFS specifies what to split on.

oldIFS=$IFS
IFS=/
set -f
set -- $path
set +f
IFS=$oldIFS
for component in "$@"; do
    echo "$component"
done

This could be refactored in many ways, but I want the change to IFS to only govern the actual splitting.

The use of set to split a string into positional parameters is slightly obscure, but well worth knowing.

You should properly take care to unset IFS if it was originally unset, but I'm skimping on that.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • Very interesting approach. I like the way you make use of IFS and `set` to accomplish this. Also it allows you to work with each one of the components. In case the OP wants to do something with each block, this is more useful than my answer. – fedorqui Aug 22 '14 at 13:44
  • 1
    You should add `set -f` to disable pathname expansion of the unquoted `$path`, and reset it when you're done. – user2719058 Aug 23 '14 at 23:24
  • @user2719058 Thanks, incorporated – tripleee Aug 24 '14 at 08:29
2

a dash answer:

path="gui/site with spaces/junior/profile.py"
oldIFS=$IFS
IFS=/
set -- $(dirname "$path")
IFS=$oldIFS
accumulated=""
for dir in "$@"; do 
  accumulated="${accumulated}${dir}/"
  echo "$accumulated"
done
gui/
gui/site with spaces/
gui/site with spaces/junior/
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Hmmm, this is suspiciously similar to mine? – tripleee Aug 22 '14 at 13:47
  • And both buggy with respect to globbing characters in the pathname. – user2719058 Aug 23 '14 at 23:26
  • can always throw in a `set -f` to handle that – glenn jackman Aug 24 '14 at 00:29
  • Yes. Unfortunately, the inherent elegance of the `for ...` loop is destroyed by all that boilerplate modifying and resetting `IFS` and `set -f`. I have recently thought about ways to solve, in shell, the simple problem of iterating over tokens separated by some delimiter character, and coulnd't find a truely elegant method. – user2719058 Aug 25 '14 at 12:58
1

Perl variant:

perl -F/ -nlE 'say join("/",@F[0..$_])||"/"for(0..$#F-1)' <<< "gui/site with spaces/junior/profile.py"

produces

gui
gui/site with spaces
gui/site with spaces/junior

if you have NULL separates pathnames, you can add 0 to arguments:

perl -F/ -0nlE 'say join("/",@F[0..$_])||"/"for(0..$#F-1)'
          ^

e.g from

printf "/some/path/name/here/file.py\0" |  perl -F/ -nlE 'say join("/",@F[0..$_])||"/"for(0..$#F-1)'
#                                   ^^

produces

/
/some
/some/path
/some/path/name
/some/path/name/here

For iterating over the paths, you can use the next:

origpath="some/long/path/here/py.py"

do_something() {
        local path="$1"
        #add here what you need to do with the partial path
        echo "Do something here with the ==$path=="
}

while read -r part
do
        do_something "$part"
done < <(perl -F/ -nlE 'say join("/",@F[0..$_])||"/"for(0..$#F-1)' <<< "$origpath")

it produces:

Do something here with the ==some==
Do something here with the ==some/long==
Do something here with the ==some/long/path==
Do something here with the ==some/long/path/here==
clt60
  • 62,119
  • 17
  • 107
  • 194