Was ist die eleganteste Methode, um einen Pfad aus der Variablen $ PATH in Bash zu entfernen?

114

Oder allgemeiner: Wie entferne ich ein Element aus einer durch Doppelpunkte getrennten Liste in einer Bash-Umgebungsvariablen?

Ich dachte, ich hätte vor Jahren einen einfachen Weg gesehen, dies mit den fortgeschritteneren Formen der Bash-Variablenerweiterung zu tun, aber wenn ja, habe ich den Überblick verloren. Eine schnelle Suche bei Google ergab überraschend wenige relevante Ergebnisse und keine, die ich als "einfach" oder "elegant" bezeichnen würde. Zum Beispiel zwei Methoden mit sed bzw. awk:

PATH=$(echo $PATH | sed -e 's;:\?/home/user/bin;;' -e 's;/home/user/bin:\?;;')
PATH=!(awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PATH)

Gibt es nichts Unkompliziertes? Gibt es etwas Analoges zu einer split () - Funktion in Bash?

Update:
Es sieht so aus, als müsste ich mich für meine absichtlich vage Frage entschuldigen. Ich war weniger daran interessiert, einen bestimmten Anwendungsfall zu lösen, als eine gute Diskussion anzuregen. Zum Glück habe ich es verstanden!

Hier gibt es einige sehr clevere Techniken. Am Ende habe ich meiner Toolbox die folgenden drei Funktionen hinzugefügt. Die Magie geschieht in path_remove, das weitgehend auf Martin Yorks kluger Verwendung der awkRS-Variablen basiert .

path_append ()  { path_remove $1; export PATH="$PATH:$1"; }
path_prepend () { path_remove $1; export PATH="$1:$PATH"; }
path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; }

Die einzige echte Kruft darin ist die Verwendung sed, um den hinteren Doppelpunkt zu entfernen. Wenn man bedenkt, wie einfach der Rest von Martins Lösung ist, bin ich durchaus bereit, damit zu leben!


Verwandte Frage: Wie manipuliere ich $ PATH-Elemente in Shell-Skripten?

Ben Blank
quelle
Für jede Variable: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"aber Sie müssen es nennen func $VAR VAR pattern(basierend auf @ martin-york und @ andrew-aylett)
vesperto

Antworten:

51

Eine Minute mit awk:

# Strip all paths with SDE in them.
#
export PATH=`echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}'`

Bearbeiten: Es Antwort auf Kommentare unten:

$ export a="/a/b/c/d/e:/a/b/c/d/g/k/i:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i"
$ echo ${a}
/a/b/c/d/e:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i

## Remove multiple (any directory with a: all of them)
$ echo ${a} | awk -v RS=: -v ORS=: '/a/ {next} {print}'
## Works fine all removed

## Remove multiple including last two: (any directory with g)
$ echo ${a} | awk -v RS=: -v ORS=: '/g/ {next} {print}'
/a/b/c/d/e:/a/b/c/d/f:
## Works fine: Again!

Als Antwort auf ein Sicherheitsproblem bearbeiten: (das ist für die Frage nicht relevant)

export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}' | sed 's/:*$//')

Dadurch werden alle nachfolgenden Doppelpunkte entfernt, die durch Löschen der letzten Einträge übrig bleiben. Dies würde .Ihren Pfad effektiv erweitern .

Martin York
quelle
1
Schlägt fehl, wenn versucht wird, das letzte Element oder mehrere Elemente zu entfernen: Im ersten Fall wird das aktuelle Verzeichnis hinzugefügt (als leere Zeichenfolge; eine potenzielle Sicherheitslücke), im zweiten Fall wird `` als Pfadelement hinzugefügt.
Fred Foo
1
@larsmans: Funktioniert gut für mich. Hinweis: Leer ist nicht dasselbe wie das aktuelle Verzeichnis "./"
Martin York,
2
Eine leere Zeichenkette als „Mitglied“ der PATHVariable tut , als eine spezielle Regel bezeichnet aktuelles Verzeichnis in alle Unix - Shells seit mindestens V7 Unix 1979 Es ist immer noch in tut bash. Überprüfen Sie das Handbuch oder versuchen Sie es selbst.
Fred Foo
1
@ Martin: POSIX erfordert dieses Verhalten nicht, dokumentiert und erlaubt es jedoch: pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Fred Foo
1
Es gibt ein noch subtileres Problem beim Entfernen des letzten Elements: awk scheint am Ende der Zeichenfolge ein Null-Byte hinzuzufügen . Das heißt, wenn Sie später ein anderes Verzeichnis an PATH anhängen, wird es tatsächlich nicht durchsucht.
sschuberth
55

Mein dreckiger Hack:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Martin York
quelle
18
Es ist nie ein gutes Zeichen , wenn die naheliegendste Lösung ist auf de -Automatisierung den Prozess. :-D
Ben Blank
So viel sicherer als die "eleganten" Alternativen.
Jortstek
44

Da das große Problem bei der Substitution die Endfälle sind, können Sie die Endfälle nicht von den anderen Fällen unterscheiden. Wenn der Pfad am Anfang und am Ende bereits Doppelpunkte hatte, könnten wir einfach nach unserer gewünschten Zeichenfolge suchen, die mit Doppelpunkten umwickelt ist. So wie es ist, können wir diese Doppelpunkte einfach hinzufügen und anschließend entfernen.

# PATH => /bin:/opt/a dir/bin:/sbin
WORK=:$PATH:
# WORK => :/bin:/opt/a dir/bin:/sbin:
REMOVE='/opt/a dir/bin'
WORK=${WORK/:$REMOVE:/:}
# WORK => :/bin:/sbin:
WORK=${WORK%:}
WORK=${WORK#:}
PATH=$WORK
# PATH => /bin:/sbin

Pure Bash :).

Andrew Aylett
quelle
2
Ich würde diesen Tutorial-Abschnitt für etwas mehr Zuckerguss hinzufügen
Cyber ​​Oliveira
1
Ich habe das benutzt, weil es nach der einfachsten Lösung aussah. Es war super schnell und einfach, und Sie können Ihre Arbeit mit echo $ WORK direkt vor der letzten Zeile überprüfen, in der Sie die PATH-Variable tatsächlich ändern.
Phil Gran
2
Absolut ein kleines Juwel. Genau das, was ich versucht habe, als ich diesen Beitrag gefunden habe. - Danke Andrew! Übrigens: Vielleicht möchten Sie ": $ PATH:" in doppelte Anführungszeichen setzen, nur für den Fall, dass es Leerzeichen (dasselbe gilt für "/ usr / bin") und die letzte Zeile "$ WORK" enthalten sollte.
2
Danke, @ PacMan-- :). Ich bin mir ziemlich sicher (habe es gerade versucht), dass Sie keine Leerzeichen für die Zuweisungen benötigen WORKund PATHda die Variablenerweiterung erfolgt, nachdem die Zeile für die Variablenzuweisung und die Befehlsausführung in Abschnitte analysiert wurde. REMOVEMöglicherweise muss ein Anführungszeichen angegeben werden, oder Sie können Ihre Zeichenfolge direkt in den Ersatz einfügen, wenn es sich um eine Konstante handelt.
Andrew Aylett
Ich war immer verwirrt darüber, wann Zeichenfolgen beibehalten wurden, und begann daher, Zeichenfolgen immer in doppelte Anführungszeichen zu setzen. Nochmals vielen Dank für die Klarstellung. :)
26

Hier ist die einfachste Lösung, die ich finden kann:

#!/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[*]}"

Im obigen Beispiel wird jedes Element in $ PATH entfernt, das "usr" enthält. Sie können "* usr *" durch "/ home / user / bin" ersetzen, um nur dieses Element zu entfernen.

Update pro Sschuberth

Obwohl ich denke, dass Leerzeichen in a $PATHeine schreckliche Idee sind, ist hier eine Lösung, die damit umgeht:

PATH=$(IFS=':';t=($PATH);n=${#t[*]};a=();for ((i=0;i<n;i++)); do p="${t[i]%%*usr*}"; [ "${p}" ] && a[i]="${p}"; done;echo "${a[*]}");

oder

IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
  p="${t[i]%%*usr*}"
  [ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
Nicerobot
quelle
2
Als ein Liner: PATH = $ (IFS = ':'; t = ($ PATH); nicht gesetztes IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ");
Nicerobot
1
Diese Lösung funktioniert nicht mit Pfaden in PATH, die Leerzeichen enthalten. es ersetzt sie durch Doppelpunkte.
Sschuberth
11

Hier ist ein Einzeiler, der trotz der aktuell akzeptierten und am höchsten bewerteten Antworten keine unsichtbaren Zeichen zu PATH hinzufügt und mit Pfaden umgehen kann, die Leerzeichen enthalten:

export PATH=$(p=$(echo $PATH | tr ":" "\n" | grep -v "/cygwin/" | tr "\n" ":"); echo ${p%:})

Persönlich finde ich das auch leicht zu lesen / verstehen, und es beinhaltet nur allgemeine Befehle, anstatt awk zu verwenden.

sschuberth
quelle
2
... und wenn Sie etwas wollen, das auch mit Zeilenumbrüchen in Dateinamen zurechtkommt, können Sie dies verwenden: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (obwohl Sie sich vielleicht fragen möchten, warum Sie das brauchen, wenn Sie dies tun :))
ehdr
Dadurch werden teilweise Übereinstimmungen entfernt, was wahrscheinlich nicht das ist, was Sie wollen. Ich würde grep -v "^/path/to/remove\$"odergrep -v -x "/path/to/remove"
ShadSterling
Gute Lösung, aber denken Sie wirklich, trist häufiger als awk? ;)
K.-Michael Aye
1
Absolut. Leichte Umgebungen wie Git Bash unter Windows verfügen treher über ein einfaches Tool als über einen Interpreter awk.
sschuberth
1
@ehdr: Sie ersetzen müssen echo "..."mit printf "%s" "..."für sie auf Wegen wie zu arbeiten -eund ähnliche. Siehe stackoverflow.com/a/49418406/102441
Eric
8

Hier ist eine Lösung, die:

  • ist reiner Bash,
  • ruft keine anderen Prozesse auf (wie 'sed' oder 'awk'),
  • ändert sich nicht IFS,
  • gabelt keine Unterschale,
  • behandelt Pfade mit Leerzeichen und
  • Entfernt alle Vorkommen des Arguments in PATH.

    removeFromPath () {
       lokale pd
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {d // $ p /:}
       d = $ {d / #: /}
       PFAD = $ {d /%: /}
    }}
Robinbb
quelle
4
Ich mag diese Lösung. Vielleicht die Variablennamen aussagekräftiger machen?
Anukool
Sehr schön. Funktioniert jedoch nicht für Pfadsegmente, die ein Sternchen (*) enthalten. Manchmal kommen sie versehentlich dorthin.
Jörg
6

Funktion __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:}";
PATH = "$ {PATH / #: /}";
export PATH = "$ {PATH /%: /}";
}}

Habe es aus meiner .bashrc-Datei ausgegraben. Wenn Sie mit PATH herumspielen und es verloren geht, ist awk / sed / grep nicht mehr verfügbar :-)

GreenFox
quelle
1
Das ist ein sehr guter Punkt. (Ich habe es nie gemocht, externe Dienstprogramme für einfache Dinge wie diese auszuführen).
6

Die beste reine Bash-Option, die ich bisher gefunden habe, ist die folgende:

function path_remove {
  PATH=${PATH/":$1"/} # delete any instances in the middle or at the end
  PATH=${PATH/"$1:"/} # delete any instances at the beginning
}

Dies basiert auf der nicht ganz korrekten Antwort auf Verzeichnis zu $ ​​PATH hinzufügen, falls es nicht bereits in Superuser vorhanden ist.

Mark Booth
quelle
Das ist auch ganz gut. Ich habe es getestet. Wenn PATH einen doppelten Pfad enthält (z. B. zwei, die genau gleich sind), wird nur einer davon entfernt. Sie können es auch zu einem removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }
Diese Lösung schlägt fehl, wenn sie $PATHeinen Unterordner des Zielpfads enthält (dh gelöscht werden soll). Zum Beispiel: a:abc/def/bin:b-> a/bin:b, wann abc/defsoll gelöscht werden.
Robin Hsu
5

Ich habe gerade die Funktionen in der Bash-Distribution verwendet, die anscheinend seit 1991 vorhanden sind. Diese befinden sich immer noch im Bash-Docs-Paket auf Fedora und wurden früher verwendet /etc/profile, aber nicht mehr ...

$ rpm -ql bash-doc |grep pathfunc
/usr/share/doc/bash-4.2.20/examples/functions/pathfuncs
$ cat $(!!)
cat $(rpm -ql bash-doc |grep pathfunc)
#From: "Simon J. Gerraty" <[email protected]>
#Message-Id: <[email protected]>
#Subject: Re: a shell idea?
#Date: Mon, 09 Oct 1995 21:30:20 +1000


# NAME:
#       add_path.sh - add dir to path
#
# DESCRIPTION:
#       These functions originated in /etc/profile and ksh.kshrc, but
#       are more useful in a separate file.
#
# SEE ALSO:
#       /etc/profile
#
# AUTHOR:
#       Simon J. Gerraty <[email protected]>

#       @(#)Copyright (c) 1991 Simon J. Gerraty
#
#       This file is provided in the hope that it will
#       be of use.  There is absolutely NO WARRANTY.
#       Permission to copy, redistribute or otherwise
#       use this file is hereby granted provided that
#       the above copyright notice and this notice are
#       left intact.

# is $1 missing from $2 (or PATH) ?
no_path() {
        eval "case :\$${2-PATH}: in *:$1:*) return 1;; *) return 0;; esac"
}
# if $1 exists and is not in path, append it
add_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="\$${2:-PATH}:$1"
}
# if $1 exists and is not in path, prepend it
pre_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="$1:\$${2:-PATH}"
}
# if $1 is in path, remove it
del_path () {
  no_path $* || eval ${2:-PATH}=`eval echo :'$'${2:-PATH}: |
    sed -e "s;:$1:;:;g" -e "s;^:;;" -e "s;:\$;;"`
}
Mr. Wacky
quelle
4

Ich habe dazu eine Antwort schreiben hier (mit awk auch). Aber ich bin mir nicht sicher, ob Sie danach suchen? Zumindest scheint mir klar zu sein, was es tut, anstatt zu versuchen, in eine Zeile zu passen. Für einen einfachen Einzeiler, der nur Sachen entfernt, empfehle ich

echo $PATH | tr ':' '\n' | awk '$0 != "/bin"' | paste -sd:

Ersetzen ist

echo $PATH | tr ':' '\n' | 
    awk '$0 != "/bin"; $0 == "/bin" { print "/bar" }' | paste -sd:

oder (kürzer aber weniger lesbar)

echo $PATH | tr ':' '\n' | awk '$0 == "/bin" { print "/bar"; next } 1' | paste -sd:

Wie auch immer, für dieselbe Frage und viele nützliche Antworten siehe hier .

Johannes Schaub - litb
quelle
Und wenn Sie Zeilen entfernen möchten, die eine teilweise Zeichenfolge enthalten, verwenden Sie awk '$0 !~ "/bin"'. Dh mit dem awk-Operator Zeilen behalten, die nicht '/ bin' enthalten !~.
Thoni56
3

Nun, in Bash, da es den regulären Ausdruck unterstützt, würde ich einfach tun:

PATH=${PATH/:\/home\/user\/bin/}
Matte
quelle
Ist es nicht nur eine Pfadnamenerweiterung, keine regulären Ausdrücke?
Dreamlax
2
Während bash reguläre Ausdrücke unterstützt (ab bash 3), ist dies kein Beispiel dafür, sondern eine variable Substitution.
Robert Gamble
4
Dies ist eine Erweiterung der Mustervariablen, und die Lösung weist mehrere Probleme auf. 1) Es stimmt nicht mit dem ersten Element überein. 2) Es stimmt mit allem überein, was mit "/ home / user / bin" beginnt, nicht nur mit "/ home / user / bin". 3) Es erfordert das Entkommen von Sonderzeichen. Bestenfalls würde ich sagen, dass dies ein unvollständiges Beispiel ist.
Nicerobot
2

Ich mag die drei Funktionen, die in @ BenBlanks Update zu seiner ursprünglichen Frage gezeigt werden. Um sie zu verallgemeinern, verwende ich ein Formular mit zwei Argumenten, mit dem ich PATH oder eine andere gewünschte Umgebungsvariable festlegen kann:

path_append ()  { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend () { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove ()  { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }

Anwendungsbeispiele:

path_prepend PATH /usr/local/bin
path_append PERL5LIB "$DEVELOPMENT_HOME/p5/src/perlmods"

Beachten Sie, dass ich auch einige Anführungszeichen hinzugefügt habe, um die ordnungsgemäße Verarbeitung von Pfadnamen zu ermöglichen, die Leerzeichen enthalten.

Cary Millsap
quelle
2

Was ist die eleganteste Methode, um einen Pfad aus der Variablen $ PATH in Bash zu entfernen?

Was ist eleganter als awk?

path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; 

Python! Es ist eine besser lesbare und wartbare Lösung, und es ist leicht zu überprüfen, ob es wirklich das tut, was Sie wollen.

Angenommen, Sie möchten das erste Pfadelement entfernen?

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"

(Anstatt von zu leiten echo, os.getenv['PATH']wäre es etwas kürzer und würde das gleiche Ergebnis wie oben liefern, aber ich mache mir Sorgen, dass Python etwas mit dieser Umgebungsvariablen machen könnte, daher ist es wahrscheinlich am besten, es direkt von der Umgebung zu leiten, die Sie interessiert .)

Ähnlich vom Ende zu entfernen:

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"

Um diese wiederverwendbaren Shell-Funktionen zu erstellen, die Sie beispielsweise in Ihre .bashrc-Datei einfügen können:

strip_path_first () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"
}

strip_path_last () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"
}
Aaron Hall
quelle
1

Ja, wenn Sie beispielsweise einen Doppelpunkt am Ende von PATH einfügen, wird das Entfernen eines Pfads weniger umständlich und fehleranfällig.

path_remove ()  { 
   declare i newPATH
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      #echo ${@:${i}:1}
      newPATH="${newPATH//${@:${i}:1}:/}" 
   done
   export PATH="${newPATH%:}" 
   return 0; 
} 

path_remove_all ()  {
   declare i newPATH
   shopt -s extglob
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//+(${@:${i}:1})*([^:]):/}" 
      #newPATH="${newPATH//+(${@:${i}:1})*([^:])+(:)/}" 
   done
   shopt -u extglob 
   export PATH="${newPATH%:}" 
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
Cyrill
quelle
1

Wenn Sie Bedenken haben, Duplikate in $ PATH zu entfernen , ist es meiner Meinung nach am elegantesten, sie gar nicht erst hinzuzufügen. In 1 Zeile:

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

Der Ordner $ kann durch irgendetwas ersetzt werden und Leerzeichen enthalten ("/ home / user / my documents").

MestreLion
quelle
1

Die eleganteste reine Bash-Lösung, die ich bisher gefunden habe:

pathrm () {                                                                      
  local IFS=':'                                                                  
  local newpath                                                                  
  local dir                                                                      
  local pathvar=${2:-PATH}                                                       
  for dir in ${!pathvar} ; do                                                    
    if [ "$dir" != "$1" ] ; then                                                 
      newpath=${newpath:+$newpath:}$dir                                          
    fi                                                                           
  done                                                                           
  export $pathvar="$newpath"                                                        
}

pathprepend () {                                                                 
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="$1${!pathvar:+:${!pathvar}}"                                  
}

pathappend () {                                                                    
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="${!pathvar:+${!pathvar}:}$1"                                  
} 
TriangleTodd
quelle
1

Die meisten anderen vorgeschlagenen Lösungen beruhen nur auf String - Matching und sie berücksichtigen nicht die Pfadsegmente mit speziellen Namen wie ., ..oder ~. Die folgende Bash-Funktion löst Verzeichniszeichenfolgen in ihrem Argument und in Pfadsegmenten auf, um logische Verzeichnisübereinstimmungen sowie Zeichenfolgenübereinstimmungen zu finden.

rm_from_path() {
  pattern="${1}"
  dir=''
  [ -d "${pattern}" ] && dir="$(cd ${pattern} && pwd)"  # resolve to absolute path

  new_path=''
  IFS0=${IFS}
  IFS=':'
  for segment in ${PATH}; do
    if [[ ${segment} == ${pattern} ]]; then             # string match
      continue
    elif [[ -n ${dir} && -d ${segment} ]]; then
      segment="$(cd ${segment} && pwd)"                 # resolve to absolute path
      if [[ ${segment} == ${dir} ]]; then               # logical directory match
        continue
      fi
    fi
    new_path="${new_path}${IFS}${segment}"
  done
  new_path="${new_path/#${IFS}/}"                       # remove leading colon, if any
  IFS=${IFS0}

  export PATH=${new_path}
}

Prüfung:

$ mkdir -p ~/foo/bar/baz ~/foo/bar/bif ~/foo/boo/bang
$ PATH0=${PATH}
$ PATH=~/foo/bar/baz/.././../boo/././../bar:${PATH}  # add dir with special names
$ rm_from_path ~/foo/boo/../bar/.  # remove same dir with different special names
$ [ ${PATH} == ${PATH0} ] && echo 'PASS' || echo 'FAIL'
jwfearn
quelle
plus eins für außerhalb der Box
Martin York
1

Linux from Scratch definiert drei Bash-Funktionen in /etc/profile:

# Functions to help us manage paths.  Second argument is the name of the
# path variable to be modified (default: PATH)
pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
                if [ "$DIR" != "$1" ] ; then
                  NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
                fi
        done
        export $PATHVARIABLE="$NEWPATH"
}

pathprepend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
}

pathappend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
}

export -f pathremove pathprepend pathappend

Ref: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html

Kevinarpe
quelle
1

Ich weiß, dass diese Frage nach BASH fragt, was jeder bevorzugen sollte, aber da ich Symmetrie mag und manchmal "csh" verwenden muss, habe ich das Äquivalent zu "path_prepend ()", "path_append ()" und "path_remove" erstellt () "elegante Lösung oben.

Das Wesentliche ist, dass "csh" keine Funktionen hat, also habe ich kleine Shell-Skripte in mein persönliches bin-Verzeichnis gestellt, die sich wie die Funktionen verhalten. Ich erstelle Aliase, um diese Skripte zu SOURCE, um die Änderungen der angegebenen Umgebungsvariablen vorzunehmen.

~ / bin / _path_remove.csh:

set _resolve = `eval echo $2`
setenv $1 `eval echo -n \$$1 | awk -v RS=: -v ORS=: '$1 != "'${_resolve}'"' | sed 's/:$//'`;
unset _resolve

~ / bin / _path_append.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_base}:${_resolve}
unset _base _resolve

~ / bin / _path_prepend.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_resolve}:${_base}
unset _base _resolve

~ / bin / .cshrc:


alias path_remove  "source ~/bin/_path_remove.csh  '\!:1' '\!:2'"
alias path_append  "source ~/bin/_path_append.csh  '\!:1' '\!:2'"
alias path_prepend "source ~/bin/_path_prepend.csh '\!:1' '\!:2'"

Sie können sie so verwenden ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles
Lance ET Compte
quelle
0

Da dies in der Regel recht problematisch ist, da es KEINE elegante Art gibt, empfehle ich, das Problem zu vermeiden, indem Sie die Lösung neu anordnen: Bauen Sie Ihren Pfad auf, anstatt zu versuchen, ihn abzureißen.

Ich könnte genauer sein, wenn ich Ihren wirklichen Problemkontext kennen würde. In der Zwischenzeit werde ich einen Software-Build als Kontext verwenden.

Ein häufiges Problem bei Software-Builds besteht darin, dass es auf einigen Computern nicht funktioniert, was letztendlich darauf zurückzuführen ist, wie jemand seine Standard-Shell (PATH und andere Umgebungsvariablen) konfiguriert hat. Die elegante Lösung besteht darin, Ihre Build-Skripte durch vollständige Angabe der Shell-Umgebung immun zu machen. Codieren Sie Ihre Build-Skripte, um den PATH und andere Umgebungsvariablen basierend auf den von Ihnen gesteuerten Assemblierungselementen festzulegen, z. B. den Speicherort des Compilers, der Bibliotheken, Tools, Komponenten usw. Machen Sie jedes konfigurierbare Element zu etwas, das Sie einzeln festlegen, überprüfen und überprüfen können Verwenden Sie dann entsprechend in Ihrem Skript.

Zum Beispiel habe ich einen Maven-basierten WebLogic-bezogenen Java-Build, den ich bei meinem neuen Arbeitgeber geerbt habe. Das Build-Skript ist bekannt dafür, dass es zerbrechlich ist, und ein anderer neuer Mitarbeiter und ich haben drei Wochen (nicht Vollzeit, nur hier und da, aber immer noch viele Stunden) damit verbracht, es auf unseren Maschinen zum Laufen zu bringen. Ein wesentlicher Schritt war, dass ich die Kontrolle über den PATH übernahm, damit ich genau wusste, welches Java, welcher Maven und welches WebLogic aufgerufen wurde. Ich habe Umgebungsvariablen erstellt, um auf jedes dieser Tools zu verweisen, und dann den PATH basierend auf diesen und einigen anderen berechnet. Ähnliche Techniken zähmten die anderen konfigurierbaren Einstellungen, bis wir schließlich einen reproduzierbaren Build erstellten.

Verwenden Sie Maven übrigens nicht, Java ist in Ordnung und kaufen Sie WebLogic nur, wenn Sie das Clustering unbedingt benötigen (aber ansonsten nein, und insbesondere nicht die proprietären Funktionen).

Die besten Wünsche.

Rob Williams
quelle
Manchmal haben Sie keinen Root-Zugriff und Ihr Administrator verwaltet Ihren PATH. Sicher, Sie könnten Ihre eigenen bauen, aber jedes Mal, wenn Ihr Administrator etwas bewegt, müssen Sie herausfinden, wo er es abgelegt hat. Diese Art der Niederlage hat den Zweck, einen Administrator zu haben.
Shep
0

Wie bei @litb habe ich eine Antwort auf die Frage " Wie manipuliere ich $ PATH-Elemente in Shell-Skripten " beigesteuert, sodass meine Hauptantwort dort ist.

Die "Split" -Funktionalität in bashund anderen Bourne-Shell-Derivaten wird am besten mit $IFSdem Inter-Field-Separator erreicht. Zum Beispiel wird die Positions Argumente zu setzen ( $1, $2, ...) zu den Elementen der PATH, zu verwenden:

set -- $(IFS=":"; echo "$PATH")

Es wird in Ordnung funktionieren, solange in $ PATH keine Leerzeichen vorhanden sind. Es ist nicht trivial, es für Pfadelemente mit Leerzeichen zum Laufen zu bringen - es bleibt dem interessierten Leser überlassen. Es ist wahrscheinlich einfacher, mit einer Skriptsprache wie Perl damit umzugehen.

Ich habe auch ein Skript, clnpathdas ich ausgiebig zum Einstellen meines Pfads verwende. Ich habe es in der Antwort auf " Wie man verhindert, dass PATH-Variablen in csh dupliziert werden " dokumentiert .

Jonathan Leffler
quelle
IFS =: a = ($ PATH); IFS = Splitting ist auch schön. funktioniert, wenn sie auch Leerzeichen enthalten. Aber dann hast du ein Array und musst mit for-Schleifen und dergleichen herumspielen, um die Namen zu entfernen.
Johannes Schaub - litb
Ja; es wird fummelig - wie bei meinem aktualisierten Kommentar ist es an dieser Stelle wahrscheinlich einfacher, eine Skriptsprache zu verwenden.
Jonathan Leffler
0

Was dieses Problem ärgerlich macht, sind die Zaunpfostenfälle zwischen dem ersten und dem letzten Element. Das Problem kann elegant gelöst werden, indem IFS geändert und ein Array verwendet wird, aber ich weiß nicht, wie der Doppelpunkt wieder eingeführt werden soll, sobald der Pfad in die Array-Form konvertiert wurde.

Hier ist eine etwas weniger elegante Version, die ein Verzeichnis von der $PATHVerwendung nur der Zeichenfolgenmanipulation entfernt. Ich habe es getestet.

#!/bin/bash
#
#   remove_from_path dirname
#
#   removes $1 from user's $PATH

if [ $# -ne 1 ]; then
  echo "Usage: $0 pathname" 1>&2; exit 1;
fi

delendum="$1"
NEWPATH=
xxx="$IFS"
IFS=":"
for i in $PATH ; do
  IFS="$xxx"
  case "$i" in
    "$delendum") ;; # do nothing
    *) [ -z "$NEWPATH" ] && NEWPATH="$i" || NEWPATH="$NEWPATH:$i" ;;
  esac
done

PATH="$NEWPATH"
echo "$PATH"
Norman Ramsey
quelle
0

Hier ist ein Perl-Einzeiler:

PATH=`perl -e '$a=shift;$_=$ENV{PATH};s#:$a(:)|^$a:|:$a$#$1#;print' /home/usr/bin`

Die $aVariable erhält den zu entfernenden Pfad. Die Befehle s(Ersatz) und printBefehle wirken sich implizit auf die $_Variable aus.

JA Faucett
quelle
0

Gutes Zeug hier. Ich benutze dieses, um überhaupt keine Dupes hinzuzufügen.

#!/bin/bash
#
######################################################################################
#
# Allows a list of additions to PATH with no dupes
# 
# Patch code below into your $HOME/.bashrc file or where it
# will be seen at login.
#
# Can also be made executable and run as-is.
#
######################################################################################

# add2path=($HOME/bin .)                  ## uncomment space separated list 
if [ $add2path ]; then                    ## skip if list empty or commented out
for nodup in ${add2path[*]}
do
    case $PATH in                 ## case block thanks to MIKE511
    $nodup:* | *:$nodup:* | *:$nodup ) ;;    ## if found, do nothing
    *) PATH=$PATH:$nodup          ## else, add it to end of PATH or
    esac                          ## *) PATH=$nodup:$PATH   prepend to front
done
export PATH
fi
## debug add2path
echo
echo " PATH == $PATH"
echo
ongoto
quelle
1
Sie können Ihre case-Anweisung vereinfachen, indem Sie der PATH-Zeichenfolge einen führenden und einen case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
nachfolgenden
0

Bei aktiviertem Extended Globbing ist Folgendes möglich:

# delete all /opt/local paths in PATH
shopt -s extglob 
printf "%s\n" "${PATH}" | tr ':' '\n' | nl
printf "%s\n" "${PATH//+(\/opt\/local\/)+([^:])?(:)/}" | tr ':' '\n' | nl 

man bash | less -p extglob
carlo
quelle
0

Erweiterter Globbing-Einzeiler (na ja, irgendwie):

path_remove ()  { shopt -s extglob; PATH="${PATH//+(${1})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 

Es scheint nicht nötig zu sein, Schrägstrichen in Höhe von 1 USD zu entkommen.

path_remove ()  { shopt -s extglob; declare escArg="${1//\//\\/}"; PATH="${PATH//+(${escArg})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 
carlo
quelle
0

Durch Hinzufügen von Doppelpunkten zu PATH können wir auch Folgendes tun:

path_remove ()  { 
   declare i newPATH
   # put a colon at the beginning & end AND double each colon in-between
   newPATH=":${PATH//:/::}:"   
   for ((i=1; i<=${#@}; i++)); do
       #echo ${@:${i}:1}
       newPATH="${newPATH//:${@:${i}:1}:/}"   # s/:\/fullpath://g
   done
   newPATH="${newPATH//::/:}"
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 


path_remove_all ()  {
   declare i newPATH extglobVar
   extglobVar=0
   # enable extended globbing if necessary
   [[ ! $(shopt -q extglob) ]]  && { shopt -s extglob; extglobVar=1; }
   newPATH=":${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}"     # s/:\/path[^:]*//g
   done
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   # disable extended globbing if it was enabled in this function
   [[ $extglobVar -eq 1 ]] && shopt -u extglob
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
Proxy
quelle
0

In path_remove_all (per Proxy):

-newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}" 
+newPATH="${newPATH//:${@:${i}:1}*([^:])/}"        # s/:\/path[^:]*//g 
Marius
quelle
0

Obwohl dies ein sehr alter Thread ist, dachte ich, dass diese Lösung von Interesse sein könnte:

PATH="/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
REMOVE="ccache" # whole or part of a path :)
export PATH=$(IFS=':';p=($PATH);unset IFS;p=(${p[@]%%$REMOVE});IFS=':';echo "${p[*]}";unset IFS)
echo $PATH # outputs /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

fand es in diesem Blog-Beitrag . Ich denke, ich mag dieses am meisten :)

mjc
quelle
0

Ich habe einen etwas anderen Ansatz gewählt als die meisten Leute hier und mich speziell auf die Manipulation von Saiten konzentriert, wie zum Beispiel:

path_remove () {
    if [[ ":$PATH:" == *":$1:"* ]]; then
        local dirs=":$PATH:"
        dirs=${dirs/:$1:/:}
        export PATH="$(__path_clean $dirs)"
    fi
}
__path_clean () {
    local dirs=${1%?}
    echo ${dirs#?}
}

Das Obige ist ein vereinfachtes Beispiel für die endgültigen Funktionen, die ich verwende. Ich habe auch einen Pfad vor / nach einem angegebenen Pfad erstellt path_add_beforeund zugelassen path_add_after, der bereits in PATH enthalten ist.

Der vollständige Funktionsumfang ist in path_helpers.sh in meinen Punktedateien verfügbar . Sie unterstützen das Entfernen / Anhängen / Voranstellen / Einfügen am Anfang / Mitte / Ende der PATH-Zeichenfolge vollständig.

Jimeh
quelle