#!/usr/bin/env perl # run-next --- run next instance of program in path after specified directory # Author: Noah Friedman # Created: 1995-09-07 # Public domain. # $Id: run-next,v 1.12 2016/07/28 15:12:39 friedman Exp $ # Commentary: # This script can be used by front-end wrapper scripts in a user's personal # path to set environment variables, manipulate args, etc. before calling # the real program which resides in a directory specified somewhere later # in the user's path. For example, a front end to a program could be # written as: # # #!/bin/sh # export LD_PRELOAD=... # exec run-next $0 ${1+"$@"} # # This script looks at the full path of the specified argument and will # only search directories in PATH after that occurence. # Code: $^W = 1; # enable warnings use strict; use Getopt::Long; (my $progname = $0) =~ s|.*/||; my $opt_print = 0; my $opt_skip = 1; sub usage { print "Usage: $progname {options} [program {program options}]\n Options are: -h, --help You're looking at it. -p, --print Just print full name of program to run, without running it. Any program options are discarded. -s, --skip N Run Nth instance of program in path. This is relative to absolute pathname specified in program name to run, if any. Default is 1.\n"; exit (1); } sub err { my $fatal = shift; my $msg = join (": ", $progname, @_); print STDERR $msg, (substr ($msg, -1, 1) eq "\n"? "" : "\n"); exit ($fatal) if $fatal; return undef; } # Use the `access' syscall if available; this is more reliable than just # checking the mode bits on the file since other ACLs might apply. # Some perl ports don't have the POSIX module, so we use an exception # handler to catch that error and examine mode bits as a last resort. sub executable_p { eval { require POSIX; import POSIX; # runtime; 'use' would be compile-time local $^W = 0; return 1 if access ($_[0], &X_OK); return 0; }; return (-f $_[0] && -x _ && -s _); # _ reuses last stat struct } sub main { Getopt::Long::config ('bundling', 'autoabbrev', 'require_order'); GetOptions ("h|help", \&usage, "p|print", \$opt_print, "s|skip=i", \$opt_skip); my $arg = shift @ARGV; my ($progdir, $prog) = ($1, $2) if ($arg =~ m|^(.*)/([^/]*)$|); $progdir = "/" if (defined $progdir && $progdir eq ""); $prog = $arg unless (defined $prog); my $found_progdir = defined $progdir ? 0 : 1; my %progdirs; my $seen = 0; my $execdir; my $dir; for my $dir (split (/:/, $ENV{PATH})) { $dir =~ s/\/$//g; # strip any trailing slashes (xemacs subprocesses) $dir = "." if ($dir eq ""); $found_progdir = 1 if (!$found_progdir && $dir eq $progdir); next unless ($found_progdir); next if (exists $progdirs{$dir}); my $f = join ("/", $dir, $prog); next unless executable_p ($f); $progdirs{$dir} = $seen++; next unless ($seen > $opt_skip); $execdir = $dir; last; } err (1, $progdir, "directory not in PATH") unless ($found_progdir); err (1, $prog, "program not in PATH") unless (defined $progdir || scalar keys %progdirs > 0); err (1, $prog, "program not in any directories after \`$progdir' in PATH") if (defined $progdir && scalar keys %progdirs == 0); if ($seen <= $opt_skip) { err (0, $prog, sprintf ("only %d instances of program in path; " . "cannot skip more than %d:", $seen, $seen-1)); map { err (0, " " . join ("/", $_, $prog)) } sort { $progdirs{$a} <=> $progdirs{$b} } keys %progdirs; exit (1); } my $execprog = join ("/", $execdir, $prog); if ($opt_print) { print $execprog, "\n"; exit (0); } exec ($execprog, @ARGV) || err (1, $execprog, "$!"); } main; # run-next ends here