in reply to Problem with exit status of bash script

Athanasius is correct, & is a shell thing and doesn't do what you want (man bash: "If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0."). And you don't want the shell in the first place. Use a module, or just use fork, exec and wait... this is what bash does anyway, except it doesn't wait in your script, so you don't get ping's exit code.

Something like that:

use strict; use warnings; use autodie; use POSIX ':sys_wait_h'; my @sites = qw( www.perlmonks.org www.stackoverflow.com www.whitehouse.gov ); open my $stdout, '>&', \*STDOUT; # dup STDOUT open STDOUT, '>', '/dev/null'; # to make ping shut up my %kids; for my $site (@sites) { my $pid = fork; $pid == 0 and exec 'ping', '-c', '3', $site; $kids{$pid} = $site; } while ( ( my $kid = wait ) != -1 ) { print $stdout "$kids{$kid} returned $?\n"; }
Output:
www.stackoverflow.com returned 0 www.perlmonks.org returned 0 www.whitehouse.gov returned 0

Replies are listed 'Best First'.
Re^2: Problem with exit status of bash script
by dizzyd719 (Novice) on Dec 05, 2014 at 00:31 UTC

    interesting code you have there. I need some helping understanding it though

    after you fork, does the child process actually modify "my %kids" ? I tried to do something similar with an array, but when I called it I got back a reference to an array. do you know why? (code is below)

    in your second loop, you wait for the kids to finish their process and print to stdout the actual return value, is this possible to track? for example I want to have this run all day so I'm thinking I could keep a hash that has these return values and times for tracking purposes

    foreach $element (@updevices){ @ip = split(':',$element); die "Could not fork()\n" unless defined ($pid = fork); if ($pid) { push @pids, $pid; next;} system("ping $ip[2] 1 > /dev/null"); $result = $?; print "ping $ip[0] $ip[2] $result\n"; if ( $result != 0) { print "$ip[0] is dead\n"; push(@dead, $ip[0]); } exit; }
      after you fork, does the child process actually modify "my %kids" ?
      No it doesn't because "The 'exec' function executes a system command and never returns". So
      ... my $pid = fork; $pid == 0 and exec 'ping', '-c', '3', $site; # child ($pid 0) never reaches code below. # exec doesn't return, it can only exit # or it can fail to find ping, but I use autodie # (for this kind of program that's probably # not a great idea in something # that can be called 'production'... but w/e) $kids{$pid} = $site; # only parent gets here ...
      I tried to do something similar with an array, but when I called it I got back a reference to an array. do you know why? (code is below)
      What did you expect? Also I don't see any reference to an array (reference in Perl is something different). Anyway, parent can use the array @pids because why not.
      foreach $element (@updevices){ @ip = split(':',$element); die "Could not fork()\n" unless defined ($pid = fork); if ($pid) { push @pids, $pid; next;} # ^ this is parent code, it pushes $pid to array # and goes back to the top of the loop # below is child code # it does something and then exits system("ping $ip[2] 1 > /dev/null"); $result = $?; print "ping $ip[0] $ip[2] $result\n"; if ( $result != 0) { print "$ip[0] is dead\n"; push(@dead, $ip[0]); } exit; # ^ child exits here # and basically stops (that's how exit works, after all) # and its exit code is 0; see perdoc -f exit } # after parent is done with the loop # it continues here # it has the array @pids # and all other global vars that your script # is full of...
      BTW, note that $? is not exactly exit code. See the manual for details.
      in your second loop, you wait for the kids to finish their process and print to stdout the actual return value, is this possible to track? for example I want to have this run all day so I'm thinking I could keep a hash that has these return values and times for tracking purposes
      There are probably tons of modules on CPAN that help to automatize these kinds of things... I'd really recommend to look there.

      If not, then you probably should use signal handlers ($SIG{CHLD} specifically). See peripc.

        thanks for the help, I did add additional exit codes in the my child processes and it solved my problems (I posted code below)

        in the end, I figured the easiest way is for the parent to keep track of the children's exit status; because in the end that's all I care about.

        I don't have access to "update" my version of perl (or add modules), but I'm definitely going to look more into the IPC, it really interests me.

        foreach $element (@updevices) { @ip = split(':',$element); die "Could not fork()\n" unless defined ($pid = fork); if ($pid) { push @pids, $pid; next;} system("ping $ip[2] 2 > /dev/null"); $result = $?; if ( $result != 0) { print "$ip[0] is dead\n"; exit 1; } exit 0; } for $pid(@pids){ waitpid $pid, 0; if ($? != 0) { $i++; } } print "$i dead devices\n";