Associative array index question

I am trying to assign indexes to an associative array in a for loop but I have to use an eval command to make it work, this doesn't seem correct I don't have to do this with regular arrays

For example, the following assignment fails without the eval command:

#! /bin/bash

   read -d "\0" -a DN_ARRAY < <(ls -rlt /localbackup/ldap/ | awk '{ print $9 }' )
   declare -A DN_COUNT

   for NAME in "${DN_ARRAY[@]}" ; do
        eval DN_COUNT["$NAME"]=$(grep -E "dn: " /localbackup/ldap/${NAME} | wc -l)
   done

Why is that?

I just encountered this behavior yesterday. ls on Linux now injects single quotes around filenames with spaces in them, for your convenience! Ugh! Eval or xargs would swallow those, anything else would take them literally.

Fortunately, you don't need ls here and never did... You can just do for FILE in /localbackup/ldap/* to cut out the middleman. This is completely safe even for file names with spaces and special characters.

Also, you don't need wc's help to count matching lines with grep.

for FILE in /localbackup/ldap/*
do
        NAME=$(basename "$FILE")
        DN_COUNT["$NAME"]=$(grep -c "dn: " "$FILE")
done
1 Like

I'm afraid I don't understand the problem, except that your code is utmost cumbersome. Why do you use the read -a at all? Why the complicated "command substitution"? The -rlt options to ls ?
Replicating above (but not using eval ) with some of my data yields

declare -A DN_COUNT
for NAME in fi* ; do DN_COUNT["$NAME"]=$(grep -Ec "ron|ime|ava" $NAME ); done
echo ${!DN_COUNT[@]} ${DN_COUNT[@]}
file3~ file2~ file file1~ file4 file2 file3 file1 file4~ 0 0 0 0 9 0 2 0 9
1 Like

Actually, how about this?

grep -Hc "dn: " /localbackup/ldap/*

Will output

file1:count
file2:count
fle3:count
...

All in one efficient command.

2 Likes

Guys this forum never ceases to amaze me. Great suggestions all around. @RudiC, I used that method to hopefully extract just the file names based on the age. ( Looking at my code now and it indeed looks slightly retarded. I thought it was brilliant 6 hours ago....)

Corona688 I will actually be using your suggestion. I just started working as a Linux admin two months ago, and I'm using Bash scripting now on a daily basis to automate things.

This was a snippet from an ldap recovery script. Anyways I will get better. Thanks. Marking as solved. But one thing still bothers me, I thought I understood the order of evaluation and it still doesn't make sense about the eval part. I will think about what you wrote Corona some more.

Thanks again!

It's not about order of evaluation, it's about your filenames being mangled from passing them through a pointless pipe chain then cramming them into read -d "\0". The -d "\0" is especially pointless in this context, I suspect it was grabbed from some other script without understanding it.

I'm no longer certain of my original guess about how exactly they were mangled, but they must have been, in a way which eval would swallow. Nulls injected between records from -d "\0" maybe.

My question: How did it occur to you to use eval?

1 Like

Eval occured to me because when I trace the output ( bash -x ) I get results like this: DN_COUNT[$NAME]=98 or DN_COUNT[$NAME]=1007 and so on. So of course eval then gives me the desired result. I'm not saying your wrong, but I just don't see how the mangling is occuring.

Also I did write the whole thing from scratch, which try not to laugh, I will post here. I didn't copy / paste anything. I tend to use the named pipes to feed with the null delimiter because I know C strings are terminated that way which allows for crazy characters (?Maybe I'm wrong there too!) and the named pipe always avoids subshells which tend to trip me up. It works fine now, but I'm sure you guys could reduce this to like 25% of its size! Not expecting you too to that, just complimenting. I just got done reading "classical shell scripting" and "pro bash programming", but I have no practical experience. Anyways here it is. I will be re-writing it with your guys suggestions. This is my VERY FIRST production script, so again, mea culpa. I will get better at this...

#! /bin/bash

    systemctl stop crond


    make_dir () {
        for i in "/var/lib/ldap" "/var/lib/ldap/accesslog" "/var/tmp/bdb-log" "/etc/openldap/slapd.d" ; do
            if [ -d "$i" ] ; then
                :
            else
                mkdir "$i"
            fi
        done

        if [ $? -eq 0 ] ; then
            printf "%b\n" "Checking directories:                         [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "Missing directories, unable to create them:   [ \e[91mFAILED\e[0m ]"
        fi
        cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
    }


    set_permissions () {
        for i in "/var/lib/ldap" "/var/tmp/bdb-log" "/etc/openldap/slapd.d" ; do
            chown -R ldap:ldap "$i"
        done

        if [ $? -eq 0 ] ; then
            printf "%b\n" "Setting permissions:                          [ \e[92mOK\e[0m ]"
            return 0
        else
            printf "%b\n" "Unable to set permissions:                    [ \e[91mFAILED\e[0m ]"
            return 1
        fi
    }


    stop_ldap () {
        systemctl stop slapd
        pid_number=$(pgrep slapd)
        if [ $? -eq 0 ] ; then
            kill -9 $pid_number
        else
            :
        fi
    }


    start_ldap () {
        systemctl start slapd
        if [ $? -eq 0 ] && pgrep slapd ; then
            printf "%b\n" "slapd started:                                [ \e[92mOK\e[0m ]"
            return 0
        else
            printf "%b\n" "Unable to start slapd:                        [ \e[91mFAILED\e[0m ]"
            return 1
        fi
    }


    newest_ldif () {
       local_ldif_count="$(find /localbackup/ldap -name "*.ldif" | wc -l)"
       remote_ldif_count="$(find /backup/ldap -name "*.ldif" | wc -l)"
       if [ -d "/localbackup/ldap" ] && [ "$local_ldif_count" -ge 2 ] ; then
           readarray ldif_array < <(ls -lrt /localbackup/ldap/ )
           total=${#ldif_array[@]}
           NEWEST_LDIF="$(printf "%s" "${ldif_array[total-1]}" | awk '{ print $9 }' )"
           printf "%b%b%b\n" "Found local ldif: " "${NEWEST_LDIF}"  "       [ \e[92mOK\e[0m ]"
           PATH_TO_FILE="/localbackup/ldap/"
           LOCAL_NUMBER_OF_DN=$(grep -E "dn: " /localbackup/ldap/${NEWEST_LDIF} | wc -l )
           printf "%s\n" "Selected LDIF has ${LOCAL_NUMBER_OF_DN} DNs"
       elif [ -d "/backup/ldap" ] && [ "$remote_ldif_count" -ge 1 ] ; then
           printf "%b\n" "Local ldif not found checking NFS server:     [ \e[93mWARNING\e[0m ]"
           readarray ldif_array < <(ls -lrt /backup/ldap/ )
           total=${#ldif_array[@]}
           NEWEST_LDIF="$(printf "%s" "${ldif_array[total-1]}" | awk '{ print $9 }' )"
           printf "%b%b%b\n" "Found remote ldif: " "${NEWEST_LDIF}"  "          [ \e[92mOK\e[0m ]"
           PATH_TO_FILE="/backup/ldap/"
           REMOTE_NUMBER_OF_DN=$(grep -E "dn: " /backup/ldap/${NEWEST_LDIF} | wc -l )
           printf "%s\n" "Selected LDIF has ${REMOTE_NUMBER_OF_DN} DNs"
       else
          printf "%b\n" "Can not find an ldif, exiting...                [ \e[91mFAILED\e[0m ]"
          sleep 5s
          exit 1
       fi
    }


    dn_number () {
       LDIF_LOCAL_NUMBER=$(find /localbackup/ldap -name "*.ldif" 2> /dev/null | wc -l)
       LDIF_REMOTE_NUMBER=$(find /backup/ldap -name "*.ldif" 2> /dev/null | wc -l )

       read -d "\0" -a DN_ARRAY < <(ls -rlt /localbackup/ldap/ | awk '{ print $9 }' )

       if [ -d /localbackup/ldap ] && [ $LDIF_LOCAL_NUMBER -ge 1 ]
       then
           DN_PATH="/localbackup/ldap/"
       elif [ -d /backup/ldap ] && [ $LDIF_REMOTE_NUMBER -ge 1 ]
       then
           DN_PATH="/backup/ldap/"
       else
           printf "%s\n" "Can't find an LDIF file, have to exit...."
           exit 1
       fi

       printf "%s\n" "The following LDIFs have the highest DN counts:"


       for k in "${DN_ARRAY[@]}"
       do
           NUMBER_OF_DN=$(grep -E "dn: " ${DN_PATH}${k} | wc -l)
           printf "%s%s\n" "${k}:  " "$NUMBER_OF_DN"
       done |
       sort -rn -k 2 | head -n 5
       sleep 10s
    }

    case $1 in
    repair)    repair=true
               ;;
    testdb)    testdb=true
               ;;
    restore)   restore=true
               ;;
    rebuild)   rebuild=true
               ;;
    *)         printf "%s\n\n" "Usage ldap.sh <repair|restore|rebuild|test>"
               printf "%s\n"   "repair     ---> try to repair existing db via db_recover"
               printf "%s\n"   "restore    ---> try to restore from latest backup via slapadd"
               printf "%s\n"   "rebuild    ---> rebuild everything and restore latest backup"
               printf "%s\n\n" "testdb     ---> test service health; return a search"
               exit 1
               ;;
    esac

    if [ "$repair" = true ] ; then
        stop_ldap
        db_recover -v -f -h /var/lib/ldap
        set_permissions
        if [ $? -eq 0 ] ; then
            printf "%b\n" "db recovery complete:                         [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "db recovery could not complete:               [ \e[91mFAILED\e[0m ]"
        fi
        start_ldap

    fi

    if [ "$restore" = true ] ; then
        stop_ldap
        printf "%s\n" "backing up db directory"
        tar czf /localbackup/ldap-db-backup.tar /var/lib/ldap
        rm -rf /var/lib/ldap/*
        rm -rf /etc/openldap/slapd.d/*
        rm -rf /var/tmp/bdb-log/*
        make_dir
        set_permissions
        slaptest -f /localbackup/slapd.conf -F /etc/openldap/slapd.d
        set_permissions
        start_ldap
        sleep 5s
        stop_ldap
        newest_ldif
        dn_number
        slapadd -n 1 -l ${PATH_TO_FILE}${NEWEST_LDIF}
        if [ $? -eq 0 ] ; then
            printf "%b\n" "ldif import successful:                       [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "ldif import was not successful:               [ \e[91mFAILED\e[0m ]"
        fi
        set_permissions
        start_ldap
    fi

    if [ "$rebuild" = true ] ; then
        stop_ldap
        printf "%s" "backing up database"
        tar czf /localbackup/ldap-db-backup.tar /var/lib/ldap
        yum -y remove openldap-servers
        yum -y install openldap-servers
        rm -rf /var/lib/ldap/*
        rm -rf /etc/openldap/slapd.d/*
        rm -rf /var/tmp/bdb-log/*
        make_dir
        set_permissions
        slaptest -f /localbackup/slapd.conf -F /etc/openldap/slapd.d
        set_permissions
        start_ldap
        sleep 5s
        stop_ldap
        newest_ldif
        dn_number
        slapadd -n 1 -l ${PATH_TO_FILE}${NEWEST_LDIF}
        if [ $? -eq 0 ] ; then
            printf "%b\n" "ldif import successful:                       [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "ldif import was not successful:               [ \e[91mFAILED\e[0m ]"
        fi
        set_permissions
        start_ldap
      fi

    if [ "$testdb" = true ] ; then
        for i in 'cn=Standard' ; do
           if ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed' -b "dc=removed" \
               "(${i})" 2> /dev/null | grep -E "numEntries: 1"
           then
               printf "%b\n" "found ${i}:                                    [ \e[92mOK\e[0m ]"
               ldapsearch -h localhost -p 389 -D "cn=root,dc=removeddc=com" -w 'removed -b "dc=celeritas,dc=com" \
               "(${i})" | grep -E "(dn: )|(cn: )|(uid: )"
           elif ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed -b "dc=removed" \
                "(${i})" 2>&1 | grep -E "Can't contact LDAP server"
           then
               printf "%b\n" "Can not find ${i}:                             [ \e[91mFAILED\e[0m ]"
           fi
        done
    fi

    systemctl start crond


And I wan't to say thanks again for your help and patience.

If your code's as you post it, it has to have occurred somewhere, but if it's not, other problems are possible.

Could you have done '$NAME' instead of "$NAME" ? That definitely will not work, and eval definitely would have "fixed" it. Using double quotes is the better answer of course.

What is the -d '\0' for?

That's a no-op unless your input actually has nulls, and yours doesn't.

That's a consideration when using read to dump large lists of things into arrays and variables. But you may not need an array at all.

    make_dir () {
        for i in "/var/lib/ldap" "/var/lib/ldap/accesslog" "/var/tmp/bdb-log" "/etc/openldap/slapd.d" ; do
            if [ -d "$i" ] ; then
                :
            else
                mkdir "$i"
            fi
        done

        if [ $? -eq 0 ] ; then
            printf "%b\n" "Checking directories:                         [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "Missing directories, unable to create them:   [ \e[91mFAILED\e[0m ]"
        fi
        cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
    }

That if [ $? ] doesn't do what you think it does. It's only checking the very last loop, not any of the ones before. You can replace the whole thing with one mkdir call, anyway:

mkdir has -p which will replace most of your function:

    make_dir () {
        if ! mkdir -p "/var/lib/ldap" "/var/lib/ldap/accesslog" "/var/tmp/bdb-log" "/etc/openldap/slapd.d"
        then
            printf "%b\n" "Missing directories, unable to create them:   [ \e[91mFAILED\e[0m ]"
            return
        fi

        printf "%b\n" "Checking directories:                         [ \e[92mOK\e[0m ]"

        cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
    }
    set_permissions () {
        for i in "/var/lib/ldap" "/var/tmp/bdb-log" "/etc/openldap/slapd.d" ; do
            chown -R ldap:ldap "$i"
        done

        if [ $? -eq 0 ] ; then
            printf "%b\n" "Setting permissions:                          [ \e[92mOK\e[0m ]"
            return 0
        else
            printf "%b\n" "Unable to set permissions:                    [ \e[91mFAILED\e[0m ]"
            return 1
        fi
    }

Same problem here, your $? doesn't do what you think it does, you could have used if directly, and chown can accept multiple names anyway.

If your ldap permissions are changing all the time, it might be a good idea to find what's doing so...

    set_permissions () {
        if chown -R ldap:ldap "/var/lib/ldap" "/var/tmp/bdb-log" "/etc/openldap/slapd.d"
        then
            printf "%b\n" "Setting permissions:                          [ \e[92mOK\e[0m ]"
            return 0
        else
            printf "%b\n" "Unable to set permissions:                    [ \e[91mFAILED\e[0m ]"
            return 1
        fi
    }

Never, ever use kill -9 as anything but a last resort. You have to ask it to stop, wait, kill it politely, wait, kill it less politely, wait, and if it doesn't die, THEN you can murder it:

    stop_ldap () {
        pid_number=$(pgrep slapd)
        systemctl stop slapd
        sleep 2
        for SIG in TERM INT HUP KILL # Increasingly severe signals to kill it with
        do
                pgrep slapd >/dev/null || break
                kill -$SIG "$pid_number"
                sleep 1
        done
    }

I'll have to think about the rest.

The permissions only get stomped on when running slaptest and slapcat because they are run as root. (During a recovery which hopefully is never again)

Also I will definitely be making the changes you suggested when I have time to work on it again.

Our servers lost access to their SAN (collective array of drives) due to a mistakenly pulled cable and something else and LDAP understandably doesn't like having its filesystem ripped out from under it.

It took us about an hour to get it back online last time simply because nobody could remember the commands to run. I've tested this script every which way I know how and it works every time in a matter of seconds, so we have it on the machine just in case disaster strikes again. ( and a remote backup too)

Thanks for the help. It is really appreciated. Seriously you have no idea. Also, one last bug in the script which I can't figure out.

When the script examines the backup files from our remote directory it doesn't display the top 5 files by DN count. Local files work fine. Overall it still works fine also, but its a bit bizarre, the trace doesn't give me any clue. No big deal as I have to move on to the next project. Just bugs me, pun intended!

Also one last comment, when I need to extract word by word from large multi-line files the "read -d "\0" -a array < <(some-input-here) always just "works" when other read commands fail, so I should be avoiding this? I think I need to revisit the read command entirely.

    case $1 in
    repair)    repair=true
               ;;
    testdb)    testdb=true
               ;;
    restore)   restore=true
               ;;
    rebuild)   rebuild=true
               ;;
    *)         printf "%s\n\n" "Usage ldap.sh <repair|restore|rebuild|test>"
               printf "%s\n"   "repair     ---> try to repair existing db via db_recover"
               printf "%s\n"   "restore    ---> try to restore from latest backup via slapadd"
               printf "%s\n"   "rebuild    ---> rebuild everything and restore latest backup"
               printf "%s\n\n" "testdb     ---> test service health; return a search"
               exit 1
               ;;
    esac

Why not cut out the middleman and just call ldap_repair, ldap_testdb, etc functions instead of setting variables?

rm -rf /var/lib/ldap/*
        rm -rf /etc/openldap/slapd.d/*
        rm -rf /var/tmp/bdb-log/*

You can put this in one call:

rm -rf /var/lib/ldap/* /etc/openldap/slapd.d/* /var/tmp/bdb-log/*

And once again, you really shouldn't be shoving the output of ls -l into variables. For one thing its redundant, and for another, did you know that if you turn off the -l, you won't need awk '{ print $9 }' ? For a third thing, the print $9 trick breaks down when filenames have spaces in them, which is a problem you seem to want to avoid.

If you're using the code I showed you, with grep -hc, it doesn't sort the output, but that's an easy fix:

grep -Hc "dn: " /localbackup/ldap/* | sort -t: -rn -k 2,2 | head -n 5

-rn for 'reverse' and 'numeric', -t: for delimiter :, -k 2,2 to sort on the second column alone.

Most of the rest of your code I will need context to understand at all.

Yes I'm going to incorporate these changes when I get some time. We have old infrastructure and I'm under a bit of a time crunch as we have other systems that tend to halt because of various hardware related issues (Sudden losses of power, failing drives, etc....). I've tested all logical cases I can think of and it works everytime so I have to move on unfortunately. (Actually now that I think about it, with your suggestions I think I can cut out about half the script)

For context (sorry for the lack on comments), LDAP outputs a backup file of its database in an ldif file. We store 12 days worth on the local machine under /localbackup/ldap/ and these are also copied to an NFS share mounted at /backup/ldap/ in case of a total disaster on the local drive.. Each entry starts with the string "dn: " in this text file so getting the count of this is just a bit of a sanity check for the user in the case LDAP went really wonky and spit out an incomplete or empty backup. It just displays the count of the top 5 and the one your using. I was going to make it simply find the ldif with the highest "dn " entry but I ran out of time to finish :frowning:

Anyways you've helped a lot.

Hi.

I put your script into file z9, then ran shellcheck on it, receiving this:

In z9 line 205:
        for i in 'cn=Standard' ; do
        ^-- SC1009: The mentioned parser error was in this for loop.

In z9 line 206:
           if ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed' -b "dc=removed" \
           ^-- SC1046: Couldn't find 'fi' for this 'if'.
           ^-- SC1073: Couldn't parse this if expression.

In z9 line 210:
               ldapsearch -h localhost -p 389 -D "cn=root,dc=removeddc=com" -w 'removed -b "dc=celeritas,dc=com" \
                                                                               ^-- SC1078: Did you forget to close this single quoted string?

In z9 line 212:
           elif ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed -b "dc=removed" \
                                                                                 ^-- SC1079: This is actually an end quote, but due to next char it looks suspect.

In z9 line 214:
           then
           ^-- SC1047: Expected 'fi' matching previously mentioned 'if'.
               ^-- SC1072: Unexpected keyword/token. Fix any mentioned problems and try again.

Some details for shellcheck:

shellcheck      analyse shell scripts (man)
Path    : /usr/bin/shellcheck
Version : 0.3.4
Type    : ELF 64-bit LSB executable, x86-64, version 1 (SYSV ...)
Help    : probably available with -h
Repo    : Debian 8.9 (jessie) 
Home    : http://hackage.haskell.org/package/ShellCheck (pm)

Best wishes ... cheers, drl

Those missing quotes and errors from the end are simply copy and paste errors I was in a hurry to remove passwords from the document. I clipped some quotes along with it.

Missed this question.

Without knowing what your input was and what your code was, I can't say why it didn't work. Common pitfalls with read involve backslashes and carriage returns, though.

First of all, as has been shown in this thread, an array is NOT always necessary to process lists of data. It might be worthwhile to consider other means as described above.

That's not a read problem, but has to do with the fact that the data are separated by <NL> (\n, 0x0A, ^J) characters, causing read to only get one single item per line, not assigning the array but just the first element. For assigning the array's elements, the items must be in one line separated by chars from the $IFS variable.
A caveat: even though a simple ls prints file names across the screen, it separates them by \n when not outputting to screen, e. g. being redirected. A solution to your special problem - besides suppressing \n in your overcomplicated awk pipe - could be

read -a DN_ARRAY < <(ls fi* | tr $'\n' ' ' )

, or the -m option if your ls offers that.

Hey guys I added most of your suggestions and it works great.

I just now realized the beauty of that grep pipe. I can simply use it to find the highest DN count file from the get go, which is the ultimate goal anyway.

I still need to fix the case statement and substitute a 0 in case the either one of the get_ldif comparison variables are empty. I will add and test that shortly.

#! /bin/bash

    systemctl stop crond


    make_dir () {
        if ! mkdir -p "/var/lib/ldap" "/var/lib/ldap/accesslog" "/var/tmp/bdb-log" "/etc/openldap/slapd.d"
        then
            printf "%b\n" "Missing directories, unable to create them:   [ \e[91mFAILED\e[0m ]"
            return 1
        fi

        printf "%b\n" "Checking directories:                         [ \e[92mOK\e[0m ]"

        cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
    }


    set_permissions () {
        if chown -R ldap:ldap "/var/lib/ldap" "/var/tmp/bdb-log" "/etc/openldap/slapd.d"
        then
            printf "%b\n" "Setting permissions:                          [ \e[92mOK\e[0m ]"
            return 0
        else
            printf "%b\n" "Unable to set permissions:                    [ \e[91mFAILED\e[0m ]"
            return 1
        fi
    }


    stop_ldap () {
        pid_number=$(pgrep slapd)
        systemctl stop slapd
        sleep 3
        for SIG in TERM INT HUP KILL # Increasingly severe signals to kill it with
        do
                pgrep slapd >/dev/null || break
                kill -$SIG "$pid_number"
                sleep 3
        done
    }



    start_ldap () {
        systemctl start slapd
        if [ $? -eq 0 ] && pgrep slapd ; then
            printf "%b\n" "slapd started:                                [ \e[92mOK\e[0m ]"
            return 0
        else
            printf "%b\n" "Unable to start slapd:                        [ \e[91mFAILED\e[0m ]"
            return 1
        fi
    }

    get_ldif () {
            TOP_LOCAL_LDIF=$(grep -Hc "dn: " /localbackup/ldap/* | sort -t: -rn -k 2,2 | head -n 1)
            TOP_REMOTE_LDIF=$(grep -Hc "dn: " /backup/ldap/* | sort -t: -rn -k 2,2 | head -n 1)
            LOCAL_COUNT=$(echo "${TOP_LOCAL_LDIF}" | cut -d ":" -f 2 )
            REMOTE_COUNT=$(echo "${TOP_REMOTE_LDIF}" | cut -d ":" -f 2 )
            
            if [ ${LOCAL_COUNT} -ge ${REMOTE_COUNT} ] ; then
                FILE_PATH=$(echo "${TOP_LOCAL_LDIF}" | cut -d ":" -f 1)
                printf "%b\n%b\n" "Found the following LDIF with DN count:" "${TOP_LOCAL_LDIF}                            [ \e[92mOK\e[0m ]"
            else
                FILE_PATH=$(echo "${TOP_REMOTE_LDIF}" | cut -d ":" -f 1)
                printf "%b\n%b\n" "Found the following LDIF with DN count:" "${TOP_REMOTE_LDIF}                            [ \e[92mOK\e[0m ]"
            fi
            sleep 7s
    }



    case $1 in
    repair)    repair=true
               ;;
    testdb)    testdb=true
               ;;
    restore)   restore=true
               ;;
    rebuild)   rebuild=true
               ;;
    *)         printf "%s\n\n" "Usage ldap.sh <repair|restore|rebuild|test>"
               printf "%s\n"   "repair     ---> try to repair existing db via db_recover"
               printf "%s\n"   "restore    ---> try to restore from latest backup via slapadd"
               printf "%s\n"   "rebuild    ---> rebuild everything and restore latest backup"
               printf "%s\n\n" "testdb     ---> test service health; return a search"
               exit 1
               ;;
    esac

    if [ "$repair" = true ] ; then
        stop_ldap
        db_recover -v -f -h /var/lib/ldap
        if [ $? -eq 0 ] ; then
            printf "%b\n" "db recovery complete:                         [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "db recovery could not complete:               [ \e[91mFAILED\e[0m ]"
        fi
        set_permissions
        start_ldap

    fi

    if [ "$restore" = true ] ; then
        stop_ldap
        printf "%s\n" "backing up db directory"
        tar czf /localbackup/ldap-db-backup.tar /var/lib/ldap
        rm -rf /var/lib/ldap/*
        rm -rf /etc/openldap/slapd.d/*
        rm -rf /var/tmp/bdb-log/*
        make_dir
        set_permissions
        slaptest -f /localbackup/slapd.conf -F /etc/openldap/slapd.d
        set_permissions
        start_ldap
        sleep 5s
        stop_ldap
        get_ldif
        slapadd -n 1 -l "${FILE_PATH}"
        if [ $? -eq 0 ] ; then
            printf "%b\n" "ldif import successful:                       [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "ldif import was not successful:               [ \e[91mFAILED\e[0m ]"
        fi
        set_permissions
        start_ldap
    fi

    if [ "$rebuild" = true ] ; then
        stop_ldap
        printf "%s" "backing up database"
        tar czf /localbackup/ldap-db-backup.tar /var/lib/ldap
        yum -y remove openldap-servers
        yum -y install openldap-servers
        rm -rf /var/lib/ldap/*
        rm -rf /etc/openldap/slapd.d/*
        rm -rf /var/tmp/bdb-log/*
        make_dir
        set_permissions
        slaptest -f /localbackup/slapd.conf -F /etc/openldap/slapd.d
        set_permissions
        start_ldap
        sleep 5s
        stop_ldap
        get_ldif
        slapadd -n 1 -l "${FILE_PATH}"
        if [ $? -eq 0 ] ; then
            printf "%b\n" "ldif import successful:                       [ \e[92mOK\e[0m ]"
        else
            printf "%b\n" "ldif import was not successful:               [ \e[91mFAILED\e[0m ]"
        fi
        set_permissions
        start_ldap
      fi

    if [ "$testdb" = true ] ; then
        for i in 'cn=Standard' ; do
           if ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed' -b "dc=removed,dc=com" \
               "(${i})" 2> /dev/null | grep -E "numEntries: 1"
           then
               printf "%b\n" "found ${i}:                                    [ \e[92mOK\e[0m ]"
               ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed' -b "dc=removed,dc=com" \
               "(${i})" | grep -E "(dn: )|(cn: )|(uid: )"
           elif ldapsearch -h localhost -p 389 -D "cn=root,dc=removed,dc=com" -w 'removed' -b "dc=removed,dc=com" \
                "(${i})" 2>&1 | grep -E "Can't contact LDAP server"
           then
               printf "%b\n" "Can not find ${i}:                             [ \e[91mFAILED\e[0m ]"
           fi
        done
    fi

    systemctl start crond

Now, with your nice set of functions, why don't you stay with those, i.e. write a "repair" , a "restore" etc. function and just call those in the case statement?

EDIT: For your get_ldif , how about something like

IFS=: read LOCAL_FP LOCAL_COUNT   < <(grep -Hc "dn: " /localbackup/ldap/* | sort -t: -rn -k 2,2 | head -n 1)
IFS=: read REMOTE_FP REMOTE_COUNT < <(grep -Hc "dn: " /backup/ldap/*      | sort -t: -rn -k 2,2 | head -n 1)
[ "${LOCAL_COUNT}" -ge "${REMOTE_COUNT}" ] && FILE_PATH="$LOCAL_FP" ||  FILE_PATH="$REMOTE_FP"
printf "%b\n%b\n" "Found the following LDIF with DN count:" "${FILE_PATH}                             [ \e[92mOK\e[0m ]"

The problem with empty variables will arise if files aren't found or directories are empty, i.e. grep fails. In this case, check for the exit code, deploy the shell's "parameter expansion - use default value", or use a trick like adding a "0" upfront:

[ 0"${LOCAL_COUNT}" -ge 0"${REMOTE_COUNT}" ]

As this is not an "arithmetic evaluation", no problem with "leading 0 are interpreted as octal numbers" should occur. Should your shell interpret the values as octal values (mine didn't), add a base descriptor like "10#".

Hi RudiC, that's exactly the plan. And I like that read function, very concise. My only point of consternation was I've never used functions inside other functions in bash, but I'm assuming there is no issue correct?

I have to "time slice" between various projects and when I make a change to the script I have to test every logical case that it works (I'm usually good for a typo or two) because my co -workers may have to use it when I'm not there, and it if doesn't work they have no idea about LDAP. No biggie for me as I can do it manually. Point being I will have to implement it when I get a little bit more time.

I will update with final product. Thanks again.

Correct, nothing strange happens.