Question

Using sed to remove differences in two arrays (updated ssh keys)

My aim is to updated automate the update of ssh keys in the authorized_keys file. Due to restrictions i am unable to use ansible which would have been a lot simpler.

Steps:

  • read keys from the file
  • compare the keys
  • print out the key that will be deleted as it is i not in the supplied array
  • delete the keys

Everything works except for the fact it will delete the last matching key:

  • if there are two keys that match in array1 and array2_from_file, it will delete the second key
  • To be more specific it would delete "key2" (seen below)

array1=(
    "ssh-ed25519 AAAAC3... key1"
    "ssh-ed25519 AAAAC3NzaC1l key2"
);

path=".ssh/authorized_keys"

mapfile -t array2_from_file < "$path" #updated here removed <(cat "$path)

escape_sed_pattern() {
    printf '%s\n' "$1" | sed 's/[\\/&]/\\&/g'
}

for key in "${array2_from_file[@]}"; do
    if ! [[ " ${array1[@]} " =~ " $key " ]]; then
        echo "To be deleted: $key"
        escaped_key=$(escape_sed_pattern "$key")

        # Remove the key from the authorized_keys file
        sed -i "/$escaped_key/d" "$path"
    fi
done

Update with more details:

  • Part of my goal is print the key that will be deleted to the console so it can be logged by the runner to basically be referenced as a log of what was deleted.
  • Arrays may be of different length (ie: a key may be added or removed later on in the script and when the automation task runs it should updated the authorized keys file)
 2  68  2
1 Jan 1970

Solution

 3

Assuming the contents of array1[] are exact line matches there's no need for the mapfile, function, and for loop.

I would expect the following to suffice:

array1=(
    "ssh-ed25519 AAAAC3... key1"
    "ssh-ed25519 AAAAC3NzaC1l key2"
);

path=".ssh/authorized_keys"

grep -vFf <(printf '%s\n' "${array1[@]}") "${path}" > tmpfile

Now overwrite the original authorized_keys:

cat tmpfile > "${path}"      # should maintain permissions on 'authorized_keys'

rm tmpfile

#######
# alternatively:

mv tmpfile "${path}"

chmod 600 "${path}"          # (re)set permissions on 'authorized_keys'
2024-07-24
markp-fuso

Solution

 1

It sounds like all you want to do is something like this, untested:

#!/usr/bin/env bash

path=".ssh/authorized_keys"

awk '
    BEGIN {
        split("ssh-ed25519 AAAAC3... key1" RS\
              "ssh-ed25519 AAAAC3NzaC1l key2",
            tmp, RS)
        for ( i in tmp ) {
            array1[tmp[i]]
        }
    }
    $0 in array1 {
        print "To be deleted:", $0
        next
    }
    { print }
' "$path"

To change the input file change awk to awk -i inplace if you have GNU awk or add > tmp && mv tmp "$infile" or similar to the end of the script otherwise.

2024-07-24
Ed Morton

Solution

 0

Assuming the final order does not matter you could use a combination of sort and comm:

#!/bin/bash

array1=(
  "ssh-ed25519 AAAAC3... key1"
  "ssh-ed25519 AAAAC3NzaC1l key2"
)
path=".ssh/authorized_keys"
trap 'rm -f -- "$tmp"' EXIT 
tmp=$(mktemp)
printf 'Keys to be deleted:\n'
comm -13 <(printf '%s\n' "${array1[@]}" | sort) <(sort "$path")
comm -12 <(printf '%s\n' "${array1[@]}" | sort) <(sort "$path") > "$tmp"
cat "$tmp" > "$path"
2024-07-25
Renaud Pacalet