1

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"'
root@192.168.1.100'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!

fabreg
  • 123
  • 5
  • 1
    Have you tried to quote the heredoc? See [How to avoid heredoc expanding variables?](http://stackoverflow.com/a/27921346/2173773) – Håkon Hægland Mar 01 '17 at 20:37
  • Yes, with -T option and it returns this error: stdin: is not a tty syntax error at -e line 1, near "( =" syntax error at -e line 1, near "( =" Execution of -e aborted due to compilation errors. – fabreg Mar 01 '17 at 20:52
  • The third uses improper escaping. You are doing `ssh root@$IPSRV "++($n = $1)"` so are passing `...++( = )` to the remote host. Same issue with the second, actually. – ikegami Mar 01 '17 at 20:55
  • Do you attempt the remote execution from shell script or perl script? – AnFi Mar 01 '17 at 20:56
  • 1
    Possible duplicate of [Here document: no parameter expansion](http://stackoverflow.com/questions/31227601/here-document-no-parameter-expansion) – chepner Mar 01 '17 at 20:57
  • @Andrzej A. Filip from bash. – fabreg Mar 01 '17 at 20:59
  • Which machine's `$SITENAME` variable do you want to use? – ikegami Mar 01 '17 at 22:12
  • $SITENAME is local variable. – fabreg Mar 01 '17 at 22:28

3 Answers3

5

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"'
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Basically this script execute many commands remotely and this perl substitution is one of the last command. – fabreg Mar 01 '17 at 21:20
  • All commands, needs to be running remotely. so ssh root@$IPSRV "first command second command third command ..." and this perl substitution it's included in the commands – fabreg Mar 01 '17 at 21:31
  • to execute the substitution on a remote file – fabreg Mar 01 '17 at 21:50
  • You're telling me your have multiple commands to run because you want to perform substitutions on a remote file? What??? You're not making any sense. Do you have some kind of problem with the answer? – ikegami Mar 01 '17 at 22:09
0

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.

ikegami
  • 367,544
  • 15
  • 269
  • 518
salva
  • 9,943
  • 4
  • 29
  • 57
  • Unfortunately this is not an option - the script is invoked by bash so if I want to use your solution I need to add the invocation of this perl script into the bash one. Thanks anyway! – fabreg Mar 02 '17 at 21:26
  • ... or you could rewrite the full script in Perl! – salva Mar 03 '17 at 10:00
0

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!

fabreg
  • 123
  • 5