Command-Line Tools

More: Getopt::Long::Descriptive (options + usage), App::Cmd (git-style subcommands), CLI::Osprey (Moo-based), Term::ANSIColor (colors, core), Term::ProgressBar, Term::ReadLine, IO::Prompter, Term::Table.

Exit codes: exit 0 for success, nonzero for errors. Write errors to STDERR. Support --help. Read from STDIN when no files are given (<> does this).

#!/usr/bin/env perl
use v5.36;
use Getopt::Long qw(GetOptions :config no_ignore_case bundling);
use Pod::Usage qw(pod2usage);

my %opt = (verbose => 0, count => 10, format => 'text');
GetOptions(
    'h|help'      => sub { pod2usage(-verbose => 1, -exitval => 0) },
    'man'         => sub { pod2usage(-verbose => 2, -exitval => 0) },
    'v|verbose+'  => \$opt{verbose},        # incremental: -vvv
    'n|count=i'   => \$opt{count},          # integer
    'o|output=s'  => \$opt{output},         # string
    'f|format=s'  => \$opt{format},
    'tag=s@'      => \$opt{tags},           # repeatable: --tag a --tag b
    'define=s%'   => \$opt{define},         # key=value pairs
    'dry-run!'    => \$opt{dry_run},        # --dry-run / --no-dry-run
) or pod2usage(2);

pod2usage('Missing input file') unless @ARGV;
die "Invalid format\n" unless $opt{format} =~ /^(text|json|csv)$/;

for my $file (@ARGV) {
    say "Processing $file (count=$opt{count})" if $opt{verbose};
}

__END__

=head1 NAME

mytool - Process files and produce reports

=head1 SYNOPSIS

mytool [options] FILE...

  -n, --count N     Number of records (default 10)
  -o, --output FILE Output file
  -f, --format FMT  text|json|csv
…