Created
August 18, 2017 06:41
-
-
Save kckrinke/f1d202bdbda1e417508acb40741b76a1 to your computer and use it in GitHub Desktop.
CLI for listing, viewing profiles and modifying ~/.ssh/config
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/env perl | |
use strict; | |
use warnings; | |
use diagnostics; | |
use Getopt::Long; | |
use Pod::Usage; | |
use constant {true=>1,false=>0}; | |
our $SSH_CONFIG_PATH = $ENV{'HOME'} . '/.ssh/config'; | |
my $usage = false; | |
my $man = false; | |
my $help = false; | |
our $QUIET = false; | |
GetOptions | |
( 'h' => \$usage, | |
'help' => \$help, | |
'man' => \$man, | |
'q|quiet' => \$QUIET, | |
'C|config=s' => \$SSH_CONFIG_PATH, | |
) | |
or pod2usage(0); | |
pod2usage(-verbose => 0) if $usage; | |
pod2usage(-verbose => 1) if $help; | |
pod2usage(-verbose => 2) if $man; | |
pod2usage(-message => "File Not Found: ".$SSH_CONFIG_PATH) | |
unless -f $SSH_CONFIG_PATH; | |
our (%PROFILES) = get_profiles_from_file($SSH_CONFIG_PATH); | |
if ((@ARGV == 0) && (-t STDIN)) { | |
print_profiles_list(list_profile_names('.')); | |
} else { | |
my $operation = shift @ARGV; | |
if ($operation eq "list") { | |
if (@ARGV==1) { | |
print_profiles_list(list_profile_names($ARGV[0])); | |
} elsif (@ARGV>=1) { | |
pod2usage | |
( -message => "The \"list\" sub-command requires a single regular expression pattern to search with" ); | |
} else { | |
print_profiles_list(list_profile_names('.')); | |
} | |
} elsif ($operation eq "show") { | |
my (@keys) = (@ARGV) ? @ARGV : (sort keys %PROFILES); | |
foreach my $key (@keys) { | |
unless (print_profile($key)) { | |
my (@names) = list_profile_names($key); | |
foreach my $name (@names) { | |
print_profile($name); | |
} | |
} | |
} | |
} elsif ($operation eq "check") { | |
if (@ARGV == 1) { | |
my (@names) = list_profile_names($ARGV[0]); | |
if (@names) { | |
print_profiles_list(@names); | |
} else { | |
stdmsg("No profiles found matching: \"".$ARGV[0]."\"\n"); | |
exit(4); | |
} | |
} else { | |
pod2usage | |
( -message => 'The "check" sub-command requires a single profile name to check for' ); | |
} | |
} elsif ($operation eq "edit") { | |
my $editor = undef; | |
# look for user's EDITOR environment | |
if (exists $ENV{EDITOR}) { | |
if (-f $ENV{EDITOR}) { | |
$editor = $ENV{EDITOR}; | |
} | |
$editor = find_bin_path($ENV{EDITOR}); | |
} | |
# look for 'editor' program (usually a wrapper) | |
$editor = find_bin_path('editor') | |
unless defined $editor; | |
# stop the esoteric-keybound-editor-wars, make nano the defacto unix | |
# default command line editor | |
$editor = find_bin_path('nano') | |
unless defined $editor; | |
# if emacs is installed - they probably really really want that | |
$editor = find_bin_path('emacs') | |
unless defined $editor; | |
# at least it's not vi | |
$editor = find_bin_path('vim') | |
unless defined $editor; | |
# vi - the real man's last ditch effort | |
$editor = find_bin_path('vi') | |
unless defined $editor; | |
# if we haven't found it by now | |
if (defined $editor) { | |
exec($editor,$SSH_CONFIG_PATH) | |
} else { | |
pod2usage(-message=>"Please set your EDITOR environment variable if you want to edit the file: ".$SSH_CONFIG_PATH); | |
} | |
} else { | |
pod2usage(-verbose=>0); | |
} | |
} | |
exit(0); | |
# | |
# Sub Functions | |
# | |
sub list_profile_names { | |
my ($pattern) = @_; | |
return sort | |
grep { m/$pattern/ } | |
keys %PROFILES; | |
} | |
sub print_profiles_list { | |
my (@keys) = @_; | |
if (@keys) { | |
stdout( join("\n",@keys)."\n" ); | |
return true; | |
} | |
return false; | |
} | |
sub find_bin_path { | |
my ($command) = @_; | |
if (exists $ENV{PATH}) { | |
foreach my $dir ( split(m/\:/,$ENV{PATH}) ) { | |
return $dir . '/' . $command | |
if -x $dir . '/' . $command; | |
} | |
} | |
return undef; | |
} | |
sub stdmsg { | |
foreach my $line (@_) { | |
stdout( $line ) | |
unless $QUIET; | |
} | |
} | |
sub stdout { | |
foreach my $line (@_) { | |
print STDOUT $line; | |
} | |
} | |
sub stderr { | |
foreach my $line (@_) { | |
print STDERR $line; | |
} | |
} | |
sub print_profile { | |
my ($key) = @_; | |
if (exists $PROFILES{$key}) { | |
my $profile = $PROFILES{$key}; | |
stdout( "Host ".$key."\n" ); | |
stdout( $profile->{_raw}."\n" ); | |
stdout( "\n" ); | |
return true; | |
} | |
return false; | |
} | |
sub get_profiles_from_file { | |
my ($input_file) = @_; | |
my %profiles = (); | |
open( FILE, '<', $input_file ) | |
or die 'Could not open file: ' . $!; | |
my $whole_file = ''; | |
{ | |
local $/; | |
$whole_file = <FILE>; | |
} | |
my (%blobs) = $whole_file =~ m!^Host ([_\w]+?)\s*\r??\n(.+?)\s*\r??\n(?:\s*\r??\n|\Z)!msg; | |
foreach my $key (keys %blobs) { | |
my %profile = (); | |
$profile{_raw} = $blobs{$key}; | |
foreach my $line (split(m/\r??\n/,$blobs{$key})) { | |
if ($line =~ m!^\s*(\w+)\s+(.+?)\s*$!) { | |
$profile{$1} = $2; | |
} | |
} | |
$profiles{$key} = \%profile; | |
} | |
return %profiles; | |
} | |
__END__ | |
=head1 NAME | |
ssh-profile - CLI for listing, viewing profiles and modifying ~/.ssh/config | |
=head1 SYNOPSIS | |
ssh-profile [-h|--help|--man] [-C|--config=/path] [-q|--quiet] <operation> | |
=head1 OPTIONS | |
=over 8 | |
=item B<-h> | |
Print only the synopsis message and exit. | |
=item B<--help> | |
Print a brief help message and exit. | |
=item B<--man> | |
Prints the manual page and exits. | |
=item B<--config=/path> | |
Specify the path to an OpenSSH config file. Defaults: ~/.ssh/config | |
=item B<-q|--quiet> | |
Be less verbose about the operations being performed. | |
=back | |
=head2 OPERATIONS | |
If no arguments are given, beyond the base options, then the default behaviour | |
is to perform the default "list" operation. | |
=over 8 | |
=item B<list [pattern]> | |
Display a listing of just the profile names matching the given (regex) pattern. | |
With no arguments, displays a sorted listing of all profile names. | |
=item B<show [profile]> | |
Display one or more profile definitions. If no profile name given, all profiles | |
are displayed. Profile name can also be a Perl regular expression. | |
=item B<check pattern> | |
Exit with code 0 if given pattern matches any profile names, code 4 if not | |
found. Pattern must be given and can be any valid Perl regular expression. | |
=item B<edit> | |
Open the SSH config file in a text editor. Set EDITOR evironment variable to | |
specify the prefered program. | |
=back | |
=head1 DESCRIPTION | |
B<ssh-profile> is a command line interface for listing, viewing Host profiles and | |
modifying the ~/.ssh/config file. | |
=head1 DEPENDENCIES | |
=over | |
=item OpenSSH | |
https://www.openssh.com/ | |
=item EDITOR Environment Variable | |
For edit functionality, a text editor must be present on the system. The | |
default heuristics for selecting which editor to use can be overridden via | |
setting the EDITOR environment variable prior to execution. | |
=back | |
=head1 COPYRIGHT | |
ssh-profile - CLI for listing, viewing profiles and modifying ~/.ssh/config | |
Copyright (C) 2017 Kevin C. Krinke <kevin@krinke.ca> | |
This program is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Library General Public | |
License as published by the Free Software Foundation; specifically | |
version 2.1 of the License and no other version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Library General Public License for more details. | |
You should have received a copy of the GNU Library General Public | |
License along with this library; if not, write to the Free Software | |
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
=cut |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment