1

I have the following directory structure that I need to create, and while doing so, I need to also create the zip files which are symlinks.

/a/2015-08-17/a.zip  
/a/2015-08-17/a_b/a_b.zip
/a/2015-08-17/a_b/a_b_c/a_b_c.zip
/a/2015-08-17/a_b/a_b_c/a_b_c_1/a_b_c_1.zip 
/a/2015-08-17/a_b/a_b_c/a_b_c_2/a_b_c_2.zip
/a/2015-08-17/a_b/a_b_c/a_b_c_3/a_b_c_3.zip

The archives 1stdepth.zip, 2nddepth.zip etc exist and are the same each time. I am given the directory structure like the one above and I need to create the following sym links according to the depth in the directory structure.

ln -s 1stdepth.zip /a/2015-08-17/a.zip  
ln -s 2nddepth.zip /a/2015-08-17/a_b/a_b.zip
ln -s 3rddepth.zip /a/2015-08-17/a_b/a_b_c/a_b_c.zip
ln -s 4thdepth.zip /a/2015-08-17/a_b/a_b_c/a_b_c_1/a_b_c_1.zip  
ln -s 4thdepth.zip /a/2015-08-17/a_b/a_b_c/a_b_c_2/a_b_c_2.zip
ln -s 4thdepth.zip /a/2015-08-17/a_b/a_b_c/a_b_c_3/a_b_c_3.zip

How would I go about doing this in a script? Should I count the / in the path and realise according to that what depth i am at? Is there a smarter way of doing it? I am using Solaris.

1

Here is how I solved my problem. It may not be the best way but I needed a quick solution. Maybe someone else will need it

#!/usr/bin/bash

function main()
{
[ $# -ne 1 ] && { echo "Usage: $0 file"; exit 1; }
depth1=/a.zip
depth2=/b.zip
depth2_special=/special.zip
depth3=/c.zip
depth4=/d.zip

cat $1 | while read line; do
#Count the number of occurences of / in each line 
x=`echo $line |awk -F/ '{c += NF - 1} END {print c}'`
if [ $x -eq 13 ] ;then echo "The path has 13 / " 
    ln -sf $depth4 $line 
else
    if [ $x -eq 12 ] ;then echo "The path has 12 /"
        ln -sf $depth3 $line 
    else
        if [ $x -eq 11 ] ; then echo "The path has 11 /"
            #If there is no _ in the file path treat it as a special case
            underscore=`echo $line |awk -F_ '{c += NF - 1} END {print c}'`
                if [ $underscore -eq 1 ] ; then
                    ln -sf $depth2 $line
                else
                    ln -sf $depth2_special $line 
                fi; 
        else
            if [ $x -eq 10 ] ;then echo "The path has 10 /" 
                ln -sf $depth1 $line 
            else
                echo "Error - the path is not correct" 
            fi;
        fi;
    fi; 
fi;
done

}

main "$@"
0

I guess the tree command in linux can help you in getting the depth of Directory and landing up your problem some how.

Ps: I have no idea about Solarix, but in Debian based we have tree command

  • Hello, Thank you for your answer but there is no tree command in my version of solaris. – Lara Oct 8 '15 at 15:01
0

Take a deep breath and solve the problems one at a time, striving for simple solutions.

First, everything we need to do happens under the directory /a/2015-08-17. So let's change to that directory.

cd /a/2015-08-17

Now we need to create a bunch of symbolic links based on existing directories. (I assume that the directories already exist since you don't explain how to determine what directories to create.) The basic command to do something on all subdirectories of the current directory is

find . -type d …

What we need to do is create a symbolic link based on the directory depth. The name of the symbolic link is the base name of the directory plus .zip at the end (except that it's a.zip at the top), and the target is calculated from the depth. Let's do the link name first.

We're going to need to do some calculations on each directory found by find, so we need to invoke a shell.

find . -type d -exec sh -c '???' {} \;

The path to the directory (starting with .) is available in the script as $0. If $0 is just ., we need to create a.zip.

find . -type d -exec sh -c '
    if [ "$0" = "." ]; then ln -s 1stdepth.zip a.zip; fi
' {} \;

Otherwise we need to extract the base name of the directory. That can be done with the shell parameter expansion construct ${0##*/} (value of $0 minus the longest prefix ending in a /):

find . -type d -exec sh -c '
    if [ "$0" = "." ]; then
      ln -s 1stdepth.zip a.zip
    else
      ln -s ???depth.zip "$0/${0##*/}.zip"
    fi
' {} \;

Now we need to calculate the depth. This is the number of slashes in $0, plus one since the root has depth 1. One way to count the number of slashes is to remove all other characters and count the length of what remains.

find . -type d -exec sh -c '
    depth=$(printf %s "$0" | tr -dc / | wc -c)
    if [ "$0" = "." ]; then
      ln -s 1stdepth.zip a.zip
    else
      ln -s $((depth+1))???depth.zip "$0/${0##*/}.zip"
    fi
' {} \;

Almost there; we need to calculate the suffix to get 1st, 2nd, etc.

find . -type d -exec sh -c '
    depth=$(($(printf %s "$0" | tr -dc / | wc -c) + 1))
    if [ "$0" = "." ]; then
      ln -s 1stdepth.zip a.zip
    else
      case $depth in
        *1?) suffix=th;;
        *1) suffix=st;;
        *2) suffix=nd;;
        *3) suffix=rd;;
        *) suffix=th;;
      esac
      ln -s "${depth}${suffix}depth.zip" "$0/${0##*/}.zip"
    fi
' {} \;
0

With zsh (optional package on Solaris), using some typically write-only syntax:

$ for i (*/**/*(/)) echo ln -s -- $((${#${(s:/:)i}}-1))??depth.zip $i/${${i%/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]}:t}.zip
ln -s -- 1stdepth.zip a/2015-08-17/a.zip
ln -s -- 2nddepth.zip a/2015-08-17/a_b/a_b.zip
ln -s -- 3rddepth.zip a/2015-08-17/a_b/a_b_c/a_b_c.zip
ln -s -- 4thdepth.zip a/2015-08-17/a_b/a_b_c/a_b_c_1/a_b_c_1.zip
ln -s -- 4thdepth.zip a/2015-08-17/a_b/a_b_c/a_b_c_2/a_b_c_2.zip
ln -s -- 4thdepth.zip a/2015-08-17/a_b/a_b_c/a_b_c_3/a_b_c_3.zip

Remove the "echo" to actually do it.

A little break down of the zsh-specific features in there:

  • for f (...) cmd: short form of the for i in ...; do ...; done. Only syntactic difference.
  • **/*: recursive glob (some other shells have copied that since)
  • (/): glob qualifier: here, select only the files of type directory. With other shells, you can do */ though that would also include symlinks to directories.
  • ${(s:/:)i}: variable expansion flags. Here, s to split on /.
  • ${#${...}}: zsh expansions can usually be nested. Here ${#array} returns the number of elements in the array, so for a/b/c, 3.
  • ${var:t}: like in csh, return the tail (the basename).

Now, that's what you asked for but probably not what you want as in effect, that creates broken symlinks. You'd probably want either

ln -s -- /path/to/1stdepth.zip a/2015-08-17/a.zip

Which is easily solved by adding $PWD:

for i (*/**/*(/)) echo ln -s -- $PWD/$((${#${(s:/:)i}}-1))??depth.zip $i/${${i%/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]}:t}.zip

Or probably better, a relative link:

ln -s ../../1stdepth.zip a/2015-08-17/a.zip

Which you could do with:

setopt extendedglob
for i (*/**/*(/)) {
  z=($((${#${(s:/:)i}}-1))??depth.zip)
  echo ln -s -- ${i//[^\/]##/..}/$z $i/${${i%/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]}:t}.zip
}

With ${i//[^\/]##/..} we replace sequences of non-slash characters with ...

(note that those -- are not needed on Solaris but would be on GNU systems if there may be directories whose name starts with -).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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