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!
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"'
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.
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!
Related
Stuck trying to figure out how to single quotes withing single quotes within double quotes. Here's what I'm trying to do....
From perl, I want to run a system command that...
- does an ssh into a remote machine
- executes 'uptime' and then plucks the last field out of that (avg load last 15 min).
\#\!/usr/bin/env perl
my $cmd = "ssh othermachine 'uptime | awk '{print $NF}'' > local_file.dat";
system($cmd);
Of course this won't run ...
% ./try.pl
Missing }.
%
Missing "}" ??? Looks like it's interpreting the $NF} as a var? I tried escaping the {} chars with no luck. I tried escaping the $, no luck. I tried a space before the }, no luck but different msg (Undefined variable).
c-shell BTW and thanks in advance !
You want the following to be ssh's second argument:
uptime | awk '{print $NF}'
To do that, you simply placed single quotes around it. But that doesn't work because it contains single quotes.
You want to build a string that contains $NF, but you did it as follows:
"...$NF..."
That will place the value of (non-existent) Perl variable $NF in the string.
Do it step by step.
Static:
Remote command:
uptime | awk '{print $NF}'
Local command:
ssh othermachine 'uptime | awk '\''{print $NF}'\''' >local_file.dat
String literal:
my $local_cmd = q{ssh othermachine 'uptime | awk '\''{print $NF}'\''' >local_file.dat}
Dynamic:
use String::ShellQuote qw( shell_quote );
my $remote_cmd = q{uptime | awk '{print $NF}'};
my $local_cmd = shell_quote('ssh', 'othermachine', $remote_cmd) . ' >local_file.dat';
Use Net::OpenSSH and let it do the quoting for you:
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new($othermachine,
remote_shell => 'tcsh');
$ssh->system({stdout_file => 'local_file.dat'},
'uptime', \\'|', 'awk', '{print $NF}')
or die "ssh command failed: " . $ssh->error;
I want to run this command in perl
for dir in *; do
test -d "$dir" && ( find "$dir" -name '*test' | grep -q . || echo "$dir" );
done
I have tried :
system ("for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done");
but does not work .
A pure Perl implementation using File::Find module's find function:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
find \&find_directories, '.';
sub find_directories {
if ( -d && $File::Find::name =~ /test$/ ) {
print "$File::Find::name\n";
}
}
Your quoting is off.
"for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done"
You have decided to delimit your string with double quotes ", but they are included in your string.
Either escape the other quotes:
"for dir in *; do
test -d \"\$dir\" && ( find \"\$dir\" -name '*test' | grep -q . || echo \"\$dir\" );
done"
(error prone, ugly)
… or use another delimiter: Perl offers you a wide range of possibilities. These quoting syntaxes interpolate variables inside: "…" and qq{…} where you can use any character in [^\s\w] as delimiter, and non-interpolating syntaxes are: '…' and q{…} with the same delimiter flexibility as before:
qq{for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done}
The q and qq constructs can include the delimiter inside the string, if the occurrence is balanced: q( a ( b ) c ) works.
The third quoting mechanism is a here-doc:
system( <<END_OF_BASH_SCRIPT );
for dir in *; do
test -d "\$dir" && ( find "\$dir" -name '*test' | grep -q . || echo "\$dir" );
done
END_OF_BASH_SCRIPT
This is usefull for including longer fragments without worrying about a delimitor. The String is ended by a predefined token that has to appear on a line of its own. If the delimitor declaration is placed in single quotes (<<'END_OF_SCRIPT'), no variables will be interpolated:
system( <<'END_OF_BASH_SCRIPT' );
for dir in *; do
test -d "$dir" && ( find "$dir" -name '*test' | grep -q . || echo "$dir" );
done
END_OF_BASH_SCRIPT
Note on the q{} and qq{} syntax: This is a feature never to be used outside of obfuscation, but it is possible to use a character in \w as the delimiter. You have to include a space between the quoting operator q or qq and the delimiter. This works: q xabcx and is equal to 'abc'.
Instead of starting the script, try starting a bash instance that runs the script. E.g.
system("bash -c 'for dir bla bla bla'");
system() uses your default system shell, which is probably not Bash. The solution is to call Bash explicitly with the system() command.
cat $INPUT_FILE| while read LINE
do
abc=cut -d ',' -f 4 $LINE
Perl:
cat $INPUT_FILE | perl -ne '{my #fields = split /,/; print $fields[3];}'
The key is to use command substitution if you want the output of a command saved in a variable.
POSIX shell (sh):
while read -r LINE
do
abc=$(cut -d ',' -f 4 "$LINE")
done < "$INPUT_FILE"
If you're using a legacy Bourne shell, use backticks instead of the preferred $():
abc=`cut -d ',' -f 4 "$LINE"`
In some shells, you may not need to use an external utility.
Bash, ksh, zsh:
while read -r LINE
do
IFS=, read -r f1 f2 f3 abc remainder <<< "$LINE"
done < "$INPUT_FILE"
or
while read -r LINE
do
IFS=, read -r -a array <<< "$LINE"
abc=${array[3]}
done < "$INPUT_FILE"
or
saveIFS=$IFS
while read -r LINE
do
IFS=,
array=($LINE)
IFS=$saveIFS
abc=${array[3]}
done < "$INPUT_FILE"
Bash:
while read line ; do
cut -d, -f4 <<<"$line"
done < $INPUT_FILE
Straight Perl:
open (INPUT_FILE, "<$INPUT_FILE") or die ("Could not open $INPUT_FILE");
while (<INPUT_FILE>) {
#fields = split(/,/, $_);
$use_this_field_value = $fields[3];
# do something with field value here
}
close (INPUT_FILE);
#!/bin/bash
i="0"
echo ""
echo "##################"
echo "LAUNCHING REQUESTS"
echo " COUNT: $2 "
echo " DELAY: $3 "
echo " SESSID: $1"
echo "##################"
echo ""
while [ $2 -gt "$i" ]
do
i=$[$i+1]
php avtest.php $1 $4 &
echo "EXECUTING REQUEST $i"
sleep $3
done
here is a better/modified script in bash
#!/bin/bash
i="0"
#startTime=`date +%s`
startTime=$(date -u +%s)
startTime=$[$startTime+$1+5]
#startTime=$($startTime+$1+5)
dTime=`date -d #$startTime`
echo ""
echo "##################"
echo "LAUNCHING REQUESTS"
echo " COUNT: $1 "
echo " DELAY: 1 "
#echo " EXECUTION: $startTime "
echo " The scripts will fire at : $dTime "
echo "##################"
echo ""
while [ $1 -gt "$i" ]
do
i=$[$i+1]
php avtestTimed.php $1 $3 $startTime &
echo "QUEUEING REQUEST $i"
sleep 1
done
Here's a direct translation
#!/usr/bin/env perl
use strict;
use warnings;
print <<HERE;
##################
LAUNCHING REQUESTS
COUNT: $ARGV[1]
DELAY: $ARGV[2]
SESSID: $ARGV[0]
##################
HERE
my $i = 0;
while($ARGV[1] > $i){
$i += 1;
system("php avtest.php $ARGV[0] $ARGV[3] &");
print "EXECUTING REQUEST $i\n";
sleep $ARGV[2];
}
But it would make more sense to read the command line parameters into variables named after what they're for and not rely on remembering argument ordering.
A brief errata in the conversion:
I use a here string to represent multiline text. I could also have put in multiple print statements to more closely mimic the bash version
In bash arguments are accessed as numbered variables, starting with $1 and going up. In Perl the argument list is represented by the array #ARGV, which is numbered starting at zero (like arrays in most languages). In both bash and Perl the name of the script can be found in the variable $0.
In Perl arrays are written as #arrayname when refering to the entire array, but they use $arrayname[index] when accessing array members. So the Perl $list[0] is like the bash ${list[0]} and the Perl #list is like the bash ${list[#]}.
In Perl variables are declared with the my keyword; the equivalent in bash would be declare.
I've used the system function for spawning background processes. Its argument can be simply the command line as you might use it in bash.
Unlike echo, print requires to be told if there should be a newline at the end of the line. For recent versions of Perl the say function exists which will append a newline for you.
The Perl sleep function is pretty self-explanatory.
EDIT: Due to a typo $i in the print statement had been represented as $ni leading to runtime errors. This has been corrected.
I have a file of 1000 lines, each line has 2 words, separated by a space. How can I print each line only if the last word length is greater than 7 chars? Can I use awk RLENGTH? is there an easy way in perl?
#OP, awk's RLENGTH is used when you call match() function. Instead, use the length() function to check for length of characters
awk 'length($2)>7' file
if you are using bash, a shell solution
while read -r a b
do
if [ "${#b}" -gt 7 ];then
echo $a $b
fi
done <"file"
perl -ane 'print if length($F[1]) > 7'
You can do:
perl -ne '#a=split/\s+/; print if length($a[1]) > 7' input_file.txt
Options used:
-n assume 'while () { ... }' loop around program
-e 'command' one line of program (several -e's allowed, omit programfile)
You can use the auto-split option as used by Chris
-a autosplit mode with -n or -p (splits $_ into #F)
perl -ane 'length $F[1] > 7 && print' <input_file>
perl -lane 'print if (length($F[$#F]) > 7)' fileName
or
perl -pae '$_ = "" if (length($F[$#F]) <= 7)' fileName