Search code examples
bashperl

Is it possible to call bash function from Perl?


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?


Solution

  • 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.