-1

I want to backup mikrotiks via scp. This script loops through the hosts from the hosts.txt. One by one, connects to each device from the list. Does backup and all manipulations. If at some stage it was not possible to connect to the device, then an empty backup is formed, which is then sent to the cloud. I want to check. If it was not possible to connect to the host, then write this host into a variable, line by line, and go to the next device. Next, I will notify about all failed connections. The problem is that only the first error is written to the variable, all subsequent ones are ignored. Tell me who knows what.

#!/bin/bash
readarray -t hosts < hosts.txt

DATE=$(date +'%Y-%m-%d_%H-%M-%S')
ROS='<br>'
ERR=( )

 #Get values from main list
for host in ${hosts[*]}
do
    #Get values from sub list
    hostname=($(echo ${host} | tr "_" " "))
    echo ${hostname[0]} - ${hostname[1]}
    #connect & backup & transfer & archive & rm old files & moove to cloud
    if ssh backup@${hostname[0]} -C "/system backup save name=${hostname[1]}_$DATE"; then
        scp backup@${hostname[0]}:"${hostname[1]}_$DATE.backup" ./
        ssh backup@${hostname[0]} -C "rm ${hostname[1]}_$DATE.backup"
        tar -czvf ./${hostname[1]}_$DATE.tar.gz ${hostname[1]}_$DATE.backup
        scp ./${hostname[1]}_$DATE.tar.gz my@cloud.com:/var/www/my.cloud.com/backups/mikrotik/
        rm ${hostname[1]}_$DATE.backup ${hostname[1]}_$DATE.tar.gz
        ROS=$ROS${hostname[1]}"<br>"
    else
        ERR+=(${hosts[*]} "is not ready")
    fi
done

hosts.txt
10.10.8.11_CAP-1
10.10.9.12_CAP-2
10.10.10.13_CAP-3
sdvjke
  • 55
  • 6
  • Is it the first or the last unavailable host that's recorded? Your line `ERR=(${hosts[*]} "is not ready")` should be `ERR+=(${hosts[*]} "is not ready")` and you should define `ERR` as an array, not a scalar: `ERR=( )` for example, or `declare -a ERR`. Similarly with `ROS`. – Jonathan Leffler Dec 22 '20 at 16:58
  • copy/paste your script into http://shellcheck.net and fix the issues it tells you about then post the result in your question if you still have a problem. – Ed Morton Dec 22 '20 at 17:06
  • @JonathanLeffler Thank you for the replacements, now I'll fix it and try. – sdvjke Dec 22 '20 at 17:07
  • @JonathanLeffler The first unavailable host is written to the file. – sdvjke Dec 22 '20 at 17:16
  • Since none of the status information is written to a file in the code you show, I conclude you're discussing code you have not shown us. You should probably write `ROS=$ROS${hostname[1]}"
    "` as `ROS="$ROS${hostname[1]}
    "`, enclosing the whole RHS in double quotes.
    – Jonathan Leffler Dec 22 '20 at 17:46

1 Answers1

0

As I noted in the comments, you're misusing the array notation. Your line ERR=(${hosts[*]} "is not ready") should be ERR+=(${hosts[*]} "is not ready") and you should define ERR as an array, not a scalar: ERR=( ) for example, or declare -a ERR. Similarly with ROS.

Here's a test script that avoids all the ssh and scp work to demonstrate that lists of passing and failing hosts work — that the arrays hosts, ROS and ERR are handled correctly.

Note the use of "${ERR[@]}" with double quotes and @ instead of no quotes and *. The difference matters because the values in the array contain spaces. Try the alternatives. Note, too, that printf always prints, even when there is no argument corresponding to the %s in the format string. Hence the check on the number of elements in the array before invoking printf.

#!/bin/bash
# Needs Bash 4.x - Bash 3.2 as found on Macs does not support readarray

# readarray -t hosts < hosts.txt
hosts=( passed-1 failed-2 passed-3 failed-4 passed-5 )

declare -a ERR
declare -a ROS
status=passed
for host in "${hosts[@]}"
do
    if [ "$status" = "passed" ]
    then ROS+=( "$host $status" ); status="failed"
    else ERR+=( "$host $status" ); status="passed"
    fi
done

# Brute force but handles empty lists
for passed in "${ROS[@]}"
do printf "== PASS == [%s]\n" "$passed"
done
for failed in "${ERR[@]}"
do printf "!! FAIL !! [%s]\n" "$failed"
done

# Alternative - better spread over multiple lines each
if [ "${#ROS}" -gt 0 ]; then printf "== PASS == [%s]\n" "${ROS[@]}"; fi
if [ "${#ERR}" -gt 0 ]; then printf "!! FAIL !! [%s]\n" "${ERR[@]}"; fi

Output:

== PASS == [passed-1 passed]
== PASS == [passed-3 passed]
== PASS == [passed-5 passed]
!! FAIL !! [failed-2 failed]
!! FAIL !! [failed-4 failed]
== PASS == [passed-1 passed]
== PASS == [passed-3 passed]
== PASS == [passed-5 passed]
!! FAIL !! [failed-2 failed]
!! FAIL !! [failed-4 failed]

I'm sorry there are so many failures to backup your data!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • I'm sure you know this but for the OPs sake - don't use all upper case for non-exported variable names (`ROS` and `ERR`), see [correct-bash-and-shell-script-variable-capitalization](https://stackoverflow.com/questions/673055/correct-bash-and-shell-script-variable-capitalization) and [are-there-naming-conventions-for-variables-in-shell-scripts](https://unix.stackexchange.com/questions/42847/are-there-naming-conventions-for-variables-in-shell-scripts) – Ed Morton Dec 22 '20 at 17:26
  • I studiously ignore it as contrary to the way I learned to code shell scripts, but you're probably mostly right for those learning shell scripting these days. In fact, I mostly (but far from exclusively) use upper-case underscored names for environment variables, and lower-case underscored names for other variables. There's an SO question (linked from the Unix & Linux question) on the topic of [Correct Bash and shell script variable capitalization](https://stackoverflow.com/q/673055/15168) too. I rarely (if ever) use a lower-case environment variable name in the absence of external duress. – Jonathan Leffler Dec 22 '20 at 17:44
  • That doesn't sound like contrary to the advice I gave, it sounds the same to me - use upper case for environment vars, lower case otherwise. Maybe my use of a double negative made that unclear! – Ed Morton Dec 22 '20 at 17:57