output asked headers in the order they were asked; avoid header name spoofing by...
[hband-tools.git] / user-tools / multicmd
blobb7595f8792a2ea892a2ef9a5aaa26e7366b2db9c
1 #!/usr/bin/env perl
3 =pod
5 =head1 NAME
7 multicmd - Run multiple commands in series
9 =head1 SYNOPSIS
11 multicmd [I<OPTIONS>] [--] I<COMMAND-1> I<ARGS-1> ";" I<COMMAND-2> I<ARGS-2> ";" ...
13 Run I<COMMAND-1>, I<COMMAND-2>, ... I<COMMAND-n> after each other,
14 similarly like shells would do, except not involving any shell.
16 =head1 OPTIONS
18 =over 4
20 =item -d, --delimiter I<STRING>
22 Set command delimiter to I<STRING>.
23 Default is a literal C<;> semicolon.
24 Probably need to shell-escape.
25 If you want C<--> (double dash) for delimiter, to avoid confusion, put it as:
26 C<--delimiter=-->.
28 =item -e, --errexit
30 Exit if a command did not run successfully (ie. non-zero exit status or signaled)
31 and do not run further commands.
32 Similar to bash(1)'s errexit (set -e) mode.
33 multicmd(1)'s exit code will be the failed command exit code
34 (128+B<n> if terminated by a signal B<n>).
36 =back
38 =head1 CAVEATS
40 Note, that C<;> (or the non-default delimiter set by B<--delimiter>) is a shell meta-char
41 in your shell, so you need to escape/quote it, but it's a separate literal argument
42 when you call multicmd(1) in other layers (eg. execve(2)),
43 so don't just stick to the preceding word. Ie:
45 B<WRONG>: multicmd date\; ls
47 B<WRONG>: multicmd 'date; ls'
49 B<WRONG>: multicmd 'date ; ls'
51 B<CORRECT>: multicmd date \; ls
53 B<CORRECT>: multicmd date ';' ls
55 =head1 EXIT STATUS
57 multicmd(1) exit with the exit code of the last command.
59 =cut
62 use Getopt::Long qw/:config no_ignore_case no_bundling no_getopt_compat no_auto_abbrev require_order pass_through/;
63 use Pod::Usage;
64 use Data::Dumper;
65 use POSIX;
67 $Delimiter = ';';
68 $Errexit = 0;
70 GetOptions(
71 'd|delimiter=s' => \$Delimiter,
72 'e|errexit!' => \$Errexit,
73 'help' => sub { pod2usage(-exitval=>0, -verbose=>99); },
74 ) or pod2usage(-exitval=>2, -verbose=>99);
76 # Getopt's pass_through mode leaves "--" there
77 shift @ARGV if $ARGV[0] eq '--';
79 if(not @ARGV)
81 pod2usage(-exitval=>2, -verbose=>99);
84 @cmd_args = ([]);
85 $cmd_idx = 0;
87 for my $arg (@ARGV)
89 if($arg eq $Delimiter)
91 $cmd_idx++;
92 $cmd_args[$cmd_idx] = [];
94 else
96 push @{$cmd_args[$cmd_idx]}, $arg;
100 # filter out empty commands
101 @cmd_args = grep {@$_} @cmd_args;
103 $last_cmd_ref = pop @cmd_args;
105 for my $cmd_ref (@cmd_args)
107 system @$cmd_ref;
108 my $error_code = ${^CHILD_ERROR_NATIVE};
109 my $sys_errmsg = $!;
110 my $sys_errno = int $!;
111 my $exit_status = 0;
113 if($error_code == -1)
115 warn "$0: $cmd_ref->[0]: $sys_errmsg\n";
116 $exit_status = 125 + $sys_errno; # "file not found" becomes 127.
118 else
120 if(WIFSIGNALED($error_code)) { $exit_status = 128 + WTERMSIG($error_code); }
121 else { $exit_status = WEXITSTATUS($error_code); }
124 if($Errexit and $exit_status != 0)
126 exit $exit_status;
130 exec {$last_cmd_ref->[0]} @$last_cmd_ref;
131 # if last command not found:
132 ($errno, $errstr) = (int $!, $!);
133 warn "$0: $last_cmd_ref->[0]: $errstr\n";
134 exit 125+$errno;