Dodaj katalog do $ PATH, jeśli jeszcze go nie ma

126

Czy ktoś napisał funkcję bash, aby dodać katalog do $ PATH tylko wtedy, gdy jeszcze go nie ma?

Zazwyczaj dodaję do PATH używając czegoś takiego:

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

Jeśli zbuduję ŚCIEŻKĘ w .bash_profile, to nie zostanie odczytana, chyba że sesja, w której jestem, to sesja logowania - co nie zawsze jest prawdą. Jeśli zbuduję moją ŚCIEŻKĘ w .bashrc, to działa ona z każdą podpowłoką. Jeśli więc uruchomię okno terminala, a następnie uruchomię ekran, a następnie uruchom skrypt powłoki, otrzymuję:

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

Spróbuję zbudować funkcję bash o nazwie, add_to_path()która dodaje katalog tylko wtedy, gdy go nie ma. Ale jeśli ktoś już napisał (lub znalazł) coś takiego, nie spędzę na tym czasu.

Doug Harris
źródło
Zobacz stackoverflow.com/questions/273909/..., aby uzyskać informacje na temat infrastruktury, która może pomóc.
dmckee,
unix.stackexchange.com/questions/4965/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Jeśli określisz problem jako „tylko dodawanie, jeśli jeszcze go nie ma”, będziesz niegrzecznie zaskoczony, gdy nadejdzie dzień, w którym ważne jest, aby wstawiony element był na początku, ale tam się nie kończy. Lepszym rozwiązaniem byłoby wstawienie elementu, a następnie usunięcie duplikatów, więc jeśli nowy wpis już tam był, zostanie skutecznie przeniesiony na początek.
Don Hatch

Odpowiedzi:

125

Z mojego .bashrc:

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

Pamiętaj, że PATH powinien być już oznaczony jako wyeksportowany, więc ponowny eksport nie jest potrzebny. To sprawdza, czy katalog istnieje i jest katalogiem przed dodaniem go, co może Cię nie obchodzić.

Ponadto dodaje to nowy katalog na końcu ścieżki; na początek, użyj PATH="$1${PATH:+":$PATH"}"zamiast powyższej PATH=linii.

Gordon Davisson
źródło
26
Zależy mi.
Dennis Williamson,
4
@Neil: To działa, ponieważ porównuje ze ":$PATH:"zamiast po prostu"$PATH"
Gordon Davisson
3
@GordonDavisson: przepraszam, mój test był zły i masz rację.
Neil,
2
@GordonDavisson Jaki jest sens rzeczy w nawiasach klamrowych. Wydaje mi się, że nie potrafię go rozwiązać „ ${PATH:+"$PATH:"}$ 1”
dekoder łodzi
5
@ Mark0978: Właśnie to zrobiłem, aby rozwiązać problem wskazany przez bukzor. ${variable:+value}oznacza sprawdzenie, czy variablejest zdefiniowane i ma niepustą wartość, a jeśli tak, daje wynik oceny value. Zasadniczo, jeśli PATH jest niepuste na początku, ustawia ją na "$PATH:$1"; jeśli jest pusty, ustawia go tylko "$1"(zauważ brak dwukropka).
Gordon Davisson,
19

Rozwijając odpowiedź Gordona Davissona, obsługuje wiele argumentów

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

Więc możesz zrobić pathappend path1 path2 path3 ...

Do przygotowania

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

Możesz to zrobić podobnie do pathappend

ścieżkaprepend ścieżka1 ścieżka2 ścieżka3 ...

Guillaume Perrault-Archambault
źródło
3
To jest świetne! Dokonałem jednej małej zmiany. W przypadku funkcji „pathprepend” wygodnie jest czytać argumenty w odwrotnej kolejności, dzięki czemu można na przykład powiedzieć pathprepend P1 P2 P3i skończyć z PATH=P1:P2:P3. Aby uzyskać takie zachowanie, zmień for ARG in "$@" donafor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael
Dzięki @ishmael, dobra sugestia, zredagowałem odpowiedź. Zdaję sobie sprawę, że twój komentarz ma więcej niż dwa lata, ale od tamtej pory nie wróciłem. Muszę wymyślić, jak zdobyć e-maile z wymianą stosów, aby wylądowały w mojej skrzynce odbiorczej!
Guillaume Perrault-Archambault,
14

Oto moja odpowiedź na to pytanie w połączeniu ze strukturą funkcji Douga Harrisa. Używa wyrażeń regularnych Bash:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
Dennis Williamson
źródło
Działa to dla mnie tylko przy użyciu $1zamiast${1}
Andrei
@Andrei: Tak, nawiasy klamrowe są w tym przypadku niepotrzebne. Nie jestem pewien, dlaczego je zawarłem.
Dennis Williamson,
10

Umieść to w komentarzach do wybranej odpowiedzi, ale wydaje się, że komentarze nie obsługują formatowania PRE, więc dodaj odpowiedź tutaj:

@ Gordon-Davisson Nie jestem wielkim fanem niepotrzebnych cytatów i konkatenacji. Zakładając, że używasz wersji bash> = 3, możesz zamiast tego użyć wbudowanych wyrażeń regularnych bash i wykonać:

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

To poprawnie obsługuje przypadki, w których w katalogu lub ŚCIEŻCE są spacje. Jest pewne pytanie, czy wbudowany silnik regex bash jest wystarczająco wolny, aby to mogło być mniej wydajne niż konkatenacja i interpolacja łańcuchów w porównaniu z twoją wersją, ale jakoś wydaje mi się bardziej czysta estetycznie.

Christopher Smith
źródło
1
Komentarze obsługują formatting using the backticktylko, ale nie masz żadnej przyzwoitej kontroli akapitu.
Boatcoder
To stawia dodatek na końcu. Często pożądane jest dodanie na początku, aby zastąpić istniejące lokalizacje.
Dennis Williamson,
@DennisWilliamson To uczciwa kwestia, choć nie zalecałbym tego jako zachowania domyślnego. Nietrudno wymyślić, jak zmienić na prepend.
Christopher Smith
@ChristopherSmith - re: unnecessary quotingoznacza, że ​​wiesz z góry, że $PATHnie jest zerowa. "$PATH"sprawia, że ​​OK jest, czy ŚCIEŻKA ma wartość zerową, czy nie. Podobnie jeśli $1zawiera znaki, które mogą pomylić parser poleceń. Użycie wyrażenia regularnego w cudzysłowie "(^|:)$1(:|$)"zapobiega temu.
Jesse Chisholm,
@JesseChisholm: W rzeczywistości uważam, że Christopher ma na myśli to, że reguły różnią się między [[i ]]. Wolę cytować wszystko, co może ewentualnie trzeba być cytowane, chyba że powoduje to się nie powiedzie, ale wierzę, że ma rację i że cytuje naprawdę nie są potrzebne wokół $PATH. Z drugiej strony wydaje mi się, że masz rację $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
}

Gdy potrzebujesz $ HOME / bin, aby pojawił się dokładnie raz na początku ścieżki $ PATH i nigdzie indziej, nie akceptuj substytutów.

Russell
źródło
Dzięki, to miłe, eleganckie rozwiązanie, ale stwierdziłem, że musiałem zrobić PATH=${PATH/"... zamiast PATH=${PATH//"... aby to zadziałało.
Mark Booth
Formularz podwójnego ukośnika powinien pasować do dowolnej liczby dopasowań; pojedynczy ukośnik pasuje tylko do pierwszego (wyszukaj „Podstawienie wzorca” na stronie podręcznika bash). Nie jestem pewien, dlaczego to nie zadziałało ...
andybuckley
Nie udaje się to w nietypowym przypadku, który $1jest jedynym wpisem (bez dwukropków). Wpis zostaje podwojony.
Dennis Williamson,
Usuwa również zbyt agresywnie, jak wskazał PeterS6g .
Dennis Williamson,
6

Oto alternatywne rozwiązanie, które ma dodatkową zaletę polegającą na usuwaniu zbędnych wpisów:

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

Pojedynczy argument tej funkcji jest dodawany do PATH, a pierwsza instancja tego samego ciągu jest usuwana z istniejącej ścieżki. Innymi słowy, jeśli katalog już istnieje na ścieżce, jest on promowany na przód, a nie dodawany jako duplikat.

Funkcja działa, przygotowując dwukropek do ścieżki, aby upewnić się, że wszystkie wpisy mają dwukropek z przodu, a następnie przygotowując nowy wpis do istniejącej ścieżki z usuniętym wpisem. Ostatnia część jest wykonywana przy użyciu ${var//pattern/sub}notacji bash ; więcej szczegółów znajdziesz w instrukcji bash .

Rob Hague
źródło
Dobra myśl; wadliwe wdrożenie. Zastanów się, co się dzieje, jeśli już /home/robertw twojej PATHa wami pathadd /home/rob.
Scott,
5

Oto moja (wydaje mi się, że została napisana lata temu przez Oscara, administratora mojego starego laboratorium, wszystko mu się należy), była w moim baszkrze od wieków. Ma tę dodatkową zaletę, że pozwala na dodanie lub dodanie nowego katalogu według potrzeb:

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

Stosowanie:

$ 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
źródło
5

Jeśli chodzi o dodawanie, podoba mi się rozwiązanie @ Russella, ale jest mały błąd: jeśli spróbujesz dodać coś takiego jak „/ bin” do ścieżki „/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin „zastępuje” / bin: „3 razy (kiedy tak naprawdę w ogóle nie pasował). Łącząc naprawę tego z dołączającym rozwiązaniem @ gordon-davisson, otrzymuję to:

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
źródło
4

Prosty alias taki jak ten poniżej powinien załatwić sprawę:

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

Wystarczy podzielić ścieżkę na znak: i porównać każdy składnik z argumentem, który przekazujesz. Grep sprawdza pełne dopasowanie linii i wypisuje liczbę.

Przykładowe użycie:

$ 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

Zamień polecenie echo na addToPath lub podobny alias / funkcję.

nagul
źródło
Używanie „grep -x” jest prawdopodobnie szybsze niż pętla, którą umieściłem w funkcji bash.
Doug Harris,
2

Oto co przygotowałem:

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
}

Teraz w .bashrc mam:

add_to_path /usr/local/mysql/bin

Zaktualizowana wersja po komentarzu na temat tego, jak mój oryginał nie będzie obsługiwał katalogów ze spacjami (dzięki temu pytaniu za wskazanie mnie do używania 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
źródło
1
To może zawieść jeśli nazwa katalogu już PATHzawiera spacje, *, ?albo [... ].
Scott
Dobra uwaga ... ale jestem oldschoolowym facetem z Linuksem i nigdy nie użyłbym ścieżki z białymi znakami :-)
Doug Harris
Dobrze dla ciebie, ponieważ nie tworzysz rzeczy z białymi spacjami w ich nazwach. Wstydź się za pisanie kodu, który nie poradzi sobie z nimi, jeśli istnieją. I co ma z tym wspólnego bycie „oldschoolowym facetem od Linuksa”? Windoze mógł spopularyzować ten pomysł (dziękuję Documents and Settingsi Program Files), ale Unix wspierał ścieżki zawierające białe spacje jeszcze zanim istniało Windoze.
Scott,
2

Jestem trochę zaskoczony, że nikt jeszcze o tym nie wspominał, ale możesz użyć go readlink -fdo konwersji ścieżek względnych na ścieżki bezwzględne i dodania ich do ŚCIEŻKI jako takiej.

Na przykład, aby poprawić odpowiedź Guillaume Perrault-Archambault,

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

staje się

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. Podstawy - co to robi?

readlink -fKomenda (między innymi) przekonwertować ścieżkę względną do ścieżki bezwzględnej. To pozwala ci zrobić coś takiego

$ cd / path / to / my / bin / dir
$ pathappend .
$ echo „$ PATH”
<twoja_ stara_ścieżka> : / path / to / my / bin / dir

2. Dlaczego dwa razy testujemy obecność w ŚCIEŻCE?

Rozważmy powyższy przykład. Jeśli użytkownik powie z katalogu po raz drugi, będzie . Oczywiście nie będzie obecny w . Ale wtedy zostanie ustawiony na (bezwzględny ekwiwalent ścieżki ), który już jest w . Musimy więc unikać dodawania do drugiego razu.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Być może, co ważniejsze, głównym celem readlinkjest, jak sama nazwa wskazuje, przyjrzenie się dowiązaniu symbolicznemu i odczytanie zawartej w nim ścieżki (tj. Wskazuje). Na przykład:

$ 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

Otóż, jeśli powiecie pathappend /usr/lib/perl/5.14, a macie już /usr/lib/perl/5.14ŚCIEŻKĘ, to w porządku; możemy po prostu zostawić to tak, jak jest. Ale jeśli /usr/lib/perl/5.14nie ma go już w ŚCIEŻCE, wywołujemy readlinki otrzymujemy ARGA= /usr/lib/perl/5.14.2, a następnie dodajemy to do PATH. Ale poczekaj chwilę - jeśli już powiedziałeś pathappend /usr/lib/perl/5.14, to masz już /usr/lib/perl/5.14.2ŚCIEŻKĘ i ponownie musimy to sprawdzić, aby uniknąć dodania go PATHpo raz drugi.

3. O co chodzi if ARGA=$(readlink -f "$ARG")?

W przypadku niejasności linia ta sprawdza, czy się readlinkpowiedzie. To tylko dobra, defensywna praktyka programowania. Jeśli zamierzamy wykorzystać dane wyjściowe polecenia  m jako część polecenia  n (gdzie m  <  n ), rozsądnie jest sprawdzić, czy polecenie  m nie powiodło się i jakoś sobie z tym poradzić. Nie sądzę, że prawdopodobnie readlinkzawiedzie - ale, jak omówiono w Jak odzyskać bezwzględną ścieżkę dowolnego pliku z OS X i innych, readlinkjest wynalazkiem GNU. Nie jest on określony w POSIX, więc jego dostępność w Mac OS, Solaris i innych systemach uniksowych innych niż Linux jest wątpliwa. (W rzeczywistości właśnie przeczytałem komentarz, który mówi „readlink -fwydaje się, że nie działa w systemie Mac OS X 10.11.6, ale realpathdziała po wyjęciu z pudełka. ”Jeśli więc korzystasz z systemu, który go nie ma readlinklub gdzie readlink -fnie działa, możesz to zmodyfikować skrypt do użycia realpath.) Instalując siatkę bezpieczeństwa, zwiększamy przenośność naszego kodu.

Oczywiście, jeśli korzystasz z systemu, który nie ma readlink(lub  realpath), nie będziesz chciał tego robić .pathappend .

Drugi -dtest ( [ -d "$ARGA" ]) jest prawdopodobnie prawdopodobnie niepotrzebny. Nie mogę wymyślić żadnego scenariusza, w którym $ARGkatalog jest readlinkudany, ale  $ARGAnie jest katalogiem. Właśnie skopiowałem i wkleiłem pierwsze ifzdanie, aby utworzyć trzecie, i zostawiłem  -dtest bez lenistwa.

4. Wszelkie inne komentarze?

Tak. Podobnie jak wiele innych odpowiedzi tutaj, ta sprawdza, czy każdy argument jest katalogiem, przetwarza go, jeśli jest, i ignoruje go, jeśli nie jest. Może to (lub nie musi) być odpowiednie, jeśli używasz pathappend tylko .plików „ ” (takich jak .bash_profilei .bashrc) i innych skryptów. Ale, jak pokazała ta odpowiedź (powyżej), użycie jej interaktywnie jest całkowicie wykonalne. Będziesz bardzo zdziwiony, jeśli to zrobisz

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

Czy zauważyłeś, że powiedziałem nysql w pathappendpoleceniu, a nie mysql? I to pathappendnic nie powiedział; po prostu cicho zignorował niewłaściwy argument?

Jak powiedziałem powyżej, dobrą praktyką jest obsługa błędów. Oto przykład:

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
źródło
(1) Należy dodać cytaty: readlink -f "$ARG". (2) Nie wiem, dlaczego tak się stało (szczególnie po pomyślnym zakończeniu -d "$ARG"testu), ale możesz chcieć sprawdzić, czy się readlinknie powiódł. (3) Wydaje się, że pomijasz readlinkpodstawową funkcję - mapowanie symbolicznej nazwy linku na „prawdziwą nazwę pliku”. Jeśli (na przykład) /binjest dowiązaniem symbolicznym /bin64, powtarzane wywołania do pathappend /binmogą spowodować powiedzenie ŚCIEŻKI …:/bin64:/bin64:/bin64:/bin64:…. Prawdopodobnie powinieneś (również) sprawdzić, czy nowa wartość $ARGjest już w PATH.
Scott,
(1) Dobra obserwacja, naprawiłem to. (2) w którym przypadku readlink nie powiedzie się? Zakładając, że ścieżka jest poprawna i odnosi się do prawidłowej lokalizacji. (3) Nie jestem pewien, co decyduje o podstawowej funkcji readlink, uważam, że większość (jeśli nie wszystkie?) Ścieżek w systemie plików unix można podzielić na 2 typy łączy, dowiązania symboliczne i dowiązania twarde. Jeśli chodzi o wpis zduplikowanej ścieżki, masz rację, ale celem mojej odpowiedzi nie było dodanie tego (jak zauważyłem, że inne odpowiedzi już o tym wspominały). Jedynym powodem Dodaję kolejny odpowiedź jest przyczynienie się coś, czego myśl nie była już przyczynił
qwertyzw
(2) Mam na myśli, że jeśli przynajmniej nazwa polecenia sugeruje / sugeruje jego cel, wówczas „link” w readlink może odnosić się do linków twardych lub miękkich. Masz jednak rację: man readlink mówi „readlink - drukuj rozstrzygnięte dowiązania symboliczne lub kanoniczne nazwy plików”, .w moim przykładzie uważam, że można go uznać za kanoniczną nazwę pliku. Poprawny?
qwertyzw
(1) Dla osób, które rozumieją dowiązania symboliczne, readlinknazwa wyraźnie wskazuje na ich cel - patrzy na dowiązanie symboliczne i odczytuje zawartą w nim nazwę ścieżki (tzn. Wskazuje na). (2) Powiedziałem , że nie wiem, dlaczego readlinkmiałbym zawieść. Zwracałem ogólną uwagę na to, że jeśli skrypt lub funkcja zawiera wiele poleceń, a polecenie  n ma katastrofalną awarię (lub w ogóle nie ma sensu), jeśli polecenie  m nie powiedzie się (gdzie m  <  n ), rozsądną dobrą praktyką jest sprawdź, czy polecenie m nie powiodło się, i jakoś sobie z tym poradzić - przynajmniej… (ciąg dalszy)
Scott
(Ciąg dalszy)… przerwij skrypt / funkcję za pomocą diagnostyki. Hipotetycznie sytuacja readlinkmoże się nie powieść, jeśli (a) nazwa katalogu zostanie zmieniona lub usunięta (przez inny proces) między Twoimi połączeniami do testi readlinklub (b) jeśli /usr/bin/readlinkzostanie usunięty (lub uszkodzony). (3) Wygląda na to, że brakuje ci mojego punktu. Nie zachęcam do powielania innych odpowiedzi; Mówię, że sprawdzając, czy oryginał ARG(z wiersza poleceń) jest już w środku PATH, ale nie powtarzając sprawdzania nowego ARG(wyjścia z readlink), twoja odpowiedź jest niepełna, a zatem nieprawidłowa. … (Ciąg dalszy)
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
źródło
1

Ten sposób działa dobrze:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
Akceptor
źródło
0

Moje wersje mniej ostrożnie podchodzą do pustych ścieżek i nalegają, aby ścieżki były prawidłowe i katalogi, niż niektóre zamieszczone tutaj, ale znajduję dużą kolekcję prepend / append / clean / unique-ify / itp. funkcje powłoki, które będą przydatne do manipulacji ścieżkami. Cała partia, w obecnym stanie, jest tutaj: http://pastebin.com/xS9sgQsX (opinie i ulepszenia bardzo mile widziane!)

andybuckley
źródło
0

Możesz użyć perl one liner:

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;' "$@"
}

Oto w 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
źródło
0

Lekko zmodyfikowałem odpowiedź Gordona Davissona, aby użyć aktualnego katalogu, jeśli nie został podany. Możesz więc po prostu zrobić paddz katalogu, który chcesz dodać do ŚCIEŻKI.

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

Możesz sprawdzić, czy zmienna niestandardowa została ustawiona, w przeciwnym razie ustaw ją, a następnie dodaj nowe wpisy:

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

Oczywiście wpisy te mogą być nadal duplikowane, jeśli zostaną dodane przez inny skrypt, na przykład /etc/profile.

David Kennedy
źródło
0

Ten skrypt umożliwia dodanie na końcu $PATH:

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

Lub dodaj na początku $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
źródło
0

Oto sposób zgodny z POSIX.

# 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"
        }
}

Jest żłobienia z Guillaume Perrault-Archambault „s odpowiedź na to pytanie i mike511 ” s odpowiedź tutaj .

AKTUALIZACJA 23.11.2017: Naprawiono błąd na @Scott

go2null
źródło
Zamierzałem głosować za udostępnieniem opcji wiersza polecenia do wyboru między wstępnym a późniejszym (dołączaniem), z domyślnym ustawieniem. Ale potem pomyślałem: to okropnie dużo nieco tajemniczego kodu bez wyjaśnienia. (A fakt, że masz dwie funkcje, z których jedna zmienia PATH i odzwierciedla jej nową wartość, a druga przechwytuje dane wyjściowe i przypisuje je ponownie do PATH , jest po prostu niepotrzebną złożonością.)… (Ciąg dalszy)
Scott
(Ciąg dalszy)… A potem zauważyłem, że linki były nieprawidłowe. (I nie jestem pewien, dlaczego obwiniasz tych facetów; chyba nie skopiowałeś zbyt wiele z ich odpowiedzi.) I wtedy zauważyłem, że kod był zły. Wydaje mi się, że dobrze działa z utrzymaniem dobrze uformowanej ŚCIEŻKI, ale nie ma gwarancji, że jest ona już dobrze uformowana, szczególnie jeśli masz nieoświeconą /etc/profile. Katalog, który próbujesz dodać do PATH, może już tam być więcej niż jeden raz , a twój kod dusi się. … (Ciąg dalszy)
Scott,
(Ciąg dalszy)… Na przykład, jeśli spróbujesz /a/cksię przygotować /b:/a/ck:/tr:/a/ck, otrzymasz /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Scott,