Perl command-line script and shell variables: How to quote? - perl

I have a shell script (csh) calling Perl like this:
set SHELL_VAR = "foo"
set RES = `perl -e 'my $perl_var = uc("$SHELL_VAR"); print "$perl_var\n"'`
echo $RES
I did not manage to use single or double quotes properly, no matter which combination I tried.
How do I properly mix variables in Perl and shell?
Both are starting with $. Double quotes use variable values, but returns
error perl_var: Undefined variable.
in shell. Enclosing a Perl script by single quotes led to an empty result. Escaping like \$perl_var does not succeed either.

You are trying to insert the value of a shell var into a Perl literal of a program that's in a command line. That's two levels of escaping! Instead, just pass it to Perl as an argument.
% set SHELL_VAR = "foo"
% set RES = `perl -e'print uc($ARGV[0])' "$SHELL_VAR"`
% echo "$RES"
FOO
Doh! It's not quite right.
csh% set SHELL_VAR = foo\"\'\(\ \ \ \$bar
csh% set RES = `perl -e'print uc($ARGV[0])' "$SHELL_VAR"`
csh% echo "$RES"
FOO"'( $BAR
I should get
FOO"'( $BAR
In sh, I'd use RES="` ... `" aka "$( ... "), but I don't know the csh equivalent. If someone could fix the above so multiple spaces are printed, I'd appreciate it!
sh$ shell_var=foo\"\'\(\ \ \ \$bar
sh$ res="$( perl -e'print uc($ARGV[0])' "$shell_var" )"
sh$ echo "$res"
FOO"'( $BAR

You can push your SHELL_VAR into the environment and let Perl pull it from there:
#!/bin/csh
setenv SHELL_VAR "foo"
set RES = `perl -e 'my $perl_var = uc($ENV{SHELL_VAR}); print "$perl_var\n"'`
echo $RES
Note that the setenv command lacks the expicit '=' for assignment. The environmental variable is availab e to Perl from the %ENV hash with the variable name as its key.

I found another solution, you can simply concatenate several expressions, i.e. you can write
set XYZ = "foo"`date`bar$HOST'gugus'
for example. This is a concatenation of
foo + `date` + bar + $HOST + gugus
Thus the following works:
set SHELL_VAR = "foo"
set RES = `perl -e 'my $perl_var = uc("'$SHELL_VAR'"); print "$perl_var\n"'`
echo $RES

Related

perl parse command line arguments using shift command

I have a question regarding parsing command line arguments and the use of the shift command in Perl.
I wanted to use this line to launch my Perl script
/home/scripts/test.pl -a --test1 -b /path/to/file/file.txt
So I want to parse the command line arguments. This is part of my script where I do that
if ($arg eq "-a") {
$main::john = shift(#arguments);
} elsif ($arg eq "-b") {
$main::doe = shift(#arguments);
}
I want to use then these arguments in a $command variable that will be executed afterwards
my $var1=$john;
my $var2=$doe;
my $command = "/path/to/tool/tool --in $line --out $outputdir $var1 $var2";
&execute($command);
Now here are two problems that I encounter:
It should not be obligatory to specify -a & -b at the command line. But what happens now is that when I don't specify -a, I get the message that I'm using an uninitialized value at the line where the variable is defined
Second problem: $var2 will now equal $doe so it will be in this case /path/to/file/file.txt. However I want $var2 to be equal to --text /path/to/file/file.txt. Where should I specify this --text. It cannot be standardly in the $command, because then it will give a problem when I don't specify -b. Should I do it when I define $doe, but how then?
You should build your command string according to the contents of the variables
Like this
my $var1 = $john;
my $var2 = $doe;
my $command = "/path/to/tool/tool --in $line --out $outputdir";
$command .= " $var1" if defined $var1;
$command .= " --text $var2" if defined $var2;
execute($command);
Also
Don't use ampersands & when you are calling Perl subroutine. That hasn't been good practice for eighteen years now
Don't use package variables like $main:xxx. Lexical variables (declared with my) are almost all that is necessary
As Alnitak says in the comment you should really be using the Getopt::Long module to avoid introducing errors into your command-line parsing
GetOpt::Long might be an option: http://search.cpan.org/~jv/Getopt-Long-2.48/lib/Getopt/Long.pm
Regarding your sample:
You didn't say what should happen if -a or -b are missing, but defaults may solve your problem:
# Use 'join' as default if $var1 is not set
my $var1 = $john // 'john';
# Use an empty value as default if $var2 is not set
my $var2 = $doe // '';
Regarding the --text prefix:
Do you want to set it always?
my $command = "/path/to/tool/tool --in $line --out $outputdir $var1 --text $var2";
Or do you want to set it if -b = $var2 has been set?
# Prefix
my $var2 = "--text $john";
# Prefix with default
my $var2 = defined $john ? "--text $john" : '';
# Same, but long format
my $var2 = ''; # Set default
if ($john) {
$var2 = "--text $john";
}

How to use pipe in perl

My syntax is
my $pstree = `pstree -p $pid|wc`;
but i am getting an error.
sh: -c: line 1: syntax error near unexpected token `|'
any thoughts?
Your variable $pid isn't just a number; it probably has a trailing newline character.
See it with:
use Data::Dumper;
print Data::Dumper->new([$pid])->Terse(1)->Useqq(1)->Dump;
It's valid perl, your shell is what is complaining. Did you put the #!/bin/perl at the top of the script? It's probably being interpreted by bash, not perl.
host:/var/tmp root# ./try.pl
5992 zsched
6875 /usr/local/sbin/sshd -f /usr/local/etc/sshd_config
3691 /usr/local/sbin/sshd -f /usr/local/etc/sshd_config -R
3711 -tcsh
6084 top 60
===
5 16 175
host:/var/tmp root# cat try.pl
#!/bin/perl
my $pstree = `ptree 3691`;
my $wc = `ptree 3691 | wc`;
print STDOUT $pstree;
print STDOUT "===\n";
print STDOUT $wc;
Instead of using the shell to do your counting, you can use Perl, which saves you a process and some complexity in your shell command:
my $count = () = qx(pstree -p $pid);
qx() does the same thing as backticks. The empty parentheses puts the qx() in list context, which makes it return a list, which then in scalar context is the size. It is a shortcut for:
my #list = qx(pstree -p $pid);
my $count = #list;

How can I let perl interpret a string variable that represents an address

I want to feed input to a C program with a perl script like this
./cprogram $(perl -e 'print "\xab\xcd\xef";').
However, the string must be read from a file. So I get something like this:
./cprogram $(perl -e 'open FILE, "<myfile.txt"; $file_contents = do { local $/; <FILE> }; print $file_contents'. However, now perl interprets the string as the string "\xab\xcd\xef", and I want it to interpret it as the byte sequence as in the first example.
How can this be achieved? It has to be ran on a server without File::Slurp.
In the first case, you pass the three bytes AB CD EF (produced by the string literal "\xAB\xCD\xEF") to print.
In the second case, you must be passing something other than those three bytes to print. I suspect you are passing the twelve character string \xAB\xCD\xEF to print.
So your question becomes: How does one convert the twelve-character string \xAB\xCD\xEF into the three bytes AB CD EF. Well, you'd require some kind of parser such as
s/\\x([0-9a-fA-F][0-9a-fA-F])|\\([^x])|([^\\]+)/
$1 ? chr(hex($1)) : $2 ? $2 : $3
/eg
And here it is at work:
$ perl -e'print "\\xAB\\xCD\\xEF";' >file
$ echo -n "$( perl -0777pe'
s{\\x([0-9a-fA-F][0-9a-fA-F])|\\([^x])|([^\\]+)}{
$1 ? chr(hex($1)) : $2 // $3
}eg;
' file )" | od -t x1
0000000 ab cd ef
0000003
Is Perl's eval too evil? If not, end in print eval("\"$file_contents\"");
Or can you prepare the file in advance using Perl? EG print FILE "\xAB\xCD\xED"; then read the resulting file with your existing code.
using a bash trick:
perl -e "$(echo "print \"$(cat input)"\")"
which for your example becomes:
./cprogram "$(perl -e "$(echo "print \"$(cat myfile.txt)"\")")"

variable for field separator in perl

In awk I can write: awk -F: 'BEGIN {OFS = FS} ...'
In Perl, what's the equivalent of FS? I'd like to write
perl -F: -lane 'BEGIN {$, = [what?]} ...'
update with an example:
echo a:b:c:d | awk -F: 'BEGIN {OFS = FS} {$2 = 42; print}'
echo a:b:c:d | perl -F: -ane 'BEGIN {$, = ":"} $F[1] = 42; print #F'
Both output a:42:c:d
I would prefer not to hard-code the : in the Perl BEGIN block, but refer to wherever the -F option saves its argument.
To sum up, what I'm looking for does not exist:
there's no variable that holds the argument for -F, and more importantly
Perl's "FS" is fundamentally a different data type (regular expression) than the "OFS" (string) -- it does not make sense to join a list of strings using a regex.
Note that the same holds true in awk: FS is a string but acts as regex:
echo a:b,c:d | awk -F'[:,]' 'BEGIN {OFS=FS} {$2=42; print}'
outputs "a[:,]42[:,]c[:,]d"
Thanks for the insight and workarounds though.
You can use perl's -s (similar to awk's -v) to pass a "FS" variable, but the split becomes manual:
echo a:b:c:d | perl -sne '
BEGIN {$, = $FS}
#F = split $FS;
$F[1] = 42;
print #F;
' -- -FS=":"
If you know the exact length of input, you could do this:
echo a:b:c:d | perl -F'(:)' -ane '$, = $F[1]; #F = #F[0,2,4,6]; $F[1] = 42; print #F'
If the input is of variable lengths, you'll need something more sophisticated than #f[0,2,4,6].
EDIT: -F seems to simply provide input to an automatic split() call, which takes a complete RE as an expression. You may be able to find something more suitable by reading the perldoc entries for split, perlre, and perlvar.
You can sort of cheat it, because perl is actually using the split function with your -F argument, and you can tell split to preserve what it splits on by including capturing parens in the regex:
$ echo a:b:c:d | perl -F'(:)' -ane 'print join("/", #F);'
a/:/b/:/c/:/d
You can see what perl's doing with some of these "magic" command-line arguments by using -MO=Deparse, like this:
$ perl -MO=Deparse -F'(:)' -ane 'print join("/", #F);'
LINE: while (defined($_ = <ARGV>)) {
our(#F) = split(/(:)/, $_, 0);
print join('/', #F);
}
-e syntax OK
You'd have to change your #F subscripts to double what they'd normally be ($F[2] = 42).
Darnit...
The best I can do is:
echo a:b:c:d | perl -ne '$v=":";#F = split("$v"); $F[1] = 42; print join("$v", #F) . "\n";'
You don't need the -F: this way, and you're only stating the colon once. I was hoping there was someway of setting variables on the command line like you can with Awk's -v switch.
For one liners, Perl is usually not as clean as Awk, but I remember using Awk before I knew of Perl and writing 1000+ line Awk scripts.
Trying things like this made people think Awk was either named after the sound someone made when they tried to decipher such a script, or stood for AWKward.
There is no input record separator in Perl. You're basically emulating awk by using the -a and -F flags. If you really don't want to hard code the value, then why not just use an environmental variable?
$ export SPLIT=":"
$ perl -F$SPLIT -lane 'BEGIN { $, = $ENV{SPLIT}; } ...'

perl backticks: use bash instead of sh

I noticed that when I use backticks in perl the commands are executed using sh, not bash, giving me some problems.
How can I change that behavior so perl will use bash?
PS. The command that I'm trying to run is:
paste filename <(cut -d \" \" -f 2 filename2 | grep -v mean) >> filename3
The "system shell" is not generally mutable. See perldoc -f exec:
If there is more than one argument in LIST, or if LIST is an array with more than one value, calls execvp(3) with the arguments in LIST. If
there is only one scalar argument or an array with one element in it, the argument is checked for shell metacharacters, and if there are any, the
entire argument is passed to the system's command shell for parsing (this is "/bin/sh -c" on Unix platforms, but varies on other platforms).
If you really need bash to perform a particular task, consider calling it explicitly:
my $result = `/usr/bin/bash command arguments`;
or even:
open my $bash_handle, '| /usr/bin/bash' or die "Cannot open bash: $!";
print $bash_handle 'command arguments';
You could also put your bash commands into a .sh file and invoke that directly:
my $result = `/usr/bin/bash script.pl`;
Try
`bash -c \"your command with args\"`
I am fairly sure the argument of -c is interpreted the way bash interprets its command line. The trick is to protect it from sh - that's what quotes are for.
This example works for me:
$ perl -e 'print `/bin/bash -c "echo <(pwd)"`'
/dev/fd/63
To deal with running bash and nested quotes, this article provides the best solution: How can I use bash syntax in Perl's system()?
my #args = ( "bash", "-c", "diff <(ls -l) <(ls -al)" );
system(#args);
I thought perl would honor the $SHELL variable, but then it occurred to me that its behavior might actually depend on your system's exec implementation. In mine, it seems that exec
will execute the shell
(/bin/sh) with the path of the
file as its first argument.
You can always do qw/bash your-command/, no?
Create a perl subroutine:
sub bash { return `cat << 'EOF' | /bin/bash\n$_[0]\nEOF\n`; }
And use it like below:
my $bash_cmd = 'paste filename <(cut -d " " -f 2 filename2 | grep -v mean) >> filename3';
print &bash($bash_cmd);
Or use perl here-doc for multi-line commands:
$bash_cmd = <<'EOF';
for (( i = 0; i < 10; i++ )); do
echo "${i}"
done
EOF
print &bash($bash_cmd);
I like to make some function btck (which integrates error checking) and bash_btck (which uses bash):
use Carp;
sub btck ($)
{
# Like backticks but the error check and chomp() are integrated
my $cmd = shift;
my $result = `$cmd`;
$? == 0 or confess "backtick command '$cmd' returned non-zero";
chomp($result);
return $result;
}
sub bash_btck ($)
{
# Like backticks but use bash and the error check and chomp() are
# integrated
my $cmd = shift;
my $sqpc = $cmd; # Single-Quote-Protected Command
$sqpc =~ s/'/'"'"'/g;
my $bc = "bash -c '$sqpc'";
return btck($bc);
}
One of the reasons I like to use bash is for safe pipe behavior:
sub safe_btck ($)
{
return bash_btck('set -o pipefail && '.shift);
}