Lines 10-17
use File::Path;
Link Here
|
10 |
use C4::Biblio; |
10 |
use C4::Biblio; |
11 |
use C4::AuthoritiesMarc; |
11 |
use C4::AuthoritiesMarc; |
12 |
use C4::Items; |
12 |
use C4::Items; |
13 |
use Koha::RecordProcessor; |
13 |
use C4::ZebraIndex; |
14 |
use XML::LibXML; |
|
|
15 |
|
14 |
|
16 |
# script that checks zebradir structure & create directories & mandatory files if needed |
15 |
# script that checks zebradir structure & create directories & mandatory files if needed |
17 |
# |
16 |
# |
Lines 172-183
if ( $verbose_logging ) {
Link Here
|
172 |
} |
171 |
} |
173 |
if ($keep_export) { |
172 |
if ($keep_export) { |
174 |
print "NOTHING cleaned : the export $directory has been kept.\n"; |
173 |
print "NOTHING cleaned : the export $directory has been kept.\n"; |
175 |
print "You can re-run this script with the -s "; |
174 |
print "You can re-run this script with the -s and -d $directory parameters"; |
176 |
if ($use_tempdir) { |
|
|
177 |
print " and -d $directory parameters"; |
178 |
} else { |
179 |
print "parameter"; |
180 |
} |
181 |
print "\n"; |
175 |
print "\n"; |
182 |
print "if you just want to rebuild zebra after changing the record.abs\n"; |
176 |
print "if you just want to rebuild zebra after changing the record.abs\n"; |
183 |
print "or another zebra config file\n"; |
177 |
print "or another zebra config file\n"; |
Lines 212-279
sub check_zebra_dirs {
Link Here
|
212 |
sub index_records { |
206 |
sub index_records { |
213 |
my ($record_type, $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $server_dir) = @_; |
207 |
my ($record_type, $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $server_dir) = @_; |
214 |
|
208 |
|
215 |
my $num_records_exported = 0; |
|
|
216 |
my $records_deleted; |
217 |
my $need_reset = check_zebra_dirs($server_dir); |
209 |
my $need_reset = check_zebra_dirs($server_dir); |
218 |
if ($need_reset) { |
210 |
if ($need_reset) { |
219 |
print "$0: found broken zebra server directories: forcing a rebuild\n"; |
211 |
print "$0: found broken zebra server directories: forcing a rebuild\n"; |
220 |
$reset = 1; |
212 |
$reset = 1; |
221 |
} |
213 |
} |
222 |
if ($skip_export && $verbose_logging) { |
214 |
|
|
|
215 |
if ( $verbose_logging ) { |
223 |
print "====================\n"; |
216 |
print "====================\n"; |
224 |
print "SKIPPING $record_type export\n"; |
217 |
print "REINDEXING zebra\n"; |
225 |
print "====================\n"; |
218 |
print "====================\n"; |
226 |
} else { |
|
|
227 |
if ( $verbose_logging ) { |
228 |
print "====================\n"; |
229 |
print "exporting $record_type\n"; |
230 |
print "====================\n"; |
231 |
} |
232 |
mkdir "$directory" unless (-d $directory); |
233 |
mkdir "$directory/$record_type" unless (-d "$directory/$record_type"); |
234 |
if ($process_zebraqueue) { |
235 |
my $entries = select_zebraqueue_records($record_type, 'deleted'); |
236 |
mkdir "$directory/del_$record_type" unless (-d "$directory/del_$record_type"); |
237 |
$records_deleted = generate_deleted_marc_records($record_type, $entries, "$directory/del_$record_type", $as_xml); |
238 |
mark_zebraqueue_batch_done($entries); |
239 |
$entries = select_zebraqueue_records($record_type, 'updated'); |
240 |
mkdir "$directory/upd_$record_type" unless (-d "$directory/upd_$record_type"); |
241 |
$num_records_exported = export_marc_records_from_list($record_type, |
242 |
$entries, "$directory/upd_$record_type", $as_xml, $noxml, $records_deleted); |
243 |
mark_zebraqueue_batch_done($entries); |
244 |
} else { |
245 |
my $sth = select_all_records($record_type); |
246 |
$num_records_exported = export_marc_records_from_sth($record_type, $sth, "$directory/$record_type", $as_xml, $noxml, $nosanitize); |
247 |
unless ($do_not_clear_zebraqueue) { |
248 |
mark_all_zebraqueue_done($record_type); |
249 |
} |
250 |
} |
251 |
} |
219 |
} |
252 |
|
220 |
|
253 |
# |
221 |
my $record_fmt = ($as_xml) ? 'marcxml' : 'iso2709' ; |
254 |
# and reindexing everything |
222 |
if ($process_zebraqueue) { |
255 |
# |
223 |
# Process 'deleted' records |
256 |
if ($skip_index) { |
224 |
my $entries = select_zebraqueue_records( $record_type, 'deleted' ); |
257 |
if ($verbose_logging) { |
225 |
my @entries_to_delete = map { $_->{biblio_auth_id} } @$entries; |
258 |
print "====================\n"; |
226 |
|
259 |
print "SKIPPING $record_type indexing\n"; |
227 |
C4::ZebraIndex::DeleteRecordIndex($record_type, \@entries_to_delete, { |
260 |
print "====================\n"; |
228 |
as_xml => $as_xml, noshadow => $noshadow, record_format => $record_fmt, |
261 |
} |
229 |
zebraidx_log_opt => $zebraidx_log_opt, verbose => $verbose_logging, |
|
|
230 |
skip_export => $skip_export, skip_index => $skip_index, |
231 |
keep_export => $keep_export, directory => $directory |
232 |
}); |
233 |
|
234 |
mark_zebraqueue_batch_done($entries); |
235 |
|
236 |
# Process 'updated' records' |
237 |
$entries = select_zebraqueue_records( $record_type, 'updated' ); |
238 |
my @entries_to_update = map { |
239 |
my $id = $_->{biblio_auth_id}; |
240 |
# do not try to update deleted records |
241 |
(0 == grep { $id == $_ } @entries_to_delete) ? $id : () |
242 |
} @$entries; |
243 |
|
244 |
C4::ZebraIndex::IndexRecord($record_type, \@entries_to_update, { |
245 |
as_xml => $as_xml, noxml => $noxml, reset_index => $reset, |
246 |
noshadow => $noshadow, record_format => $record_fmt, |
247 |
zebraidx_log_opt => $zebraidx_log_opt, verbose => $verbose_logging, |
248 |
skip_export => $skip_export, skip_index => $skip_index, |
249 |
keep_export => $keep_export, directory => $directory |
250 |
}); |
251 |
|
252 |
mark_zebraqueue_batch_done($entries); |
262 |
} else { |
253 |
} else { |
263 |
if ( $verbose_logging ) { |
254 |
my $sth = select_all_records($record_type); |
264 |
print "====================\n"; |
255 |
my $entries = $sth->fetchall_arrayref([]); |
265 |
print "REINDEXING zebra\n"; |
256 |
my @entries_to_update = map { $_->[0] } @$entries; |
266 |
print "====================\n"; |
257 |
|
267 |
} |
258 |
C4::ZebraIndex::IndexRecord($record_type, \@entries_to_update, { |
268 |
my $record_fmt = ($as_xml) ? 'marcxml' : 'iso2709' ; |
259 |
as_xml => $as_xml, noxml => $noxml, reset_index => $reset, |
269 |
if ($process_zebraqueue) { |
260 |
noshadow => $noshadow, record_format => $record_fmt, |
270 |
do_indexing($record_type, 'adelete', "$directory/del_$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt) |
261 |
zebraidx_log_opt => $zebraidx_log_opt, nosanitize => $nosanitize, |
271 |
if %$records_deleted; |
262 |
verbose => $verbose_logging, skip_export => $skip_export, |
272 |
do_indexing($record_type, 'update', "$directory/upd_$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt) |
263 |
skip_index => $skip_index, keep_export => $keep_export, |
273 |
if $num_records_exported; |
264 |
directory => $directory |
274 |
} else { |
265 |
}); |
275 |
do_indexing($record_type, 'update', "$directory/$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt) |
266 |
|
276 |
if ($num_records_exported or $skip_export); |
267 |
unless ($do_not_clear_zebraqueue) { |
|
|
268 |
mark_all_zebraqueue_done($record_type); |
277 |
} |
269 |
} |
278 |
} |
270 |
} |
279 |
} |
271 |
} |
Lines 343-683
sub select_all_biblios {
Link Here
|
343 |
return $sth; |
335 |
return $sth; |
344 |
} |
336 |
} |
345 |
|
337 |
|
346 |
sub include_xml_wrapper { |
|
|
347 |
my $as_xml = shift; |
348 |
my $record_type = shift; |
349 |
|
350 |
return 0 unless $as_xml; |
351 |
return 1 if $record_type eq 'biblio' and $bib_index_mode eq 'dom'; |
352 |
return 1 if $record_type eq 'authority' and $auth_index_mode eq 'dom'; |
353 |
return 0; |
354 |
|
355 |
} |
356 |
|
357 |
sub export_marc_records_from_sth { |
358 |
my ($record_type, $sth, $directory, $as_xml, $noxml, $nosanitize) = @_; |
359 |
|
360 |
my $num_exported = 0; |
361 |
open my $fh, '>:encoding(UTF-8) ', "$directory/exported_records" or die $!; |
362 |
if (include_xml_wrapper($as_xml, $record_type)) { |
363 |
# include XML declaration and root element |
364 |
print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>'; |
365 |
} |
366 |
my $i = 0; |
367 |
my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",''); |
368 |
while (my ($record_number) = $sth->fetchrow_array) { |
369 |
print "." if ( $verbose_logging ); |
370 |
print "\r$i" unless ($i++ %100 or !$verbose_logging); |
371 |
if ( $nosanitize ) { |
372 |
my $marcxml = $record_type eq 'biblio' |
373 |
? GetXmlBiblio( $record_number ) |
374 |
: GetAuthorityXML( $record_number ); |
375 |
if ($record_type eq 'biblio'){ |
376 |
my @items = GetItemsInfo($record_number); |
377 |
if (@items){ |
378 |
my $record = MARC::Record->new; |
379 |
$record->encoding('UTF-8'); |
380 |
my @itemsrecord; |
381 |
foreach my $item (@items){ |
382 |
my $record = Item2Marc($item, $record_number); |
383 |
push @itemsrecord, $record->field($itemtag); |
384 |
} |
385 |
$record->insert_fields_ordered(@itemsrecord); |
386 |
my $itemsxml = $record->as_xml_record(); |
387 |
$marcxml = |
388 |
substr($marcxml, 0, length($marcxml)-10) . |
389 |
substr($itemsxml, index($itemsxml, "</leader>\n", 0) + 10); |
390 |
} |
391 |
} |
392 |
# extra test to ensure that result is valid XML; otherwise |
393 |
# Zebra won't parse it in DOM mode |
394 |
eval { |
395 |
my $doc = $tester->parse_string($marcxml); |
396 |
}; |
397 |
if ($@) { |
398 |
warn "Error exporting record $record_number ($record_type): $@\n"; |
399 |
next; |
400 |
} |
401 |
if ( $marcxml ) { |
402 |
$marcxml =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!; |
403 |
print {$fh} $marcxml; |
404 |
$num_exported++; |
405 |
} |
406 |
next; |
407 |
} |
408 |
my ($marc) = get_corrected_marc_record($record_type, $record_number, $noxml); |
409 |
if (defined $marc) { |
410 |
eval { |
411 |
my $rec; |
412 |
if ($as_xml) { |
413 |
$rec = $marc->as_xml_record(C4::Context->preference('marcflavour')); |
414 |
eval { |
415 |
my $doc = $tester->parse_string($rec); |
416 |
}; |
417 |
if ($@) { |
418 |
die "invalid XML: $@"; |
419 |
} |
420 |
$rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!; |
421 |
} else { |
422 |
$rec = $marc->as_usmarc(); |
423 |
} |
424 |
print {$fh} $rec; |
425 |
$num_exported++; |
426 |
}; |
427 |
if ($@) { |
428 |
warn "Error exporting record $record_number ($record_type) ".($noxml ? "not XML" : "XML"); |
429 |
warn "... specific error is $@" if $verbose_logging; |
430 |
} |
431 |
} |
432 |
} |
433 |
print "\nRecords exported: $num_exported\n" if ( $verbose_logging ); |
434 |
print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type)); |
435 |
close $fh; |
436 |
return $num_exported; |
437 |
} |
438 |
|
439 |
sub export_marc_records_from_list { |
440 |
my ($record_type, $entries, $directory, $as_xml, $noxml, $records_deleted) = @_; |
441 |
|
442 |
my $num_exported = 0; |
443 |
open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!; |
444 |
if (include_xml_wrapper($as_xml, $record_type)) { |
445 |
# include XML declaration and root element |
446 |
print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>'; |
447 |
} |
448 |
my $i = 0; |
449 |
|
450 |
# Skip any deleted records. We check for this anyway, but this reduces error spam |
451 |
my %found = %$records_deleted; |
452 |
foreach my $record_number ( map { $_->{biblio_auth_number} } |
453 |
grep { !$found{ $_->{biblio_auth_number} }++ } |
454 |
@$entries ) { |
455 |
print "." if ( $verbose_logging ); |
456 |
print "\r$i" unless ($i++ %100 or !$verbose_logging); |
457 |
my ($marc) = get_corrected_marc_record($record_type, $record_number, $noxml); |
458 |
if (defined $marc) { |
459 |
eval { |
460 |
my $rec; |
461 |
if ($as_xml) { |
462 |
$rec = $marc->as_xml_record(C4::Context->preference('marcflavour')); |
463 |
$rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!; |
464 |
} else { |
465 |
$rec = $marc->as_usmarc(); |
466 |
} |
467 |
print {$fh} $rec; |
468 |
$num_exported++; |
469 |
}; |
470 |
if ($@) { |
471 |
warn "Error exporting record $record_number ($record_type) ".($noxml ? "not XML" : "XML"); |
472 |
} |
473 |
} |
474 |
} |
475 |
print "\nRecords exported: $num_exported\n" if ( $verbose_logging ); |
476 |
print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type)); |
477 |
close $fh; |
478 |
return $num_exported; |
479 |
} |
480 |
|
481 |
sub generate_deleted_marc_records { |
482 |
my ($record_type, $entries, $directory, $as_xml) = @_; |
483 |
|
484 |
my $records_deleted = {}; |
485 |
open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!; |
486 |
if (include_xml_wrapper($as_xml, $record_type)) { |
487 |
# include XML declaration and root element |
488 |
print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>'; |
489 |
} |
490 |
my $i = 0; |
491 |
foreach my $record_number (map { $_->{biblio_auth_number} } @$entries ) { |
492 |
print "\r$i" unless ($i++ %100 or !$verbose_logging); |
493 |
print "." if ( $verbose_logging ); |
494 |
|
495 |
my $marc = MARC::Record->new(); |
496 |
if ($record_type eq 'biblio') { |
497 |
fix_biblio_ids($marc, $record_number, $record_number); |
498 |
} else { |
499 |
fix_authority_id($marc, $record_number); |
500 |
} |
501 |
if (C4::Context->preference("marcflavour") eq "UNIMARC") { |
502 |
fix_unimarc_100($marc); |
503 |
} |
504 |
|
505 |
my $rec; |
506 |
if ($as_xml) { |
507 |
$rec = $marc->as_xml_record(C4::Context->preference('marcflavour')); |
508 |
$rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!; |
509 |
} else { |
510 |
$rec = $marc->as_usmarc(); |
511 |
} |
512 |
print {$fh} $rec; |
513 |
|
514 |
$records_deleted->{$record_number} = 1; |
515 |
} |
516 |
print "\nRecords exported: $i\n" if ( $verbose_logging ); |
517 |
print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type)); |
518 |
close $fh; |
519 |
return $records_deleted; |
520 |
|
521 |
|
522 |
} |
523 |
|
524 |
sub get_corrected_marc_record { |
525 |
my ($record_type, $record_number, $noxml) = @_; |
526 |
|
527 |
my $marc = get_raw_marc_record($record_type, $record_number, $noxml); |
528 |
|
529 |
if (defined $marc) { |
530 |
fix_leader($marc); |
531 |
if ($record_type eq 'authority') { |
532 |
fix_authority_id($marc, $record_number); |
533 |
} elsif ($record_type eq 'biblio' && C4::Context->preference('IncludeSeeFromInSearches')) { |
534 |
my $normalizer = Koha::RecordProcessor->new( { filters => 'EmbedSeeFromHeadings' } ); |
535 |
$marc = $normalizer->process($marc); |
536 |
} |
537 |
if (C4::Context->preference("marcflavour") eq "UNIMARC") { |
538 |
fix_unimarc_100($marc); |
539 |
} |
540 |
} |
541 |
|
542 |
return $marc; |
543 |
} |
544 |
|
545 |
sub get_raw_marc_record { |
546 |
my ($record_type, $record_number, $noxml) = @_; |
547 |
|
548 |
my $marc; |
549 |
if ($record_type eq 'biblio') { |
550 |
if ($noxml) { |
551 |
my $fetch_sth = $dbh->prepare_cached("SELECT marc FROM biblioitems WHERE biblionumber = ?"); |
552 |
$fetch_sth->execute($record_number); |
553 |
if (my ($blob) = $fetch_sth->fetchrow_array) { |
554 |
$marc = MARC::Record->new_from_usmarc($blob); |
555 |
unless ($marc) { |
556 |
warn "error creating MARC::Record from $blob"; |
557 |
} |
558 |
} |
559 |
# failure to find a bib is not a problem - |
560 |
# a delete could have been done before |
561 |
# trying to process a record update |
562 |
|
563 |
$fetch_sth->finish(); |
564 |
return unless $marc; |
565 |
} else { |
566 |
eval { $marc = GetMarcBiblio($record_number, 1); }; |
567 |
if ($@ || !$marc) { |
568 |
# here we do warn since catching an exception |
569 |
# means that the bib was found but failed |
570 |
# to be parsed |
571 |
warn "error retrieving biblio $record_number"; |
572 |
return; |
573 |
} |
574 |
} |
575 |
} else { |
576 |
eval { $marc = GetAuthority($record_number); }; |
577 |
if ($@) { |
578 |
warn "error retrieving authority $record_number"; |
579 |
return; |
580 |
} |
581 |
} |
582 |
return $marc; |
583 |
} |
584 |
|
585 |
sub fix_leader { |
586 |
# FIXME - this routine is suspect |
587 |
# It blanks the Leader/00-05 and Leader/12-16 to |
588 |
# force them to be recalculated correct when |
589 |
# the $marc->as_usmarc() or $marc->as_xml() is called. |
590 |
# But why is this necessary? It would be a serious bug |
591 |
# in MARC::Record (definitely) and MARC::File::XML (arguably) |
592 |
# if they are emitting incorrect leader values. |
593 |
my $marc = shift; |
594 |
|
595 |
my $leader = $marc->leader; |
596 |
substr($leader, 0, 5) = ' '; |
597 |
substr($leader, 10, 7) = '22 '; |
598 |
$marc->leader(substr($leader, 0, 24)); |
599 |
} |
600 |
|
601 |
sub fix_biblio_ids { |
602 |
# FIXME - it is essential to ensure that the biblionumber is present, |
603 |
# otherwise, Zebra will choke on the record. However, this |
604 |
# logic belongs in the relevant C4::Biblio APIs. |
605 |
my $marc = shift; |
606 |
my $biblionumber = shift; |
607 |
my $biblioitemnumber; |
608 |
if (@_) { |
609 |
$biblioitemnumber = shift; |
610 |
} else { |
611 |
my $sth = $dbh->prepare( |
612 |
"SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?"); |
613 |
$sth->execute($biblionumber); |
614 |
($biblioitemnumber) = $sth->fetchrow_array; |
615 |
$sth->finish; |
616 |
unless ($biblioitemnumber) { |
617 |
warn "failed to get biblioitemnumber for biblio $biblionumber"; |
618 |
return 0; |
619 |
} |
620 |
} |
621 |
|
622 |
# FIXME - this is cheating on two levels |
623 |
# 1. C4::Biblio::_koha_marc_update_bib_ids is meant to be an internal function |
624 |
# 2. Making sure that the biblionumber and biblioitemnumber are correct and |
625 |
# present in the MARC::Record object ought to be part of GetMarcBiblio. |
626 |
# |
627 |
# On the other hand, this better for now than what rebuild_zebra.pl used to |
628 |
# do, which was duplicate the code for inserting the biblionumber |
629 |
# and biblioitemnumber |
630 |
C4::Biblio::_koha_marc_update_bib_ids($marc, '', $biblionumber, $biblioitemnumber); |
631 |
|
632 |
return 1; |
633 |
} |
634 |
|
635 |
sub fix_authority_id { |
636 |
# FIXME - as with fix_biblio_ids, the authid must be present |
637 |
# for Zebra's sake. However, this really belongs |
638 |
# in C4::AuthoritiesMarc. |
639 |
my ($marc, $authid) = @_; |
640 |
unless ($marc->field('001') and $marc->field('001')->data() eq $authid){ |
641 |
$marc->delete_field($marc->field('001')); |
642 |
$marc->insert_fields_ordered(MARC::Field->new('001',$authid)); |
643 |
} |
644 |
} |
645 |
|
646 |
sub fix_unimarc_100 { |
647 |
# FIXME - again, if this is necessary, it belongs in C4::AuthoritiesMarc. |
648 |
my $marc = shift; |
649 |
|
650 |
my $string; |
651 |
if ( length($marc->subfield( 100, "a" )) == 36 ) { |
652 |
$string = $marc->subfield( 100, "a" ); |
653 |
my $f100 = $marc->field(100); |
654 |
$marc->delete_field($f100); |
655 |
} |
656 |
else { |
657 |
$string = POSIX::strftime( "%Y%m%d", localtime ); |
658 |
$string =~ s/\-//g; |
659 |
$string = sprintf( "%-*s", 35, $string ); |
660 |
} |
661 |
substr( $string, 22, 6, "frey50" ); |
662 |
unless ( length($marc->subfield( 100, "a" )) == 36 ) { |
663 |
$marc->delete_field($marc->field(100)); |
664 |
$marc->insert_grouped_field(MARC::Field->new( 100, "", "", "a" => $string )); |
665 |
} |
666 |
} |
667 |
|
668 |
sub do_indexing { |
669 |
my ($record_type, $op, $record_dir, $reset_index, $noshadow, $record_format, $zebraidx_log_opt) = @_; |
670 |
|
671 |
my $zebra_server = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver'; |
672 |
my $zebra_db_name = ($record_type eq 'biblio') ? 'biblios' : 'authorities'; |
673 |
my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'}; |
674 |
my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'}; |
675 |
|
676 |
system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name init") if $reset_index; |
677 |
system("zebraidx -c $zebra_config $zebraidx_log_opt $noshadow -g $record_format -d $zebra_db_name $op $record_dir"); |
678 |
system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name commit") unless $noshadow; |
679 |
|
680 |
} |
681 |
|
338 |
|
682 |
sub print_usage { |
339 |
sub print_usage { |
683 |
print <<_USAGE_; |
340 |
print <<_USAGE_; |
684 |
- |
|
|