Pregunta

Es típico tener algo como esto en su archivo cshrc para configurar la ruta:

set path = ( . $otherpath $path )

pero, la ruta se duplica cuando obtiene su archivo cshrc varias veces, ¿cómo evita la duplicación?

EDITAR: Esta es una forma sucia de hacerlo:

set localpaths = ( . $otherpaths )
echo ${path} | egrep -i "$localpaths" >& /dev/null
if ($status != 0) then
    set path = ( . $otherpaths $path )
endif
¿Fue útil?

Solución

puede usar el siguiente script de Perl para podar rutas de duplicados.


#!/usr/bin/perl
#
# ^^ ensure this is pointing to the correct location.
#
# Title:    SLimPath
# Author:   David "Shoe Lace" Pyke <eselle@users.sourceforge.net >
#   :   Tim Nelson 
# Purpose: To create a slim version of my envirnoment path so as to eliminate
#       duplicate entries and ensure that the "." path was last.
# Date Created: April 1st 1999
# Revision History:
#   01/04/99: initial tests.. didn't wok verywell at all
#       : retreived path throught '$ENV' call
#   07/04/99: After an email from Tim Nelson <wayland@ne.com.au> got it to
#         work.
#       : used 'push' to add to array
#       : used 'join' to create a delimited string from a list/array.
#   16/02/00: fixed cmd-line options to look/work better
#   25/02/00: made verbosity level-oriented
#
#

use Getopt::Std;

sub printlevel;

$initial_str = "";
$debug_mode = "";
$delim_chr = ":";
$opt_v = 1;

getopts("v:hd:l:e:s:");

OPTS: {
    $opt_h && do {
print "\n$0 [-v level] [-d level] [-l delim] ( -e varname | -s strname | -h )";
print "\nWhere:";
print "\n   -h  This help";
print "\n   -d  Debug level";
print "\n   -l  Delimiter (between path vars)";
print "\n   -e  Specify environment variable (NB: don't include \$ sign)";
print "\n   -s  String (ie. $0 -s \$PATH:/looser/bin/)";
print "\n   -v  Verbosity (0 = quiet, 1 = normal, 2 = verbose)";
print "\n";
        exit;
    };
    $opt_d && do {
        printlevel 1, "You selected debug level $opt_d\n";
        $debug_mode = $opt_d;
    };
    $opt_l && do {
        printlevel 1, "You are going to delimit the string with \"$opt_l\"\n";
        $delim_chr = $opt_l;
    };
    $opt_e && do {
        if($opt_s) { die "Cannot specify BOTH env var and string\n"; }
        printlevel 1, "Using Environment variable \"$opt_e\"\n";
        $initial_str = $ENV{$opt_e};
    };
    $opt_s && do {
        printlevel 1, "Using String \"$opt_s\"\n";
        $initial_str = $opt_s;
    };
}

if( ($#ARGV != 1) and !$opt_e and !$opt_s){
    die "Nothing to work with -- try $0 -h\n";
}

$what = shift @ARGV;
# Split path using the delimiter
@dirs = split(/$delim_chr/, $initial_str);

$dest;
@newpath = ();
LOOP: foreach (@dirs){
    # Ensure the directory exists and is a directory
    if(! -e ) { printlevel 1, "$_ does not exist\n"; next; }
    # If the directory is ., set $dot and go around again
    if($_ eq '.') { $dot = 1; next; }

#   if ($_ ne `realpath $_`){
#           printlevel 2, "$_ becomes ".`realpath $_`."\n";
#   }
    undef $dest;
    #$_=Stdlib::realpath($_,$dest);
    # Check for duplicates and dot path
    foreach $adir (@newpath) { if($_ eq $adir) { 
        printlevel 2, "Duplicate: $_\n";
        next LOOP; 
    }}

    push @newpath, $_;
}

# Join creates a string from a list/array delimited by the first expression
print join($delim_chr, @newpath) . ($dot ? $delim_chr.".\n" : "\n");

printlevel 1, "Thank you for using $0\n";
exit;

sub printlevel {
    my($level, $string) = @_;

    if($opt_v >= $level) {
        print STDERR $string;
    }
}

Espero que sea útil.

Otros consejos

Estoy sorprendido de que nadie haya usado la técnica tr ":" "\n" | grep -x para buscar si una carpeta dada ya existe en $ PATH. ¿Alguna razón para no hacerlo?

En 1 línea:

if ! $(echo "$PATH" | tr ":" "\n" | grep -qx "$dir") ; then PATH=$PATH:$dir ; fi

Aquí hay una función que me he hecho para agregar varias carpetas a la vez a $ PATH (use " aaa: bbb: ccc " notación como argumento), verificando cada una por duplicados antes de agregar:

append_path()
{
    local SAVED_IFS="$IFS"
    local dir
    IFS=:
    for dir in $1 ; do
        if ! $( echo "$PATH" | tr ":" "\n" | grep -qx "$dir" ) ; then
            PATH=$PATH:$dir
        fi
    done
    IFS="$SAVED_IFS"
}

Se puede invocar en un script como este:

append_path "/test:$HOME/bin:/example/my dir/space is not an issue"

Tiene las siguientes ventajas:

  • Sin bashismos ni ninguna sintaxis específica de shell. Funciona perfectamente con !#/bin/sh (he probado con guión)
  • Se pueden agregar varias carpetas a la vez
  • Sin clasificación, conserva el orden de las carpetas
  • Trata perfectamente con espacios en los nombres de carpetas
  • Una sola prueba funciona sin importar si $ folder está al principio, final, en el medio o es la única carpeta en $ PATH (evitando así probar x: *, *: x, : x: , x, como muchas de las soluciones aquí hacen implícitamente)
  • Funciona (y preserva) si $ PATH comienza o termina con ": " ;, o tiene " :: " en ella (que significa carpeta actual)
  • No se necesita awk o sed.
  • EPA friendly;) Se conserva el valor IFS original, y todas las demás variables son locales para el alcance de la función.

¡Espero que eso ayude!

ok, no en csh, pero así es como agrego $ HOME / bin a mi ruta en bash ...

case $PATH in
    *:$HOME/bin | *:$HOME/bin:* ) ;;
    *) export PATH=$PATH:$HOME/bin
esac

sazonar al gusto ...

He estado usando el siguiente script (Bourne / Korn / POSIX / Bash) durante la mayor parte de una década:

:   "@(#)$Id: clnpath.sh,v 1.6 1999/06/08 23:34:07 jleffler Exp $"
#
#   Print minimal version of $PATH, possibly removing some items

case $# in
0)  chop=""; path=${PATH:?};;
1)  chop=""; path=$1;;
2)  chop=$2; path=$1;;
*)  echo "Usage: `basename $0 .sh` [$PATH [remove:list]]" >&2
    exit 1;;
esac

# Beware of the quotes in the assignment to chop!
echo "$path" |
${AWK:-awk} -F: '#
BEGIN   {   # Sort out which path components to omit
            chop="'"$chop"'";
            if (chop != "") nr = split(chop, remove); else nr = 0;
            for (i = 1; i <= nr; i++)
                omit[remove[i]] = 1;
        }
{
    for (i = 1; i <= NF; i++)
    {
        x=$i;
        if (x == "") x = ".";
        if (omit[x] == 0 && path[x]++ == 0)
        {
            output = output pad x;
            pad = ":";
        }
    }
    print output;
}'

En Korn shell, uso:

export PATH=$(clnpath /new/bin:/other/bin:$PATH /old/bin:/extra/bin)

Esto me deja con PATH que contiene los directorios bin nuevos y otros en la parte delantera, más una copia de cada nombre de directorio en el valor de la ruta principal, excepto que los directorios bin antiguos y adicionales tienen bin eliminado.

Tendrías que adaptar esto a C shell (lo siento, pero creo firmemente en las verdades enunciadas en La programación de C Shell se considera dañina ). Principalmente, no tendrá que jugar con el separador de colon, por lo que la vida es realmente más fácil.

Bueno, si no te importa en qué orden están tus caminos, puedes hacer algo como:

set path=(`echo $path | tr ' ' '\n' | sort | uniq | tr '\n' ' '`)

Eso ordenará sus rutas y eliminará cualquier ruta adicional que sea la misma. Si usted tiene . en su camino, es posible que desee eliminarlo con un grep -v y volver a agregarlo al final.

Aquí hay una larga línea sin clasificar:
establecer ruta = (echo $path | tr ' ' '\n' | perl -e 'while (<>) { print $_ unless $s{$_}++; }' | tr '\n' ' ')

dr_peper,

Por lo general, prefiero apegarme a las capacidades de script del shell en el que vivo. Lo hace más portátil. Entonces, me gustó su solución usando scripts de csh. Simplemente lo extendí para trabajar por directorio en los localdirs para que funcione para mí mismo.

foreach dir ( $localdirs )
    echo ${path} | egrep -i "$dir" >& /dev/null
    if ($status != 0) then
        set path = ( $dir $path )
    endif
end

Usando sed (1) para eliminar duplicados.

$ PATH=$(echo $PATH | sed -e 's/$/:/;s/^/:/;s/:/::/g;:a;s#\(:[^:]\{1,\}:\)\(.*\)\1#\1\2#g;ta;s/::*/:/g;s/^://;s/:$//;')

Esto eliminará los duplicados después de la primera instancia, que puede o no ser lo que desea, por ejemplo:

$ NEWPATH=/bin:/usr/bin:/bin:/usr/local/bin:/usr/local/bin:/bin
$ echo $NEWPATH | sed -e 's/$/:/; s/^/:/; s/:/::/g; :a; s#\(:[^:]\{1,\}:\)\(.*\)\1#\1\2#g; t a; s/::*/:/g; s/^://; s/:$//;'
/bin:/usr/bin:/usr/local/bin
$

¡Disfruta!

Esto es lo que uso, quizás alguien más lo encuentre útil:

#!/bin/csh
#  ABSTRACT
#    /bin/csh function-like aliases for manipulating environment
#    variables containing paths.
#
#  BUGS
#    - These *MUST* be single line aliases to avoid parsing problems apparently related
#      to if-then-else
#    - Aliases currently perform tests in inefficient in order to avoid parsing problems
#    - Extremely fragile - use bash instead!!
#
#  AUTHOR
#    J. P. Abelanet - 11/11/10

#  Function-like alias to add a path to the front of an environment variable
#    containing colon (':') delimited paths, without path duplication
#
#  Usage: prepend_path ENVVARIABLE /path/to/prepend
alias prepend_path \
  'set arg2="\!:2";  if ($?\!:1 == 0) setenv \!:1 "$arg2";  if ($?\!:1 && $\!:1 !~ {,*:}"$arg2"{:*,}) setenv \!:1 "$arg2":"$\!:1";'

#  Function-like alias to add a path to the back of any environment variable 
#    containing colon (':') delimited paths, without path duplication
#
#  Usage: append_path ENVVARIABLE /path/to/append
alias append_path \
  'set arg2="\!:2";  if ($?\!:1 == 0) setenv \!:1 "$arg2";  if ($?\!:1 && $\!:1 !~ {,*:}"$arg2"{:*,}) setenv \!:1 "$\!:1":"$arg2";'

Siempre establezco mi camino desde cero en .cshrc. Es decir, empiezo con una ruta básica, algo como:

set path = (. ~/bin /bin /usr/bin /usr/ucb /usr/bin/X11)

(dependiendo del sistema).

Y luego haz:

set path = ($otherPath $path)

para agregar más cosas

Tengo la misma necesidad que la pregunta original. Sobre la base de sus respuestas anteriores, he usado en Korn / POSIX / Bash:

export PATH=$(perl -e 'print join ":", grep {!$h{$_}++} split ":", "'$otherpath:$PATH\")

Tuve dificultades para traducirlo directamente en csh (las reglas de escape de csh son una locura). He usado (como lo sugiere dr_pepper):

set path = ( `echo $otherpath $path | tr ' ' '\n' | perl -ne 'print $_ unless $h{$_}++' | tr '\n' ' '`)

¿Tiene ideas para simplificarlo más (reducir el número de tuberías)?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top