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?


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ę.


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)

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 :).


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

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.


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 /%: /}
    }

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 :-)


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.


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" <sjg@zen.void.oz.au>
#Message-Id: <199510091130.VAA01188@zen.void.oz.au>
#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 <sjg@zen.void.oz.au>

#       @(#)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;:\$;;"`
}

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 .


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

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.


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

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 

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”)


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

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'

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


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

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.


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 ”.


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"

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.


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

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

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

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 

0

W path_remove_all (przez proxxy):

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

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 :)


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.