7

The Problem

I am attempting to reuse a shell function I have defined in bash script later on in the script, within a perl cmd execution block. The call to perl cmd basically needs to to run the defined shell function after matching a piece of the regex (capture group #2). See code definitions below.

The Code

The pertinent function definition in bash shell script:

    evalPS() {
        PS_ARGS=$(eval 'echo -en "'${1}'"' | sed -e 's#\\\[##g' -e 's#\\\]##g')
        PS_STR=$((set +x; (PS4="+.$PS_ARGS"; set -x; :) 2>&1) | cut -d':' -f1 | cut -d'.' -f2)
        echo -en "${PS_STR}"
    }

The definition above uses some bashisms and hacks to evaluate the users real prompt to a string.

That function needs to be called within perl in the next function:

    remPS() {
        # store evalPS definition
        EVALPS_SOURCE=$(declare -f evalPS)
        # initalize prompt
        eval "$PROMPT_COMMAND" &> /dev/null
        # handle args
        ( [[ $# -eq 0 ]] && cat - || cat "${1}" ) |
        # ridiculous regex
        perl -pe 's/^[^\e].*//gs' | 
        perl -s -0777 -e '`'"$EVALPS_SOURCE"'`; while (<>) { s%(.*?\Q$ps1\E)(?{`eval $PROMPT_COMMAND`})|(.*?\Q$ps2\E)(?{$ps2=`$\(evalPS "${PS2}"\)`})|(\Q$ps3\E)|(^\Q$ps4\E+.*?\b)%%g; } continue {print;}' -- \
             -ps1="$(evalPS "${PS1}")" -ps2="$(evalPS "${PS2}")" \
             -ps3="${PS3}" -ps4="${PS4:0:1}" |
        perl -pe 's/(.*?)\e\[[A-Z](\a)*/\1/g'
    }

The call to perl could be moved to a separate script but either way the issue is I can not find a way to "import" or "source" the remPS() function, within the perl script. I also tried sourcing the function from a separate file definition, into the perl command. Like so:

    perl -s -0777 -e '`. /home/anon/Desktop/flyball_labs/scripts/recsesh_lib.sh`; while (<>) { s%(.*?\Q$ps1\E)(?{`eval $PROMPT_COMMAND`})|(.*?\Q$ps2\E)(?{$ps2=`$\(evalPS "${PS2}"\)`})|(\Q$ps3\E)|(^\Q$ps4\E+.*?\b)%%g; } continue {print;}'
    ...

Or using the source builtin:

    perl -s -0777 -e '`source /home/anon/Desktop/flyball_labs/scripts/recsesh_lib.sh`; while (<>) { s%(.*?\Q$ps1\E)(?{`eval $PROMPT_COMMAND`})|(.*?\Q$ps2\E)(?{$ps2=`$\(evalPS "${PS2}"\)`})|(\Q$ps3\E)|(^\Q$ps4\E+.*?\b)%%g; } continue {print;}'
    ...

And for clarity, the final attempt was passing the function declaration into perl like so:

    perl -s -0777 -e '`'"$EVALPS_SOURCE"'`; while (<>) { s%(.*?\Q$ps1\E)(?{`eval $PROMPT_COMMAND`})|(.*?\Q$ps2\E)(?{$ps2=`$\(evalPS "${PS2}"\)`})|(\Q$ps3\E)|(^\Q$ps4\E+.*?\b)%%g; } continue {print;}'
    ...

The Results

With no luck in any of the above cases.. It seems that the . cmd runs whereas the source cmd does not, and the syntax for passing the declaration of the function into perl may not be possible, as shown from the output of my tests:

Sourcing library definitions w/ source cmd

    (1)|anon@devbox /tmp|$ remPS "${TEXT_FILE}"
    sh: 1: source: not found
    ...

Sourcing w/ shell . cmd

    (1)|anon@devbox /tmp|$ remPS "${TEXT_FILE}"
    sh: 1: evalPS: not found
    ...

Passing declaration to perl

    (1)|anon@devbox /tmp|$ remPS "${TEXT_FILE}"
    sh: 3: Syntax error: ")" unexpected (expecting "}")
    sh: 1: evalPS: not found
    ...

To summarize

Q) How to "import" and run a user defined shell command within perl?

A) 2 Possible solutions:

  1. source the function from separate file definition
  2. pass into perl command from bash using variable expansion

Sources & Research

Evaluating real bash prompt value:

how-to-print-current-bash-prompt echo-expanded-ps1

Note: I chose this implementation of evalPS() because using the script cmd workaround was unreliable and using call bind_variable() bash function required root privileges (effectively changing user's prompt).

Perl regex embeded code

Note: the function has to be run after every match of $PS2 to re-evaluate the new prompt and effectively match the next iteration (as it would in a real shell session). The use case I have for this is many people have (including myself) set their $PROMPT_COMMAND to iterate an integer indicating which line number (or offset from $PS1) the current line is, and displayed within $PS2.

running a shell command in perl

Sourcing shell code in perl:

how-to-run-source-command-linux-from-a-perl-script can-we-source-a-shell-script-in-perl-script sourcing-a-shell-script-from-a-perl-script

Alternatively if anyone knows how to translate my implementation of evalPS() into perl code, that would work too, but I believe this is impossible because the evaluated string is obtained using a "set hack" which as far as I know is strictly a bashism.

how-can-i-translate-a-shell-script-to-perl

Any suggestions would be much appreciated!

Edit

Some more info on the data being parsed..

The text file looks like the following (cat -A output):

^[]0;anon@ - 3^G^[[1m^[[31m(^[[36m1^[[31m)|^[[33manon^[[32m@^[[34mdevbox ^[[35m/tmp^[[31m|^[[36m^[[37m$ ^[(B^[[mecho test^M$
test^M$
^[[1m^[[31m(^[[36m1^[[31m)|^[[33manon^[[32m@^[[34mdevbox ^[[35m/tmp^[[31m|^[[36m^[[37m$ ^[(B^[[mecho \^M$
^[[1m^[[31m[^[[36m2^[[31m]|^[[33m-^[[32m-^[[34m-^[[35m> ^[(B^[[m\^M$
^[[1m^[[31m[^[[36m3^[[31m]|^[[33m-^[[32m-^[[34m-^[[35m> ^[(B^[[m\^M$
^[[1m^[[31m[^[[36m4^[[31m]|^[[33m-^[[32m-^[[34m-^[[35m> ^[(B^[[mtest^M$
test^M$
^[[1m^[[31m(^[[36m1^[[31m)|^[[33manon^[[32m@^[[34mdevbox ^[[35m/tmp^[[31m|^[[36m^[[37m$ ^[(B^[[mexit^M$
exit^M$

Or similarly (less formatted):

ESC]0;anon@ - 3^GESC[1mESC[31m(ESC[36m1ESC[31m)|ESC[33manonESC[32m@ESC[34mdevbox ESC[35m/tmpESC[31m|ESC[36mESC[37m$ ESC(BESC[mecho test
test
ESC[1mESC[31m(ESC[36m1ESC[31m)|ESC[33manonESC[32m@ESC[34mdevbox ESC[35m/tmpESC[31m|ESC[36mESC[37m$ ESC(BESC[mecho \
ESC[1mESC[31m[ESC[36m2ESC[31m]|ESC[33m-ESC[32m-ESC[34m-ESC[35m> ESC(BESC[m\
ESC[1mESC[31m[ESC[36m3ESC[31m]|ESC[33m-ESC[32m-ESC[34m-ESC[35m> ESC(BESC[m\
ESC[1mESC[31m[ESC[36m4ESC[31m]|ESC[33m-ESC[32m-ESC[34m-ESC[35m> ESC(BESC[mtest
test
ESC[1mESC[31m(ESC[36m1ESC[31m)|ESC[33manonESC[32m@ESC[34mdevbox ESC[35m/tmpESC[31m|ESC[36mESC[37m$ ESC(BESC[mexit
exit

My $PROMPT_COMMAND and corresponding prompts ($PS1-$PS4) for example:

PROMPT_COMMAND='TERM_LINE_NO=1'
PS1="\[$(tput bold)\]\[$(tput setaf 1)\](\[$(tput setaf 6)\]\${TERM_LINE_NO}\[$(tput setaf 1)\])|\[$(tput setaf 3)\]\u\[$(tput setaf 2)\]@\[$(tput setaf 4)\]\h \[$(tput setaf 5)\]\w\[$(tput setaf 1)\]|\[$(tput setaf 6)\]\$(parse_git_branch)\[$(tput setaf 7)\]\\$ \[$(tput sgr0)\]"
PS2="\[$(tput bold)\]\[$(tput setaf 1)\][\[$(tput setaf 6)\]\$((++TERM_LINE_NO))\[$(tput setaf 1)\]]|\[$(tput setaf 3)\]-\[$(tput setaf 2)\]-\[$(tput setaf 4)\]-\[$(tput setaf 5)\]> \[$(tput sgr0)\]"
PS3=""
PS4="+ "
Tyler Moore
  • 111
  • 1
  • 7
  • [`Env::Modify`](http://metacpan.org/pod/Env::Modify) might be able to help with this. But this is some pretty dense input, so I'm not sure. – mob Jun 20 '18 at 23:44
  • Try using the dot command (spelled `.`) instead of `source`. The errors are saying that `sh` doesn't understand `source`, so it probably isn't Bash (or Csh, which is where `source` originated; Bourne shell use the name `.`). Running multiple copies of Perl feeding into each other is peculiar. The function you want to use looks ripe for conversion to Perl. I can't (be bothered to) make out exactly what you're doing, but it looks fiendishly complex — and to this '80s era shell programmer, it looks absurd. – Jonathan Leffler Jun 21 '18 at 03:40
  • Sourcing with the `.` command originally from Dash shell gives the 2nd output example. i.e. `sh: 1: evalPS: not found`. The only issue with converting the function to perl is I don't know how to go about doing the "set debug" hack I used in the shell function in perl's syntax.. Essentially I parse the evaluated prompt value from `set -x` ---> what would a comparable perl statement be? – Tyler Moore Jun 21 '18 at 16:21

1 Answers1

0

The answer was to scrap this whole idea and use a better one..

Lets step back first.. Big Picture:
Goal was to make the script program output an executable shell script of the entire recorded session.

Back to Answers..
The above implementation was supposed to remove all prompts and control characters from the output of script (which is the input examples I gave) and then remove the output of each command (i.e. any line that didn't contain control characters).
Passing the evalPS function to perl to execute proved to be quite redundant and getting bash and perl to expand the parameters correctly was a nightmare..

The Final Solution
Scrapped the perl regex idea and used a combination of subshell and history redirection to grab the commands for the entire script session, while it was running.

The entire implementation looks like this:

# log cmds to script file as they are entered (unbuffered)
# spawn script cmd in subshell and wait for it to finish
wait -n
( 
    history -c
    export HISTFILE="${SCRIPT_FILE}"
    shopt -s histappend
    script -q --timing="${TIME_FILE}" "${REC_FILE}"
    history -a
)
...

Simple and much easier to read! :)
Hope this helps anyone trying to make their own mods to script in the future, cheers!

Tyler Moore
  • 111
  • 1
  • 7