I cannot use %ENV var on my Perl script to use Oracle libs.
BEGIN {
$ORACLE_HOME = "/usr/lib/oracle/10.2.0.3/client64";
$LD_LIBRARY_PATH = "$ORACLE_HOME/lib";
$ORACLE_SID="prod";
$ENV{ORACLE_SID}=$ORACLE_SID;
$ENV{ORACLE_HOME}= $ORACLE_HOME;
$ENV{LD_LIBRARY_PATH}= $LD_LIBRARY_PATH;
};
If I print $ENV{'ORACLE_HOME'} and $ENV{'LD_LIBRARY_PATH'} all seems ok but, when I run my script I have the error:
install_driver(Oracle) failed: Can't load '/usr/local/lib64/perl5/auto/DBD/Oracle/Oracle.so' for module DBD::Oracle: libclntsh.so.10.1: cannot open shared object file: No such file or directory at /usr/lib64/perl5/DynaLoader.pm line 200.
at (eval 3) line 3
Compilation failed in require at (eval 3) line 3.
Perhaps a required shared library or dll isn't installed where expected
at persistence.perl line 22
Searching on web I saw that the correct way to set env vars on Perl is to use %ENV hash.
Exporting ORACLE_HOME and LD_LIBRARY_PATH through unix shell (export LD_LIBRARY_PATH=...) it works correctly. Any advice?
The LD_LIBRARY_PATH environment variable has to be set before your program starts — before perl itself is loaded. Changing it in BEGIN{} will affect new programs that you start, but it won't affect the loading of shared libraries — in this case (although I've never used the DBD::Oracle) you're loading an Oracle .so into the already-running program, so it's “too late” to change the LD_LIBRARY_PATH. The dynamic linker /lib/ld.so (or so) is started before perl, so by the time your script is compiled and BEGIN{} runs, it's already set up.
You could try to re-exec your script as its own successor or something*, but a short shell script is almost certainly going to be the simplest solution:
#!/bin/sh
export LD_LIBRARY_PATH=/usr/lib/oracle/10.2.0.3/client64/lib
export ORACLE_SID=prod
exec /usr/local/bin/your-db-program "$#"
*- this would be kinda crazy, but TIMTOWTDI:
eval {
use DBD::Oracle foo bar baz; …
};
if ($# =~ /install_driver\(Oracle\) failed/) {
$ENV{LD_LIBRARY_PATH} .= ':/usr/lib/oracle/10.2.0.3/client64/lib';
$ENV{ORACLE_SID} = 'prod';
warn "Restarting with LD_LIBRARY_PATH reset:\n\n$#\n";
exec { $0 } $0 => #ARGV;
}
I wrote a few test scripts to verify that the environment is being set when you change %ENV:
use strict;
use warnings;
use feature qw(say);
BEGIN {
my $foo = "bar-bar";
$ENV{FOO} = "$foo";
}
system qq(/bin/echo printing out \$FOO);
This prints out:
printing out bar-bar
which is what I expected.
I then tried this:
use strict;
use warnings;
use feature qw(say);
BEGIN {
my $foo = "bar-bar";
$ENV{FOO} = "$foo";
}
system qq(./test.sh);
and created a test.sh program that looks like this:
#! /bin/sh
echo This is what I got: $FOO;
In this case, my Perl script is running test.sh which prints out the value of the $FOO environment variable that was set in my Perl script. Running test.pl I get:
This is what I got bar-bar
This shows that not only is Perl setting the environment variables, but that it is also exporting those variables, so called shell scripts have access to them.
You can try a similar technique to verify that both LD_LIBRARY_PATH and ORACLE_HOME are being set before they're used. I suspect you'll find that this is indeed happening, but that your program still isn't working when you set %ENV.
This points to one conclusion: Setting the environment for LD_LIBRARY_PATH and ORACLE_HOME might be occurring too late by the time your Perl script starts. I believe the operating system examines LD_LIBRARY_PATH before Perl starts. I found this doing a search on LD_LIBRARY_PATH:
LD_LIBRARY_PATH is an environment variable you set to give the run-time shared library loader (ld.so) an extra set of directories to look for when searching for shared libraries. Multiple directories can be listed, separated with a colon (:). This list is prepended to the existing list of compiled-in loader paths for a given executable, and any system default loader paths.
So, LD_LIBRARY_PATH is for the ld.so runtime shared library loader, If ld.so has already been loaded, changing LD_LIBRARY_PATH won't do anything.
I found a similar discussion on Perl Monks. I noticed someone found rerunning env seemed to work.
One solution is to modify /etc/ld.so.conf
On CentOS/RHEL 6.4, you could create etc/ld.so.conf.d/oracle with this:
/oracle/sw/product/11.2.0/dbhome_1/lib
Obviously, modify as suits your ORACLE_HOME.
Then run
ldconfig -v
You could put the export commands into the start up script for your unix shell which you should have permission to edit. That way, the environment variables will be set whenever you start a new shell and all scripts and programs that use Oracle will pick them up.
I just went through something similar. I had to make sure that the Oracle environment was setup before anything else called it. Make sure the BEGIN block is before any other "use" statements. In my case, something was being called in Apache's httpd.conf file, so I had to setup my environment there instead of in my package.
Related
I'm currently trying to set up properly a daemon to run a task on a remote machine. The thing is, I can launch the command manually properly, but when it's launched by the daemon, it won't work because the environment variables haven't been set up.
Before using my command manually, I have to do this:
sudo -s
export HOME=/home/tim
cd ~
export PERL5LIB=/usr/local/my/lib
export PATH=/my/path/to/tools/bin
And then I run my command. So I suspect it won't work if the daemon hasn't set up this. How can I do that with the daemon or get rid of the dependencies?
Maybe with a temporary folder? Not sure.
The %ENV allows access to and manipulation of environment variables. See Environment in perlrun. (There is also a core module Env for convenience.) With it we can say print $ENV{HOME}, or add environment variables by setting new hash keys, or change (write new) values for its existing ones. This environment is inherited by processes started by the program.
So with the parent
use warnings 'all';
use strict;
$ENV{VAR} = '...'; # HOME, PATH, ... or a new variable
$ENV{PERL5LIB} = '...'; # where some modules are installed
system("script.pl");
the program script.pl that is called
use warnings 'all';
use strict;
use SomeModule qw(func); # SomeModule is installed in path in PERL5LIB
print "$ENV{VAR}\n";
func();
can use $ENV{VAR} and can directly use modules installed in the path set up for PERL5LIB.
This affects only processes that your program starts. It doesn't change the environment for its parent (which a daemon won't have anyway), and has no effect on things independent of your program.
I'd like to add – make sure that you have a proper daemon running. See, for example, Complete Dissociation of Child from Parent in perlipc. You can also see in perlipc
how to utilize SIGHUP.
How can one use the Environment Modules system* in Perl?
Running
system("load module <module>");
does not work, presumably because it forks to another environment.
* Not to be confused with Perl modules. According to the Wikipedia entry:
The Environment Modules system is a tool to help users manage their Unix or Linux shell environment, by allowing groups of related environment-variable settings to be made or removed dynamically.
It looks like the Perl module Env::Modulecmd will do what you want. From the documentation:
Env::Modulecmd provides an automated interface to modulecmd from Perl. The most straightforward use of Env::Modulecmd is for loading and unloading modules at compile time, although many other uses are provided.
Example usage:
use Env::Modulecmd { load => 'foo/1.0' };
Alternately, to do it less perl-namespace like and more environment module shell-like, you can source the Environment Modules initialization perl code like the other shells:
do( '/usr/share/Modules/init/perl');
module('load use.own');
print module('list');
For a one-line example:
perl -e "do ('/usr/share/Modules/init/perl');print module('list');"
(This problem, "source perl environment module" uses such generic words, that it is almost un-searchable.)
system("load module foo ; foo bar");
or, if that doesn't work, then
system("load module foo\nfoo bar");
I'm guessing it makes changes to the environment variables. To change Perl's environment variables, it would have to be executed within the Perl process. That's not going to work since it was surely only designed to be integrated into the shell. (It might not be too hard to port it, though.)
If you are ok with restarting the script after loading the module, you can use the following workaround:
use String::ShellQuote qw( shell_quote );
BEGIN {
if (!#ARGV || $ARGV[0] ne '!!foo_loaded!!') {
my $perl_cmd = shell_quote($^X, '--', $0, '!!foo_loaded!!', #ARGV);
exec("load module foo ; $perl_cmd")
or die $!;
}
shift(#ARGV);
}
I have to add /oracle/v10.2.0/lib to LD_LIBRARY_PATH to use DBI module to connect to Oracle.
If I set LD_LIBRARY_PATH in shell before executing perl script, everything is ok.
But it does not work from script:
BEGIN {
$ENV{'LD_LIBRARY_PATH'}='/oracle/v10.2.0/lib';
}
use DBI;
When I execute the script I get the error:
install_driver(Oracle) failed: Can't load '/usr/apps/perl5/site_perl/5.8.8/i686-linux/auto/DBD/Oracle/Oracle.so' for module DBD::Oracle: libclntsh.so.10.1: cannot open shared object file: No such file or directory at /usr/apps/perl5/5.8.8/i686-linux/DynaLoader.pm line 230.
at (eval 3) line 3
Compilation failed in require at (eval 3) line 3.
Perhaps a required shared library or dll isn't installed where expected
at /var/tmp/getTraceDB.pl line 23
BEGIN {
$ENV{'LD_LIBRARY_PATH'}.='/oracle/v10.2.0/lib';
exec($^X, $0, #ARGV);
}
use DBI;
Refer Runtime Linker and LD_LIBRARY_PATH for proper description.
Far better would be to have LD_LIBRARY_PATH set in your .profile or similar. It has to be visible at the time the ELF loader starts up, which is waaay before even /usr/bin/perl is running, let alone the BEGIN block of your script. The exec() trick works by restarting the entire binary if it had to add the path, but better would be for it to always exist in your environment always.
I have a shell script that does nothing but set a bunch of environment variables:
export MYROOTDIR=/home/myuser/mytools
export PATH=$MYROOTDIR/bin:$PATH
export MYVERSION=0.4a
I have a perl script, and I want the perl script to somehow get the perl script to operate with the env vars listed in the shell script. I need this to happen from within the perl script though, I do not want the caller of the perlscript to have to manually source the shellscript first.
When trying to run
system("sh myshell.sh")
the env vars do not "propagate up" to the process running the perl script.
Is there a way to do this?
To answer this question properly, I need to know a bit more.
Is it okay to actually run the shell script from within the perl script?
Are the variable assignments all of the form export VAR=value (i.e. with fixed assignments, no variable substitutions or command substitutions)?
Does the shell script do anything else but assign variables?
Depending on answers to these, options of different complexity exist.
Thanks for the clarification. Okay, here's how to do it. Other than assigning variables, your script has no side effects. This allows to run the script from within perl. How do we know what variables are exported in the script? We could try to parse the shell script, but that's not the Unix way of using tools that do one thing well and chain them together. Instead we use the shell's export -p command to have it announce all exported variables and their values. In order to find only the variables actually set by the script, and not all the other noise, the script is started with a clean environment using env -i, another underestimated POSIX gem.
Putting it all together:
#!/usr/bin/env perl
use strict;
use warnings;
my #cmd = (
"env", "-i", "PATH=$ENV{PATH}", "sh", "-c", ". ./myshell.sh; export -p"
);
open (my $SCRIPT, '-|', #cmd) or die;
while (<$SCRIPT>) {
next unless /^export ([^=]*)=(.*)/;
print "\$ENV{$1} = '$2'\n";
$ENV{$1} = $2;
}
close $SCRIPT;
Notes:
You need to pass to env -i all environment your myshell.sh needs, e.g. PATH.
Shells will usually export the PWD variable; if you don't want this in your perl ENV hash, add next if $1 eq 'PWD'; after the first next.
This should do the trick. Let me know if it works.
See also:
http://pubs.opengroup.org/onlinepubs/009695399/utilities/export.html
http://pubs.opengroup.org/onlinepubs/009695399/utilities/env.html
Try Shell::Source.
You can set the environment variables inside the BEGIN block.
BEGIN block is executed before the rest of the code, setting the environment variables in this block makes them visible to the rest of the code before it is compiled and run.
If you have any perl modules to 'use' based on the enviornment settings, BEGIN block makes it possible.
Perl uses a special hash %ENV to maintain the environment variables.
You can modify the contents of this hash to set the env variables.
EXAMPLE :
BEGIN
{
$ENV { 'MYROOTDIR' } = '/home/myuser/mytools';
$ENV { 'PATH' } = "$ENV{ 'MYROOTDIR' }/bin:$ENV{ 'PATH' }";
}
Wouldn't it be easier for a shell script to set the variables, and then call the perl program?
i.e.:
run.sh:
#!/bin/sh
export MYROOTDIR=/home/myuser/mytools
export PATH=$MYROOTDIR/bin:$PATH
export MYVERSION=0.4a
./program.pl
This can now be done with Env::Modify with few changes to your existing code.
use Env::Modify 'system';
...
system("sh myshell.sh");
print $ENV{MYROOTDIR}; # "/home/myuser/mytools"
or if all your shell script does is modify the environment, you can use the source function
use Env::Modify `source`;
source("myshell.sh");
print $ENV{MYVERSION}; # "0.4a"
Is there a way to load a profile inside Perl?
Like in shell files:
. ~user/.profile
What specifically do you mean by "profile"?
If you mean "retrieve the values of the shell environmental variables that your shell set via .profile", then yes - you do it through a special %ENV hash.
If you mean "read the actual variables set in .profile" like the shell itself does, it's possible but doing it "right" requires either parsing an arbitrary shell script and scrubbing anything that's not an environmental variable assignment, OR executing ". ~/.profile; env`"` and parsing the output.
If you mean "supply a generic configuration to any Perl program that runs via a separate configuration file", you need to add code to those Perl programs to read this configuration file (there are a number of CPAN modules for reading various config files).
If you mean "supply a generic configuration to any Perl program that runs without any special code in those Perl programs to read a separate configuration file, sort of like any shell script gets the stuff from .profile thanks to shell", then the asnwer is "may be". You can leverage PERLOPT environmental variable to supply options which would load up a special module (via -I) containing the configuration that gets set via its "import()". While somewhat doable, it seems like an awful hack that I would strongly recommend against using.
Env::Sourced should do what you need.
use Env::Sourced qw(~/user/profile);
print $ENV{VARAIBLE};
If you have bash configuration values in (or other configuration set up by) a shell script and want that to take effect only for the duration of one execution of a program, you can use a subshell:
( source ~/my_bash_file.sh; perl my_perl_script.pl )
You can access shell environment variables in Perl using the %ENV hash (see the index of Perl's special variables, perldoc perlvar):
my $user = $ENV{USER};
my $home_dir = $ENV{HOME};
Example:
my_bash_file.sh:
#!/bin/bash
export HOME="/home/nowhere"
my_perl_script.pl:
#!/usr/bin/perl
print "the value of HOME is $ENV{HOME}\n";
When executed as perl my_perl_script.pl, my_perl_script.pl prints:
the value of HOME is /home/ether
When executed as ( source ~/my_bash_file.sh; perl my_perl_script.pl ), the output is:
the value of HOME is /home/nowhere
$u=`echo -n ~user`;
open F, "<$u/.profile" || die;
while(<F>)
{print}