-
-
Save sirhopcount/4148227 to your computer and use it in GitHub Desktop.
RANCID for Vyatta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/perl | |
# | |
# usage: vrancid [-dV] [-l] [-f filename | hostname] | |
# | |
use Getopt::Std; | |
getopts('dflV'); | |
if ($opt_V) { | |
print "rancid 2.3.6\n"; | |
exit(0); | |
} | |
$log = $opt_l; | |
$debug = $opt_d; | |
$file = $opt_f; | |
$host = $ARGV[0]; | |
$proc = ""; | |
$ios = "IOS"; | |
$clean_run = 0; | |
$found_end = 0; | |
$found_version = 0; | |
$found_env = 0; | |
$found_diag = 0; | |
$timeo = 90; # clogin timeout in seconds | |
my(@commandtable, %commands, @commands);# command lists | |
my($aclsort) = ("ipsort"); # ACL sorting mode | |
my($config_register); # configuration register value | |
my($filter_commstr); # SNMP community string filtering | |
my($filter_pwds); # password filtering mode | |
# This routine is used to print out the router configuration | |
sub ProcessHistory { | |
my($new_hist_tag,$new_command,$command_string,@string) = (@_); | |
if ((($new_hist_tag ne $hist_tag) || ($new_command ne $command)) | |
&& scalar(%history)) { | |
print eval "$command \%history"; | |
undef %history; | |
} | |
if (($new_hist_tag) && ($new_command) && ($command_string)) { | |
if ($history{$command_string}) { | |
$history{$command_string} = "$history{$command_string}@string"; | |
} else { | |
$history{$command_string} = "@string"; | |
} | |
} elsif (($new_hist_tag) && ($new_command)) { | |
$history{++$#history} = "@string"; | |
} else { | |
print "@string"; | |
} | |
$hist_tag = $new_hist_tag; | |
$command = $new_command; | |
1; | |
} | |
sub numerically { $a <=> $b; } | |
# This is a sort routine that will sort numerically on the | |
# keys of a hash as if it were a normal array. | |
sub keynsort { | |
local(%lines) = @_; | |
local($i) = 0; | |
local(@sorted_lines); | |
foreach $key (sort numerically keys(%lines)) { | |
$sorted_lines[$i] = $lines{$key}; | |
$i++; | |
} | |
@sorted_lines; | |
} | |
# This is a sort routine that will sort on the | |
# keys of a hash as if it were a normal array. | |
sub keysort { | |
local(%lines) = @_; | |
local($i) = 0; | |
local(@sorted_lines); | |
foreach $key (sort keys(%lines)) { | |
$sorted_lines[$i] = $lines{$key}; | |
$i++; | |
} | |
@sorted_lines; | |
} | |
# This is a sort routine that will sort on the | |
# values of a hash as if it were a normal array. | |
sub valsort{ | |
local(%lines) = @_; | |
local($i) = 0; | |
local(@sorted_lines); | |
foreach $key (sort values %lines) { | |
$sorted_lines[$i] = $key; | |
$i++; | |
} | |
@sorted_lines; | |
} | |
# This is a numerical sort routine (ascending). | |
sub numsort { | |
local(%lines) = @_; | |
local($i) = 0; | |
local(@sorted_lines); | |
foreach $num (sort {$a <=> $b} keys %lines) { | |
$sorted_lines[$i] = $lines{$num}; | |
$i++; | |
} | |
@sorted_lines; | |
} | |
# This is a sort routine that will sort on the | |
# ip address when the ip address is anywhere in | |
# the strings. | |
sub ipsort { | |
local(%lines) = @_; | |
local($i) = 0; | |
local(@sorted_lines); | |
foreach $addr (sort sortbyipaddr keys %lines) { | |
$sorted_lines[$i] = $lines{$addr}; | |
$i++; | |
} | |
@sorted_lines; | |
} | |
# These two routines will sort based upon IP addresses | |
sub ipaddrval { | |
my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#); | |
$a[3] + 256 * ($a[2] + 256 * ($a[1] +256 * $a[0])); | |
} | |
sub sortbyipaddr { | |
&ipaddrval($a) <=> &ipaddrval($b); | |
} | |
# This routine processes a "write term" | |
sub WriteTerm { | |
print STDERR " In WriteTerm: $_" if ($debug); | |
my($lineauto,$comment,$linecnt) = (0,0,0); | |
while (<INPUT>) { | |
tr/\015//d; | |
last if (/^$prompt/); | |
return(1) if (!$linecnt && /^\s+\^\s*$/); | |
next if (/^\s*$cmd\s*$/); | |
return(1) if (/Line has invalid autocommand /); | |
return(1) if (/(Invalid (input|command) detected|Type help or )/i); | |
return(1) if (/\%Error: No such file or directory/); | |
return(1) if (/(Open device \S+ failed|Error opening \S+:)/); | |
return(0) if ($found_end); # Only do this routine once | |
return(-1) if (/command authorization failed/i); | |
return(-1) if (/% ?configuration buffer full/i); | |
# the pager can not be disabled per-session on the PIX | |
if (/^(<-+ More -+>)/) { | |
my($len) = length($1); | |
s/^$1\s{$len}//; | |
} | |
/^! no configuration change since last restart/i && next; | |
# skip emtpy lines at the beginning | |
if (!$linecnt && /^\s*$/) { | |
next; | |
} | |
if (!$linecnt && defined($config_register)) { | |
ProcessHistory("","","", "!\nconfig-register $config_register\n"); | |
} | |
/Non-Volatile memory is in use/ && return(-1); # NvRAM is locked | |
/% Configuration buffer full, / && return(-1); # buffer is in use | |
$linecnt++; | |
$lineauto = 0 if (/^[^ ]/); | |
# skip the crap | |
if (/^(##+|(building|current) configuration)/i) { | |
while (<INPUT>) { | |
next if (/^Current configuration\s*:/i); | |
next if (/^:/); | |
next if (/^([%!].*|\s*)$/); | |
next if (/^ip add.*ipv4:/); # band-aid for 3620 12.0S | |
last; | |
} | |
tr/\015//d; | |
} | |
# skip ASA 5520 configuration author line | |
/^: written by /i && next; | |
# some versions have other crap mixed in with the bits in the | |
# block above | |
/^! (Last configuration|NVRAM config last)/ && next; | |
# and for the ASA | |
/^: (Written by \S+ at|Saved)/ && next; | |
# skip consecutive comment lines to avoid oscillating extra comment | |
# line on some access servers. grrr. | |
if (/^!\s*$/) { | |
next if ($comment); | |
ProcessHistory("","","",$_); | |
$comment++; | |
next; | |
} | |
$comment = 0; | |
# Dog gone Cool matches to process the rest of the config | |
/^tftp-server flash / && next; # kill any tftp remains | |
/^ntp clock-period / && next; # kill ntp clock-period | |
/^ length / && next; # kill length on serial lines | |
/^ width / && next; # kill width on serial lines | |
$lineauto = 1 if /^ modem auto/; | |
/^ speed / && $lineauto && next; # kill speed on serial lines | |
/^ clockrate / && next; # kill clockrate on serial interfaces | |
if (/^(enable )?(password|passwd)( level \d+)? / && $filter_pwds >= 1) { | |
ProcessHistory("ENABLE","","","!$1$2$3 <removed>\n"); | |
next; | |
} | |
if (/^(enable secret) / && $filter_pwds >= 2) { | |
ProcessHistory("ENABLE","","","!$1 <removed>\n"); | |
next; | |
} | |
if (/^username (\S+)(\s.*)? secret /) { | |
if ($filter_pwds >= 2) { | |
ProcessHistory("USER","keysort","$1", | |
"!username $1$2 secret <removed>\n"); | |
} else { | |
ProcessHistory("USER","keysort","$1","$_"); | |
} | |
next; | |
} | |
if (/^username (\S+)(\s.*)? password ((\d) \S+|\S+)/) { | |
if ($filter_pwds >= 2) { | |
ProcessHistory("USER","keysort","$1", | |
"!username $1$2 password <removed>\n"); | |
} elsif ($filter_pwds >= 1 && $4 ne "5"){ | |
ProcessHistory("USER","keysort","$1", | |
"!username $1$2 password <removed>\n"); | |
} else { | |
ProcessHistory("USER","keysort","$1","$_"); | |
} | |
next; | |
} | |
# cisco AP w/ IOS | |
if (/^(wlccp \S+ username (\S+)(\s.*)? password) (\d \S+|\S+)/) { | |
if ($filter_pwds >= 1) { | |
ProcessHistory("USER","keysort","$2","!$1 <removed>\n"); | |
} else { | |
ProcessHistory("USER","keysort","$2","$_"); | |
} | |
next; | |
} | |
# filter auto "rogue ap" configuration lines | |
/^rogue ap classify / && next; | |
if (/^( set session-key (in|out)bound ah \d+ )/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1<removed>\n"); | |
next; | |
} | |
if (/^( set session-key (in|out)bound esp \d+ (authenticator|cypher) )/ | |
&& $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1<removed>\n"); | |
next; | |
} | |
if (/^(\s*)password / && $filter_pwds >= 1) { | |
ProcessHistory("LINE-PASS","","","!$1password <removed>\n"); | |
next; | |
} | |
if (/^(\s*)secret / && $filter_pwds >= 2) { | |
ProcessHistory("LINE-PASS","","","!$1secret <removed>\n"); | |
next; | |
} | |
if (/^\s*neighbor (\S*) password / && $filter_pwds >= 1) { | |
ProcessHistory("","","","! neighbor $1 password <removed>\n"); | |
next; | |
} | |
if (/^(ppp .* password) 7 .*/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
if (/^(ip ftp password) / && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
if (/^( ip ospf authentication-key) / && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
# isis passwords appear to be completely plain-text | |
if (/^\s+isis password (\S+)( .*)?/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!isis password <removed>$2\n"); next; | |
} | |
if (/^\s+(domain-password|area-password) (\S+)( .*)?/ | |
&& $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>$3\n"); next; | |
} | |
# this is reversable, despite 'md5' in the cmd | |
if (/^( ip ospf message-digest-key \d+ md5) / && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
# this is also reversable, despite 'md5 encrypted' in the cmd | |
if (/^( message-digest-key \d+ md5 (7|encrypted)) / | |
&& $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
if (/^((crypto )?isakmp key) \S+ / && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed> $'"); next; | |
} | |
# filter HSRP passwords | |
if (/^(\s+standby \d+ authentication) / && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
# this appears in "measurement/sla" images | |
if (/^(\s+key-string \d?)/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
if (/^( l2tp tunnel \S+ password)/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
# i am told these are plain-text on the PIX | |
if (/^(vpdn username (\S+) password)/) { | |
if ($filter_pwds >= 1) { | |
ProcessHistory("USER","keysort","$2","!$1 <removed>\n"); | |
} else { | |
ProcessHistory("USER","keysort","$2","$_"); | |
} | |
next; | |
} | |
# ASA/PIX keys in more system:running-config | |
if (/^( pre-shared-key | key |failover key ).*/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed> $'"); next; | |
} | |
if (/(\s+ldap-login-password )\S+(.*)/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed> $'"); next; | |
} | |
# | |
if (/^( cable shared-secret )/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); | |
next; | |
} | |
/fair-queue individual-limit/ && next; | |
# sort ip explicit-paths. | |
if (/^ip explicit-path name (\S+)/) { | |
my($key) = $1; | |
my($expath) = $_; | |
while (<INPUT>) { | |
tr/\015//d; | |
last if (/^$prompt/); | |
last if (/^$prompt/ || ! /^(ip explicit-path name |[ !])/); | |
if (/^ip explicit-path name (\S+)/) { | |
ProcessHistory("EXPATH","keysort","$key","$expath"); | |
$key = $1; | |
$expath = $_; | |
} else { | |
$expath .= $_; | |
} | |
} | |
ProcessHistory("EXPATH","keysort","$key","$expath"); | |
} | |
# sort route-maps | |
if (/^route-map (\S+)/) { | |
my($key) = $1; | |
my($routemap) = $_; | |
while (<INPUT>) { | |
tr/\015//d; | |
last if (/^$prompt/ || ! /^(route-map |[ !])/); | |
if (/^route-map (\S+)/) { | |
ProcessHistory("ROUTEMAP","keysort","$key","$routemap"); | |
$key = $1; | |
$routemap = $_; | |
} else { | |
$routemap .= $_; | |
} | |
} | |
ProcessHistory("ROUTEMAP","keysort","$key","$routemap"); | |
} | |
# filter out any RCS/CVS tags to avoid confusing local CVS storage | |
s/\$(Revision|Id):/ $1:/; | |
# order access-lists | |
/^access-list\s+(\d\d?)\s+(\S+)\s+(\S+)/ && | |
ProcessHistory("ACL $1 $2","$aclsort","$3","$_") && next; | |
# order extended access-lists | |
/^access-list\s+(\d\d\d)\s+(\S+)\s+ip\s+host\s+(\S+)/ && | |
ProcessHistory("EACL $1 $2","$aclsort","$3","$_") && next; | |
/^access-list\s+(\d\d\d)\s+(\S+)\s+ip\s+(\d\S+)/ && | |
ProcessHistory("EACL $1 $2","$aclsort","$3","$_") && next; | |
/^access-list\s+(\d\d\d)\s+(\S+)\s+ip\s+any/ && | |
ProcessHistory("EACL $1 $2","$aclsort","0.0.0.0","$_") && next; | |
# order arp lists | |
/^arp\s+(\d+\.\d+\.\d+\.\d+)\s+/ && | |
ProcessHistory("ARP","$aclsort","$1","$_") && next; | |
/^ip(v6)? prefix-list\s+(\S+)\s+seq\s+(\d+)\s+(permit|deny)\s+(\S+)(\/.*)$/ | |
&& ProcessHistory("PACL $2 $4","$aclsort","$5", | |
"ip$1 prefix-list $2 $4 $5$6\n") | |
&& next; | |
# order logging statements | |
/^logging (\d+\.\d+\.\d+\.\d+)/ && | |
ProcessHistory("LOGGING","ipsort","$1","$_") && next; | |
# order/prune snmp-server host statements | |
# we only prune lines of the form | |
# snmp-server host a.b.c.d <community> | |
if (/^snmp-server host (\d+\.\d+\.\d+\.\d+) /) { | |
if ($filter_commstr) { | |
my($ip) = $1; | |
my($line) = "snmp-server host $ip"; | |
my(@tokens) = split(' ', $'); | |
my($token); | |
while ($token = shift(@tokens)) { | |
if ($token eq 'version') { | |
$line .= " " . join(' ', ($token, shift(@tokens))); | |
if ($token eq '3') { | |
$line .= " " . join(' ', ($token, shift(@tokens))); | |
} | |
} elsif ($token eq 'vrf') { | |
$line .= " " . join(' ', ($token, shift(@tokens))); | |
} elsif ($token =~ /^(informs?|traps?|(no)?auth)$/) { | |
$line .= " " . $token; | |
} else { | |
$line = "!$line " . join(' ', ("<removed>", | |
join(' ',@tokens))); | |
last; | |
} | |
} | |
ProcessHistory("SNMPSERVERHOST","ipsort","$ip","$line\n"); | |
} else { | |
ProcessHistory("SNMPSERVERHOST","ipsort","$1","$_"); | |
} | |
next; | |
} | |
if (/^(snmp-server community) (\S+)/) { | |
if ($filter_commstr) { | |
ProcessHistory("SNMPSERVERCOMM","keysort","$_", | |
"!$1 <removed>$'") && next; | |
} else { | |
ProcessHistory("SNMPSERVERCOMM","keysort","$_","$_") && next; | |
} | |
} | |
# prune tacacs/radius server keys | |
if (/^((tacacs|radius)-server\s(\w*[-\s(\s\S+])*\s?key) (\d )?\w+/ | |
&& $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>$'"); next; | |
} | |
# order clns host statements | |
/^clns host \S+ (\S+)/ && | |
ProcessHistory("CLNS","keysort","$1","$_") && next; | |
# order alias statements | |
/^alias / && ProcessHistory("ALIAS","keysort","$_","$_") && next; | |
# delete ntp auth password - this md5 is a reversable too | |
if (/^(ntp authentication-key \d+ md5) / && $filter_pwds >= 1) { | |
ProcessHistory("","","","!$1 <removed>\n"); next; | |
} | |
# order ntp peers/servers | |
if (/^ntp (server|peer) (\d+)\.(\d+)\.(\d+)\.(\d+)/) { | |
$sortkey = sprintf("$1 %03d%03d%03d%03d",$2,$3,$4,$5); | |
ProcessHistory("NTP","keysort",$sortkey,"$_"); | |
next; | |
} | |
# order ip host statements | |
/^ip host (\S+) / && | |
ProcessHistory("IPHOST","keysort","$1","$_") && next; | |
# order ip nat source static statements | |
/^ip nat (\S+) source static (\S+)/ && | |
ProcessHistory("IP NAT $1","ipsort","$2","$_") && next; | |
# order atm map-list statements | |
/^\s+ip\s+(\d+\.\d+\.\d+\.\d+)\s+atm-vc/ && | |
ProcessHistory("ATM map-list","ipsort","$1","$_") && next; | |
# order ip rcmd lines | |
/^ip rcmd/ && ProcessHistory("RCMD","keysort","$_","$_") && next; | |
# system controller | |
/^syscon address (\S*) (\S*)/ && | |
ProcessHistory("","","","!syscon address $1 <removed>\n") && | |
next; | |
if (/^syscon password (\S*)/ && $filter_pwds >= 1) { | |
ProcessHistory("","","","!syscon password <removed>\n"); | |
next; | |
} | |
/^ *Cryptochecksum:/ && next; | |
# catch anything that wasnt matched above. | |
ProcessHistory("","","","$_"); | |
# end of config. the ": " game is for the PIX | |
if (/^(: +)?}$/) { | |
$found_end = 1; | |
return(0); | |
} | |
} | |
# The ContentEngine lacks a definitive "end of config" marker. If we | |
# know that it is a CE, SAN, or NXOS and we have seen at least 5 lines | |
# of write term output, we can be reasonably sure that we got the config. | |
if (($type == "CE" || $type == "SAN" || $type == "NXOS" ) && $linecnt > 5) { | |
$found_end = 1; | |
return(0); | |
} | |
return(0); | |
} | |
# dummy function | |
sub DoNothing {print STDOUT;} | |
# Main | |
@commandtable = ( | |
{'show configuration' => 'WriteTerm'} | |
); | |
# Use an array to preserve the order of the commands and a hash for mapping | |
# commands to the subroutine and track commands that have been completed. | |
@commands = map(keys(%$_), @commandtable); | |
%commands = map(%$_, @commandtable); | |
$cisco_cmds = join(";",@commands); | |
$cmds_regexp = join("|", map quotemeta($_), @commands); | |
if (length($host) == 0) { | |
if ($file) { | |
print(STDERR "Too few arguments: file name required\n"); | |
exit(1); | |
} else { | |
print(STDERR "Too few arguments: host name required\n"); | |
exit(1); | |
} | |
} | |
open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n"; | |
select(OUTPUT); | |
# make OUTPUT unbuffered if debugging | |
if ($debug) { $| = 1; } | |
if ($file) { | |
print STDERR "opening file $host\n" if ($debug); | |
print STDOUT "opening file $host\n" if ($log); | |
open(INPUT,"<$host") || die "open failed for $host: $!\n"; | |
} else { | |
print STDERR "executing clogin -t $timeo -c\"$cisco_cmds\" $host\n" if ($debug); | |
print STDOUT "executing clogin -t $timeo -c\"$cisco_cmds\" $host\n" if ($log); | |
if (defined($ENV{NOPIPE})) { | |
system "clogin -t $timeo -c \"$cisco_cmds\" $host </dev/null > $host.raw 2>&1" || die "clogin failed for $host: $!\n"; | |
open(INPUT, "< $host.raw") || die "clogin failed for $host: $!\n"; | |
} else { | |
open(INPUT,"clogin -t $timeo -c \"$cisco_cmds\" $host </dev/null |") || die "clogin failed for $host: $!\n"; | |
} | |
} | |
# determine ACL sorting mode | |
if ($ENV{"ACLSORT"} =~ /no/i) { | |
$aclsort = ""; | |
} | |
# determine community string filtering mode | |
if (defined($ENV{"NOCOMMSTR"}) && | |
($ENV{"NOCOMMSTR"} =~ /yes/i || $ENV{"NOCOMMSTR"} =~ /^$/)) { | |
$filter_commstr = 1; | |
} else { | |
$filter_commstr = 0; | |
} | |
# determine password filtering mode | |
if ($ENV{"FILTER_PWDS"} =~ /no/i) { | |
$filter_pwds = 0; | |
} elsif ($ENV{"FILTER_PWDS"} =~ /all/i) { | |
$filter_pwds = 2; | |
} else { | |
$filter_pwds = 1; | |
} | |
ProcessHistory("","","","!RANCID-CONTENT-TYPE: vyatta\n!\n"); | |
ProcessHistory("COMMENTS","keysort","B0","!\n"); | |
ProcessHistory("COMMENTS","keysort","D0","!\n"); | |
ProcessHistory("COMMENTS","keysort","F0","!\n"); | |
ProcessHistory("COMMENTS","keysort","G0","!\n"); | |
TOP: while(<INPUT>) { | |
tr/\015//d; | |
if (/[>#]\s?exit$/) { | |
$clean_run = 1; | |
last; | |
} | |
if (/^Error:/) { | |
print STDOUT ("$host clogin error: $_"); | |
print STDERR ("$host clogin error: $_") if ($debug); | |
$clean_run = 0; | |
last; | |
} | |
while (/[>#]\s*($cmds_regexp)\s*$/) { | |
$cmd = $1; | |
if (!defined($prompt)) { | |
$prompt = ($_ =~ /^([^#>]+[#>])/)[0]; | |
$prompt =~ s/([][}{)(\\])/\\$1/g; | |
print STDERR ("PROMPT MATCH: $prompt\n") if ($debug); | |
} | |
print STDERR ("HIT COMMAND:$_") if ($debug); | |
if (! defined($commands{$cmd})) { | |
print STDERR "$host: found unexpected command - \"$cmd\"\n"; | |
$clean_run = 0; | |
last TOP; | |
} | |
$rval = &{$commands{$cmd}}; | |
delete($commands{$cmd}); | |
if ($rval == -1) { | |
$clean_run = 0; | |
last TOP; | |
} | |
} | |
} | |
print STDOUT "Done $logincmd: $_\n" if ($log); | |
# Flush History | |
ProcessHistory("","","",""); | |
# Cleanup | |
close(INPUT); | |
close(OUTPUT); | |
if (defined($ENV{NOPIPE})) { | |
unlink("$host.raw") if (! $debug); | |
} | |
# check for completeness | |
if (scalar(%commands) || !$clean_run || !$found_end) { | |
if (scalar(%commands)) { | |
printf(STDOUT "$host: missed cmd(s): %s\n", join(',', keys(%commands))); | |
printf(STDERR "$host: missed cmd(s): %s\n", join(',', keys(%commands))) if ($debug); | |
} | |
if (!$clean_run || !$found_end) { | |
print STDOUT "$host: End of run not found\n"; | |
print STDERR "$host: End of run not found\n" if ($debug); | |
system("/usr/bin/tail -1 $host.new"); | |
} | |
unlink "$host.new" if (! $debug); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment