#!/usr/bin/perl -w =head1 NAME cdhistory - web-browser style history for UNIX shells =head1 SYNOPSIS [use the bash functions and aliases instead of invoking directly] =head1 INSTALLATION Install these bash functions and aliases in your C<$HOME/.bashrc> file to use this: # override the default "cd" with a custom one, so that cd history # is recorded cd() { command cd "$*" && _cdhist="${_cdhist:-}|$PWD" } # cdh - display the cd history cdh() { cdhistory ls "$_cdhist" } # cdfwd - go "forward" through the history, or switch between the # current and previous dirs, if we're already at the "front" of history cdfwd() { _cdhist=`cdhistory fwd "$_cdhist" "$1"` new=`cdhistory entry "$_cdhist" "$1" -1` echo "$new" command cd "$new" } # cdback - go "back" through the cd history cdback() { new=`cdhistory entry "$_cdhist" "$1"` _cdhist=`cdhistory back "$_cdhist" "$1"` echo "$new" command cd "$new" } # a few short helper aliases, easy to type quickly alias +='cdfwd 1' alias ++='cdfwd 2' alias +++='cdfwd 3' alias -- -='cdback 1' =head1 DESCRIPTION C is a perl script used to implement web-browser style "history" for UNIX shells; as you use the C command to explore the filesystem, your moves are remembered, and you can go "back" through history, and "forward" again, as you like. It's easier to display an example here. First, let's build up a few directories in the history: $ cd /tmp ; cdh : (1); cd "/tmp" $ cd /dev ; cdh : (1); cd "/dev" : (2); cd "/tmp" $ cd /etc ; cdh : (1); cd "/etc" : (2); cd "/dev" : (3); cd "/tmp" Now, let's start going backwards! $ - ; cdh /dev : (1) [forward]; cd "/etc" : (2); cd "/dev" : (3); cd "/tmp" $ - ; cdh /tmp : (1) [forward]; cd "/etc" : (2) [forward]; cd "/dev" : (3); cd "/tmp" OK, let's say instead of going forwards, I decide to cd to another dir: $ cd /usr ; cdh : (1); cd "/usr" : (2); cd "/tmp" See, web-browser style, even when it may be an annoying feature that should probably be revisited later. ;) =head1 VERSION Dec 7 2004 jm =head1 LICENSE GPL =head1 AUTHOR Justin Mason, http://taint.org/ =cut sub usage { die "cdhistory: web-browser style history for UNIX shells\n\n". "run 'perldoc cdhistory' for documentation\n"; } use strict; my $cmd = shift @ARGV; my $cdhist = shift @ARGV; usage() unless ($cmd && defined($cdhist)); # warn "JMD: _cdhist='".$cdhist."'\n"; # debug my @wds = (); my @popped = (); parse_cdhist_string($cdhist); if ($cmd eq 'ls') { cmd_ls(); } elsif ($cmd eq 'entry') { cmd_entry(); } elsif ($cmd eq 'back') { cmd_back(); } elsif ($cmd eq 'fwd') { cmd_fwd(); } else { usage(); } exit; ########################################################################### sub parse_cdhist_string { my $cdhist = shift; $cdhist =~ s/\|\|+/\|/g; $cdhist =~ s/^\|//; $cdhist =~ s/\|$//; # now parse the string into the "back" stack and the "forward" stack. # e.g. if we're here: # # BACK <------ CURRENT -----> FORWARD # a b c d e # # the string looks like: "a|b|c|>d|>e". parse that. foreach my $dir (reverse split(/\|/, $cdhist)) { next if ($dir eq ''); if ($dir =~ /^>/) { # popped directories; the "forward" stack next if (scalar @wds > 0); # already seen a real history entry push (@popped, $dir); } else { # history; the "back" stack push (@wds, $dir); } } } sub cmd_ls { my $cnt = scalar @popped + scalar @wds; foreach my $dir (@popped) { $dir =~ s/^>//; printf ": %4s [forward]; cd \"%s\"\n", "($cnt)", $dir; $cnt--; } foreach my $dir (@wds) { printf ": %4s; cd \"%s\"\n", "($cnt)", $dir; $cnt--; } } sub cmd_entry { my $levels; if (defined $ARGV[0]) { $levels = $ARGV[0] + 0; if ($ARGV[1] && $ARGV[1] =~ /^[\+\-]/) { $levels += $ARGV[1]; } } else { $levels = 1; } if (scalar @wds < $levels) { warn "only ".(scalar(@wds) + 1)." dirs in cd history\n"; $levels = scalar(@wds) - 1; } my $ent = $wds[$levels]; $ent ||= $wds[scalar(@wds) - 1]; $ent ||= '.'; print $ent,"\n"; } sub cmd_back { my $levels; if (defined $ARGV[0]) { $levels = $ARGV[0] + 0; } else { $levels = 1; } for (; $levels > 0; $levels--) { last if (scalar @wds == 1); my $popped = shift @wds; next if (!defined ($popped) || $popped eq ''); push (@popped, ">".$popped); } print join("|", reverse(@wds), reverse(@popped)), "\n"; } sub cmd_fwd { my $levels; if (defined $ARGV[0]) { $levels = $ARGV[0] + 0; } else { $levels = 1; } if (scalar @popped >= $levels) { for (; $levels > 0; $levels--) { my $pop = pop @popped; $pop =~ s/^>//; unshift (@wds, $pop); } print join("|", reverse(@wds), reverse(@popped)), "\n"; } elsif (scalar @wds >= $levels) { my @moved = splice(@wds, 0, ($levels+1)); my $pop = pop @moved; unshift (@wds, $pop, @moved); print join("|", reverse @wds), "\n"; } else { warn "only ".(scalar(@wds) + 1)." dirs in cd history\n"; print join("|", reverse(@wds), reverse(@popped)), "\n"; exit 2; } } ###########################################################################