admin@admin:~$ function fun1() { echo "abc"; }
admin@admin:~$ fun1
abc
I want to call this function from Perl.
admin@admin:~$ perl -e 'fun1'
admin@admin:~$
There is no output. What do I need to do to run my shell functions and other variables or Perl isn't able to do this?
A function can be exported in bash, then called in a subshell
$ function fun1 { echo "abc"; }
$ export -f fun1
$ perl -wE'system "bash", "-c", "fun1"'
(where $
signifies a shell prompt)
Tested in bash 5.1.8(1)
If the shell function returns values (by echo
-ing) and the Perl program needs them then use "backticks" (qx in operator form)† instead of system
perl -we'$ret = qx(bash -c "f1 args-for-f1"); print $ret'
This assigns a (possibly) multiline string, with all lines of output, to the scalar $ret
. Or
perl -we'@ret = qx ...'
to have lines of output as elements of @ret
array.
See How to call a function (defined in shell script) in a Perl script.
† However, things with qx
aren't actually so simple when we need to collect the return.
The system
can take a list, as used above, and then directly execute the program given as the first element of the list. It passes it to a syscall from the execvp
family and the rest of the list as its arguments. See system and exec. No shell invoked. So we can ensure that bash -c
takes the quoted string.
The qx
doesn't provide this. While one can pass it a list it will concatenate it and do with the string as it pleases -- that is, if there are shell metacharacters a shell is called. Thus those "
needed for the bash
function call trigger the invocation of /bin/sh
. This is normally a link, and if it is a link to bash
(most common case) then things somehow do work out. However, if it links to, say, dash
then it won't work. Thanks toi andrzejdoro
for bringing this up.
Then a general solution is to use a way to call bash
(so to run a function in it) which does not trigger another shell. There are a number of good modules in Perl for executing external commands, which allow us to make certain we don't go through a shell. The most rounded and potent tool for that is IPC::Run
With IPC::Run
in a command-line program ("one-liner") for easy testing, at a terminal prompt in bash
function f1 { t1=$1; echo "In f1(): t1=$t1"; }
export -c f1
perl -wE'use IPC::Run qw(run);
$cmd = ["bash", "-c", "f1 hi"];
run $cmd, \undef, \$out, \$err;
say "out: $out" if $out;
say "err: $err" if $err'
(The perl ...
command broken into multiple lines for readability. In bash
it can still be copy-pasted into a terminal, or join into one line in a shell which can't do that.)
This prints
out: In f1(): t1=hi
See the link above, to another SO post, for how to do this if the bash functions are in a file.
Caveat: Right now I have no acess to a system where sh
is linked to a shell other than bash
so I can't fully check this
Another idea, that needs checking, is to use pipe open
perl -wE'
open $fh, "|-", "bash", "-c", "f1 ho" or die "Cant pipe-open: $!";
close $fh or die "Error closing: $!"'
This prints as expected on systems I have access to but I don't know whether it goes through a shell and can't check right now.
See pipe open in perlipc for a lot more.