Folder comparison

I have two folders with similar subfolder structures, which I would like to compare. For example:

A 
├── child-1
├── child-2
├── child-3
├── child-4
├── child-5

and

B 
├── child-1-some-text
├── child-2-more-text
├── child-3-nothing
├── child-6-random-text
├── child-7-more-random-text

I would like to list all those subfolders from A which are prefix for a subfolder in B and list corresponding subfolders from B as well. The expected output is

child-1 -- child-1-some-text
child-2 -- child-2-more-text
child-3 -- child-3-nothing

A secondary requirement: If multiple matches in B, then it should give an error / warning.

My solution:

cd A
for f in `ls -d */`; 
do
    cd B;
    new_dirs=(`ls -1d $f*`);
    cd -;
    if [ ${#new_dirs[@]} -eq 0 ]
    then
        ## DO_Nothing
        continue;
    elif  [ ${#new_dirs[@]} -gt 1 ]
    then
        echo "Multiple matches to $f";
        continue;
    else
        echo "Unique Match found to $f -- ${new_dirs[0]}";
        continue;
    fi;    
done

Problem:

For those values of $f, which have no corresponding subfolders in B, the array construction is giving me an error. e.g.:

ls: cannot access 'child-4*': No such file or directory

Question

  • How to get rid of these errors?
  • Is there better way to achieve the goal(s) then the one in my code?

Thanks in advance!


Solution 1:

The better way

Don't parse ls; use globs instead. In fact you're already using globs, just wrapping them in ls, which is pointless. You just need nullglob turned on for when there are no matches.

Also avoiding cd simplifies things.

#!/bin/bash

shopt -s nullglob

dir1=A
dir2=B

for dir in "$dir1"/*/; do
    basename="$(basename -- "$dir")"
    dirs_match=( "$dir2/$basename"*/ )
    case ${#dirs_match[@]} in
    0)
        ;;
    1)
        echo "Unique match for $dir: ${dirs_match[*]}"
        ;;
    *)
        echo "Multiple matches for $dir: ${dirs_match[*]}" >&2
        ;;
    esac
done

Output:

Unique match for A/child-1/: B/child-1-some-text/
Unique match for A/child-2/: B/child-2-more-text/
Multiple matches for A/child-3/: B/child-3-nothing/ B/child-3-something/

I added B/child-3-something to test the secondary requirement. This creates the directory structure for testing:

mkdir -p A/child-{1..5} B/child-{1-some-text,2-more-text,3-nothing,3-something,6-random-text,7-more-random-text}

By the way, ShellCheck is very useful for finding problems in shell scripts.

Solution 2:

Calling ls on a non existent folder throws the error message that you encountered. The easy way is to just ignore this by replacing line 5 in your script with this: new_dirs=(`ls -1d $f* 2> /dev/null`);.