fabreg
fabreg

Reputation: 123

Execute a perl substitution through ssh [perl one-oneliner from shell script]

I'm trying to execute this script remotely:

perl -i -pe 's/nginx-cache\K(\d+)/ ++($n = $1) /e; s/MYSITE\K(\w+)/ ++($n = $1) /e;' $SITENAME

with the following solution:

ssh -T root@$IPSRV <<EOI
perl -i -pe 's/nginx-cache\K(\d+)/ ++($n = $1) /e; s/MYSITE\K(\w+)/ ++($n = $1) /e;' /etc/nginx/sites-available/$SITENAME"
exit
EOI

I tried also without the "-T" option of ssh

ssh root@$IPSRV "
> perl -i -pe 's/nginx-cache\K(\d+)/ ++($n = $1) /e; s/MYSITE\K(\w+)/ ++($n = $1) /e;' /etc/nginx/sites-available/$SITENAME"

but unfortunately it does not work:

syntax error at -e line 1, near "( ="
syntax error at -e line 1, near "( ="
Execution of -e aborted due to compilation errors.

Could someone please suggest me a solution for running this command remotely? Thanks in advance!

Note that $SITENAME is a variable on the local machine.

[EDIT]

I have made some progress, based on the @ikegami's answer. I tried

root@$IPSRV 'perl -i -pe'\''s/nginx-cache\K(\d+)/ ++($n = $1) /e; s/MYSITE\K(\w+)/ ++($n = $1) /e;'\'' /etc/nginx/sites-available/"$SITENAME"'
[email protected]'s password: 
Can't do in place edit: /etc/nginx/sites-available/ is not a regular file.

I think it's related to the missing substitution of $SITENAME variable.

Another important thing to keep in mind is the use of single quote after ssh root@IPSRV - it should be replaced by quotes because I have other variables into the script and if I use single quote they are not translated. Example:

ssh root@$IPSRV "mkdir $TESTDIR
    cp /tmp/file $TESTDIR"

This works, but if I try with single quote:

ssh root@$IPSRV 'mkdir $TESTDIR
        cp /tmp/file $TESTDIR'

it does not. So I have to consider also this aspect if the only way for running the perl substitution is 'perl -i -pe'\''s ...

Thanks!

Upvotes: 1

Views: 641

Answers (3)

fabreg
fabreg

Reputation: 123

After a lot of tries I got it working! ;-))

The problem was related to the shell that tries to expand $n or $1 environmental variables prior to sending all this to remote SSH. And on remote side script turns into:

perl -i -pe 's/nginx-cache\K(\d+)/ ++( = ) /e; s/MYSITE\K(\w+)/ ++( = ) /e;'

which yields error on "( = )" places. Just escaping them as \$n sends these string untouched:

perl -i -pe 's/nginx-cache\\K(\\d+)/ ++(\$n = \$1) /e; s/MYSITE\\K(\\w+)/ ++(\$n = \$1) /e;' $SITENAME

So, the complete answer:

ssh root@IPSRV "
  first command
  second command
  perl -i -pe 's/nginx-cache\\K(\\d+)/ ++(\$n = \$1) /e; s/MYSITE\\K(\\w+)/ ++(\$n = \$1) /e;' $SITENAME
  "

Thanks anyone for pointing me in the right direction!

Upvotes: 0

salva
salva

Reputation: 10244

Quoting by hand is hard an error prone. Let's Perl do all the work for you:

use Net::OpenSSH;

my $one_liner = <<'EOOL';
s/nginx-cache\K(\d+)/ ++($n = $1) /e; s/MYSITE\K(\w+)/ ++($n = $1) /e
EOOL

my $ssh = Net::OpenSSH->new("root\@$ENV{IPSRV}");
$ssh->system('perl', '-i', '-pe', $one_liner, $ENV{SITENAME});
$ssh->die_on_error;

Don't forget to export $IPSRV and $SITENAME.

Upvotes: 0

ikegami
ikegami

Reputation: 385829

Improper escaping.

ssh root@$IPSRV "...++($n = $1)..." passes ...++( = )... to the remote host. Same with the here-doc version. (The here-doc version also has a stray quote.)

Handling multiple levels of escaping is complicated, so let's do the escaping programmatically. This also allows us to pass values from variables, as they need to be converted into shell literals.

quote() {
    prefix=''
    for p in "$@" ; do
        printf "$prefix"\'
        printf %s "$p" | sed "s/'/'\\\\''/g"
        printf \'
        prefix=' '
    done
}

ssh root@$IPSRV "$( quote perl -i -pe'...' "$SITENAME" )"

or

quote() {
    perl -MString::ShellQuote=shell_quote -e'print(shell_quote(@ARGV))' "$@"
}

ssh root@$IPSRV "$( quote perl -i -pe'...' "$SITENAME" )"

In case it's of help to others, the following shows how to use the remote machine's $SITENAME var instead:

quote() {
    prefix=''
    for p in "$@" ; do
        printf "$prefix"\'
        printf %s "$p" | sed "s/'/'\\\\''/g"
        printf \'
        prefix=' '
    done
}

ssh root@$IPSRV "$( quote perl -i -pe'...' )"' "$SITENAME"'

or

quote() {
    perl -MString::ShellQuote=shell_quote -e'print(shell_quote(@ARGV))' "$@"
}

ssh root@$IPSRV "$( quote perl -i -pe'...' )"' "$SITENAME"'

or

ssh localhost sh <<'EOI'       # Notice the quotes around the token.
perl -i -pe'...' "$SITENAME"
EOI

Or, since it doesn't need any local variables, you can do it manually rather easily. Take the remote command, replace every ' with '\'', then wrap the whole with quotes.

Remote command:

perl -i -pe'...' "$SITENAME"

Local command:

ssh root@$IPSRV 'perl -i -pe'\''...'\'' "$SITENAME"'

Upvotes: 5

Related Questions