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;
Related
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!
I'm having some trouble with backtics and pipe in perl. I have following code:
my #arr_lsdev = `lsdev -C | grep inet | awk '{print \$1}'` ;
print Dumper #arr_lsdev ;
But I get following error:
sh[2]: 0403-057 Syntax error : `|' is not expected
I'm guessing it has something to with my escape commands. I have tried escaping the | but it still results in the same error.
OS: AIX
Shell: KSH
Notice that the error is on line 2. You are actually executing
my #arr_lsdev = `lsdev -C | grep inet
| awk '{print \$1}'` ;
You can reduce the number of pipes:
my #arr_lsdev = map {(split ' ')[0]} grep {/inet/} `lsdev -C`;
I have a command in perl
system("grep LISTEN /root/Desktop/m.txt | awk {'print $5'} | cut -f 1 -d ":"| sort | uniq > /tmp/entropy/dsip.txt");
The output error near $5.
How to write $5 the same string, not a valuable ?
Calling awk and cut and sort from Perl? Really? Perl can do all the work itself:
open my $LISTEN, '<', '/root/Desktop/m.txt' or die $!;
my %uniq;
while (<$LISTEN>) {
next unless /LISTEN/;
my $fifth = (split)[4];
$fifth =~ s/:.*//;
undef $uniq{$fifth};
}
print $_, "\n" for sort keys %uniq;
Use non interpolating parentheses,
system(q{grep LISTEN ..});
Just escape the $s:
print "\$literal\n";
to prevent Perl from interfering. Also escape the $s, to prevent shell from interfering:
system("echo \\\$1");
in which case shell will see \$1 and echo will see $1.
awk {'print $5'}
should be
awk '{print $5}'
ikegami says: The above is wrong since the two are equivalent
$ perl -E'say "<$_>" for #ARGV' awk {'print $5'}
<awk>
<{print $5}>
$ perl -E'say "<$_>" for #ARGV' awk '{print $5}'
<awk>
<{print $5}>
I'm trying to turn a big list of data into a CSV. Its basically a giant list with no spaces, and the rows are separated by newlines. I have made a bash script that basically loops through the document, awks out the line, cuts the byte range, and then adds a comma and appends it to the end of the line. It looks like this:
awk -v n=$x 'NR==n { print;exit}' PROP.txt | cut -c 1-12 | tr -d '\n' >> $x.tmp
awk -v n=$x 'NR==n { print;exit}' PROP.txt | cut -c 13-17 | tr -d '\n' | xargs -I {} sed -i '' -e 's~$~,{}~' $x.tmp
awk -v n=$x 'NR==n { print;exit}' PROP.txt | cut -c 18-22 | tr -d '\n' | xargs -I {} sed -i '' -e 's~$~,{}~' $x.tmp
awk -v n=$x 'NR==n { print;exit}' PROP.txt | cut -c 23-34 | tr -d '\n' | xargs -I {} sed -i '' -e 's~$~,{}~' $x.tmp
The problem is this is EXTREMELY slow, and the data has about 400k rows. I know there must be a better way to accomplish this. Essentially I just need to add a comma after every 12/17/22/34 etc character of a line.
Any help is appreciated, thank you!
There are many many ways to do this with Perl. Here is one way:
perl -pe 's/(.{12})(.{5})(.{5})(.{12})/$1,$2,$3,$4,/' < input-file > output-file
The matching pattern in the substitution captures four groups of text from the beginning of each line with 12, 5, 5, and 12 arbitrary characters. The replacement pattern places a comma after each group.
With GNU awk, you could write
gawk 'BEGIN {FIELDWIDTHS="12 5 5 12"; OFS=","} {$1=$1; print}'
The $1=$1 part is to force awk to rewrite the like, incorporating the output field separator, without changing anything.
This is very much a job for substr.
use strict;
use warnings;
my #widths = (12, 5, 5, 12);
my $offset;
while (my $line = <DATA>) {
for my $width (#widths) {
$offset += $width;
substr $line, $offset, 0, ',';
++$offset;
}
print $line;
}
__DATA__
1234567890123456789012345678901234567890
output
123456789012,34567,89012,345678901234,567890
I am having trouble on stripping characters within a filename.
For example:
1326847080_MUNDO-Cinco-Cosas-Que-Aprendimos-Del-Debate-De-Los-Republicanos-1.xml
1326836220_PLANETACNN-Una-Granja-De-Mariposas-Ayuda-A-Reducir-La-Tala-De-Bosques-En-Tanzania-3.xml
This is the output I want:
1326847080_MUNDO-1.xml
1326836220_PLANETACNN-3.xml
for i in *.xml
do
j=$(echo $i | sed -e s/-.*-/-/)
echo mv $i $j
done
or in one line:
for i in *.xml; do echo mv $i $(echo $i | sed -e s/-.*-/-/); done
remove echo to actually perform the mv command.
Or, without sed, using bash builtin pattern replacement:
for i in *.xml; do echo mv $i ${i//-*-/-}; done
rename to the rescue, with Perl regular expressions. This command will show which moves will be made; just remove -n to actually rename the files:
$ rename -n 's/([^-]+)-.*-([^-]+)/$1-$2/' *.xml
1326836220_PLANETACNN-Una-Granja-De-Mariposas-Ayuda-A-Reducir-La-Tala-De-Bosques-En-Tanzania-3.xml renamed as 1326836220_PLANETACNN-3.xml
1326847080_MUNDO-Cinco-Cosas-Que-Aprendimos-Del-Debate-De-Los-Republicanos-1.xml renamed as 1326847080_MUNDO-1.xml
The regular expression explained:
Save the part up to (but excluding) the first dash as match 1.
Save the part after the last dash as match 2.
Replace the part from the start of match 1 to the end of match 2 with match 1, a dash, and match 2.
sorry for the late reply , but i saw it today :( .
I think you are looking for the following
input file ::
cat > abc
1326847080_MUNDO-Cinco-Cosas-Que-Aprendimos-Del-Debate-De-Los-Republicanos-1.xml
1326836220_PLANETACNN-Una-Granja-De-Mariposas-Ayuda-A-Reducir-La-Tala-De-Bosques-En-Tanzania-3.xml
code : (its a bit too basic , even for my liking)
while read line
do
echo $line ;
fname=`echo $line | cut -d"-" -f1`;
lfield=`echo $line | sed -n 's/\-/ /gp' | wc -w`;
lname=`echo $line | cut -d"-" -f${lfield}`;
new_name="${fname}-${lname}";
echo "new name is :: $new_name";
done < abc ;
output ::
1326847080_MUNDO-Cinco-Cosas-Que-Aprendimos-Del-Debate-De-Los-Republicanos-1.xml
new name is :: 1326847080_MUNDO-1.xml
1326836220_PLANETACNN-Una-Granja-De-Mariposas-Ayuda-A-Reducir-La-Tala-De-Bosques-En-Tanzania-3.xml
new name is :: 1326836220_PLANETACNN-3.xml