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

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


├── 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 */`; 
    cd B;
    new_dirs=(`ls -1d $f*`);
    cd -;
    if [ ${#new_dirs[@]} -eq 0 ]
        ## DO_Nothing
    elif  [ ${#new_dirs[@]} -gt 1 ]
        echo "Multiple matches to $f";
        echo "Unique Match found to $f -- ${new_dirs[0]}";


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


  • 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!

  • 4
    +1 for providing an almost working solution! – user5325 Mar 24 at 13:03
  • This is not an answer to your specific question, but you can use diff -rq DIR1 DIR2 to compare not just directory structure, but file contents. – John Wiersba Mar 28 at 18:32

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.


shopt -s nullglob


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


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.

  • ShellCheck.net is interesting, do you know if it uploads everything to it's own servers, or is it all done locally? Just wondering about the privacy of entered info. [Installing the shellcheck package would be the most secure] – Xen2050 Mar 24 at 16:28
  • @Xen2050 Just tried toggling my internet off while on the site, and it seems to upload. I would imagine it doesn't keep it, but not sure. And yes the package is good; I use an Atom plugin that uses it. – wjandrea Mar 24 at 17:20
  • Thanks for the suggestions. And also tons of thanks for pointing towards ShellCheck. I loved the part where it not only tells you your errors, but also gives suggestions! @Xen2050, about the uploading part, I just installed shellcheck using apt and then disabled network. It seems to be working without internet. – Mike V.D.C. Mar 25 at 4:53

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`);.

  • Have you tested this? Stderr seems to get ignored by default, when I run t=(`echo ok; echo err 1>&2`) $t (or ${t[@]}) only contains ok, err is seen in the terminal but not saved anyway. Or is there something funny about my test? – Xen2050 Mar 25 at 22:51

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.