Samstag, 29. Januar 2011

cd Reloaded

Was mich an der Shell unter Linux nervt, ist das langsame Bewegen durch Verzeichnisse mit "cd". Deshalb hab ich mir ein Script geschrieben, welches "cd" bei mir inzwischen abgelöst hat (im folgenden heißt das Programm "c" für change).

Was es kann

Im Grunde ist es eine Tiefenverzeichniswechsel mit regulärem Ausdruck. Hier ein paar Beispiele:

/etc$ c site enab
/etc/apache2/sites-enabled$ c site ava
/etc/apache2/sites-available$ c php5
/etc/php5/$

Wie ihr an Zeile 2+3 seht, ist aber auch ein Wechsel in höhere Ebenen möglich, wenn nichts in der Tiefe gefunden wurde. Wobei der Wechsel aufwärts auf angegebene Verzeichnisse beschränkt ist, damit das Script nicht irgendwann in / landet.
Diese "Roots" (die Verzeichnisse, in denen auch aufwärts gesucht wird) sind über Parameter änderbar.
Daüberhinaus sollten alle Features von cd zur Verfügung stehen, auch die Autovervollständigung.

Ausprobieren

Zuerst muss php5-cli installiert werden.
sudo apt-get install php5-cli 

Kopiert dann das folgende Script "cds" in ~/bin.

#!/usr/bin/php

define('DEBUG', false);

/*
--------------------- Installation
Execute the following command on the bash:
c() { cd "`~/bin/cds $@`"; }; complete -o filenames -o nospace -F _cd c

caution: if you have a symlink in one of your root folders you can jump into the symlink folder but not return

--------------------- Changelog
0.9
"/var/www/project/folder1$ c www" changes now to "/var/www" instead of staying in the same dir
"/var/www/yoho/nextyoho$ c yoho changes now to "/var/www/yoho" instead of staying in the same dir

--------------------- Known Bugs
*/

// define __DIR__ before PHP 5.3
if(!defined('__DIR__')) { define("__DIR__", dirname(__FILE__)); } 

// init file error handler 
set_error_handler('errorhandler');

// get arguments
$args = array_slice($argv, 1);

// get current working dir
$cwd = exec('pwd');
$home = $_SERVER['HOME'];
dump($cwd, 'CWD: ');

// ---------------------------- imitate cd
// because it is also a replacement for cd imitate its behaviour
// first try cd 
if (count($args) === 0) $args = array($home);
exec('cd ' . implode(' ', $args) . ' 2>/dev/null', $output, $return_var);
if ($return_var == 0) { if (isset($args[0])) stdout($args[0]); die(); }

// ---------------------------- handle options
/*
-a : add current directory as root
-r : remove current directory as root
-l : list all roots
*/
$options = getopt('alr:');

// get roots
$conf = __DIR__ . '/cds.conf';
if (!is_file($conf)) touch($conf);
$roots = array_map('trim', file($conf));

// add new root
if (isset($options['a']))
{
// we do not want duplicates
$roots[] = $cwd;
$roots = array_unique($roots);
file_put_contents($conf, implode("\n", $roots));
stderr('Added new root: ' . $cwd);
die($cwd);
}
// list all roots
elseif (isset($options['l']))
{
foreach ($roots as $i=>$root)
{
stderr('['.($i+1).'] '.$root);
}
die($cwd);
}
// remove root
elseif (isset($options['r']))
{
$index = $options['r']-1;
if (!isset($roots[$index]))
{
stderr('There is no entry '.$options['r'].'.');
die($cwd);
}
$old = $roots[$index];
unset($roots[$index]);
file_put_contents($conf, implode("\n", $roots));
stderr('Removed root: ' . $old);
die($cwd);
}

// ---------------------------- find current root dir
$crd = $cwd;
foreach ($roots as $root)
{
if (strpos($cwd, $root) === 0) $crd = $root;
}

dump($crd, 'ROOT: ');
// create regex
$args = array_map('preg_quote', $args);
$regex = '|' . implode('.*', $args) . '|i';

// the last part of the regex has to be the last dir
$regex = '|' . implode('.*', $args) . '[^/]*$|i';

// now search
$temp = $cwd;
$i = 0;
dump('Searching ...');
while (strpos($temp, $crd) === 0 or $crd === false)
{
dump('   '.$temp);
$result = matchDir($temp, $regex, $cwd);
if ($result) die($result);

// if crd is unknown do not do a reverse search
if ($crd === false) break;
// create current search dir (strip last dir)
$temp = substr($temp, 0, strrpos($temp, '/'));
}

// no match so return current dir
stderr('No match found.');
die($cwd);

// this function does not search recursive
function matchDir($dir, $regex, $cwd)
{
$depth = 0;
$stack = array();
$new_stack = @scandir($dir);
foreach ($new_stack as $k => $v)
{
if ($dir == '/') $dir = '';
$actual = $dir . '/' . $v;
if (!is_dir($actual) || $v{0} == '.') continue;
$stack[] = $actual;
}

while (count($stack) > 0)
{
// check first for match
foreach ($stack as $full)
{
if (preg_match($regex, $full) && $full != $cwd) return $full;
}
$depth++;
if ($depth > 5) return false;
// if we got here we had no match
// so put all subfolders to the stack
foreach ($stack as $fullkey => $full)
{
if (!is_readable($full))
{
unset($stack[$fullkey]);
continue;
}
$new_stack = @scandir($full);
foreach ($new_stack as $subdir)
{
$actual = $full . '/' . $subdir;
if (!is_dir($actual) || $subdir{0} == '.') continue;
$stack[] = $actual;
}
unset($stack[$fullkey]);
}
}
return false;
}

function stderr($var) { fwrite(STDERR, print_r($var, true) . "\n"); }
function stdout($var) { fwrite(STDOUT, print_r($var, true) . "\n"); }
function dump($args, $title = null) { if (DEBUG) stderr($title . print_r($args, true)); }
function errorhandler($errno, $errstr, $errfile, $errline) { file_put_contents(__DIR__ . '/cds_error.log', "$errstr\n$errfile ($errline)\n---\n", FILE_APPEND); }


Macht cds ausführbar:
chmod +x ~/bin/cds

Führt dann folgende Zeile auf der Shell aus oder packt sie in eure ~/.bashrc:
c() { cd "`~/bin/cds $@`"; }; complete -o filenames -o nospace -F _cd c 

Parameter

-a
Fügt den aktuellen Pfad als root-Pfad hinzu, in welchem auch aufwärts gesucht werden kann.

-l
Zeigt alle root-Pfade an.

-r
Löscht den root-Pfad mit den angegeben Nummer (die aus "c -l")

Viel Spaß und danke fürs Feedback!

Keine Kommentare:

Kommentar veröffentlichen