Fügen Sie das Verzeichnis zu $ ​​PATH hinzu, falls es noch nicht vorhanden ist

126

Hat jemand eine Bash-Funktion geschrieben, um $ PATH ein Verzeichnis hinzuzufügen, wenn es noch nicht vorhanden ist?

Normalerweise füge ich PATH hinzu, indem ich Folgendes verwende:

export PATH=/usr/local/mysql/bin:$PATH

Wenn ich meinen PATH in .bash_profile konstruiere, wird er nur gelesen, wenn es sich bei der Sitzung, in der ich mich befinde, um eine Anmeldesitzung handelt. Dies ist nicht immer der Fall. Wenn ich meinen PATH in .bashrc konstruiere, wird er mit jeder Subshell ausgeführt. Wenn ich also ein Terminal-Fenster starte und dann screen und dann ein Shell-Skript ausführe, erhalte ich:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....

Ich werde versuchen, eine Bash-Funktion namens zu add_to_path()erstellen, die das Verzeichnis nur hinzufügt, wenn es nicht vorhanden ist. Aber wenn jemand schon so etwas geschrieben (oder gefunden) hat, werde ich nicht die Zeit darauf verwenden.

Doug Harris
quelle
Eine hilfreiche Infrastruktur finden Sie unter stackoverflow.com/questions/273909/… .
dmckee
Wenn Sie das Problem als "nur hinzufügen, wenn es noch nicht vorhanden ist" einrahmen, werden Sie grob überrascht sein, wenn der Tag kommt, an dem es wichtig ist, dass das eingefügte Element am Anfang ist, es dort jedoch nicht auftaucht. Ein besserer Ansatz wäre, das Element einzufügen und dann Duplikate zu entfernen. Wenn der neue Eintrag also bereits vorhanden ist, wird er effektiv an den Anfang verschoben.
Don Hatch

Antworten:

125

Aus meiner .bashrc:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

Beachten Sie, dass PATH bereits als exportiert markiert sein sollte, sodass ein erneutes Exportieren nicht erforderlich ist. Dies prüft, ob das Verzeichnis existiert & ein Verzeichnis ist, bevor es hinzugefügt wird, was Sie möglicherweise nicht interessiert.

Dadurch wird auch das neue Verzeichnis am Ende des Pfads hinzugefügt. Verwenden Sie PATH="$1${PATH:+":$PATH"}"anstelle der obigen PATH=Zeile , um am Anfang zu setzen .

Gordon Davisson
quelle
26
Ich kümmre mich.
Dennis Williamson
4
@Neil: Es funktioniert, weil es mit ":$PATH:"statt nur"$PATH"
Gordon Davisson
3
@ GordonDavisson: Ich entschuldige mich, mein Test war falsch und Sie sind richtig.
Neil
2
@ GordonDavisson Was ist der Sinn des Zeugs in den geschweiften Klammern. Ich kann es scheinbar nicht ${PATH:+"$PATH:"}
rätseln
5
@ Mark0978: Das habe ich gemacht, um das Problem zu beheben bukzor darauf hingewiesen. ${variable:+value}bedeutet zu überprüfen, ob variabledefiniert ist und einen nicht leeren Wert hat und ob es das Ergebnis der Auswertung gibt value. Grundsätzlich gilt: Wenn PATH von Anfang an nicht leer ist, wird es auf gesetzt "$PATH:$1". Wenn es leer ist, setzt es es auf nur "$1"(beachten Sie das Fehlen eines Doppelpunkts).
Gordon Davisson
19

In Anbetracht der Antwort von Gordon Davisson unterstützt dies mehrere Argumente

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

Sie können also pathappend path1 path2 path3 ...

Zum Voranstellen

pathprepend() {
  for ((i=$#; i>0; i--)); 
  do
    ARG=${!i}
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}

Ähnlich wie bei pathappend können Sie dies tun

pathprepend path1 path2 path3 ...

Guillaume Perrault-Archambault
quelle
3
Das ist toll! Ich habe eine kleine Änderung vorgenommen. Für die 'pathprepend'-Funktion ist es praktisch, die Argumente in umgekehrter Reihenfolge zu lesen, damit Sie beispielsweise sagen können, pathprepend P1 P2 P3und am Ende mit PATH=P1:P2:P3. Um dieses Verhalten zu erhalten, wechseln Sie for ARG in "$@" dozu "for ((i=$#; i>0; i--)); do ARG=${!i}
ishmael"
Danke @ishmael, guter Vorschlag, ich habe die Antwort bearbeitet. Mir ist klar, dass Ihr Kommentar älter als zwei Jahre ist, aber ich bin seitdem nicht mehr zurück. Ich muss herausfinden, wie ich Stapelwechsel-E-Mails in meinen Posteingang bekomme!
Guillaume Perrault-Archambault
14

Hier ist etwas aus meiner Antwort auf diese Frage in Verbindung mit der Struktur der Funktion von Doug Harris. Es verwendet reguläre Bash-Ausdrücke:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
Dennis Williamson
quelle
Dieser arbeitete für mich nur mit , $1anstatt${1}
Andrei
@Andrei: Ja, die Klammern sind in diesem Fall nicht erforderlich. Ich bin nicht sicher, warum ich sie aufgenommen habe.
Dennis Williamson
10

Fügen Sie dies in die Kommentare der ausgewählten Antwort ein, aber Kommentare scheinen die PRE-Formatierung nicht zu unterstützen. Fügen Sie die Antwort hier hinzu:

@ Gordon-Davisson Ich bin kein großer Fan von unnötigem Zitieren und Verketten. Angenommen, Sie verwenden eine Bash-Version> = 3, können Sie stattdessen Bashs eingebaute Regex-Befehle verwenden und Folgendes ausführen:

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

Dies behandelt Fälle korrekt, in denen sich Leerzeichen im Verzeichnis oder im PATH befinden. Es ist fraglich, ob die in bash eingebaute Regex-Engine langsam genug ist, um weniger effizient zu sein als die String-Verkettung und -Interpolation, die Ihre Version durchführt, aber irgendwie fühlt sie sich für mich nur ästhetisch sauberer an.

Christopher Smith
quelle
1
Kommentare werden formatting using the backticknur unterstützt, aber Sie erhalten keine anständige Absatzkontrolle.
Boatcoder
Damit ist der Zusatz am Ende. Es ist häufig wünschenswert, den Anfang zu ergänzen, um vorhandene Standorte zu überschreiben.
Dennis Williamson
@DennisWilliamson Das ist ein fairer Punkt, obwohl ich das nicht als Standardverhalten empfehlen würde. Es ist nicht schwer herauszufinden, wie man ein Prepend ändert.
Christopher Smith
@ChristopherSmith - re: unnecessary quotingbedeutet, dass Sie im Voraus wissen, dass dies $PATHnicht null ist. "$PATH"macht es OK, ob PATH null ist oder nicht. In ähnlicher Weise $1enthält if Zeichen, die den Befehlsparser verwirren könnten. Das Setzen des regulären Ausdrucks in Anführungszeichen "(^|:)$1(:|$)"verhindert dies.
Jesse Chisholm
@JesseChisholm: Eigentlich glaube ich, dass Christophers Punkt darin besteht, dass die Regeln zwischen [[und unterschiedlich sind ]]. Ich ziehe alles zu zitieren , die möglicherweise brauchen kann angegeben werden, es sei denn , die bewirkt , dass es zum Scheitern verurteilt, aber ich glaube , er ist richtig, und dass Zitate wirklich nicht benötigt um $PATH. Auf der anderen Seite scheint es mir, dass Sie Recht haben $1.
Scott
7
idempotent_path_prepend ()
{
    PATH=${PATH//":$1"/} #delete any instances in the middle or at the end
    PATH=${PATH//"$1:"/} #delete any instances at the beginning
    export PATH="$1:$PATH" #prepend to beginning
}

Wenn $ HOME / bin genau einmal am Anfang Ihres $ PATH und an keiner anderen Stelle erscheinen soll, akzeptieren Sie keine Ersatzzeichen.

Russell
quelle
Danke, das ist eine schöne, elegante Lösung, aber ich habe festgestellt, dass ich PATH=${PATH/"... anstatt ... etwas tun musste PATH=${PATH//", damit es funktioniert.
Mark Booth
Das Doppel-Schrägstrich-Formular sollte mit einer beliebigen Anzahl von Übereinstimmungen übereinstimmen. der einzelne Schrägstrich stimmt nur mit dem ersten überein (Suche nach "Mustersubstitution" in der Bash-Manpage). Ich
bin
Dies schlägt in dem ungewöhnlichen Fall fehl, dass dies $1der einzige Eintrag ist (keine Doppelpunkte). Der Eintrag wird verdoppelt.
Dennis Williamson
Es wird auch zu aggressiv gelöscht, wie von PeterS6g ausgeführt .
Dennis Williamson
6

Hier ist eine alternative Lösung, die den zusätzlichen Vorteil hat, redundante Einträge zu entfernen:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

Das einzige Argument für diese Funktion wird dem Pfad vorangestellt, und die erste Instanz derselben Zeichenfolge wird aus dem vorhandenen Pfad entfernt. Mit anderen Worten, wenn das Verzeichnis bereits im Pfad vorhanden ist, wird es nach vorne verschoben, anstatt als Duplikat hinzugefügt zu werden.

Bei dieser Funktion wird dem Pfad ein Doppelpunkt vorangestellt, um sicherzustellen, dass alle Einträge vorne einen Doppelpunkt haben. Anschließend wird der neue Eintrag dem vorhandenen Pfad vorangestellt, wobei dieser Eintrag entfernt wird. Der letzte Teil wird in der Bash- ${var//pattern/sub}Notation ausgeführt. Weitere Informationen finden Sie im Bash-Handbuch .

Rob Hague
quelle
Guter Gedanke; fehlerhafte Umsetzung. Überlegen Sie, was passiert, wenn Sie bereits /home/robertin Ihrem PATHund Ihnen haben pathadd /home/rob.
Scott
5

Hier ist meins (ich glaube, es wurde vor Jahren von Oscar geschrieben, dem Systemadministrator meines alten Labors, der ihm alle Ehre macht). Es hat den zusätzlichen Vorteil, dass Sie das neue Verzeichnis wie gewünscht voranstellen oder anhängen können:

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

Verwendungszweck:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/
terdon
quelle
5

Zum Voranstellen mag ich die Lösung von @ Russell, aber es gibt einen kleinen Fehler: Wenn Sie versuchen, etwas wie "/ bin" vor einen Pfad von "/ sbin: / usr / bin: / var / usr / bin: / usr / local" zu stellen / bin: / usr / sbin "es ersetzt" / bin: "3 mal (wenn es überhaupt nicht wirklich passt). Wenn ich einen Fix dafür mit der anhängenden Lösung von @ gordon-davisson kombiniere, erhalte ich Folgendes:

path_prepend() {
    if [ -d "$1" ]; then
        PATH=${PATH//":$1:"/:} #delete all instances in the middle
        PATH=${PATH/%":$1"/} #delete any instance at the end
        PATH=${PATH/#"$1:"/} #delete any instance at the beginning
        PATH="$1${PATH:+":$PATH"}" #prepend $1 or if $PATH is empty set to $1
    fi
}
PeterS6g
quelle
4

Ein einfacher Alias ​​wie dieser unten sollte den Trick machen:

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "

Sie müssen lediglich den Pfad des Zeichens: aufteilen und jede Komponente mit dem Argument vergleichen, das Sie übergeben. Grep prüft, ob eine vollständige Zeilenübereinstimmung vorliegt, und gibt die Anzahl aus.

Beispielnutzung:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No

Ersetzen Sie den Befehl echo durch addToPath oder einen ähnlichen Alias ​​/ eine ähnliche Funktion.

Nagul
quelle
Die Verwendung von "grep -x" ist wahrscheinlich schneller als die Schleife, die ich in meine Bash-Funktion eingefügt habe.
Doug Harris
2

Folgendes habe ich mir ausgedacht:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=$1
    for d in $path_list
    do
        if [ $d == $new_dir ]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}

Jetzt in .bashrc habe ich:

add_to_path /usr/local/mysql/bin

Aktualisierte Version mit folgendem Kommentar darüber, wie mein Original Verzeichnisse mit Leerzeichen nicht behandelt (dank dieser Frage, die mich auf die Verwendung hinweist IFS):

add_to_path ()
{
    new_dir=$1
    local IFS=:
    for d in $PATH
    do
        if [[ "$d" == "$new_dir" ]]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}
Doug Harris
quelle
1
Dies kann fehlschlagen , wenn jeder Verzeichnisname bereits in PATHLeerzeichen enthält, *, ?, oder [... ].
Scott
Guter Punkt ... aber ich bin ein alter Linux-Typ und würde niemals einen Pfad mit Leerzeichen verwenden :-)
Doug Harris
Gut für dich, dass du keine Dinge mit Leerzeichen in ihren Namen erschaffst. Schande, dass du Code schreibst, der mit ihnen nicht umgehen kann, wenn sie existieren. Und was hat es damit zu tun, ein alter Linux-Typ zu sein? Windoze hat die Idee vielleicht populär gemacht (danke Documents and Settingsund Program Files), aber Unix hat Pfadnamen mit Leerzeichen unterstützt , seitdem Windoze existiert hat.
Scott
2

Ich bin ein bisschen überrascht, dass dies noch niemand erwähnt hat, aber Sie können readlink -frelative Pfade in absolute Pfade konvertieren und sie als solche zum PATH hinzufügen.

Um beispielsweise die Antwort von Guillaume Perrault-Archambault zu verbessern,

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

wird

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
        then
            if ARGA=$(readlink -f "$ARG")               #notice me
            then
                if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
                then
                    PATH="${PATH:+"$PATH:"}$ARGA"
                fi
            else
                PATH="${PATH:+"$PATH:"}$ARG"
            fi
        fi
    done
}

1. Grundlagen - Was nützt das?

Der readlink -fBefehl konvertiert (unter anderem) einen relativen Pfad in einen absoluten Pfad. Dies ermöglicht es Ihnen, etwas zu tun

$ cd / path / to / my / bin / dir
$ pathappend .
$ echo "$ PATH"
<Ihr_alter_Pfad> : / path / to / my / bin / dir

2. Warum testen wir zweimal, ob wir im Pfad sind?

Betrachten Sie das obige Beispiel. Wenn der Benutzer aus dem Verzeichnis ein zweites Mal sagt , wird . Natürlich wird in nicht vorhanden sein . Dann aber wird eingestellt (die absoluten Pfad äquivalent ), das ist in bereits . Wir müssen also vermeiden , ein zweites Mal etwas hinzuzufügen .pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Vielleicht noch wichtiger ist, dass der Hauptzweck von readlink, wie der Name schon sagt, darin besteht, einen symbolischen Link zu betrachten und den darin enthaltenen Pfadnamen auszulesen (dh auf diesen zu zeigen). Zum Beispiel:

$ ls -ld /usr/lib/perl/5.14
-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/perl/5.14 -> 5.14.2
$ readlink /usr/lib/perl/5.14
5.14.2
$ readlink -f /usr/lib/perl/5.14
/usr/lib/perl/5.14.2

Nun, wenn Sie sagen pathappend /usr/lib/perl/5.14, und Sie haben bereits /usr/lib/perl/5.14in Ihrem Pfad, nun, das ist in Ordnung; wir können es einfach so lassen, wie es ist. Aber, wenn /usr/lib/perl/5.14nicht bereits in Ihrer PATH ist, rufen wir readlinkund bekommen ARGA= /usr/lib/perl/5.14.2und dann fügen wir , dass zu PATH. Aber warte eine Minute - wenn du es schon gesagt hast pathappend /usr/lib/perl/5.14, dann hast du es bereits /usr/lib/perl/5.14.2in deinem PFAD und wir müssen es erneut überprüfen, um zu vermeiden, dass es PATHein zweites Mal hinzugefügt wird.

3. Mit was ist das Geschäft if ARGA=$(readlink -f "$ARG")?

Falls es unklar ist, prüft diese Zeile, ob das readlinkerfolgreich ist. Dies ist nur eine gute, defensive Programmierpraxis. Wenn wir die Ausgabe von Befehl  m als Teil von Befehl  n verwenden (wobei m  <  n ist ), ist es ratsam, zu überprüfen, ob Befehl  m fehlgeschlagen ist, und dies in irgendeiner Weise zu handhaben. Ich glaube nicht, dass dies wahrscheinlich readlinkscheitern wird - aber wie in Wie man den absoluten Pfad einer beliebigen Datei von OS X und anderswo abruft , readlinkhandelt es sich um eine GNU-Erfindung. Es ist in POSIX nicht spezifiziert, daher ist seine Verfügbarkeit unter Mac OS, Solaris und anderen Nicht-Linux-Unixen fraglich. (Tatsächlich habe ich gerade einen Kommentar gelesen , der besagt: „readlink -ffunktioniert anscheinend nicht unter Mac OS X 10.11.6, realpathfunktioniert aber sofort . “Wenn Sie sich also auf einem System befinden, das nicht über ein System verfügt readlinkoder auf dem readlink -fes nicht funktioniert, können Sie dies möglicherweise ändern Skript zu verwenden realpath.) Durch die Installation eines Sicherheitsnetzes machen wir unseren Code etwas portabler.

Wenn Sie sich auf einem System ohne readlink(oder  realpath) befinden, werden Sie das natürlich nicht wollen .pathappend .

Der zweite -dTest ( [ -d "$ARGA" ]) ist wirklich wahrscheinlich unnötig. Ich kann mir kein Szenario vorstellen, in dem $ARGein Verzeichnis vorhanden und readlinkerfolgreich ist, das jedoch  $ARGAkein Verzeichnis ist. Ich habe nur die erste ifAnweisung kopiert und eingefügt , um die dritte Anweisung zu erstellen, und den  -dTest dort aus Faulheit verlassen.

4. Andere Kommentare?

Ja. Wie viele der anderen Antworten hier testet dieses Argument, ob es sich um ein Verzeichnis handelt, verarbeitet es, wenn dies der Fall ist, und ignoriert es, wenn dies nicht der Fall ist. Dies kann (oder auch nicht) angemessen sein, wenn Sie pathappend nur .Dateien (like .bash_profileund .bashrc) und andere Skripte verwenden. Aber wie diese Antwort (oben) gezeigt hat, ist es durchaus machbar, sie interaktiv zu nutzen. Sie werden sehr verwirrt sein, wenn Sie dies tun

$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found

Ist Ihnen aufgefallen, dass ich nysql im pathappendBefehl statt gesagt habe mysql? Und das pathappendsagte nichts; es einfach stillschweigend das falsche argument ignoriert?

Wie ich oben sagte, ist es eine gute Praxis, mit Fehlern umzugehen. Hier ist ein Beispiel:

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ]
        then
            if [[ ":$PATH:" != *":$ARG:"* ]]
            then
                if ARGA=$(readlink -f "$ARG")           #notice me
                then
                    if [[ ":$PATH:" != *":$ARGA:"* ]]
                    then
                        PATH="${PATH:+"$PATH:"}$ARGA"
                    fi
                else
                    PATH="${PATH:+"$PATH:"}$ARG"
                fi
            fi
        else
            printf "Error: %s is not a directory.\n" "$ARG" >&2
        fi
    done
}
qwertyzw
quelle
(1) Sie sollten Anführungszeichen hinzufügen: readlink -f "$ARG". (2) Ich weiß nicht, warum dies passieren würde (insbesondere nachdem der -d "$ARG"Test erfolgreich war), aber Sie möchten möglicherweise testen, ob der Test readlinkfehlgeschlagen ist. (3) Sie scheinen die readlinkHauptfunktion von übersehen zu haben - einen symbolischen Linknamen einem "echten Dateinamen" zuzuordnen. Wenn (zum Beispiel) /bineine symbolische Verknüpfung zu ist /bin64, kann ein wiederholter Aufruf von pathappend /binPATH zu einer Meldung führen …:/bin64:/bin64:/bin64:/bin64:…. Sie sollten wahrscheinlich (auch) überprüfen, ob der neue Wert von $ARGbereits in ist PATH.
Scott
(1) Gute Beobachtung, ich habe es behoben. (2) In welchem ​​Fall würde Readlink fehlschlagen? Angenommen, ein Pfad ist korrekt und verweist auf einen gültigen Standort. (3) Ich bin mir nicht sicher, was die Hauptfunktion von readlink bestimmt. Ich glaube, dass die meisten (wenn nicht alle?) Pfade in einem Unix-Dateisystem in zwei Arten von Links, symbolische Links und feste Links, unterteilt werden können. Bezüglich der doppelten Pfadangabe haben Sie Recht, aber der Zweck meiner Antwort bestand nicht darin, dies hinzuzufügen (da mir aufgefallen ist, dass andere Antworten dies bereits erwähnt haben). Der einzige Grund, warum ich noch eine Antwort hinzufüge, ist, etwas beizutragen, von dem ich dachte, dass es noch nicht dazu beigetragen hat
qwertyzw
(2) Ich meine, wenn zumindest der Name des Befehls seinen Zweck impliziert / andeutet, dann kann sich 'link' in readlink entweder auf harte oder weiche Links beziehen. Sie haben jedoch Recht: man readlink sagt 'readlink - drucke aufgelöste symbolische Links oder kanonische Dateinamen', was .ich in meinem Beispiel für einen kanonischen Dateinamen halte . Richtig?
Qwertyzw
(1) Für Menschen, die symbolische Verknüpfungen verstehen, readlinkimpliziert der Name eindeutig seinen Zweck - er betrachtet eine symbolische Verknüpfung und liest den darin enthaltenen Pfadnamen aus (dh zeigt auf). (2) Nun, ich sagte , ich wüsste nicht, warum readlinkes scheitern würde. Ich machte den allgemeinen Punkt, dass, wenn ein Skript oder eine Funktion mehrere Befehle enthält und der Befehl  n garantiert katastrophal fehlschlägt (oder überhaupt keinen Sinn ergibt), wenn der Befehl  m fehlschlägt (wobei m  <  n ist ), es ratsam ist, dies zu tun Überprüfen Sie, ob der Befehl m fehlgeschlagen ist, und behandeln Sie das auf irgendeine Weise - zumindest,… (Fortsetzung)
Scott
(Fortsetzung)… das Skript / die Funktion mit einer Diagnose abbrechen. Hypothetisch readlinkkönnte dies fehlschlagen, wenn (a) das Verzeichnis zwischen Ihren Aufrufen von testund umbenannt oder (durch einen anderen Prozess) gelöscht readlinkwurde oder (b) /usr/bin/readlinkgelöscht (oder beschädigt) wurde. (3) Sie scheinen meinen Standpunkt zu verfehlen. Ich ermutige Sie nicht, andere Antworten zu duplizieren. Ich sage, dass Ihre Antwort unvollständig und daher falsch ist , wenn Sie prüfen, ob das Original ARG(von der Befehlszeile) bereits vorhanden ist PATH, aber die Prüfung für das Neue (die Ausgabe von ) nicht wiederholen . … (Fortsetzung)ARGreadlink
Scott
1
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
GreenFox
quelle
1

Dieser Weg funktioniert gut:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
Akceptor
quelle
0

Meine Versionen sind weniger vorsichtig mit leeren Pfaden und bestehen darauf, dass Pfade und Verzeichnisse gültig sind als einige hier veröffentlichte, aber ich finde eine umfangreiche Sammlung von prepend / append / clean / unique-ify / etc. Shell-Funktionen sind nützlich für die Manipulation von Pfaden. Die ganze Menge in ihrem aktuellen Zustand finden Sie hier: http://pastebin.com/xS9sgQsX (Feedback und Verbesserungen sehr willkommen!)

andybuckley
quelle
0

Sie können einen Perl-Einzeiler verwenden:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

Hier ist es in Bash:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding $1 to PATH."
    if [[ ! -d "$1" ]]; then
        echo "$1 is not a directory.";
    elif [[ ":$PATH:" == *":$1:"* ]]; then
        echo "$1 is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}$1" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}
dpatru
quelle
0

Ich habe die Antwort von Gordon Davisson leicht geändert , um das aktuelle Verzeichnis zu verwenden, falls keines angegeben ist. Sie können dies also einfach paddaus dem Verzeichnis heraus tun, das Sie Ihrem PATH hinzufügen möchten.

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}
Thorsten Lorenz
quelle
0

Sie können überprüfen, ob eine benutzerdefinierte Variable festgelegt wurde, andernfalls festlegen und dann die neuen Einträge hinzufügen:

if [ "$MYPATHS" != "true" ]; then
    export MYPATHS="true"
    export PATH="$PATH:$HOME/bin:"

    # java stuff
    export JAVA_HOME="$(/usr/libexec/java_home)"
    export M2_HOME="$HOME/Applications/apache-maven-3.3.9"
    export PATH="$JAVA_HOME/bin:$M2_HOME/bin:$PATH"

    # etc...
fi

Natürlich können diese Einträge immer noch dupliziert werden, wenn sie von einem anderen Skript hinzugefügt werden, z /etc/profile.

David Kennedy
quelle
0

Mit diesem Skript können Sie am Ende hinzufügen $PATH:

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

Oder am Anfang hinzufügen $PATH:

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2

# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case $1 in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && PATH=$prefix:$PATH
}
Tom Hale
quelle
0

Hier ist ein POSIX-konformer Weg.

# USAGE: path_add [include|prepend|append] "dir1" "dir2" ...
#   prepend: add/move to beginning
#   append:  add/move to end
#   include: add to end of PATH if not already included [default]
#          that is, don't change position if already in PATH
# RETURNS:
# prepend:  dir2:dir1:OLD_PATH
# append:   OLD_PATH:dir1:dir2
# If called with no paramters, returns PATH with duplicate directories removed
path_add() {
    # use subshell to create "local" variables
    PATH="$(path_unique)"
    export PATH="$(path_add_do "$@")"
}

path_add_do() {
    case "$1" in
    'include'|'prepend'|'append') action="$1"; shift ;;
    *)                            action='include'   ;;
    esac

    path=":$PATH:" # pad to ensure full path is matched later

    for dir in "$@"; do
        #       [ -d "$dir" ] || continue # skip non-directory params

        left="${path%:$dir:*}" # remove last occurrence to end

        if [ "$path" = "$left" ]; then
            # PATH doesn't contain $dir
            [ "$action" = 'include' ] && action='append'
            right=''
        else
            right=":${path#$left:$dir:}" # remove start to last occurrence
        fi

        # construct path with $dir added
        case "$action" in
            'prepend') path=":$dir$left$right" ;;
            'append')  path="$left$right$dir:" ;;
        esac
    done

    # strip ':' pads
    path="${path#:}"
    path="${path%:}"

    # return
    printf '%s' "$path"
}

# USAGE: path_unique [path]
# path - a colon delimited list. Defaults to $PATH is not specified.
# RETURNS: `path` with duplicated directories removed
path_unique() {
    in_path=${1:-$PATH}
    path=':'

    # Wrap the while loop in '{}' to be able to access the updated `path variable
    # as the `while` loop is run in a subshell due to the piping to it.
    # https://stackoverflow.com/questions/4667509/shell-variables-set-inside-while-loop-not-visible-outside-of-it
    printf '%s\n' "$in_path" \
    | /bin/tr -s ':' '\n'    \
    | {
            while read -r dir; do
                left="${path%:$dir:*}" # remove last occurrence to end
                if [ "$path" = "$left" ]; then
                    # PATH doesn't contain $dir
                    path="$path$dir:"
                fi
            done
            # strip ':' pads
            path="${path#:}"
            path="${path%:}"
            # return
            printf '%s\n' "$path"
        }
}

Es ist aus cribbed Guillaume Perrault-Archambault ‚s Antwort auf diese Frage und mike511 ‘ s Antwort hier .

UPDATE 23.11.2017: Fehler pro @Scott behoben

go2null
quelle
Ich wollte dies befürworten, um eine Befehlszeilenoption bereitzustellen, mit der zwischen dem Voranstellen und dem Nachanhängen (Anhängen) mit einer Standardeinstellung gewählt werden kann. Aber dann dachte ich: Das ist eine Menge etwas kryptischer Code ohne Erklärung. (Und die Tatsache, dass Sie zwei Funktionen haben, bei denen eine den Pfad ändert und seinen neuen Wert wiedergibt und die andere den ausgegebenen Wert erfasst und erneut dem Pfad zuweist , ist nur eine unnötige Komplexität.)… (Fortsetzung)
Scott,
(Fortsetzung)… Und dann bemerkte ich, dass die Links falsch waren. (Und ich bin mir nicht sicher, warum Sie diese Jungs beschuldigen; Sie scheinen nicht viel von ihren Antworten kopiert zu haben.) Und dann bemerkte ich, dass der Code falsch war. Ich denke, es ist in Ordnung , einen wohlgeformten PFAD beizubehalten , aber es gibt keine Garantie dafür, dass er bereits gut geformt ist, besonders wenn Sie einen nicht aufgeklärten haben /etc/profile. Das Verzeichnis, das Sie PATH hinzufügen möchten, ist möglicherweise bereits mehr als einmal vorhanden , und Ihr Code ist daran gehindert. … (Fortsetzung)
Scott
(Fortsetzung) ... Zum Beispiel, wenn Sie versuchen , vorangestellt /a/ckzu /b:/a/ck:/tr:/a/ck, Sie erhalten /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott