0

I'm writing a script that sends the result of commands to an output array. It involves checking servers for logs and retrieving their sizes, however there are cases in which the server has a failover host. In these cases I need the script to check both hosts and return the values. The issue I'm running into is if any of the output array elements are empty (meaning there is no file existing in the server to check, returning just a space in its respective echo display), it causes a shift in the array indices. This means that what should be in the secondary host array set gets bumped into the primary one. I would instead like to have these empty indices be stored as 0 as a placeholder in order to prevent the shifting taking place. This is where the index shift is apparent:

echo The primary overall log value is ${output[0]}.
echo The primary obs log value is ${output[2]}.    
echo The primary tracks log value is ${output[4]}.  
echo
echo The secondary overall log value is ${output[6]}.
echo The secondary overall log value is ${output[8]}.
echo The secondary overall log value is ${output[10]}.

What happens for example is if output[2] and [4] are empty, output[6] will shift up to the line corresponding to output [2]. I've tried this solution but with no luck:

  s=0

  for x in "${output[@]}"
     do(
     x=
        if [[ -z $x ]];
        then(
        x=0
        echo $x)
        else echo
        fi
     s=s+1)
 done

All this does is spit out zeros before the echo of the outputs takes place, and does not adjust for any of the index shift.

Note: this is where the output array comes from, along with an attempt at a fix below it:

for h in "${host[@]}"
do
    for path in "${paths[@]}"
    do
            output+=( $(ssh $h du -sh $path) )
            for x in "${!output[@]}";
            do(
                    if [[ -z "${output[$x]}" ]];
                    then output[$x]=0;
                    fi;
            )
            done

    done
done

Where host and path are already defined arrays. I'd also note that the script functions fine for the cases in which there is only one host to access, and there is no issue with array indices there.

1 Answer 1

2

What happens for example is if output[2] and [4] are empty, output[6] will shift up to the line corresponding to output [2].

In this case, you don't really have empty values in place of 2 and 4, it's just that you have less values than you expect.

Consider the array assignments:

array1=(a b c d)
array2=(A D)

The first sets array1[0] to a, array1[1] to b, etc. The second sets array2[0] to A and array2[1] to D. There's no way to know if there was supposed to be empty values between A and D. They'd need to be in the assignment explicitly:

array2=(A "" "" D)

You're probably filling the array output some other way, but since you don't show how it's done, there's no way to comment on it. You probably have a case similar to the above: the "empty" values are not seen as values at all by whatever fills the output array.

If you're assigning to the array from an expansion, and relying on word-splitting, you can't really avoid this. (Even if IFS contains just newline, it will fold consecutive empty newlines to one, removing empty lines). If you're using mapfile, then empty lines should appear as array elements by default.


In any case, your loop to zero empty array elements doesn't work because you don't assign to the array within the loop (at all), and even if you did, the loop body runs in a subshell (as marked by the parenthesis (...)), so any assignments wouldn't take place outside the loop body anyway.

You can't really use a for x in "${output[@]}" to usefully modify array elements, as x only gets a copy of the array values, modifying it doesn't change the originals. You'd need to loop over the array indices to be able to point back at the array:

somearray=(1 "" "" 4)
for i in "${!somearray[@]}"; do
    if [[ -z "${somearray[$i]}" ]]; then
        somearray[$i]=0;
    fi;
done
echo "${somearray[@]}"

prints 1 0 0 4.


In your added sample code, the assignment output+=( $(ssh $h du -sh $path) ) will not add any empty elements. The missing paths will just be omitted from the array. Consider that first, du $path doesn't print anything if the path doesn't exist. (Apart from an error, but that goes to stderr, and is not captured by the command substitution.) Also, even if it did print an empty line (or spaces/tabs) word splitting removes consecutive whitespace.

Try e.g. array=( $( printf "foo\n\nbar\n" ) ), it creates an array of two elements.

Besides, the parenthesis in the replacement loop start a subshell, so any modifications to the array would only take effect within that subshell. In general, you don't want to group shell commands with parenthesis, unless you know you want a subshell in particular.

You could put quotes around the command substitution ("$(ssh ... du)") to make sure you get exactly one string, but you probably want to separate the size from the path name.

Try something like this to collect the paths and their sizes to a single array:

for host in "${hosts[@]}"; do
    for path in "${paths[@]}"; do
        output="$(ssh "$host" du -sh "$path")"
        size="${output%%$'\t'*}"           # needs Bash/ksh/zsh
        size="${size:-0}"
        sizes+=( "$size" "$host:$path"  )
    done
done

Now, every even array element contains a size, and every odd one the corresponding hostname and path, a bit like what splitting the output of du would result in. The two parameter expansions of output and size remove everything after a tab, and then replace the size with zero if it's empty.

Alternatively, you could build an associative array, indexed by the host+path:

declare -A array
...
    array["$host:$path"]=$size
2
  • I tried this as well and ran into the same issue. Also just added some background as to where the output array comes from.
    – MLopes7
    Jul 18, 2018 at 12:33
  • @MLopes7, yeah, see edit.
    – ilkkachu
    Jul 20, 2018 at 15:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .