FAQ
Good morning everybody,

I am writing a small console application, allowing the user to perform
some actions via a shell-like interface. The basics of this were rather
easy, and with the help of some very nice CPAN modules (i.e.
Base::Shell), I have got tab-completion, a help system and much more
already.

The core of the application looks (a bit simplifid and in pseudo-code)
more or less like this:

while ("true") {
$cmd = $term->ReadLine($prompt);
if ($cmd eq "help") {
# process help
}
elsif ($cmd eq "exit"}
last;
}
# ...process other actions
}

Now I would like to be able to execute a lengthy action that takes
several minutes. I think I can execute this with a fork, allowing the
user to keep on working while the action is being executed. The
appropriate action code would look more or less like this:

# ...
elsif ($cmd eq 'long_action') {
if (!fork) {
# execute the action in the child process
sleep 10;

# TODO: notify the user that the action is finished.

exit;
}

}

What I would like to do is perform the action in the forked child
process, and on completion the user should get a message about the
result of the action (which would be true or false, most likely).
The result should look like this for the user:

myshell> normal_action
performing normal action...done.
myshell> long_action
initiating lengthy action....done.
myshell>
lenghty action completed successfully.
myshell>

Please note that the user should get back the prompt after he started
'long_action', and when the action is finished, he should be informed by
a message in the shell.

With this I have got two problems:
a) I am executing the action in a child process. What would be the best
way for the child process to inform its parent that the execution has
been finished?
b) How can I set up the ReadLine() part in a way that the user is able
to type new actions, but can receive new messages (from finished long
actions) as well? I have played around with Term::ReadKey, and ended up
with something like this:

my ($complete, $key);
my $string = '';
print $prompt;
# ask for input until we have got a complete string
while (not $complete) {
print "so far : $string\n";
# read a single key
while (not defined ($key = ReadKey(-1))) {
# No key yet
# $string seems to be empty here!
print "*" . $string;
sleep 1;
if ($string eq 'test') {
print "youve written nothing serious so far!\n";
print $prompt . $string;
$string = '';
}
}
if ($key eq "\n") {
$complete = 1;
}
else {
$string .= $key;
}
}

This construct should theoretically more or less allow me to check for
individual keystrokes of the user and parallely to print messages if
necessary, but it does not do what I would expect: If I call
ReadKey(-1), it does not seem to "know" the $string variable in the
inner while loop, and if I call ReadKey(0), it looks like all keys are
fetched first, and afterwards the "print" statements are executed.

I hope you see my problem, though I find it hard to describe.

Can anybody give me a clue in which direction I could go with that?

Thank you very much in advance for your attention and your help.

Philipp Traeder
philipp@hitchhackers.net

Search Discussions

  • Douglas Lentz at Nov 27, 2003 at 6:09 pm

    Philipp Traeder wrote:
    Good morning everybody,

    I am writing a small console application, allowing the user to perform
    some actions via a shell-like interface. The basics of this were rather
    easy, and with the help of some very nice CPAN modules (i.e.
    Base::Shell), I have got tab-completion, a help system and much more
    already.

    The core of the application looks (a bit simplifid and in pseudo-code)
    more or less like this:

    while ("true") {
    $cmd = $term->ReadLine($prompt);
    if ($cmd eq "help") {
    # process help
    }
    elsif ($cmd eq "exit"}
    last;
    }
    # ...process other actions
    }

    Now I would like to be able to execute a lengthy action that takes
    several minutes. I think I can execute this with a fork, allowing the
    user to keep on working while the action is being executed. The
    appropriate action code would look more or less like this:

    # ...
    elsif ($cmd eq 'long_action') {
    if (!fork) {
    # execute the action in the child process
    sleep 10;

    # TODO: notify the user that the action is finished.

    exit;
    }

    }

    What I would like to do is perform the action in the forked child
    process, and on completion the user should get a message about the
    result of the action (which would be true or false, most likely).
    The result should look like this for the user:

    myshell> normal_action
    performing normal action...done.
    myshell> long_action
    initiating lengthy action....done.
    myshell>
    lenghty action completed successfully.
    myshell>

    Please note that the user should get back the prompt after he started
    'long_action', and when the action is finished, he should be informed by
    a message in the shell.

    With this I have got two problems:
    a) I am executing the action in a child process. What would be the best
    way for the child process to inform its parent that the execution has
    been finished?
    b) How can I set up the ReadLine() part in a way that the user is able
    to type new actions, but can receive new messages (from finished long
    actions) as well? I have played around with Term::ReadKey, and ended up
    with something like this:

    my ($complete, $key);
    my $string = '';
    print $prompt;
    # ask for input until we have got a complete string
    while (not $complete) {
    print "so far : $string\n";
    # read a single key
    while (not defined ($key = ReadKey(-1))) {
    # No key yet
    # $string seems to be empty here!
    print "*" . $string;
    sleep 1;
    if ($string eq 'test') {
    print "youve written nothing serious so far!\n";
    print $prompt . $string;
    $string = '';
    }
    }
    if ($key eq "\n") {
    $complete = 1;
    }
    else {
    $string .= $key;
    }
    }

    This construct should theoretically more or less allow me to check for
    individual keystrokes of the user and parallely to print messages if
    necessary, but it does not do what I would expect: If I call
    ReadKey(-1), it does not seem to "know" the $string variable in the
    inner while loop, and if I call ReadKey(0), it looks like all keys are
    fetched first, and afterwards the "print" statements are executed.

    I hope you see my problem, though I find it hard to describe.

    Can anybody give me a clue in which direction I could go with that?

    Thank you very much in advance for your attention and your help.

    Philipp Traeder
    philipp@hitchhackers.net










    Re: (A) What's the best way for the child to inform the parent that it's
    done?.

    I can't speak from experience on this, but I have a copy of Lincoln
    Stein's (He wrote the Crypt::CBC module, among others) Network
    Programming in Perl. Lincoln recommends the pipe() function and says
    (verbatim quote) "It is commonly used in conjunction with the fork()
    function in order to create a parent-child pair that can exchange data.
    The parent process keeps one filehandle open and closes the other, while
    the child process does the opposite. The parent and child process can
    now communicate across the pipe as they work in parallel."

    $result = pipe(READHANDLE,WRITEHANDLE) # pipe is a core perl function
    doc'd in the camel book.

    Sounds like what you need.

    Re: (B) I'm still thinking about this one.
  • Drieux at Nov 27, 2003 at 8:38 pm

    On Thursday, Nov 27, 2003, at 09:58 US/Pacific, Douglas Lentz wrote:

    Philipp Traeder wrote:
    Good morning everybody,
    [..]
    # ...
    elsif ($cmd eq 'long_action') {
    if (!fork) {
    # execute the action in the child process
    sleep 10;
    # TODO: notify the user that the action is
    finished.

    exit;
    }
    }
    [..]
    Re: (A) What's the best way for the child to inform the parent that
    it's done?.

    I can't speak from experience on this, but I have a copy of Lincoln
    Stein's (He wrote the Crypt::CBC module, among others) Network
    Programming in Perl. Lincoln recommends the pipe() function and says
    (verbatim quote) "It is commonly used in conjunction with the fork()
    function in order to create a parent-child pair that can exchange
    data. The parent process keeps one filehandle open and closes the
    other, while the child process does the opposite. The parent and child
    process can now communicate across the pipe as they work in parallel."

    $result = pipe(READHANDLE,WRITEHANDLE) # pipe is a core perl function
    doc'd in the camel book.

    Sounds like what you need.

    Re: (B) I'm still thinking about this one.

    Given that his question (A) is about 'informing'
    the parent that it is finished, he might really
    want to look at

    perldoc -q signal

    and specifically set up a sig handler for setting up
    a SIGCHLD so that he can set a flag,

    my $all_my_children = {};

    where the pid that would be returned from the fork,
    which would of course need to be picked up, is recorded.

    cf perldoc perlipc

    and look for 'sub REAPER'

    Then in the main loop check the status of "all_my_children"
    to see if any of them have changed - and if so editorialize.
    This of course means that the long_activity will need to
    exit with say 0 if ok, or some number if not good, and the
    initialization would be say

    $all_my_children->{$pid} = 'A';

    and as a part of the loop to check the status, one
    gets the return value and then deletes it from the hash.

    This may also help with the 'B' side question -
    as well without getting into the more complex
    set of issues of doing the pipe(), fork()
    manage the multiple selects that would need to
    poll through the long_activities to see if
    new incoming data needs to be collected, or
    is the primate banging on the keyboard.

    But one can take that strategy.


    ciao
    drieux

    ---
  • Philipp Traeder at Nov 27, 2003 at 10:15 pm

    On Thu, 2003-11-27 at 21:37, drieux wrote:
    On Thursday, Nov 27, 2003, at 09:58 US/Pacific, Douglas Lentz wrote:

    Re: (A) What's the best way for the child to inform the parent that
    it's done?.
    Given that his question (A) is about 'informing'
    the parent that it is finished, he might really
    want to look at

    perldoc -q signal

    and specifically set up a sig handler for setting up
    a SIGCHLD so that he can set a flag,

    my $all_my_children = {};

    where the pid that would be returned from the fork,
    which would of course need to be picked up, is recorded.

    cf perldoc perlipc

    and look for 'sub REAPER'

    Then in the main loop check the status of "all_my_children"
    to see if any of them have changed - and if so editorialize.
    This of course means that the long_activity will need to
    exit with say 0 if ok, or some number if not good, and the
    initialization would be say

    $all_my_children->{$pid} = 'A';

    and as a part of the loop to check the status, one
    gets the return value and then deletes it from the hash.
    Thanks for this tip - I will take a look into the topics you have
    mentioned, but it sounds very promising. :-)
    This may also help with the 'B' side question -
    as well without getting into the more complex
    set of issues of doing the pipe(), fork()
    manage the multiple selects that would need to
    poll through the long_activities to see if
    new incoming data needs to be collected, or
    is the primate banging on the keyboard.
    Though I do not know yet how the first part will work, I honestly do not
    understand how a solution to A might help me with the second part of my
    question. Could you explain this more detailed?

    The danger of primates banging on keyboards is - of course - always
    existent and quite high, but in this case I would settle for a first
    version that would be usable by more or less normal beings of the
    species homo s@piens² - v 2.0 would then focus on the keyboard-banging
    primates, managers, sales-people etc. ... ,-)

    Thank you very much for your help,

    Philipp
  • Drieux at Nov 28, 2003 at 12:13 am
    On Thursday, Nov 27, 2003, at 14:10 US/Pacific, Philipp Traeder wrote:
    [..]
    The danger of primates banging on keyboards is - of course - always
    existent and quite high, but in this case I would settle for a first
    version that would be usable by more or less normal beings of the
    species homo s@piens=B2 - v 2.0 would then focus on the
    keyboard-banging
    primates, managers, sales-people etc. ... ,-)
    [..]

    stylish, my compliments,
    you might want to try something like:

    <http://www.wetware.com/drieux/pbl/Sys/big_dog.txt>

    it has the stock REAPER from the docs, and some of
    the basic ideas. Plus I opted from John's Dispatcher
    rather than the dispatcher I normally do, but that is
    the delta between

    my $rack = {
    };

    and his more traditiona

    my %process = (
    );

    HTH.

    ciao
    drieux

    ---
  • John W. Krahn at Nov 27, 2003 at 9:37 pm

    Philipp Traeder wrote:

    Good morning everybody, Hello,
    I am writing a small console application, allowing the user to perform
    some actions via a shell-like interface. The basics of this were rather
    easy, and with the help of some very nice CPAN modules (i.e.
    Base::Shell), I have got tab-completion, a help system and much more
    already.

    The core of the application looks (a bit simplifid and in pseudo-code)
    more or less like this:

    while ("true") {
    $cmd = $term->ReadLine($prompt);
    if ($cmd eq "help") {
    # process help
    }
    elsif ($cmd eq "exit"}
    last;
    }
    # ...process other actions
    }
    Not related to your question but, have you thought of using a dispatch
    table instead?

    sub help {
    # process help
    }

    sub long_action {
    # process long_action
    }

    my %process = (
    help => \&help,
    long_action => \&long_action,
    simple_cmd => sub { print "Anonymous sub for simple command\n" },
    );

    while ( 1 ) {
    my $cmd = $term->ReadLine( $prompt );
    if ( exists $process{ $cmd } ) {
    last if $cmd eq 'exit';
    $process{ $cmd }();
    }
    else {
    print STDERR "Unknown command '$cmd'\n";
    }
    }

    Now I would like to be able to execute a lengthy action that takes
    several minutes. I think I can execute this with a fork, allowing the
    user to keep on working while the action is being executed. The
    appropriate action code would look more or less like this:

    # ...
    elsif ($cmd eq 'long_action') {
    if (!fork) {
    # execute the action in the child process
    sleep 10;

    # TODO: notify the user that the action is finished.

    exit;
    }

    }
    It is pretty simple, the perlipc man page has some good examples, but it
    is basically like this:

    elsif ( $cmd eq 'long_action' ) {
    defined( my $pid = fork ) or die "Cannot fork: $!";
    unless ( $pid ) {
    # execute the action in the child process
    sleep 10;

    # TODO: notify the user that the action is finished.
    print "'long_action' has finished processing\n";
    exit;
    }
    }

    What I would like to do is perform the action in the forked child
    process, and on completion the user should get a message about the
    result of the action (which would be true or false, most likely).
    The result should look like this for the user:

    myshell> normal_action
    performing normal action...done.
    myshell> long_action
    initiating lengthy action....done.
    myshell>
    lenghty action completed successfully.
    myshell>

    Please note that the user should get back the prompt after he started
    'long_action', and when the action is finished, he should be informed by
    a message in the shell.

    With this I have got two problems:
    a) I am executing the action in a child process. What would be the best
    way for the child process to inform its parent that the execution has
    been finished?
    This gets more complicated. Does the parent really have to know when
    the child has finished? waitpid() with the WNOHANG option might do what
    you want or you could use signals or you could use some form of IPC like
    System V IPC or sockets, etc., etc.

    b) How can I set up the ReadLine() part in a way that the user is able
    to type new actions, but can receive new messages (from finished long
    actions) as well? I have played around with Term::ReadKey, and ended up
    with something like this:

    my ($complete, $key);
    my $string = '';
    print $prompt;
    # ask for input until we have got a complete string
    while (not $complete) {
    print "so far : $string\n";
    # read a single key
    while (not defined ($key = ReadKey(-1))) {
    # No key yet
    # $string seems to be empty here!
    print "*" . $string;
    sleep 1;
    if ($string eq 'test') {
    print "youve written nothing serious so far!\n";
    print $prompt . $string;
    $string = '';
    }
    }
    if ($key eq "\n") {
    $complete = 1;
    }
    else {
    $string .= $key;
    }
    }

    This construct should theoretically more or less allow me to check for
    individual keystrokes of the user and parallely to print messages if
    necessary, but it does not do what I would expect: If I call
    ReadKey(-1), it does not seem to "know" the $string variable in the
    inner while loop, and if I call ReadKey(0), it looks like all keys are
    fetched first, and afterwards the "print" statements are executed.
    Sorry, I don't know enough about Term::ReadKey to help with that. :-(

    HTH


    John
    --
    use Perl;
    program
    fulfillment
  • Philipp Traeder at Nov 27, 2003 at 10:33 pm

    Not related to your question but, have you thought of using a dispatch
    table instead?

    sub help {
    # process help
    }

    sub long_action {
    # process long_action
    }

    my %process = (
    help => \&help,
    long_action => \&long_action,
    simple_cmd => sub { print "Anonymous sub for simple command\n" },
    );

    while ( 1 ) {
    my $cmd = $term->ReadLine( $prompt );
    if ( exists $process{ $cmd } ) {
    last if $cmd eq 'exit';
    $process{ $cmd }();
    }
    else {
    print STDERR "Unknown command '$cmd'\n";
    }
    }
    Actually, yes - I am currently using something like this, though by far
    not as elegant as your code sample.
    Then I discovered Base::Shell, and this module comes with more or less
    the same approach (for each action you implement a do_xyz method in a
    class that inherits from Base::Shell).
    I did not use this approach in my code sample because I did not want to
    obfuscate it too much...thanks, anyway. :-)
    Now I would like to be able to execute a lengthy action that takes
    several minutes. I think I can execute this with a fork, allowing the
    user to keep on working while the action is being executed. The
    appropriate action code would look more or less like this:

    # ...
    elsif ($cmd eq 'long_action') {
    if (!fork) {
    # execute the action in the child process
    sleep 10;

    # TODO: notify the user that the action is finished.

    exit;
    }

    }
    It is pretty simple, the perlipc man page has some good examples, but it
    is basically like this:

    elsif ( $cmd eq 'long_action' ) {
    defined( my $pid = fork ) or die "Cannot fork: $!";
    unless ( $pid ) {
    # execute the action in the child process
    sleep 10;

    # TODO: notify the user that the action is finished.
    print "'long_action' has finished processing\n";
    exit;
    }
    }
    If I am not mistaken, this is more or less exactly what I am doing right
    now - the only problem I have got with this is that the user is
    interrupted in his work when the 'long_action' finishes - like this:

    myshell> long_action
    initiating lengthy action...done.

    now the user gets the focus back (which was the main reason behind this
    exercise), and types a new command

    myshell> some other command with some pa

    In this very moment the long_action ends and prints

    'long_action' has finished processing

    As you can see, the user has been interrupted in the middle of typing
    his statement, and it looks to him as if he would have lost his prompt.
    My initial reaction (as a user) to this behaviour would be to press
    enter - in this case the semi-complete command is executed because
    $term->readline is still reading keyboard input...

    Therefore, I would like to re-write the readline() part so that whenever
    the user gets interrupted by a finished long_action, I can present him a
    prompt and write his partially completed command so that he can continue
    typing it.
    This gets more complicated. Does the parent really have to know when
    the child has finished? waitpid() with the WNOHANG option might do what
    you want or you could use signals or you could use some form of IPC like
    System V IPC or sockets, etc., etc.
    Thanks - I will take a look into this...this signal thing looks
    promising to me (on a first glance)...
    Sorry, I don't know enough about Term::ReadKey to help with that. :-(

    HTH


    John
    --
    use Perl;
    program
    fulfillment
    No problem - thank you very much for your support,

    Philipp
  • John W. Krahn at Nov 28, 2003 at 1:50 am

    Philipp Traeder wrote:
    It is pretty simple, the perlipc man page has some good examples, but it
    is basically like this:

    elsif ( $cmd eq 'long_action' ) {
    defined( my $pid = fork ) or die "Cannot fork: $!";
    unless ( $pid ) {
    # execute the action in the child process
    sleep 10;

    # TODO: notify the user that the action is finished.
    print "'long_action' has finished processing\n";
    exit;
    }
    }
    If I am not mistaken, this is more or less exactly what I am doing right
    now - the only problem I have got with this is that the user is
    interrupted in his work when the 'long_action' finishes - like this:

    myshell> long_action
    initiating lengthy action...done.

    now the user gets the focus back (which was the main reason behind this
    exercise), and types a new command

    myshell> some other command with some pa

    In this very moment the long_action ends and prints

    'long_action' has finished processing

    As you can see, the user has been interrupted in the middle of typing
    his statement, and it looks to him as if he would have lost his prompt.
    My initial reaction (as a user) to this behaviour would be to press
    enter - in this case the semi-complete command is executed because
    $term->readline is still reading keyboard input...
    Probably the best way to do it (the way the Unix shell does it for mail
    notification) is to output the message only when the user presses the
    "Enter" key. You could send a signal to the child when it is OK to
    print the message or send the message to the parent and let it print it
    at the appropriate time.


    John
    --
    use Perl;
    program
    fulfillment
  • Drieux at Nov 28, 2003 at 8:52 pm

    On Thursday, Nov 27, 2003, at 17:49 US/Pacific, John W. Krahn wrote:
    Philipp Traeder wrote:
    [..]
    If I am not mistaken, this is more or less exactly what I am doing
    right
    now - the only problem I have got with this is that the user is
    interrupted in his work when the 'long_action' finishes - like this:

    myshell> long_action
    initiating lengthy action...done.

    now the user gets the focus back (which was the main reason behind
    this
    exercise), and types a new command

    myshell> some other command with some pa

    In this very moment the long_action ends and prints

    'long_action' has finished processing

    As you can see, the user has been interrupted in the middle of typing
    his statement, and it looks to him as if he would have lost his
    prompt.
    My initial reaction (as a user) to this behaviour would be to press
    enter - in this case the semi-complete command is executed because
    $term->readline is still reading keyboard input...
    Probably the best way to do it (the way the Unix shell does it for mail
    notification) is to output the message only when the user presses the
    "Enter" key. You could send a signal to the child when it is OK to
    print the message or send the message to the parent and let it print it
    at the appropriate time.

    volks,

    let us step back a moment and think a bit about
    the problem here.

    IF one does not re-direct STDOUT of the long_action
    then it will be going back to the cmd line, and as
    Philipp has noted, it can blurt out while the user
    is typing, since his 'key strokes' are being 'echoed'
    back to him from the shell.

    There is a Trade off here that has to be made,
    IF the primate is not to be startled by unexpected
    output, then one must collect that and show it to
    the primate BEFORE showing them the next prompt
    but after the current command has run.

    This of course will mean that one SHOULD be using
    IO::Select, cf pod, as One REALLY does not want to
    be doing all of the bit twiddling on their own,
    TRUST ME on that.

    demonstration code is available at:
    <http://www.wetware.com/drieux/pbl/Sys/gen_sym_big_dog.txt>

    a demonstration run is of the form:

    [jeeves: 91:] perl gen_sym_big_dog.txt
    Your Question: long_action 5
    sending long action for 5 seconds
    Your Question: long_action 6
    sending long action for 6 seconds
    Your Question: children
    14795 in state: 1280
    14796 in state: A
    total of 2 sub processes
    #----
    Process 14795 exited with 5
    process 14795 said:
    14795 is out and running
    14795 is finished and about to exit
    #----
    Your Question:
    #----
    Process 14796 exited with 6
    process 14796 said:
    14796 is out and running
    14796 is finished and about to exit
    #----
    Your Question: q
    [jeeves: 92:]

    the "blank" "Your Question:" section up there is
    where I merely pressed a carriage return, to go back
    through the main loop.

    HTH.

    ciao
    drieux
  • R. Joseph Newton at Nov 28, 2003 at 8:18 pm
    Philipp Traeder wrote:

    >

    ...
    b) How can I set up the ReadLine() part in a way that the user is able
    to type new actions, but can receive new messages (from finished long
    actions) as well? I have played around with Term::ReadKey, and ended up
    with something like this:
    Are you looking for something like this, then? Note that the child can do
    its own comp;etion reporting, since STDOUT is global.:

    Greetings! E:\d_drive\perlStuff>perl -w -MTerm::ReadKey
    my $string;
    ReadMode 4;
    my $child = fork;
    if ($child) {

    while (1) {
    if (my $char = ReadKey) {
    if ($char =~ /[a-zA-Z0-9 ]/) {
    $string .= $char;
    print '*';
    } elsif ($char ne '!') {
    $string .= $char;
    print $char;
    } else {
    $string .= $char;
    print '*';
    last;
    }
    }
    }
    print "\n$string\n";


    } else {

    sleep 20;
    print "Child process all done-done\n";

    }
    ^Z
    **********,******,***************
    What silly, geeky, games we play!
    Child process all done-done

    Joseph
  • R. Joseph Newton at Nov 28, 2003 at 8:41 pm
    Philipp Traeder wrote:

    Hi Phillip,

    Sorry. That last example cheated with a long, hard-coded wait. Below is
    something that speaks a little more closely to the problem.
    ...
    b) How can I set up the ReadLine() part in a way that the user is able
    to type new actions, but can receive new messages (from finished long
    actions) as well? I have played around with Term::ReadKey, and ended up
    with something like this:
    The waitpid ($child_pid, 0) function should do something like this:

    Greetings! E:\d_drive\perlStuff>perl -w -MTerm::ReadKey
    my $string;
    ReadMode 4;
    my $child = fork;
    if (not $child) {

    while (1) {
    if (my $char = ReadKey) {
    if ($char =~ /[a-zA-Z0-9 ]/) {
    $string .= $char;
    print '*';
    } elsif ($char ne '!') {
    $string .= $char;
    print $char;
    } else {
    $string .= $char;
    print '*';
    last;
    }
    }
    }
    print "\n$string\n";


    } else {

    waitpid $child, 0;
    print "Child process all done-done\n";

    }

    ^Z
    **********,******,***************
    What silly, geeky, games we play!
    Child process all done-done

    Joseph

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupbeginners @
categoriesperl
postedNov 27, '03 at 8:04a
activeNov 28, '03 at 8:52p
posts11
users5
websiteperl.org

People

Translate

site design / logo © 2022 Grokbase