Perl String Interpolation in Bash Command - perl

I'm trying to use GNU Date to get the seconds between two dates. The reason I'm using GNU Date is for performance (in testing was 10x faster than Perl) for this purpose. However, one of my arguments is a perl variable. Like this:
my $b_row="2012-01-05 20:20:22";
my $exec =qx'CUR_DATE=`echo $(date +"%F %T")` ; echo $(($(date -d "$CUR_DATE" +%s)-$(date -d "$b_row" +%s)))';
The problem is that b_row is not being expanded. I've tried a couple different solutions (IPC::System::Simple) being one, tried adjusting the backticks etc. No success, any ideas how to do this appropriately? The main thing is I need to capture the output from the bash command.

Make it easier on yourself and do the minimum amount of work in the shell. This works for me:
my $b_row = '2012-01-05 20:20:22';
my $diff = qx(date -d "\$(date +'%F %T')" +%s) -
qx(date -d "$b_row" +%s);
Just be absolutely sure $b_row doesn't have any shell metacharacters in it.

That's because you use ' :
Using single-quote as a delimiter protects the command from
Perl's double-quote interpolation, passing it on to the shell
instead:
$perl_info = qx(ps $$); # that's Perl's $$
$shell_info = qx'ps $$'; # that's the new shell's $$

qx has the feature of letting you choose a convenient delimiter, including the option of whether to interpolate the string or not (by choosing ' as the delimiter). For this use case, sometimes you want interpolation and sometimes you don't, so qx (and backticks) may not be the right tool for the job.
readpipe is probably a better tool. Like the system EXPR command, it takes an arbitrary scalar as input, and you have all of Perl's tools at your disposal to construct that scalar. One way to do it is:
my $exec = readpipe
'CUR_DATE=`echo $(date +"%F %T")` ;' # interp not desired
. ' echo $(($(date -d "$CUR_DATE" +%s)-$(date -d "'
. qq/"$b_row"/ # now interp is desired
. ' +%s)))'; # interp not desired again

Related

ksh epoch to datetime format

I have a script for find the expire date of any user password. Script can find the expire date in seconds (epoch) but cannot convert this to datetime format.
#!/usr/bin/ksh
if (( ${#} < 1 )) ; then
print "No Arguments"
exit
fi
lastupdate=`pwdadm -q $1|awk '{print $3;}'`
((lastupdate=$lastupdate+0))
maxagestr=`lsuser -a maxage $1`
maxage=${maxagestr#*=}
let maxageseconds=$maxage*604800
expdateseconds=$(expr "$maxageseconds" + "$lastupdate")
((expdateseconds=$expdateseconds+0))
expdate=`perl -le 'print scalar(localtime($expdateseconds))'`
echo $expdateseconds
echo $expdate
In this script, expdateseconds value is true. If I type the value of expdateseconds as a parameter of localtime() function, the function show the date in datetime format.
But if I type the $expdateseconds variable, the function does not work true and return 01.01.1970 always.
How can I enter a variable as a parameter of localtime() function?
Shell variables are not expanded within single quotes. So in your code, perl is not "seeing" the shell variable, it is instead seeing an uninitialized perl variable whose value defaults to zero. Shell variables are expanded within double quotes, so in this case that's all you need to do:
expdate=`perl -le "print scalar(localtime($expdateseconds))"`
As #JeffY said, your problem is the quotes. You can also do it without perl (assuming your date command is the GNU version):
expdate=`date -d #$expdateseconds`
Although, since you're using ksh - and really, any modern POSIX shell - I recommend that you avoid `...`, which can cause confusing behavior with quoting, and use $(...) instead.
expdate=$(date -d #$expdateseconds)
This isn't codereview, but I have a few other tips regarding your script. The usual rule is to send error messages (like "No arguments") to standard error instead of standard out (with print -u2) and exit with a nonzero value (typically 1) when there's a usage error.
Whenever passing a parameter to a command, like pwadm -q $1, you run the risk of funny characters messing things up unless you double-quote the parameter: pwadm -q "$1".
You have an odd mixture of let, ((, and expr in your arithmetic. I would suggest that you declare all your numeric variables with typeset -ivarname and just use ((...)) for all arithmetic. Inside ((...)), you don't have to worry about globbing messing things up (let a=b*c will expand into a syntax error if you have a file in the current directory named a=b.c, for instance; (( a=b*c )) won't). You also don't need to put dollar signs on the variables (which just makes the shell convert them to a string and then parse their numeric value out again), or add 0 to them just to make sure they're numbers.
There is no need to use neither awk nor perl. E.g.:
#!/usr/bin/ksh93
[[ -z $1 ]] && print -u2 "No Arguments" && exit 1
typeset -i LASTUPDATE S
X=( ${ pwdadm -q "$1" ; } )
LASTUPDATE=${X[3]}
X=${ lsuser -a maxage "$1" ; }
S=${X#*=}
(( S *= 604800 ))
(( S += LASTUPDATE ))
printf "$S\n%T\n" "#$S"

Perl Variables in system()

I'm trying to access a series of webpages in perl and write them to a series of files. The code I have looks like this:
open IN , "AbsoluteFinalData.txt"; #Each line has a number and ID name separated by a tab.
while(my $line = <IN>){
chop $line; #removes newline at the end
my #first_split = split(/\t/, $line);
my $IDnum = $first_split[0];
my $Uniprot = $first_split[1];
system('Uniprot=$Uniprot; curl -o "$Uniprot.html" http://pfam.xfam.org/\protein/.$Uniprot'); #More stuff after
The program, however, is giving me fits when I try to call $Uniprot in system(). Is there any way to call a variable defined in the perl script using system()?
system('Uniprot=$Uniprot; curl -o "$Uniprot.html" http://pfam.xfam.org/\protein/.$Uniprot');
You use single quotes, which doesn't interpolate. The literal command:
Uniprot=$Uniprot; curl -o "$Uniprot.html" http://pfam.xfam.org/\protein/.$Uniprot
Is being executed.
You want to interpolate your variables, which means using double quotes (and escaping contained ones:)
system("Uniprot=$Uniprot; curl -o \"$Uniprot.html\" http://pfam.xfam.org/\protein/.$Uniprot");
Or the qq quote-like operator which functions like the double quote but avoids needing to escape contained double quotes:
system(qq(Uniprot=$Uniprot; curl -o "$Uniprot.html" http://pfam.xfam.org/\protein/.$Uniprot"));
Given that you're not relying on the system command to perform any shell interpretation or file I/O redirection, you can achieve everything you want safely like this:
system 'curl', '-o', "$Uniprot.html", "http://pfam.xfam.org/protein/.$Uniprot";
The "list" version of system is safer to use than the single string version because it prevents shell command injection attacks.
Note also the use of double quotes to enable Perl's own variable interpolation, and also that there's no need to create the shell local variable Uniprot=$Uniprot since it's not used by Curl and is only being used by you to attempt to perform variable interpolation yourself.
Perl only interpolates variables within double quotes ("..."), not single quotes ('...').
system("Uniprot=$Uniprot; curl -o \"$Uniprot.html\" http://pfam.xfam.org/\protein/.$Uniprot");
Will do the substitution you're looking for.

Why is '\t', returned from "backtick" call, expanded to tabulator? What all characters are expanded?

If I run a simple perl script like this (on linux, with bash),
$to_run = q(echo '\t');
$res = `$to_run`;
print $res
I would expect that \t will be printed - that is, the backslash character and "t" character. Indeed, if I run just in bash
echo '\t'
I see \t. However, the perl script prints the tabulator.
Why is the tabulator expanded in $res? What all characters are expanded like that? And, most importantly, how do I stop it from expanding?
Backticks are evaluated using /bin/sh, regardless of whatever shell you may want to use, and it's the POSIX XSI-conformant version of echo implemented by sh that's converting \t to a tab. Try it out yourself by running echo '\t' inside sh.
For avoiding this behavior, trying using printf '%s\n' instead of echo in backticks.

How to capture single quote when using Perl in CLi?

Suppose I have a text file with content like below:
'Jack', is a boy
'Jenny', is a girl
...
...
...
I'd like to use perl in Cli to only capture the names between pairs of single quotes
cat text| perl -ne 'print $1."\n" if/\'(\w+?)\'/'
Above command was what I ran but it didn't work. It seems like "'" messed up with Shell.
I know we have other options like writing a perl script. But given my circumstances, I'd like to find a way to fulfill this in Shell command line.
Please advise.
The shell has the interesting property of concatenating quoted strings. Or rather, '...' or "..." should not be considered strings, but modifiers for available escapes. The '...'-surrounded parts of a command have no escapes available. Outside of '...', a single quote can be passed as \'. Together with the concatenating property, we can embed a single quote like
$ perl -E'say "'\''";'
'
into the -e code. The first ' exits the no-escape zone, \' is our single quote, and ' re-enters the escapeless zone. What perl saw was
perl // argv[0]
-Esay "'"; // argv[1]
This would make your command
cat text| perl -ne 'print $1."\n" if/'\''(\w+?)'\''/'
(quotes don't need escaping in regexes), or
cat text| perl -ne "print \$1.qq(\n) if/'(\w+?)'/"
(using double quotes to surround the command, but using qq// for double quoted strings and escaping the $ sigil to avoid shell variable interpolation).
Here are some methods that do not require manually escaping the perl statement:
(Disclaimer: I'm not sure how robust these are – they haven't been tested extensively)
Cat-in-the-bag technique
perl -ne "$(cat)" text
You will be prompted for input. To terminate cat, press Ctrl-D.
One shortcoming of this: The perl statement is not reusable. This is addressed by the variation:
$pline=$(cat)
perl -ne "$pline" text
The bash builtin, read
Multiple lines:
read -rd'^[' pline
Single line:
read -r pline
Reads user input into the variable pline.
The meaning of the switches:
-r: stop read from interpreting backslashes (e.g. by default read interprets \w as w)
-d: determines what character ends the read command.
^[ is the character corresponding to Esc, you insert ^[ by pressing Ctrl-V then Esc.
Heredoc and script.
(You said no scripts, but this is quick and dirty, so might as well...)
cat << 'EOF' > scriptonite
print $1 . "\n" if /'(\w+)'/
EOF
then you simply
perl -n scriptonite text

How to expand variable literally when calling perl from csh script?

Below is a csh script.
#! /bin/csh
set alpha=10\20\30;
set beta = $alpha.alpha;
perl -p -i.bak -e 's/gamma/'$beta'/' tmp;
The tmp file contains just the word gamma. After running tmp.csh, I expect 10\20\30.alpha in tmp, but it's now 102030.alpha.
How to preserve slashes in this situation?
Note: I wouldn't prefer changing definition of alpha variable, as it is used in the script else where where it needs to be in this format (10\20\30) only.
Thanks.
In csh, for your alpha assignment, the backslash is being taken to mean 'a literal 2 or 3'. In order to keep csh from doing this, the assignment needs to be enclosed in quotes.
#! /bin/csh
set alpha="10\20\30";
set beta = $alpha.alpha;
perl -p -i.bak -e 's/gamma/'$beta'/' tmp;
If in doubt, it's often helpful to 'echo' your variables out to see exactly what they contain. I don't understand your final note, as the 'alpha' variable is not equal to 10\20\30 the way you have it originally assigned.