no
warnings
'uninitialized'
;
new
=> [
qw(-init new)
],
scalar
=> [
qw(
debuglevel
name
sourcecmd
envcmd
envsep
cmdsep
wordsep
squotechar
dquotechar
escchar
statusvar
)
],
array
=> [
qw(
flags
ignore
)
],
];
name
=>
'sh'
,
flags
=> [
'-c'
],
sourcecmd
=>
'.'
,
envcmd
=>
'env'
,
envsep
=>
'='
,
cmdsep
=>
';'
,
wordsep
=>
' '
,
squotechar
=>
"'"
,
dquotechar
=>
'"'
,
escchar
=>
"\\"
,
statusvar
=>
'$?'
,
ignore
=> [
qw(_ PWD SHLVL)
],
);
sub
init {
my
$self
=
shift
;
my
%args
=
@_
;
my
%defaults
= (DEFAULTS);
my
@fields
= (
keys
%args
,
keys
%defaults
);
my
%fields
;
@fields
{
@fields
} = (1) x
@fields
;
@fields
=
keys
%fields
;
foreach
my
$field
(
@fields
) {
if
(
$self
->can(
$field
)) {
my
$curval
=
$self
->
$field
();
my
$arg
=
exists
(
$args
{
$field
}) ?
$args
{
$field
} :
$defaults
{
$field
};
if
(
ref
(
$curval
) =~ /ARRAY/) {
$self
->
$field
(
@$arg
);
}
elsif
(
ref
(
$curval
) =~ /HASH/) {
$self
->
$field
(
%$arg
);
}
else
{
$self
->
$field
(
$arg
);
}
}
}
}
sub
run {
my
$self
=
shift
;
my
%args
=
@_
;
my
$command
=
$args
{
'command'
};
my
$tag
=
join
(
'_'
,
time
, $$,
int
(
rand
(1) * 10000000));
$self
->dprint(4,
"Output tag: $tag\n"
);
my
$rv
= Shell::EnvImporter::Result->new();
my
@script
=
$self
->make_script(
$command
,
$tag
);
$self
->dprint(3,
"EXECUTING: @script\n"
);
my
$output
=
$self
->execute(
@script
);
$self
->dprint(1,
"Parsing results\n"
);
$self
->parse_results(
$rv
,
$output
,
$tag
);
return
$rv
;
}
sub
make_script {
my
$self
=
shift
;
my
$command
=
shift
;
my
$tag
=
shift
;
my
$statusvar
=
$self
->statusvar;
my
$wordsep
=
$self
->wordsep;
my
$shellcmd
=
join
(
$wordsep
,
$self
->name,
$self
->flags);
my
$script
=
join
(
$self
->cmdsep,
$self
->echo_command(
$tag
, 0),
$command
,
$self
->echo_command(
$tag
,
$statusvar
),
$self
->envcmd,
$self
->echo_command(
$tag
,
$statusvar
),
);
return
(
$self
->name,
$self
->flags,
$script
);
}
sub
execute {
my
$self
=
shift
;
my
@script
=
@_
;
my
(
%fh
,
%h2p
);
foreach
my
$pipename
(
qw(STDIN STDOUT STDERR)
) {
my
$handle
= IO::Handle->new();
$fh
{
$pipename
} =
$handle
;
$h2p
{
"$handle"
} =
$pipename
;
}
my
$pid
= open3(
$fh
{
'STDIN'
},
$fh
{
'STDOUT'
},
$fh
{
'STDERR'
},
@script
);
$fh
{
'STDIN'
}->
close
();
my
$s
= IO::Select->new(
$fh
{
'STDOUT'
},
$fh
{
'STDERR'
});
my
$t0
=
time
;
my
%buf
;
while
(1) {
my
@ready
=
$s
->can_read();
last
unless
(
@ready
);
foreach
my
$ready
(
@ready
) {
my
$pipename
=
$h2p
{
"$ready"
};
if
(
$ready
->
eof
) {
$s
->remove(
$ready
);
last
unless
(
$s
->count);
}
else
{
$ready
->
read
(
$buf
{
$pipename
}, BLKSIZE,
length
(
$buf
{
$pipename
}));
}
}
}
if
(
$s
->count) {
kill
'TERM'
,
$pid
;
$buf
{
'STDERR'
} .=
"ERROR: Timed out waiting for output"
;
}
waitpid
(
$pid
, 0);
return
(\
%buf
);
}
sub
parse_results {
my
$self
=
shift
;
my
$rv
=
shift
;
my
$output
=
shift
;
my
$tag
=
shift
;
if
(
defined
(
$output
->{
'STDERR'
})) {
$rv
->stderr(
$output
->{
'STDERR'
});
$self
->dprint(3,
"STDERR: $output->{'STDERR'}\n"
);
}
my
@lines
=
split
(/\n/,
$output
->{
'STDOUT'
});
my
%output
;
my
@shell_output
;
while
(
@lines
) {
my
$line
=
shift
(
@lines
);
if
(
$line
=~ /^
$tag
0/) {
$rv
->shell_status(0);
$self
->dprint(4,
"SHELL STATUS: "
,
$rv
->shell_status,
"\n"
);
last
;
}
else
{
push
(
@shell_output
,
$line
);
}
}
if
(
@shell_output
) {
$rv
->shell_output(
join
(
"\n"
,
@shell_output
));
$self
->dprint(4,
"SHELL OUTPUT: "
,
$rv
->shell_output,
"\n"
);
}
my
@command_output
;
while
(
@lines
) {
my
$line
=
shift
(
@lines
);
if
(
$line
=~ /^
$tag
(\d+)/) {
$rv
->command_status($1);
$self
->dprint(4,
"COMMAND STATUS: "
,
$rv
->command_status,
"\n"
);
last
;
}
else
{
push
(
@command_output
,
$line
);
}
}
if
(
@command_output
) {
$rv
->command_output(
join
(
"\n"
,
@command_output
));
$self
->dprint(4,
"COMMAND OUTPUT: "
,
$rv
->command_output,
"\n"
);
}
my
%new_env
;
while
(
@lines
) {
my
$line
=
shift
(
@lines
);
if
(
$line
=~ /^
$tag
(\d+)/) {
$rv
->env_status($1);
$self
->dprint(4,
"ENV STATUS: "
,
$rv
->env_status,
"\n"
);
last
;
}
else
{
my
(
$key
,
$value
) =
$self
->parse_env(
$line
);
$new_env
{
$key
} =
$value
;
}
}
if
(
$rv
->shell_status == 0 and
$rv
->command_status == 0 and
$rv
->env_status == 0 ) {
$self
->dprint(1,
"Comparing environments\n"
);
$self
->env_diff(
$rv
, \
%new_env
);
}
else
{
$@ =
"Command failed -- check status and output"
;
}
}
sub
parse_env {
my
$self
=
shift
;
my
$line
=
shift
;
return
(
split
(
$self
->envsep,
$line
, 2));
}
sub
squote {
my
$self
=
shift
;
my
$string
=
shift
;
my
$qc
=
$self
->squotechar;
my
$ec
=
$self
->escchar;
$string
=~ s/
$qc
/${qc}${ec}${qc}${qc}/g;
return
join
(
''
,
$qc
,
$string
,
$qc
);
}
sub
dquote {
my
$self
=
shift
;
my
$string
=
shift
;
my
$qc
=
$self
->dquotechar;
my
$ec
=
$self
->escchar;
$string
=~ s/
$qc
/${qc}${ec}${qc}${qc}/g;
return
join
(
''
,
$qc
,
$string
,
$qc
);
}
sub
sourcecommand {
my
$self
=
shift
;
my
$file
=
shift
;
my
$filestr
=
$self
->squote(
$file
);
return
(
join
(
$self
->wordsep,
$self
->sourcecmd,
$filestr
));
}
sub
echo_command {
my
$self
=
shift
;
my
$str
=
$self
->dquote(
"@_"
);
return
"echo $str"
;
}
sub
env_export {
my
$self
=
shift
;
my
%values
= (
@_
== 1 ? %{
$_
[0]} :
@_
);
my
@sets
;
foreach
my
$var
(
sort
keys
%values
) {
if
(
defined
(
$values
{
$var
})) {
push
(
@sets
,
"${var}=$values{$var}"
);
}
else
{
push
(
@sets
,
"unset $var"
);
}
}
my
$sets
=
join
(
$self
->cmdsep,
@sets
);
my
$export
=
join
(
$self
->wordsep,
'export'
,
sort
keys
%values
);
return
join
(
$self
->cmdsep,
$sets
,
$export
);
}
sub
env_diff {
my
$self
=
shift
;
my
$rv
=
shift
;
my
$new_env
=
shift
;
my
@ignores
=
$self
->ignore;
my
%ignore
;
@ignore
{
@ignores
} = (1) x
@ignores
;
my
%old_env
=
$rv
->start_env;
foreach
my
$var
(
keys
%$new_env
) {
unless
(
$ignore
{
$var
}) {
if
(
exists
(
$old_env
{
$var
})) {
if
(
$old_env
{
$var
} ne
$new_env
->{
$var
}) {
$self
->dprint(3,
"MODIFIED: $var\n"
);
my
$change
= Shell::EnvImporter::Change->new(
type
=>
'modified'
,
value
=>
$new_env
->{
$var
},
);
$rv
->changed_set(
$var
=>
$change
);
}
}
else
{
$self
->dprint(3,
"ADDED: $var\n"
);
my
$change
= Shell::EnvImporter::Change->new(
type
=>
'added'
,
value
=>
$new_env
->{
$var
},
);
$rv
->changed_set(
$var
=>
$change
);
}
}
delete
(
$old_env
{
$var
});
}
foreach
my
$var
(
keys
%old_env
) {
next
if
(
$ignore
{
$var
});
$self
->dprint(3,
"REMOVED: $var\n"
);
my
$change
= Shell::EnvImporter::Change->new(
type
=>
'removed'
,
);
$rv
->changed_set(
$var
=>
$change
);
}
}
sub
dprint {
my
$self
=
shift
;
my
$level
=
shift
;
my
(
$package
,
$filename
,
$line
) =
caller
;
print
STDERR
"-"
x
$level
,
" $package:$line : "
,
@_
if
(
$self
->debuglevel >=
$level
);
}
1;