Open Source im professionellen Einsatz

Marke Eigenbau

Die Vorschläge, mit denen die Bash auf ein nur halb eingegebenes Kommando antwortet, lassen sich ebenso gut mit einem Perl-Skript generieren. Steht zum Beispiel die Direktive

complete -C helper command

in ».bashrc«, sodass die Shell sie beim Start bemerkt, zieht sie das Programm »helper« für Vorschläge zum Kommando »command« zu Rate.

Gibt der User »command « ein (mit abschließendem Leerzeichen) und drückt die [Tab]-Taste, ruft die Bash das Perl-Skript »helper« auf und übergibt ihm in den Environment-Variablen »COMP_LINE« und »COMP_POINT« die bisher eingegebene Kommandozeile und die Position des Cursors zum Zeitpunkt, an dem der User auf [Tab] gedrückt hat. Als Argumente (verfügbar in »@ARGV« in Perl) erhält der Helfer das erste Wort der Zeile (normalerweise das Kommando), das zu komplettierende Wort und als drittes Argument das Wort davor. Vom Helfer erwartet die Shell jetzt als Ausgabe eine Reihe von Ergänzungsvorschlägen, getrennt durch Zeilenumbrüche.

Die Shellsession in Abbildung 2 definiert als Helferlein das Skript »complete-dump« zum Kommando »ls«. Listing 1 zeigt, dass das Helferskript zu Forschungszwecken lediglich den Inhalt der Environment-Variablen »COMP_LINE« und »COMP_POINT« und das Array »@ARGV« über die Stderr-Ausgabe zurückliefert. Auf Stdout kommt nichts zurück, was den Komplettiermechanismus dazu veranlasst, dem User keinerlei Vorschläge zu unterbreiten.

Abbildung 2: Der Shellbefehl »complete« weist mit der Option »-C« dem Kommando »ls« ein Helferskript »complete-dump« zu, das die Shell dann zur Komplettierung aufruft.

Abbildung 2: Der Shellbefehl »complete« weist mit der Option »-C« dem Kommando »ls« ein Helferskript »complete-dump« zu, das die Shell dann zur Komplettierung aufruft.

Listing 1:
»complete-dump«

#!/usr/local/bin/perl -w
###########################################
# complete-dump - Debug 'complete' function
# Mike Schilli, 2010 (m@perlmeister.com)
###########################################
use strict;
use Data::Dump qw(dump);

my %matches = ();

for my $env_var (keys %ENV) {
  next if $env_var !~ /^COMP_/;
  $matches{ $env_var } = $ENV{ $env_var };
}

$matches{ ARGV } = @ARGV;

print STDERR "n", 
      dump( %matches );

Die Ausgabe zeigt, dass Bash dem Helferskript in »COMP_LINE« die bislang eingegebene Kommandozeile überreicht, einschließlich aller Leerzeichen. Warum es neben »COMP_LINE« auch noch die Cursorposition in »COMP_POINT« mitliefert, hängt wohl damit zusammen, dass der User die Kommandozeile mit den Cursortasten editieren und plötzlich in der Mitte des Kommandos [Tab] drücken könnte, obwohl dies praktisch selten von Nutzen ist. Im Normalfall entspricht »COMP_POINT« genau der Länge des Strings in »COMP_LINE«.

Kommandozeile 3 versucht das erste Argument zum »ls«-Kommando zu vervollständigen und erhält in »@ARGV« als Argumente »ls« (erstes Wort), »/etc/p« (zu vervollständigendes Wort) und noch einmal »ls« (das Wort davor). Im vierten Fall, der das zweite Argument zu »ls« er- gänzen möchte, kommt als drittes Helfer- Argument »/etc« zurück - wieder das Wort vor dem zu vervollständigen Wort.

Eingebautes Helferlein

Selbst geschriebene Skripte können die Helferfunktionen auch gleich selbst mit- bringen. Das Kommando

complete -C myscript myscript

definiert, dass die Bash »myscript« selbst um Rat fragt, falls ein User nach der Eingabe von »myscript « auf die [Tab]-Taste einhämmert. Das Skript fragt dann ab, ob »COMP_LINE« gesetzt ist, und stellt in diesem Fall Vorschläge bereit, während es sonst seine normalen Funktionen ausführt.

Das ist natürlich eine Gratwanderung, denn ein Programmierfehler im Skript könnte eine zerstörerische Funkion auslösen, während der User das Kommando noch gar nicht eingegeben hat, sondern noch auf Ergänzungsvorschläge wartet. Das CPAN-Modul Getopt::Complete, das Skripte elegant ihre eigenen Optionen komplettieren lässt, schlägt daher als konservative Lösung vor, das Skript im Helfermodus mit »perl -c myscript 2>/dev/null« nur in Perls Compile-Phase eintreten zu lassen und gar nicht erst auszuführen [3].

Listing 2 zeigt ein kurzes Beispiel, das die Option »--bgcolor« zum Setzen der Hintergrundfarbe anbietet und drei Farbwerte akzeptiert. Ruft der Anwender vorher »complete -C getopt-complete getopt-complete« auf, komplettiert die Shell nicht nur Farbwerte, sondern auch Optionsnamen:

$ getopt-complete [TAB]
-> getopt-complete --bgcolor=
$ getopt-complete --bgcolor=r[TAB]
-> getopt-complete --bgcolor=red

Fertig kompilierte Programme, die man nicht umschreiben möchte, benötigen allerdings externe Helferlein. Listing 3 zeigt ein Beispiel, das einem User, der offensichtlich ein Git-Repository mit »git clone« klonen möchte, eine Liste aller seiner auf Github.com liegenden Repositories als Vorschläge unterbreitet.

Listing 2:
»getopt-complete«

#!/usr/local/bin/perl -w
###########################################
# getopt-complete - Test Getopt::Complete
# Mike Schilli, 2010 (m@perlmeister.com)
###########################################
use strict;

use Getopt::Complete(
   'bgcolor' => ['red', 'blue', 'green'],
);

Listing 3:
»github-helper«

#!/usr/local/bin/perl -w
###########################################
# github-helper - Complete github repos
# 2010, Mike Schilli <m@perlmeister.com>
###########################################
use strict;
use Pod::Usage;
use LWP::UserAgent;
use XML::Simple;

my $netloc = 'git@github.com';
my $user   = 'mschilli';

if(!defined $ENV{COMP_LINE}) {
  pod2usage("COMP_LINE missing");
}

my($git, $clone, $args) = 
    split /s+/, $ENV{COMP_LINE}, 3;

$args = "" unless defined $args;

if(!defined $clone or
    $clone ne "clone") {
    # Only 'clone' suggestions
  exit(0);
}

if($ARGV[2] ne "clone") {
    # Do nothing if user doesn't want
    # to expand the argument after 'clone'
  exit 0;
}

  # Two pseudo choices to get their
  # common path expanded right away
if(!length $args) {
  for (1..2) {
    print "$netloc/$user/$_n";
  }
  exit 0;
}

my @repos = remote_repos( $user );

for my $repo (remote_repos( $user )) {
  my $remote = "$netloc/$user/$repo";

  if($args eq
     substr($remote, 0, length $args)) {
    print "$remoten";
  }
}

###########################################
sub remote_repos {
###########################################
  my($user) = @_;

  my @repos = ();

  my $ua = LWP::UserAgent->new();
  my $resp = $ua->get(
   "http://github.com/api/v1/xml/$user");

  if($resp->is_error) {
    die "API fetch failed: ", 
        $resp->message();
  }

  my $xml = XMLin($resp->decoded_content());

  for my $repo (keys 
    %{$xml->{repositories}->{repository}}
  ) {
    push @repos, $repo;
  }

  return @repos;
}

__END__

=head1 NAME

    github-helper - Complete github repos

=head1 SYNOPSIS

    COMP_LINE=... github-helper

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook