Skip to content

Instantly share code, notes, and snippets.

@philpennock
Last active May 25, 2022 17:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philpennock/7049516 to your computer and use it in GitHub Desktop.
Save philpennock/7049516 to your computer and use it in GitHub Desktop.
Used to iterate over PEM-encoded items in a file
#!/usr/bin/env perl
#
# For a file containing PEM objects or PGP objects or whatever, emit the file
# filtering the objects through a command-line which takes the object on stdin.
#
# foreach-beginend bundle.crt openssl x509 -noout -text
# The filename may be specified as '-' to act as a filter reading from stdin.
#
use strict;
use warnings;
BEGIN { pop @INC if $INC[-1] eq '.' };
use File::Basename qw();
use Getopt::Long;
use IO::File;
my $VERBOSE = 0;
my $PRINT_ONLY_PEMBLOCK = 0;
my $NEED_USAGE = undef;
my $SEPARATOR_TEXT = undef;
Getopt::Long::Configure qw/no_auto_abbrev no_getopt_compat require_order bundling/;
GetOptions(
'verbose|v!' => \$VERBOSE,
'help|h!' => \$NEED_USAGE,
'pemonly!' => \$PRINT_ONLY_PEMBLOCK,
'separator|s=s' => \$SEPARATOR_TEXT,
) or die "Error in command-line flags\n";
if (scalar @ARGV < 2 and not defined $NEED_USAGE) {
$NEED_USAGE = -1;
}
if (defined $NEED_USAGE and $NEED_USAGE) {
select STDERR if $NEED_USAGE < 0;
print "Usage: foreach-beginend [<flags>] <file> <command> <command-args ...>\n";
print " args may be %{TEMPLATE} based\n";
print " templates: COUNTER FILENAME BASENAME\n";
print " --pemonly do not print lines from outside PEM blocks\n";
print " --separator TXT show TXT between each command (+newline if not present)\n";
print " -s TXT same as --separator\n";
exit (($NEED_USAGE < 0) ? -$NEED_USAGE : 0);
}
my $fn = shift @ARGV;
my $fh;
if ($fn eq '-') {
$fh = \*STDIN;
} else {
$fh = new IO::File $fn, '<' or die "read-open($fn) failed: $!\n";
}
sub expand_one_template {
my ($t, $opts) = @_;
my %direct_vars = (
counter => -1,
filename => 'anonymous',
basename => 'anonymous',
);
foreach my $k (keys %direct_vars) {
next unless $t eq uc $k;
return $opts->{$k} if exists $opts->{$k};
return $direct_vars{$k};
}
return 'unknown';
}
sub argv_expand_templates {
local %_=@_;
die "no argv" unless exists $_{'argv'};
my @args;
foreach my $a (@{$_{'argv'}}) {
(my $b = $a) =~ s/%\{([^}]+)\}/expand_one_template($1, \%_)/eg;
push @args, $b;
}
return @args;
}
my $fn_base = File::Basename::basename $fn;
my $in_block = 0;
my @block;
my $counter = 0;
my $shown = 0;
while (<$fh>) {
if ($in_block) {
push @block, $_;
next unless /^-----END/;
if ($shown) {
# There's intervening lines from the input, shown unless pemonly.
# There's also optional separator text to emit, which is strictly
# a separator, not a prefix or suffix.
if (defined $SEPARATOR_TEXT) {
print $SEPARATOR_TEXT;
print "\n" unless $SEPARATOR_TEXT =~ /\n\z/;
}
}
local $| = 1;
my @args = argv_expand_templates(
argv => \@ARGV,
counter => $counter,
filename => $fn,
basename => $fn_base,
# If changing these, don't forget to update the usage text
);
open(CMD, '|-', @args) or do {
print STDERR "failed to run [@args]: $!\n";
@block = ();
$in_block = 0;
next;
};
print CMD @block;
close(CMD);
@block = ();
if ($? != 0) {
my ($ex, $sig, $core) = ($? >> 8, $? & 127, $? & 128);
my $emsg = "$args[0] died";
$emsg .= ", exiting $ex" if $ex;
$emsg .= ", signal $sig" if $sig;
$emsg .= ' (core dumped)' if $core;
print STDERR "$emsg\n";
}
$in_block = 0;
$shown++;
next;
}
if (/^-----BEGIN/) {
@block = ($_);
$in_block = 1;
$counter++;
print "\n***** Item $counter at line $. *****\n" if $VERBOSE;
next;
}
print $_ unless $PRINT_ONLY_PEMBLOCK;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment