How can SSH work with an if condition?

Solution 1:

Yes you can exexute complex scripts via ssh

#!/bin/bash -e

ssh_cmd="$(cat <<-EOF
    cd /var/www/test.com/backup;

    if [ \$(ls  | wc -l) -lt 3  ]; then
        echo "less"
    elif [ \$(ls -t *.tgz|awk 'NR >3'|xargs rm -f) ]; then
        echo "deleted"
    else
        echo "something else"
    fi
EOF
)"

ssh -t [email protected] "$ssh_cmd"

This example uses a bash here document to generate the command string. In any case, passing scripts via ssh is error prone, because the quoting and escaping of variables is difficult (mind the backslash before the commands). If the script gets too complex, it is better to copy it via scp and then execute it on the target host.

I did not try to fix your script, but here is an example on how counting and deleting on a remote host could work:

#!/bin/bash -e

tmp_dir="$(mktemp -d)"

ssh_cmd="$(cat <<-EOF
    cd "$tmp_dir"

    cnt_tgz=(\$(find . -type f -name "*.tgz"))
    if [[ \${#cnt_tgz[@]} -lt 3 ]]; then
        echo "less"
    else
        rm "\${cnt_tgz[@]}"
        echo "deleted"
    fi
EOF
)"

touch "$tmp_dir/1.tgz"
ssh -t localhost "$ssh_cmd"
touch "$tmp_dir/2.tgz" "$tmp_dir/3.tgz"
ssh -t localhost "$ssh_cmd"

The ls -t *.tgz will not work, since the globbing is only happening on the local system. Also using ls for counting files is not a good idea, since it also returns entries like ., .. and directories.

Solution 2:

NOTE: there are in fact two layers to the question here. One is 'I want to execute a non-trivial task on a remote server accessible via SSH'. The other is 'I'm trying to pass a complex string to a command, and the argument ends up being different from what I intended.' I'm answering the low-level question without discussing whether the approach used is "right" (convenient, non-error-prone, secure etc.) for solving the high-level one. As indicated by the other answers and comments, it quite possibly isn't.

Your command line is mostly right; you only have to change the quoting a bit.

The main problem is that double-quoted strings are expanded by your local shell, and so the $(...) parts will be evaluated on your local system. To pass them over to the remote system, you have to enclose the script in single quotes.

You also have some embedded quotation marks. In your original script, there are the arguments for the two echos; if you change the outer quotation to single quotes, it will be the awk script. These effecitvely result in the quotation marks being omitted, which doesn't bother the echos, but it will mess up the awk script, as the greater-than sign will become output redirection. So after you change the outer quotation marks to single quotes, change those to double quotes.

This is your script with the quoting fixed. The script may have other issues, I just fixed the syntax.

#!/bin/bash

ssh -t  [email protected] 'cd /var/www/test.com/backup ;

if [ $(ls  | wc -l) -lt 3  ]
then
  echo "Less"
else [ $(ls -t *.tgz|awk "NR >3"|xargs rm -f) ]
  echo "deleted"
fi'

Solution 3:

I think this whole complicated quoting stuff is proof enough to not use it but use a script instead. If you want to avoid multiple ssh connections, pipe the script over to the other host and let it run there in one command:

Local file, say myscript.sh:

cd /var/www/test.com/backup;

if [ $(ls  | wc -l) -lt 3  ]; then
    echo "less"
elif [ $(ls -t *.tgz|awk 'NR >3'|xargs rm -f) ]; then
    echo "deleted"
else
    echo "something else"
fi

Then:

cat myscript.sh | \
    ssh -t [email protected] \
    "cat - > /tmp/ms.sh && sh /tmp/ms.sh && rm /tmp/ms.sh"

Or (avoiding a useless use of cat):

ssh -t [email protected] \
    "cat - > /tmp/ms.sh && sh /tmp/ms.sh && rm /tmp/ms.sh" \
    < myscript.sh

This pipes the local script myscript.sh to the remote side where it is redirected to a (temporary) file /tmp/ms.sh, executed, and finally removed.

Note: I didn't check the original script for errors but just wanted to show the idea. No error prone quoting is necessary and all commands in the script are executed on the remote side.

Solution 4:

I would prefer to place the script on the remote instance and just execute it through ssh, but here is my suggestion how this could be done in the way you want:

#!/bin/bash

HOST='[email protected]'
REMOTE_PATH='/var/www/test.com/backup'

COMMAND1="ssh \"$HOST\" 'ls \"$REMOTE_PATH\" | wc -l'"
COMMAND2="ssh \"$HOST\" 'ls -t \"$REMOTE_PATH\"/*.tgz'"
COMMAND3="xargs ssh \"$HOST\" rm -rf"

if [[ $(eval "$COMMAND1") -le 3 ]]
then
    echo "Less"
else
    eval "$COMMAND2" | awk 'NR > 3' | eval "$COMMAND3" && echo "Deleted"
fi

Notes:

  • The conditional expression -lt is replaced by -le.
  • eval - construct command by concatenating arguments.
  • I'm not sure do we really need these extra quotations \" within the $COMMAND{1..3} expression, but I decide to add them.