2

I want the following command's result to redirect into a file on the remote server, as I want only unique lines in my authorized_keys file:

ssh [email protected] awk '!seen[$0]++' /root/.ssh/authorized_keys

I've tried the following without success:

ssh [email protected] "awk '!seen[$0]++' /root/.ssh/authorized_keys > /root/.ssh/authorized_keystemp"

ssh [email protected] "awk \'!seen[$0]++\' /root/.ssh/authorized_keys > /root/.ssh/authorized_keystemp"

ssh [email protected] '(awk \'!seen[$0]++\' /root/.ssh/authorized_keys > /root/.ssh/authorized_keystemp)'

My googles have failed me... any ideas on what I'm doing wrong?

3
  • I don't think there is a straight-forward method to do that. Is 10.100.10.26 the remote server?
    – Sreeraj
    Dec 4, 2014 at 19:37
  • 2
    Quoting it should work, but you need to quote it correctly...
    – derobert
    Dec 4, 2014 at 19:46
  • 2
    Whenever I run into doubly-nested quoting, I consider it time to shift the command into a script on the remote server and use ssh to run the script.
    – muru
    Dec 4, 2014 at 19:48

3 Answers 3

4

Try this (I tested on my machine and it appears to work---well, I didn't have any duplicates to remove, but...):

ssh [email protected] "awk '!seen[\$0]++' /root/.ssh/authorized_keys > /root/.ssh/authorized_keystemp"

Your "-quote attempts didn't work since you didn't backslash the $ in $0, leading it to be expanded to something like bash.

Your single-quote attempt didn't work because 'foo\'some thing\'foo' doesn't mean what you think it does. \-escapes are not interpreted inside single-quoted strings. So that's actually unbalanced quotes and two arguments; the first one is foo\some and the second is thing'foo but it isn't complete because that last ' starts a new single-quoted string.

Shell escaping can be a pain, especially with double-escaping. Sending over a shell script (with sftp, scp, etc.) first and then running that is often easier.

0

I'm going to accept derobert's answer because it gave me information I needed. I still had issues, which I think might of been my awk command, which I replaced with an alternative approach. The following is what I did to ensure my authorized_keys file on the remote server had only unique entries:

ssh [email protected] "sort /root/.ssh/authorized_keys | uniq > /root/.ssh/temp"
ssh [email protected] "mv -f /root/.ssh/temp /root/.ssh/authorized_keys"
1
  • I might also suggest sort -u -o /root/.ssh/authorized_keys /root/.ssh/authorized_keys, but check that the system's sort supports the -u flag (it's popular but not required by POSIX).
    – aecolley
    Dec 5, 2014 at 1:01
0

Here's everything you need to know about quoting something to protect it from shell expansion and word-splitting:

  1. Enclose everything other than apostrophes between apostrophes. For example, do not becomes 'do not'.
  2. Use backslashes to escape apostrophes, i.e. ' becomes \'.
  3. Break your string up into apostrophes and other characters, apply the preceding 2 rules, then concatenate the results. For example, don't becomes 'don'\''t'.

With these general rules, the command in your question can be properly quoted as follows:

ssh [email protected] 'awk '\''!seen[$0]++'\'' /root/.ssh/authorized_keys > /root/.ssh/authorized_keystemp'

There are more readable ways to quote the same string, but this approach is generally applicable and easy to visually verify as correct.

I find myself doing this often enough that I wrote a shell function/script to do it for me, and I use it all the time. Here it is:

#!/bin/sh -

# shellquote foo => foo
# shellquote foo&bar => 'foo&bar'
# shellquote foo'bar => 'foo'\''bar'
shellquote() {
  local input="$1"
  local output=""
  local backslash='\'
  local apostrophe="'"
  if [ -z "${input}" ]; then
    # Empty string => pair of apostrophes
    output="''"
  fi
  while [ -n "${input}" ]; do
    case "${input}" in
    "'"*)
      # Escape the apostrophe.
      output="${output}${backslash}${apostrophe}"
      input="${input#${apostrophe}}"
      ;;
    *"'"*)
      # Quote everything before the first apostrophe, and then escape
      # the apostrophe.
      output="${output}${apostrophe}${input%%${apostrophe}*}${apostrophe}${backslash}${apostrophe}"
      input="${input#*${apostrophe}}"
      ;;
    *[!+,./0-9:=@A-Z_a-z-]*)
      # There are no apostrophes, but at least one character needs quoting.
      # So quote the entire word.
      output="${output}${apostrophe}${input}${apostrophe}"
      input=""
      ;;
    *)
      # Nothing needs quoting. Output everything literally.
      output="${output}${input}"
      input=""
    esac
  done
  printf '%s' "${output}"
}

main() {
  local sep=''
  for arg; do
    printf '%s' "${sep}"
    shellquote "${arg}"
    sep=' '
  done
}

main "$@"

You must log in to answer this question.

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