Description: |
This script emulates the shell functions pushd, popd, and dirs. It stores the directory stacks in a data file ~/.ppushd.dat.
It needs some help from the shell because otherwise it won't be able to change the directory. Save the script with the name ppushd-bin somewhere in your path, make it executable, and add the following commands to your bashrc to be able to use it.
pdirs() { cd "`ppushd-bin dirs "$@"`"; }
pinit() { cd "`ppushd-bin init "$@"`"; }
ppushd() { cd "`ppushd-bin pushd "$@"`"; }
ppopd() { cd "`ppushd-bin popd "$@"`"; }
pinit
This script is dedicated to demerphq.
Update: it might be better to implement this fully as a shell script without perl.
Update 2016-01-06: See also "I'm trying to write a script that will change directory (or set a variable), but after the script finishes, I'm back where I started (or my variable isn't set)!" in Greg's bash FAQ |
#!/usr/bin/env perl
# pushdir implementation in perl
# usage:
# cd "`ppushd-bin command args`"
# where command can be one of: init, dirs, pushd, popd. init clears t
+he dirstack.
# the dirstacks are associated to the pid of the shell, so you should
+call
# ppushd-bin init >/dev/null
# from every shell before you use the program.
# I recommend adding the following snippets to your bashrc to use this
+:
# pdirs() { cd "`ppushd-bin dirs "$@"`"; }
# pinit() { cd "`ppushd-bin init "$@"`"; }
# ppushd() { cd "`ppushd-bin pushd "$@"`"; }
# ppopd() { cd "`ppushd-bin popd "$@"`"; }
# pinit
# you may have to adapt these to work in other shells,
# eg. converting the functions to tcsh aliases
# bugs:
# might not work for dirs with newlines in them.
# the -nclpv switches or numeric arguments aren't implemented.
# might work differently for symlinks than the shell does.
# not well tested.
use warnings;
use strict;
use Fcntl "LOCK_EX";
use Cwd;
my $ppid = getppid();
1 <= @ARGV or
die q{usage: cd "`ppushd-bin command args`"\nwhere command is one
+of: init, dirs, pushd, popd\n};
my($cmd, @arg) = @ARGV;
my $wd = getcwd();
defined($ENV{"HOME"}) or
die "HOME";
my $datname = $ENV{"HOME"} . "/.ppushd.dat";
my $D;
open $D, "+<", $datname or
$!{"ENOENT"} and open $D, "+>", $datname or
die "error: cannot open data file: $!";
flock $D, LOCK_EX() or
die "error: cannot lock data file exclusiviely: $!";
my @dat = <$D>;
my $dat = "";
for my $n (0 .. @dat - 1) {
$dat[$n] =~ /^(-?\d+)\s(.+)/ or next;
$ppid == $1 and do {
$dat = $2;
splice @dat, $n, 1;
last;
};
}
my @stk = map { pack "H*", $_ } $dat =~ /(\S+)/g;
if ($cmd eq "init") {
@arg and
warn "warning: no arguments expected to init";
@stk = ();
} elsif ($cmd eq "dirs") {
print STDERR join(" ", @stk, $wd), "\n";
} elsif ($cmd eq "pushd") {
if (0 == @arg) {
1 <= @stk or
die "error: directory stack empty when trying to swap top
+elements";
($stk[-1], $wd) = ($wd, $stk[-1]);
} elsif (1 == @arg) {
push @stk, $wd;
chdir $arg[0] or
die "cannot chdir: $!";
$wd = getcwd();
} else {
die "error: at most one argument expected after pushd";
}
} elsif ($cmd eq "popd") {
1 <= @stk or
die "error: directory stack empty when trying to popd";
@arg and
die "error: no arguments expected to popd";
$wd = pop @stk;
} else {
die "error: invalid command";
}
print $wd or die
"error: cannot print wd";
0 != @stk and
push @dat, $ppid . " " . join(" ", map { unpack "H*", $_ } @stk) .
+ "\n";
seek $D, 0, 0 or
die "error: cannot seek data file\n";
sub quickdump {
die "as you might have just lost the dirstacks of all shells, here
+'s a quick dump:\n@dat";
}
print $D @dat or do {
warn "error: cannot write to data file: $!";
die quickdump();
};
0 <= (my $o = tell $D) or do {
warn "error: cannot tell data file: $!";
die quickdump();
};
truncate $D, $o or do {
warn "error: cannot tell data file: $!";
die quickdump();
};
close $D or do {
warn "error: cannot tell data file: $!";
die quickdump();
};
__END__
|