Jaki jest najbardziej elegancki sposób usunięcia ścieżki ze zmiennej $ PATH w Bash?

114

Albo bardziej ogólnie, jak usunąć element z listy rozdzielanej dwukropkami w zmiennej środowiskowej Bash?

Wydawało mi się, że lata temu widziałem prosty sposób, aby to zrobić, wykorzystując bardziej zaawansowane formy rozwijania zmiennych Bash, ale jeśli tak, to straciłem z oczu. Szybkie wyszukiwanie w Google przyniosło zaskakująco mało trafnych wyników i żadnego, który nazwałbym „prostym” lub „eleganckim”. Na przykład dwie metody używające odpowiednio sed i 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)

Czy nie istnieje nic prostego? Czy jest coś podobnego do funkcji split () w Bash?

Aktualizacja:
Wygląda na to, że muszę przeprosić za moje celowo niejasne pytanie; Byłem mniej zainteresowany rozwiązaniem konkretnego przypadku użycia niż sprowokowaniem dobrej dyskusji. Na szczęście mam to!

Jest tu kilka bardzo sprytnych technik. W końcu dodałem do mojego zestawu narzędzi następujące trzy funkcje. Magia dzieje się w path_remove, która w dużej mierze opiera się na sprytnym wykorzystaniu awkzmiennej RS przez Martina Yorka .

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/:$//'`; }

Jedynym prawdziwym okrucieństwem jest użycie seddo usunięcia końcowej okrężnicy. Biorąc jednak pod uwagę, jak prosta jest reszta rozwiązania Martina, jestem całkiem skłonny z tym żyć!


Powiązane pytanie: Jak manipulować elementami $ PATH w skryptach powłoki?

Ben Blank
źródło
Dla dowolnej zmiennej: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}"ale musisz ją nazwać func $VAR VAR pattern(na podstawie @ martin-york i @ andrew-aylett)
vesperto

Odpowiedzi:

51

Minuta z awk:

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

Edycja: odpowiedź na poniższe komentarze:

$ 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!

Edytuj w odpowiedzi na problem bezpieczeństwa: (nie dotyczy pytania)

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

Spowoduje to usunięcie wszelkich końcowych dwukropków pozostawionych przez usunięcie ostatnich wpisów, co skutecznie dodałoby .ścieżkę.

Martin York
źródło
1
Błąd przy próbie usunięcia ostatniego elementu lub wielu elementów: w pierwszym przypadku dodaje bieżący katalog (jako pusty ciąg; potencjalna dziura w zabezpieczeniach), w drugim przypadku dodaje `` jako element ścieżki.
Fred Foo
1
@larsmans: U mnie działa dobrze. Uwaga: pusty to nie to samo, co katalog bieżący, czyli „./”
Martin York,
2
Pusty łańcuch jako „państwa” w PATHzmiennej robi , jako przepis szczególny, oznaczają bieżący katalog wszystkich powłok uniksowych od przynajmniej V7 Unix 1979. To wciąż robi w bash. Sprawdź instrukcję lub spróbuj sam.
Fred Foo
1
@Martin: POSIX nie wymaga tego zachowania, ale dokumentuje je i zezwala na to: pubs.opengroup.org/onlinepubs/9699919799/basedefs/ ...
Fred Foo
1
Jest jeszcze bardziej subtelny problem przy usuwaniu ostatniego elementu w ten sposób: awk wydaje się dodawać bajt zerowy na końcu łańcucha . Oznacza to, że jeśli później dołączysz inny katalog do PATH, w rzeczywistości nie będzie on przeszukiwany.
sschuberth
55

Mój brudny hack:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Martin York
źródło
18
To nigdy nie jest dobry znak, gdy najbardziej oczywistym rozwiązaniem jest de -automate procesu. :-D
Ben Blank,
O wiele bezpieczniejsze niż „eleganckie” alternatywy.
Jortstek
44

Ponieważ dużym problemem związanym z zastępowaniem są przypadki końcowe, co powiesz na to, aby przypadki końcowe nie różniły się od innych przypadków? Jeśli ścieżka miała już dwukropki na początku i na końcu, moglibyśmy po prostu wyszukać nasz żądany ciąg zawinięty w dwukropki. W tej chwili możemy łatwo dodać te dwukropki i później je usunąć.

# 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

Czysty bash :).

Andrew Aylett
źródło
2
Dodałbym tę sekcję samouczka, aby uzyskać dodatkowe lukier: tldp.org/LDP/abs/html/string-manipulation.html
Cyber ​​Oliveira
1
Użyłem tego, ponieważ wyglądało to na najprostsze rozwiązanie. Było to super szybkie i łatwe i możesz łatwo sprawdzić swoją pracę za pomocą echo $ WORK tuż przed ostatnią linią, w której faktycznie zmieniasz zmienną PATH.
Phil Gran,
2
Absolutnie mały klejnot. Dokładnie to, co próbowałem zrobić, kiedy znalazłem ten post. -Dziękuję Andrew! BTW: Może chciałbyś dodać podwójne cudzysłowy wokół „: $ PATH:”, na wypadek gdyby zawierał spacje (to samo dotyczy „/ usr / bin”) i ostatnią linię „$ WORK”.
2
Dzięki, @ PacMan-- :). Jestem prawie pewien (właśnie to wypróbowałem), że nie potrzebujesz spacji na przypisania do WORKi PATHponieważ rozwijanie zmiennych następuje po przeanalizowaniu wiersza na sekcje w celu przypisania zmiennych i wykonania poleceń. REMOVEmoże wymagać cudzysłowu lub możesz po prostu wstawić swój ciąg bezpośrednio do zamiany, jeśli jest to stała.
Andrew Aylett
Zawsze byłem zdezorientowany, kiedy zachowywano ciągi znaków, dlatego zacząłem zawsze umieszczać je w cudzysłowie. Jeszcze raz dziękuję za wyjaśnienie tego. :)
26

Oto najprostsze rozwiązanie, jakie mogę wymyślić:

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

Powyższy przykład usunie każdy element w $ PATH, który zawiera „usr”. Możesz zamienić „* usr *” na „/ home / user / bin”, aby usunąć tylko ten element.

aktualizacja na sschuberth

Mimo że uważam, że przestrzenie w a $PATHto okropny pomysł, oto rozwiązanie, które sobie z tym radzi:

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[*]}");

lub

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
źródło
2
Jako jedna linijka: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo "$ {t [*]} ”);
nicerobot
1
To rozwiązanie nie działa ze ścieżkami w PATH, które zawierają spacje; zastępuje je dwukropkami.
sschuberth
11

Oto jedna linijka, która pomimo aktualnie akceptowanych i najwyżej ocenianych odpowiedzi, nie dodaje niewidocznych znaków do PATH i radzi sobie ze ścieżkami zawierającymi spacje:

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

Osobiście uważam, że jest to łatwe do odczytania / zrozumienia i obejmuje tylko typowe polecenia zamiast używania awk.

sschuberth
źródło
2
... a jeśli chcesz czegoś, co poradzi sobie nawet z nowymi wierszami w nazwach plików, możesz użyć tego: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (choć prawdopodobnie możesz zadać sobie pytanie, dlaczego tego potrzebujesz, jeśli tak :))
ehdr
Spowoduje to usunięcie częściowych dopasowań, co prawdopodobnie nie jest tym, czego chcesz; Użyłbym grep -v "^/path/to/remove\$"lubgrep -v -x "/path/to/remove"
ShadSterling
Dobre rozwiązanie, ale czy naprawdę uważasz, że trjest bardziej powszechne niż awk? ;)
K.-Michael Aye
1
Absolutnie. Lekkie środowiska, takie jak Git Bash w systemie Windows, są raczej dostarczane z prostym narzędziem, tra nie z tłumaczem awk.
sschuberth
1
@ehdr: Trzeba wymienić echo "..."ze printf "%s" "..."go do pracy na drogach, jak -ei podobnych. Zobacz stackoverflow.com/a/49418406/102441
Eric
8

Oto rozwiązanie, które:

  • to czysty Bash,
  • nie wywołuje innych procesów (takich jak „sed” czy „awk”),
  • się nie zmienia IFS,
  • nie rozwidla podpowłoki,
  • obsługuje ścieżki ze spacjami i
  • usuwa wszystkie wystąpienia argumentu w PATH.

    removeFromPath () {
       lokalny pd
       p = ": $ 1:"
       d = ": $ PATH:"
       d = $ {d // $ p /:}
       d = $ {d / #: /}
       PATH = $ {d /%: /}
    }
robinbb
źródło
4
Podoba mi się to rozwiązanie. Może uczynić nazwy zmiennych bardziej opisowymi?
Anukool,
bardzo dobrze. Jednak nie działa w przypadku segmentów ścieżki zawierających gwiazdkę (*). Czasami trafiają tam przypadkowo.
Jörg,
6

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

Wykopałem to z mojego pliku .bashrc. Kiedy bawisz się z PATH i gubisz się, awk / sed / grep staje się niedostępny :-)

GreenFox
źródło
1
To bardzo dobra uwaga. (Nigdy nie lubiłem używać zewnętrznych narzędzi do takich prostych rzeczy).
6

Najlepsza opcja czystego basha, jaką do tej pory znalazłem, to:

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
}

Jest to oparte na niezupełnie poprawnej odpowiedzi na Dodaj katalog do $ PATH, jeśli jeszcze go tam nie ma na Superuser.

Mark Booth
źródło
To też jest całkiem dobre. Przetestowałem to. Jeśli istnieje zduplikowana ścieżka (np. Dwie dokładnie takie same) w PATH, tylko jedna z nich jest usuwana. Możesz również zrobić to w jednej removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; }
To rozwiązanie kończy się niepowodzeniem, gdy $PATHfolder zawiera podfolder ścieżki docelowej (tj. Do usunięcia). Na przykład: a:abc/def/bin:b-> a/bin:b, kiedy abc/defma zostać usunięty.
Robin Hsu,
5

Właśnie korzystałem z funkcji w dystrybucji bash, które były tam najwyraźniej od 1991 roku. Są one nadal w pakiecie bash-docs w Fedorze i były używane w /etc/profile, ale nie więcej ...

$ 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;:\$;;"`
}
Panie Wacky
źródło
4

Napisałem odpowiedź na to tutaj (używając również awk). Ale nie jestem pewien, czy tego szukasz? Przynajmniej wydaje mi się jasne, co robi, zamiast próbować dopasować się do jednej linii. Jednak w przypadku prostej wkładki, która usuwa tylko rzeczy, polecam

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

Wymiana jest

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

lub (krótszy, ale mniej czytelny)

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

W każdym razie, jeśli chodzi o to samo pytanie i wiele przydatnych odpowiedzi, zobacz tutaj .

Johannes Schaub - litb
źródło
A jeśli chcesz usunąć linie, które zawierają częściowy ciąg, użyj awk '$0 !~ "/bin"'. To znaczy zachowuj wiersze niezawierające „/ bin” z operatorem awk !~.
thoni56,
3

Cóż, w bashu, ponieważ obsługuje wyrażenia regularne, po prostu zrobiłbym:

PATH=${PATH/:\/home\/user\/bin/}
mata
źródło
Czy nie jest to tylko rozwinięcie nazwy ścieżki, a nie wyrażenia regularne?
dreamlax
2
Chociaż bash obsługuje wyrażenia regularne (od bash 3), to nie jest tego przykładem, jest to podstawienie zmiennej.
Robert Gamble,
4
Jest to ekspansja zmiennej wzorca i rozwiązanie ma kilka problemów. 1) nie będzie pasował do pierwszego elementu. 2) dopasuje wszystko, co zaczyna się od „/ home / user / bin”, a nie tylko „/ home / user / bin”. 3) wymaga ucieczki znaków specjalnych. W najlepszym przypadku powiedziałbym, że jest to niepełny przykład.
nicerobot
2

Podoba mi się trzy funkcje przedstawione w aktualizacji @ BenBlank do jego pierwotnego pytania. Aby je uogólnić, używam 2-argumentowego formularza, który pozwala mi ustawić PATH lub dowolną inną zmienną środowiskową, którą chcę:

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/:$//'`"; }

Przykłady użycia:

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

Zauważ, że dodałem również kilka cudzysłowów, aby umożliwić prawidłowe przetwarzanie ścieżek zawierających spacje.

Cary Millsap
źródło
2

Jaki jest najbardziej elegancki sposób usunięcia ścieżki ze zmiennej $ PATH w Bash?

Co jest bardziej eleganckie niż awk?

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

Pyton! Jest to bardziej czytelne i łatwiejsze w utrzymaniu rozwiązanie i łatwo jest sprawdzić, czy naprawdę robi to, czego chcesz.

Powiedz, że chcesz usunąć pierwszy element ścieżki?

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

(Zamiast potoku z echo, os.getenv['PATH']byłby trochę krótszy i dałby taki sam wynik jak powyżej, ale martwię się, że Python może zrobić coś z tą zmienną środowiskową, więc prawdopodobnie najlepiej jest przesłać ją bezpośrednio ze środowiska, na którym Ci zależy .)

Podobnie usunąć z końca:

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

Aby uczynić te funkcje powłoki wielokrotnego użytku, które możesz na przykład umieścić w swoim pliku .bashrc:

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

Tak, na przykład umieszczenie dwukropka na końcu ścieżki PATH sprawia, że ​​usunięcie ścieżki jest nieco mniej niezgrabne i podatne na błędy.

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

Jeśli obawiasz się usuwania duplikatów w $ PATH, najbardziej eleganckim sposobem, IMHO, byłoby nie dodawanie ich w pierwszej kolejności. W 1 linii:

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

Folder $ można zastąpić czymkolwiek i może on zawierać spacje („/ home / user / moje dokumenty”)

MestreLion
źródło
1

Najbardziej eleganckie rozwiązanie bash, jakie do tej pory znalazłem:

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

Większość innych proponowanych rozwiązań polegać tylko na dopasowanie strun i nie biorą pod uwagę segmenty ścieżki zawierające specjalne nazwy podoba ., ..albo ~. Funkcja bash poniżej rozwiązuje łańcuchy katalogów w swoim argumencie oraz w segmentach ścieżki, aby znaleźć logiczne dopasowania katalogów, a także dopasowania łańcuchów.

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

Test:

$ 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
źródło
plus jeden za poza pole
Martin York
1

Linux from Scratch definiuje trzy funkcje Bash w /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
źródło
1

Wiem, że to pytanie dotyczy BASH, które każdy powinien preferować, ale ponieważ lubię symetrię i czasami muszę używać "csh", zbudowałem odpowiednik "path_prepend ()", "path_append ()" i "path_remove () "eleganckie rozwiązanie powyżej.

Istota jest taka, że ​​"csh" nie ma funkcji, więc umieściłem małe skrypty powłoki w moim osobistym katalogu bin, które działają jak funkcje. Tworzę aliasy do SOURCE tych skryptów, aby wprowadzić zmiany w wyznaczonej zmiennej środowiskowej.

~ / 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'"

Możesz ich używać w ten sposób ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles
Lance ET Compte
źródło
0

Ponieważ jest to dość problematyczne, ponieważ NIE MA eleganckiego sposobu, zalecam uniknięcie problemu poprzez zmianę rozwiązania: zbuduj swoją ŚCIEŻKĘ zamiast próbować ją zburzyć.

Mógłbym być bardziej szczegółowy, gdybym znał Twój prawdziwy kontekst problemu. W międzyczasie użyję kompilacji oprogramowania jako kontekstu.

Typowym problemem związanym z kompilacjami oprogramowania jest to, że na niektórych komputerach psuje się, ostatecznie z powodu tego, jak ktoś skonfigurował domyślną powłokę (PATH i inne zmienne środowiskowe). Eleganckim rozwiązaniem jest uodpornienie skryptów budowania poprzez pełne określenie środowiska powłoki. Zakoduj skrypty kompilacji, aby ustawić PATH i inne zmienne środowiskowe w oparciu o elementy składające, które kontrolujesz, takie jak lokalizacja kompilatora, biblioteki, narzędzia, komponenty itp. Uczyń każdy konfigurowalny element czymś, co możesz indywidualnie ustawić, zweryfikować i następnie użyj odpowiednio w swoim skrypcie.

Na przykład mam kompilację Java opartą na Maven i ukierunkowaną na WebLogic, którą odziedziczyłem po moim nowym pracodawcy. Skrypt kompilacji jest znany z tego, że jest kruchy, a inny nowy pracownik i ja spędziliśmy trzy tygodnie (nie w pełnym wymiarze godzin, tylko tu i tam, ale wciąż wiele godzin), aby pracować na naszych maszynach. Istotnym krokiem było przejęcie kontroli nad PATH, aby dokładnie wiedzieć, która Java, która Maven i która WebLogic jest wywoływana. Utworzyłem zmienne środowiskowe, aby wskazywały na każde z tych narzędzi, a następnie obliczyłem PATH na podstawie tych i kilku innych. Podobne techniki oswoiły inne konfigurowalne ustawienia, aż w końcu stworzyliśmy odtwarzalną kompilację.

Nawiasem mówiąc, nie używaj Mavena, Java jest w porządku i kupuj WebLogic tylko wtedy, gdy absolutnie potrzebujesz jego klastrowania (ale poza tym nie, a zwłaszcza nie są to zastrzeżone funkcje).

Wszystkiego najlepszego.

Rob Williams
źródło
czasami nie masz uprawnień roota, a administrator zarządza plikiem PATH. Jasne, możesz zbudować własne, ale za każdym razem, gdy administrator coś przenosi, musisz dowiedzieć się, gdzie to umieścił. Takie podejście podważa cel posiadania administratora.
Shep
0

Podobnie jak w przypadku @litb, napisałem odpowiedź na pytanie „ Jak manipulować elementami $ PATH w skryptach powłoki ”, więc moja główna odpowiedź jest tam.

Funkcjonalność „rozdzielenia” w bashi innych pochodnych powłoki Bourne'a jest najlepiej osiągnięta za $IFSpomocą separatora międzypolowego. Na przykład, aby ustawić pozycyjnych argumentów ( $1, $2...) do elementów ścieżki, użytkowania:

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

Będzie działać poprawnie, o ile w zmiennej $ PATH nie ma spacji. Sprawienie, by działało z elementami ścieżki zawierającymi spacje, to nietrywialne ćwiczenie - pozostawione zainteresowanemu czytelnikowi. Prawdopodobnie łatwiej jest sobie z tym poradzić za pomocą języka skryptowego, takiego jak Perl.

Mam też skrypt, clnpathktórego często używam do ustawiania mojej PATH. Udokumentowałem to w odpowiedzi na pytanie „ Jak uniknąć duplikowania zmiennej PATH w csh ”.

Jonathan Leffler
źródło
IFS =: a = ($ PATH); IFS = dzielenie jest również przyjemne. działa, jeśli zawierają również spacje. ale potem masz tablicę i musisz bawić się pętlami for i tym podobnymi, aby usunąć nazwy.
Johannes Schaub - litb
Tak; robi się niewyraźnie - tak jak w przypadku mojego zaktualizowanego komentarza, prawdopodobnie w tym momencie łatwiej jest użyć języka skryptowego.
Jonathan Leffler,
0

Irytującym problemem są przypadki słupków ogrodzeniowych wśród pierwszego i ostatniego elementu. Problem można elegancko rozwiązać, zmieniając IFS i używając tablicy, ale nie wiem, jak ponownie wprowadzić dwukropek po przekonwertowaniu ścieżki na postać tablicową.

Oto nieco mniej elegancka wersja, która usuwa jeden katalog z $PATHużywania tylko manipulacji na łańcuchach. Przetestowałem to.

#!/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
źródło
0

Oto jedna linijka Perla:

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

$aZmienna uzyskuje ścieżkę do usunięcia. Polecenia s(substytut) i printniejawnie operują na $_zmiennej.

JA Faucett
źródło
0

Dobre rzeczy tutaj. Używam tego, aby przede wszystkim nie dodawać duplikatów.

#!/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
źródło
1
Możesz uprościć swoje stwierdzenie przypadku, dodając case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac
początkowy
0

Po włączeniu rozszerzonego globowania można wykonać następujące czynności:

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

Rozszerzony globbing jednoliniowy (cóż, w pewnym sensie):

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

Wydaje się, że nie ma potrzeby ucieczki przed ukośnikami w 1 dolarach.

path_remove ()  { shopt -s extglob; declare escArg="${1//\//\\/}"; PATH="${PATH//+(${escArg})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 
Carlo
źródło
0

Dodając dwukropki do PATH, moglibyśmy również zrobić coś takiego:

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

W path_remove_all (przez proxxy):

-newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}" 
+newPATH="${newPATH//:${@:${i}:1}*([^:])/}"        # s/:\/path[^:]*//g 
marius
źródło
0

Chociaż jest to bardzo stary wątek, pomyślałem, że to rozwiązanie może być interesujące:

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

znalazłem to w tym poście na blogu . Myślę, że ten mi się najbardziej podoba :)

mjc
źródło
0

Przyjąłem nieco inne podejście niż większość ludzi tutaj i skupiłem się konkretnie na manipulowaniu strunami, na przykład:

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

Powyższe jest uproszczonym przykładem końcowych funkcji, których używam. Stworzyłem również path_add_beforei path_add_afterumożliwiam wstawianie ścieżki przed / po określonej ścieżce już w PATH.

Pełny zestaw funkcji jest dostępny w path_helpers.sh w moich plikach dot . W pełni obsługują usuwanie / dołączanie / poprzedzanie / wstawianie na początku / środku / końcu ciągu PATH.

jimeh
źródło