Frage

Gibt es eine idiomatische Weise Elemente von PATH-ähnlichen Shell-Variablen zu entfernen?

Das ist ich nehmen will

PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.

und Entfernen oder ersetzen die /path/to/app/bin, ohne den Rest der Variablen clobbering. Extra-Punkte für das Erlauben mir, put neue Elemente in beliebigen Positionen. Das Ziel wird durch eine gut definierte Zeichenfolge erkennbar sein, und an einer beliebigen Stelle in der Liste auftreten können.

Ich weiß, ich habe dies getan, gesehen und kann wahrscheinlich etwas zusammen auf meinem eigenen schustern, aber ich bin auf der Suche nach einem schönen Ansatz. Portabilität und Standardisierung ein Plus.

Ich verwende bash, aber beispielsweise in Ihren Lieblingsschal sind willkommen auch.


Der Kontext hier ist eine von benötigen bequem zwischen mehreren Versionen wechseln (eine für die Analyse zu tun, ein anderer auf dem Rahmen für die Arbeit) einer großen wissenschaftlichen Analyse-Paket, das ein paar Dutzend Executables erzeugt, Daten rund um das Dateisystem gebunkert hat, und nutzt Umgebungsvariable all dieses Zeug zu finden. Ich möchte ein Skript schreiben, die eine Version auswählt, und müssen in der Lage sein, die $PATH Elemente auf die derzeit aktive Version beziehen, zu entfernen und ersetzen sie durch die gleichen Elemente auf die neue Version beziehen.


Dies ist auf das Problem verwandte Elemente zu verhindern, wiederholte $PATH wenn Anmeldeskripts erneut ausgeführt und dergleichen.


War es hilfreich?

Lösung

, die vorgeschlagene Lösung von Dmckee Adressierung:

  1. Während einige Versionen von Bash können Bindestriche in Funktionsnamen erlauben, andere (MacOS X) nicht.
  2. Ich habe keinen Bedarf sieht Rückkehr zu verwenden unmittelbar vor dem Ende der Funktion.
  3. Ich sehe nicht die Notwendigkeit für alle Semikolons.
  4. Ich verstehe nicht, warum Sie Pfad-Element-by-Muster haben einen Wert exportieren. Denken Sie an export als gleichwertig Einstellung (oder sogar die Schaffung) eine globale Variable - etwas werden, wann immer möglich zu vermeiden
  5. .
  6. Ich bin mir nicht sicher, was du ‚replace-path PATH $PATH /usr‘ zu tun erwarten, aber es tut nicht, was ich erwarten würde.

einen PATH-Wert Betrachten Sie das enthalten beginnt:

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

Das Ergebnis, das ich bekam (von 'replace-path PATH $PATH /usr') ist:

.
/Users/jleffler/bin
/local/postgresql/bin
/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/local/bin
/bin
/bin
/sw/bin
/sbin
/sbin

ich hätte erwartet, dass meine ursprünglichen Pfad zurück, da / usr nicht als (komplett) Pfadelement angezeigt wird, nur als Teil eines Pfadelement.

Dies kann durch Modifikation eines der replace-path Befehle in sed festgelegt werden:

export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" |
               tr "\n" ":" | sed "s|::|:|g")

Ich habe ':' statt '|' In den einzelnen Teilen des Ersatzes seit ‚|‘ (In der Theorie) könnte in einer Pfadkomponente erscheinen, während durch Definition von PATH, kann ein Doppelpunkt nicht. Ich beobachte, dass die zweite sed das aktuelle Verzeichnis aus der Mitte eines PATH beseitigen könnte. Das heißt, ein legitimer (wenn auch perverse) Wert von PATH könnte sein:

PATH=/bin::/usr/local/bin

Nach der Verarbeitung würde das aktuelle Verzeichnis nicht mehr auf dem Pfad sein.

Eine ähnliche Veränderung, das Spiel zu verankern ist angemessen in path-element-by-pattern:

export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\$")

Ich nehme zur Kenntnis, dass grep -m 1 nebenbei ist nicht Standard (es ist eine GNU-Erweiterung, auch auf MacOS X). Und in der Tat ist the-n Option für echo auch Nicht-Standard; Sie wäre besser dran, einfach sein, den hinteren Doppelpunkt zu löschen, die aufgrund der Umwandlung der Newline von Echos in einen Doppelpunkt hinzugefügt wird. Da Pfad-Element-für-Muster nur einmal verwendet wird, hat unerwünschte Nebenwirkungen (es irgendeine Variable namens $removestr vorbestehenden exportiert clobbers), kann er durch seinen Körper sinnvoll ersetzt werden. Dies, zusammen mit liberaler Verwendung von Zitaten Probleme mit Leerzeichen oder unerwünschten Dateinamen Expansion zu vermeiden, führt zu:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace_path         PATH $PATH /exact/path/to/remove
#    replace_path_pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
#    replace_path         PATH $PATH /exact/path/to/remove /replacement/path
#    replace_path_pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a ":" delimited list to work from (e.g. $PATH)
#   $3 the precise string to be removed/replaced
#   $4 the replacement string (use "" for removal)
function replace_path () {
    path=$1
    list=$2
    remove=$3
    replace=$4        # Allowed to be empty or unset

    export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
                   tr "\n" ":" | sed 's|:$||')
}

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a ":" delimited list to work from (e.g. $PATH)
#   $3 a grep pattern identifying the element to be removed/replaced
#   $4 the replacement string (use "" for removal)
function replace_path_pattern () {
    path=$1
    list=$2
    removepat=$3
    replacestr=$4        # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$list" "$removestr" "$replacestr"
}

Ich habe ein Perl-Skript namens echopath, die ich nützlich, wenn Probleme mit PATH-Variablen wie das Debuggen:

#!/usr/bin/perl -w
#
#   "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"
#
#   Print the components of a PATH variable one per line.
#   If there are no colons in the arguments, assume that they are
#   the names of environment variables.

@ARGV = $ENV{PATH} unless @ARGV;

foreach $arg (@ARGV)
{
    $var = $arg;
    $var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/;
    $var = $arg unless $var;
    @lst = split /:/, $var;
    foreach $val (@lst)
    {
            print "$val\n";
    }
}

Wenn ich die modifizierte Lösung auf dem Testcode laufen unter:

echo
xpath=$PATH
replace_path xpath $xpath /usr
echopath $xpath

echo
xpath=$PATH
replace_path_pattern xpath $xpath /usr/bin /work/bin
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin
echopath xpath

Die Ausgabe lautet:

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/work/bin
/bin
/sw/bin
/usr/sbin
/sbin

.
/Users/jleffler/bin
/work/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

Das sieht richtig zu mir - zumindest für meine Definition dessen, was das Problem ist,

.

Ich nehme zur Kenntnis, dass echopath LD_LIBRARY_PATH wertet $LD_LIBRARY_PATH. Es wäre schön, wenn Ihre Funktionen der Lage sind, das zu tun, so könnte der Benutzer eingeben:

replace_path PATH /usr/bin /work/bin

Das kann durch die Verwendung durchgeführt werden:

list=$(eval echo '$'$path)

Dies führt zu dieser Revision des Codes:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace_path         PATH /exact/path/to/remove
#    replace_path_pattern PATH <grep pattern for target path>
#
# To replace a path:
#    replace_path         PATH /exact/path/to/remove /replacement/path
#    replace_path_pattern PATH <target pattern> /replacement/path
#
###############################################################################

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 the precise string to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace_path () {
    path=$1
    list=$(eval echo '$'$path)
    remove=$2
    replace=$3            # Allowed to be empty or unset

    export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
                   tr "\n" ":" | sed 's|:$||')
}

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a grep pattern identifying the element to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace_path_pattern () {
    path=$1
    list=$(eval echo '$'$path)
    removepat=$2
    replacestr=$3            # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$removestr" "$replacestr"
}

Der folgende überarbeitete Test funktioniert nun auch:

echo
xpath=$PATH
replace_path xpath /usr
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath /usr/bin /work/bin
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath "/usr/.*/bin" /work/bin
echopath xpath

Es erzeugt die gleiche Ausgabe wie zuvor.

Andere Tipps

Umbuchen meiner Antwort auf Was ist die eleganteste Art und Weise einen Weg von dem $ PATH-Variable in bash zu entfernen:?

#!/bin/bash
IFS=:
# convert it to an array
t=($PATH)
unset IFS
# perform any array operations to remove elements from the array
t=(${t[@]%%*usr*})
IFS=:
# output the new array
echo "${t[*]}"

oder die Einzeiler:

PATH=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}");

Für ein Element löschen Sie sed verwenden können:

#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":")
export PATH=$NEW_PATH

werden die Pfade löschen, die „foo“ aus dem Pfad enthalten.

Sie können auch verwenden, sed vor oder nach einer bestimmten Zeile eine neue Zeile einzufügen.

Edit: Sie können Duplikate entfernen von Rohrleitungen durch Art und uniq:

echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":"

OK, Dank an alle Responder. Ich habe eine gekapselte Version von Florins Antwort vorbereitet. Der erste Durchlauf sieht wie folgt aus:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace-path         PATH $PATH /exact/path/to/remove    
#    replace-path-pattern PATH $PATH <grep pattern for target path>    
#
# To replace a path:
#    replace-path         PATH $PATH /exact/path/to/remove /replacement/path   
#    replace-path-pattern PATH $PATH <target pattern> /replacement/path
#    
###############################################################################
# Finds the _first_ list element matching $2
#
#    $1 name of a shell variable to be set
#    $2 name of a variable with a path-like structure
#    $3 a grep pattern to match the desired element of $1
function path-element-by-pattern (){ 
    target=$1;
    list=$2;
    pat=$3;

    export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 $pat);
    return
}

# Removes or replaces an element of $1
#
#   $1 name of the shell variable to set (i.e. PATH) 
#   $2 a ":" delimited list to work from (i.e. $PATH)
#   $2 the precise string to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace-path () {
    path=$1;
    list=$2;
    removestr=$3;
    replacestr=$4; # Allowed to be ""

    export $path=$(echo -n $list | tr ":" "\n" | sed "s|$removestr|$replacestr|" | tr "\n" ":" | sed "s|::|:|g");
    unset removestr
    return 
}

# Removes or replaces an element of $1
#
#   $1 name of the shell variable to set (i.e. PATH) 
#   $2 a ":" delimited list to work from (i.e. $PATH)
#   $2 a grep pattern identifying the element to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace-path-pattern () {
    path=$1;
    list=$2;
    removepat=$3; 
    replacestr=$4; # Allowed to be ""

    path-element-by-pattern removestr $list $removepat;
    replace-path $path $list $removestr $replacestr;
}

muss noch die Fehlerbehebung in allen Funktionen, und ich soll wohl in einer wiederholten Pfad Lösung bleiben, während ich es bin.

Sie verwenden es, durch eine . /include/path/path_tools.bash im Arbeits Skript zu tun und auf der die replace-path* Funktionen aufrufen.


Ich bin immer noch offen für neue und / oder bessere Antworten.

Das ist einfach awk verwendet wird.

Ersetzen

{
  for(i=1;i<=NF;i++) 
      if($i == REM) 
          if(REP)
              print REP; 
          else
              continue;
      else 
          print $i; 
}

Starten Sie es mit

function path_repl {
    echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd:
}

$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_repl /bin /baz
/baz:/usr/bin:/home/js/usr/bin
$ path_repl /bin
/usr/bin:/home/js/usr/bin

Anfügen

Einsätze an der angegebenen Position. Standardmäßig hängt es am Ende.

{ 
    if(IDX < 1) IDX = NF + IDX + 1
    for(i = 1; i <= NF; i++) {
        if(IDX == i) 
            print REP 
        print $i
    }
    if(IDX == NF + 1)
        print REP
}

Starten Sie es mit

function path_app {
    echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd:
}

$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_app /baz 0
/bin:/usr/bin:/home/js/usr/bin:/baz
$ path_app /baz -1
/bin:/usr/bin:/baz:/home/js/usr/bin
$ path_app /baz 1
/baz:/bin:/usr/bin:/home/js/usr/bin

Duplikate entfernen

Dies hält die ersten Vorkommen.

{ 
    for(i = 1; i <= NF; i++) {
        if(!used[$i]) {
            print $i
            used[$i] = 1
        }
    }
}

Starten Sie es wie folgt aus:

echo $PATH | awk -F: -f rem_dup.awk | paste -sd:

Validieren, ob alle Elemente vorhanden

Im Folgenden wird eine Fehlermeldung für alle Einträge drucken, die im Dateisystem vorhanden sind, werden nicht und geben einen Wert ungleich Null.

echo -n $PATH | xargs -d: stat -c %n

Um einfach zu überprüfen, ob alle Elemente sind Pfade und einen Return-Code erhalten, können Sie auch test verwenden:

echo -n $PATH | xargs -d: -n1 test -d

annehmen

echo $PATH
/usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin

Wenn Sie möchten, wie, wie unten /lib/jvm/java-1.6.0/bin/ zu tun entfernen

export PATH=$(echo $PATH | sed  's/\/lib\/jvm\/java-1.6.0\/bin\/://g')

sed nehmen Eingabe von echo $PATH und ersetzen /lib/jvm/java-1.6.0/bin/: mit leeren

Auf diese Weise können Sie entfernen

Ich ziehe mit Rubin, die gerne von awk / sed / foo in diesen Tagen, so ist hier mein Ansatz mit Betrogenen beschäftigen,

# add it to the path
PATH=~/bin/:$PATH:~/bin
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')

Erstellen Sie eine Funktion für die Wiederverwendung,

mungepath() {
   export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
}

Hash, Arrays und Strings in einem Rubin Motto:)

Das erste, was ich in dem Kopf Pop nur einen Teil einer Zeichenfolge zu ändern, ist eine Sed-Substitution.

Beispiel: wenn echo $ PATH => "/ usr / pkg / bin: / usr / bin: / bin: / usr / pkg / Spiele: / usr / pkg / X11R6 / bin" dann ändern "/ usr / bin" in "/ usr / local / bin", wie dies geschehen könnte:

## erzeugt Standardausgabedatei

## das Zeichen „=“ wird anstelle von Schrägstrich verwendet ( „/“), da das chaotisch wäre,  # Alternative zitierte Zeichen sollte in PATH

unwahrscheinlich

## den Weg separater Zeichen „:“ beide entfernt und erneut hinzugefügt hier,  # Könnte einen zusätzlichen Doppelpunkt nach dem letzten Weg will

echo $ PATH | sed '= / usr / bin: = / usr / local / bin: ='

ersetzt Diese Lösung ein ganzes Pfad-Element so überflüssig sein könnte, wenn neues Element ähnlich ist.

Wenn die neue PATH'-s nicht dynamisch sind, aber immer innerhalb eines konstanten Satzes könnten Sie speichern diejenigen in einer Variablen und weist je nach Bedarf:

PATH = $ TEMP_PATH_1;  # Befehle ...; \ n PATH = $ TEMP_PATH_2;  # Befehle etc ...;

Könnte nicht das, was Sie dachten. einige der entsprechenden Befehle auf bash / Unix wäre:

pushd popd CD ls # vielleicht l -1 A für einzelne Säule; finden grep worin # könnte diese Datei bestätigen, wo Sie denken, es kam; env Typ

.. und all das und mehr haben einige auf PATH oder Verzeichnisse im Allgemeinen tragen. Der Text Änderungs Teil eine beliebige Anzahl von Arten erfolgen kann!

Welche Lösung auch immer gewählt würde 4 Teile:

1) holen den Pfad wie er ist 2) dekodieren den Pfad der Teil, um Änderungen zu finden 3) Bestimmen, welche Änderungen nötig sind, um / Integration jene Änderungen 4) Validierung / Abschluss Integration / Einstellung der Variablen

Im Einklang mit dj_segfault Antwort , Ich tue dies in Skripten, die / prepend Umgebungsvariablen anhängen, die mehrfach ausgeführt werden können :

ld_library_path=${ORACLE_HOME}/lib
LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/}
export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

Unter Verwendung dieser gleichen Technik zu entfernen, ersetzen oder Einträge in PATH zu manipulieren trivial gegeben ist der Dateiname-Erweiterungsartige Musteranpassung und Musterliste Unterstützung der Schalenparameter Expansion.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top